diff --git a/.env.example b/.env.example index db09bb471f..a6ff6157ce 100644 --- a/.env.example +++ b/.env.example @@ -64,11 +64,6 @@ CONSOLE_JSON=false DEBUG_LOGGING=true DEBUG_CONSOLE=false -# Set to true to enable agent debug logging -AGENT_DEBUG_LOGGING=false - -# Enable memory diagnostics (logs heap/RSS snapshots every 60s, auto-enabled with --inspect) -# MEM_DIAG=true #=============# # Permissions # @@ -198,10 +193,10 @@ GOOGLE_KEY=user_provided # GOOGLE_AUTH_HEADER=true # Gemini API (AI Studio) -# GOOGLE_MODELS=gemini-3.1-pro-preview,gemini-3.1-pro-preview-customtools,gemini-3.1-flash-lite-preview,gemini-2.5-pro,gemini-2.5-flash,gemini-2.5-flash-lite,gemini-2.0-flash,gemini-2.0-flash-lite +# GOOGLE_MODELS=gemini-2.5-pro,gemini-2.5-flash,gemini-2.5-flash-lite,gemini-2.0-flash,gemini-2.0-flash-lite # Vertex AI -# GOOGLE_MODELS=gemini-3.1-pro-preview,gemini-3.1-pro-preview-customtools,gemini-3.1-flash-lite-preview,gemini-2.5-pro,gemini-2.5-flash,gemini-2.5-flash-lite,gemini-2.0-flash-001,gemini-2.0-flash-lite-001 +# GOOGLE_MODELS=gemini-2.5-pro,gemini-2.5-flash,gemini-2.5-flash-lite,gemini-2.0-flash-001,gemini-2.0-flash-lite-001 # GOOGLE_TITLE_MODEL=gemini-2.0-flash-lite-001 @@ -248,6 +243,10 @@ GOOGLE_KEY=user_provided # Option A: Use dedicated Gemini API key for image generation # GEMINI_API_KEY=your-gemini-api-key +# Option B: Use Vertex AI (no API key needed, uses service account) +# Set this to enable Vertex AI and allow tool without requiring API keys +# GEMINI_VERTEX_ENABLED=true + # Vertex AI model for image generation (defaults to gemini-2.5-flash-image) # GEMINI_IMAGE_MODEL=gemini-2.5-flash-image @@ -515,9 +514,6 @@ OPENID_ADMIN_ROLE_TOKEN_KIND= OPENID_USERNAME_CLAIM= # Set to determine which user info property returned from OpenID Provider to store as the User's name OPENID_NAME_CLAIM= -# Set to determine which user info claim to use as the email/identifier for user matching (e.g., "upn" for Entra ID) -# When not set, defaults to: email -> preferred_username -> upn -OPENID_EMAIL_CLAIM= # Optional audience parameter for OpenID authorization requests OPENID_AUDIENCE= @@ -542,8 +538,6 @@ 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 # @@ -627,7 +621,6 @@ 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= @@ -665,9 +658,6 @@ AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= AWS_REGION= AWS_BUCKET_NAME= -# Required for path-style S3-compatible providers (MinIO, Hetzner, Backblaze B2, etc.) -# that don't support virtual-hosted-style URLs (bucket.endpoint). Not needed for AWS S3. -# AWS_FORCE_PATH_STYLE=false #========================# # Azure Blob Storage # @@ -682,8 +672,7 @@ AZURE_CONTAINER_NAME=files #========================# ALLOW_SHARED_LINKS=true -# Allows unauthenticated access to shared links. Defaults to false (auth required) if not set. -ALLOW_SHARED_LINKS_PUBLIC=false +ALLOW_SHARED_LINKS_PUBLIC=true #==============================# # Static File Cache Control # @@ -855,24 +844,3 @@ OPENWEATHER_API_KEY= # Skip code challenge method validation (e.g., for AWS Cognito that supports S256 but doesn't advertise it) # When set to true, forces S256 code challenge even if not advertised in .well-known/openid-configuration # MCP_SKIP_CODE_CHALLENGE_CHECK=false - -# Circuit breaker: max connect/disconnect cycles before tripping (per server) -# MCP_CB_MAX_CYCLES=7 - -# Circuit breaker: sliding window (ms) for counting cycles -# MCP_CB_CYCLE_WINDOW_MS=45000 - -# Circuit breaker: cooldown (ms) after the cycle breaker trips -# MCP_CB_CYCLE_COOLDOWN_MS=15000 - -# Circuit breaker: max consecutive failed connection rounds before backoff -# MCP_CB_MAX_FAILED_ROUNDS=3 - -# Circuit breaker: sliding window (ms) for counting failed rounds -# MCP_CB_FAILED_WINDOW_MS=120000 - -# Circuit breaker: base backoff (ms) after failed round threshold is reached -# MCP_CB_BASE_BACKOFF_MS=30000 - -# Circuit breaker: max backoff cap (ms) for exponential backoff -# MCP_CB_MAX_BACKOFF_MS=300000 diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 725ac8b6bd..0000000000 --- a/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ -# Force LF line endings for shell scripts and git hooks (required for cross-platform compatibility) -.husky/* text eol=lf -*.sh text eol=lf diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index ae9e6d8e4b..ad0a75ab9b 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -26,14 +26,18 @@ Project maintainers have the right and responsibility to remove, edit, or reject ## 1. Development Setup -1. Use Node.js v20.19.0+ or ^22.12.0 or >= 23.0.0. -2. Run `npm run smart-reinstall` to install dependencies (uses Turborepo). Use `npm run reinstall` for a clean install, or `npm ci` for a fresh lockfile-based install. -3. Build all compiled code: `npm run build`. -4. Setup and run unit tests: +1. Use Node.JS 20.x. +2. Install typescript globally: `npm i -g typescript`. +3. Run `npm ci` to install dependencies. +4. Build the data provider: `npm run build:data-provider`. +5. Build data schemas: `npm run build:data-schemas`. +6. Build API methods: `npm run build:api`. +7. Setup and run unit tests: - Copy `.env.test`: `cp api/test/.env.test.example api/test/.env.test`. - Run backend unit tests: `npm run test:api`. - Run frontend unit tests: `npm run test:client`. -5. Setup and run integration tests: +8. Setup and run integration tests: + - Build client: `cd client && npm run build`. - Create `.env`: `cp .env.example .env`. - Install [MongoDB Community Edition](https://www.mongodb.com/docs/manual/administration/install-community/), ensure that `mongosh` connects to your local instance. - Run: `npx install playwright`, then `npx playwright install`. @@ -44,11 +48,11 @@ Project maintainers have the right and responsibility to remove, edit, or reject ## 2. Development Notes 1. Before starting work, make sure your main branch has the latest commits with `npm run update`. -2. Run linting command to find errors: `npm run lint`. Alternatively, ensure husky pre-commit checks are functioning. +3. Run linting command to find errors: `npm run lint`. Alternatively, ensure husky pre-commit checks are functioning. 3. After your changes, reinstall packages in your current branch using `npm run reinstall` and ensure everything still works. - Restart the ESLint server ("ESLint: Restart ESLint Server" in VS Code command bar) and your IDE after reinstalling or updating. 4. Clear web app localStorage and cookies before and after changes. -5. To check for introduced errors, build all compiled code: `npm run build`. +5. For frontend changes, compile typescript before and after changes to check for introduced errors: `cd client && npm run build`. 6. Run backend unit tests: `npm run test:api`. 7. Run frontend unit tests: `npm run test:client`. 8. Run integration tests: `npm run e2e`. @@ -114,45 +118,50 @@ Apply the following naming conventions to branches, labels, and other Git-relate - **JS/TS:** Directories and file names: Descriptive and camelCase. First letter uppercased for React files (e.g., `helperFunction.ts, ReactComponent.tsx`). - **Docs:** Directories and file names: Descriptive and snake_case (e.g., `config_files.md`). -## 7. Coding Standards - -For detailed coding conventions, workspace boundaries, and architecture guidance, refer to the [`AGENTS.md`](../AGENTS.md) file at the project root. It covers code style, type safety, import ordering, iteration/performance expectations, frontend rules, testing, and development commands. - -## 8. TypeScript Conversion +## 7. TypeScript Conversion 1. **Original State**: The project was initially developed entirely in JavaScript (JS). -2. **Frontend**: Fully transitioned to TypeScript. +2. **Frontend Transition**: + - We are in the process of transitioning the frontend from JS to TypeScript (TS). + - The transition is nearing completion. + - This conversion is feasible due to React's capability to intermix JS and TS prior to code compilation. It's standard practice to compile/bundle the code in such scenarios. -3. **Backend**: - - The legacy Express.js server remains in `/api` as JavaScript. - - All new backend code is written in TypeScript under `/packages/api`, which is compiled and consumed by `/api`. - - Shared database logic lives in `/packages/data-schemas` (TypeScript). - - Shared frontend/backend API types and services live in `/packages/data-provider` (TypeScript). - - Minimize direct changes to `/api`; prefer adding TypeScript code to `/packages/api` and importing it. +3. **Backend Considerations**: + - Transitioning the backend to TypeScript would be a more intricate process, especially for an established Express.js server. + + - **Options for Transition**: + - **Single Phase Overhaul**: This involves converting the entire backend to TypeScript in one go. It's the most straightforward approach but can be disruptive, especially for larger codebases. + + - **Incremental Transition**: Convert parts of the backend progressively. This can be done by: + - Maintaining a separate directory for TypeScript files. + - Gradually migrating and testing individual modules or routes. + - Using a build tool like `tsc` to compile TypeScript files independently until the entire transition is complete. + + - **Compilation Considerations**: + - Introducing a compilation step for the server is an option. This would involve using tools like `ts-node` for development and `tsc` for production builds. + - However, this is not a conventional approach for Express.js servers and could introduce added complexity, especially in terms of build and deployment processes. + + - **Current Stance**: At present, this backend transition is of lower priority and might not be pursued. -## 9. Module Import Conventions +## 8. Module Import Conventions -Imports are organized into three sections (in order): +- `npm` packages first, + - from longest line (top) to shortest (bottom) -1. **Package imports** — sorted from shortest to longest line length. - - `react` is always the first import. - - Multi-line (stacked) imports count their total character length across all lines for sorting. +- Followed by typescript types (pertains to data-provider and client workspaces) + - longest line (top) to shortest (bottom) + - types from package come first -2. **`import type` imports** — sorted from longest to shortest line length. - - Package type imports come first, then local type imports. - - Line length sorting resets between the package and local sub-groups. - -3. **Local/project imports** — sorted from longest to shortest line length. - - Multi-line (stacked) imports count their total character length across all lines for sorting. - - Imports with alias `~` are treated the same as relative imports with respect to line length. - -- Consolidate value imports from the same module as much as possible. -- Always use standalone `import type { ... }` for type imports; never use inline `type` keyword inside value imports (e.g., `import { Foo, type Bar }` is wrong). +- Lastly, local imports + - longest line (top) to shortest (bottom) + - imports with alias `~` treated the same as relative import with respect to line length **Note:** ESLint will automatically enforce these import conventions when you run `npm run lint --fix` or through pre-commit hooks. -For the full set of coding standards, see [`AGENTS.md`](../AGENTS.md). +--- + +Please ensure that you adapt this summary to fit the specific context and nuances of your project. --- diff --git a/.github/workflows/backend-review.yml b/.github/workflows/backend-review.yml index 9dd3905c0e..2379b8fee7 100644 --- a/.github/workflows/backend-review.yml +++ b/.github/workflows/backend-review.yml @@ -9,218 +9,11 @@ on: paths: - 'api/**' - 'packages/**' - -env: - NODE_ENV: CI - NODE_OPTIONS: '--max-old-space-size=${{ secrets.NODE_MAX_OLD_SPACE_SIZE || 6144 }}' - jobs: - build: - name: Build packages + tests_Backend: + name: Run Backend unit tests + timeout-minutes: 60 runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - uses: actions/checkout@v4 - - - name: Use Node.js 20.19 - uses: actions/setup-node@v4 - with: - node-version: '20.19' - - - name: Restore node_modules cache - id: cache-node-modules - uses: actions/cache@v4 - with: - path: | - node_modules - api/node_modules - packages/api/node_modules - packages/data-provider/node_modules - packages/data-schemas/node_modules - key: node-modules-backend-${{ runner.os }}-20.19-${{ hashFiles('package-lock.json') }} - - - name: Install dependencies - if: steps.cache-node-modules.outputs.cache-hit != 'true' - run: npm ci - - - name: Restore data-provider build cache - id: cache-data-provider - uses: actions/cache@v4 - with: - path: packages/data-provider/dist - key: build-data-provider-${{ runner.os }}-${{ hashFiles('packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/rollup.config.js', 'packages/data-provider/package.json') }} - - - name: Build data-provider - if: steps.cache-data-provider.outputs.cache-hit != 'true' - run: npm run build:data-provider - - - name: Restore data-schemas build cache - id: cache-data-schemas - uses: actions/cache@v4 - with: - path: packages/data-schemas/dist - key: build-data-schemas-${{ runner.os }}-${{ hashFiles('packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/rollup.config.js', 'packages/data-schemas/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/rollup.config.js', 'packages/data-provider/package.json') }} - - - name: Build data-schemas - if: steps.cache-data-schemas.outputs.cache-hit != 'true' - run: npm run build:data-schemas - - - name: Restore api build cache - id: cache-api - uses: actions/cache@v4 - with: - path: packages/api/dist - key: build-api-${{ runner.os }}-${{ hashFiles('packages/api/src/**', 'packages/api/tsconfig*.json', 'packages/api/server-rollup.config.js', 'packages/api/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/rollup.config.js', 'packages/data-provider/package.json', 'packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/rollup.config.js', 'packages/data-schemas/package.json') }} - - - name: Build api - if: steps.cache-api.outputs.cache-hit != 'true' - run: npm run build:api - - - name: Upload data-provider build - uses: actions/upload-artifact@v4 - with: - name: build-data-provider - path: packages/data-provider/dist - retention-days: 2 - - - name: Upload data-schemas build - uses: actions/upload-artifact@v4 - with: - name: build-data-schemas - path: packages/data-schemas/dist - retention-days: 2 - - - name: Upload api build - uses: actions/upload-artifact@v4 - with: - name: build-api - path: packages/api/dist - retention-days: 2 - - typecheck: - name: TypeScript type checks - needs: build - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - uses: actions/checkout@v4 - - - name: Use Node.js 20.19 - uses: actions/setup-node@v4 - with: - node-version: '20.19' - - - name: Restore node_modules cache - id: cache-node-modules - uses: actions/cache@v4 - with: - path: | - node_modules - api/node_modules - packages/api/node_modules - packages/data-provider/node_modules - packages/data-schemas/node_modules - key: node-modules-backend-${{ runner.os }}-20.19-${{ hashFiles('package-lock.json') }} - - - name: Install dependencies - if: steps.cache-node-modules.outputs.cache-hit != 'true' - run: npm ci - - - name: Download data-provider build - uses: actions/download-artifact@v4 - with: - name: build-data-provider - path: packages/data-provider/dist - - - name: Download data-schemas build - uses: actions/download-artifact@v4 - with: - name: build-data-schemas - path: packages/data-schemas/dist - - - name: Download api build - uses: actions/download-artifact@v4 - with: - name: build-api - path: packages/api/dist - - - name: Type check data-provider - run: npx tsc --noEmit -p packages/data-provider/tsconfig.json - - - name: Type check data-schemas - run: npx tsc --noEmit -p packages/data-schemas/tsconfig.json - - - name: Type check @librechat/api - run: npx tsc --noEmit -p packages/api/tsconfig.json - - - name: Type check @librechat/client - run: npx tsc --noEmit -p packages/client/tsconfig.json - - circular-deps: - name: Circular dependency checks - needs: build - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - uses: actions/checkout@v4 - - - name: Use Node.js 20.19 - uses: actions/setup-node@v4 - with: - node-version: '20.19' - - - name: Restore node_modules cache - id: cache-node-modules - uses: actions/cache@v4 - with: - path: | - node_modules - api/node_modules - packages/api/node_modules - packages/data-provider/node_modules - packages/data-schemas/node_modules - key: node-modules-backend-${{ runner.os }}-20.19-${{ hashFiles('package-lock.json') }} - - - name: Install dependencies - if: steps.cache-node-modules.outputs.cache-hit != 'true' - run: npm ci - - - name: Download data-provider build - uses: actions/download-artifact@v4 - with: - name: build-data-provider - path: packages/data-provider/dist - - - name: Download data-schemas build - uses: actions/download-artifact@v4 - with: - name: build-data-schemas - path: packages/data-schemas/dist - - - name: Rebuild @librechat/api and check for circular dependencies - run: | - output=$(npm run build:api 2>&1) - echo "$output" - if echo "$output" | grep -q "Circular depend"; then - echo "Error: Circular dependency detected in @librechat/api!" - exit 1 - fi - - - name: Detect circular dependencies in rollup - working-directory: ./packages/data-provider - run: | - output=$(npm run rollup:api) - echo "$output" - if echo "$output" | grep -q "Circular dependency"; then - echo "Error: Circular dependency detected!" - exit 1 - fi - - test-api: - name: 'Tests: api' - needs: build - runs-on: ubuntu-latest - timeout-minutes: 15 env: MONGO_URI: ${{ secrets.MONGO_URI }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} @@ -230,187 +23,54 @@ jobs: BAN_VIOLATIONS: ${{ secrets.BAN_VIOLATIONS }} BAN_DURATION: ${{ secrets.BAN_DURATION }} BAN_INTERVAL: ${{ secrets.BAN_INTERVAL }} + NODE_ENV: CI + NODE_OPTIONS: '--max-old-space-size=${{ secrets.NODE_MAX_OLD_SPACE_SIZE || 6144 }}' steps: - uses: actions/checkout@v4 - - - name: Use Node.js 20.19 + - name: Use Node.js 20.x uses: actions/setup-node@v4 with: - node-version: '20.19' - - - name: Restore node_modules cache - id: cache-node-modules - uses: actions/cache@v4 - with: - path: | - node_modules - api/node_modules - packages/api/node_modules - packages/data-provider/node_modules - packages/data-schemas/node_modules - key: node-modules-backend-${{ runner.os }}-20.19-${{ hashFiles('package-lock.json') }} + node-version: 20 + cache: 'npm' - name: Install dependencies - if: steps.cache-node-modules.outputs.cache-hit != 'true' run: npm ci - - name: Download data-provider build - uses: actions/download-artifact@v4 - with: - name: build-data-provider - path: packages/data-provider/dist + - name: Install Data Provider Package + run: npm run build:data-provider - - name: Download data-schemas build - uses: actions/download-artifact@v4 - with: - name: build-data-schemas - path: packages/data-schemas/dist + - name: Install Data Schemas Package + run: npm run build:data-schemas - - name: Download api build - uses: actions/download-artifact@v4 - with: - name: build-api - path: packages/api/dist + - name: Install API Package + run: npm run build:api - name: Create empty auth.json file run: | mkdir -p api/data echo '{}' > api/data/auth.json + - name: Check for Circular dependency in rollup + working-directory: ./packages/data-provider + run: | + output=$(npm run rollup:api) + echo "$output" + if echo "$output" | grep -q "Circular dependency"; then + echo "Error: Circular dependency detected!" + exit 1 + fi + - name: Prepare .env.test file run: cp api/test/.env.test.example api/test/.env.test - name: Run unit tests run: cd api && npm run test:ci - test-data-provider: - name: 'Tests: data-provider' - needs: build - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - uses: actions/checkout@v4 - - - name: Use Node.js 20.19 - uses: actions/setup-node@v4 - with: - node-version: '20.19' - - - name: Restore node_modules cache - id: cache-node-modules - uses: actions/cache@v4 - with: - path: | - node_modules - api/node_modules - packages/api/node_modules - packages/data-provider/node_modules - packages/data-schemas/node_modules - key: node-modules-backend-${{ runner.os }}-20.19-${{ hashFiles('package-lock.json') }} - - - name: Install dependencies - if: steps.cache-node-modules.outputs.cache-hit != 'true' - run: npm ci - - - name: Download data-provider build - uses: actions/download-artifact@v4 - with: - name: build-data-provider - path: packages/data-provider/dist - - - name: Run unit tests + - name: Run librechat-data-provider unit tests run: cd packages/data-provider && npm run test:ci - test-data-schemas: - name: 'Tests: data-schemas' - needs: build - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - uses: actions/checkout@v4 - - - name: Use Node.js 20.19 - uses: actions/setup-node@v4 - with: - node-version: '20.19' - - - name: Restore node_modules cache - id: cache-node-modules - uses: actions/cache@v4 - with: - path: | - node_modules - api/node_modules - packages/api/node_modules - packages/data-provider/node_modules - packages/data-schemas/node_modules - key: node-modules-backend-${{ runner.os }}-20.19-${{ hashFiles('package-lock.json') }} - - - name: Install dependencies - if: steps.cache-node-modules.outputs.cache-hit != 'true' - run: npm ci - - - name: Download data-provider build - uses: actions/download-artifact@v4 - with: - name: build-data-provider - path: packages/data-provider/dist - - - name: Download data-schemas build - uses: actions/download-artifact@v4 - with: - name: build-data-schemas - path: packages/data-schemas/dist - - - name: Run unit tests + - name: Run @librechat/data-schemas unit tests run: cd packages/data-schemas && npm run test:ci - test-packages-api: - name: 'Tests: @librechat/api' - needs: build - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - uses: actions/checkout@v4 - - - name: Use Node.js 20.19 - uses: actions/setup-node@v4 - with: - node-version: '20.19' - - - name: Restore node_modules cache - id: cache-node-modules - uses: actions/cache@v4 - with: - path: | - node_modules - api/node_modules - packages/api/node_modules - packages/data-provider/node_modules - packages/data-schemas/node_modules - key: node-modules-backend-${{ runner.os }}-20.19-${{ hashFiles('package-lock.json') }} - - - name: Install dependencies - if: steps.cache-node-modules.outputs.cache-hit != 'true' - run: npm ci - - - name: Download data-provider build - uses: actions/download-artifact@v4 - with: - name: build-data-provider - path: packages/data-provider/dist - - - name: Download data-schemas build - uses: actions/download-artifact@v4 - with: - name: build-data-schemas - path: packages/data-schemas/dist - - - name: Download api build - uses: actions/download-artifact@v4 - with: - name: build-api - path: packages/api/dist - - - name: Run unit tests + - name: Run @librechat/api unit tests run: cd packages/api && npm run test:ci diff --git a/.github/workflows/frontend-review.yml b/.github/workflows/frontend-review.yml index 9c2d4a37b1..989e2e4abe 100644 --- a/.github/workflows/frontend-review.yml +++ b/.github/workflows/frontend-review.yml @@ -2,7 +2,7 @@ name: Frontend Unit Tests on: pull_request: - branches: + branches: - main - dev - dev-staging @@ -11,200 +11,51 @@ on: - 'client/**' - 'packages/data-provider/**' -env: - NODE_OPTIONS: '--max-old-space-size=${{ secrets.NODE_MAX_OLD_SPACE_SIZE || 6144 }}' - jobs: - build: - name: Build packages + tests_frontend_ubuntu: + name: Run frontend unit tests on Ubuntu + timeout-minutes: 60 runs-on: ubuntu-latest - timeout-minutes: 15 + env: + NODE_OPTIONS: '--max-old-space-size=${{ secrets.NODE_MAX_OLD_SPACE_SIZE || 6144 }}' steps: - uses: actions/checkout@v4 - - - name: Use Node.js 20.19 + - name: Use Node.js 20.x uses: actions/setup-node@v4 with: - node-version: '20.19' - - - name: Restore node_modules cache - id: cache-node-modules - uses: actions/cache@v4 - with: - path: | - node_modules - client/node_modules - packages/client/node_modules - packages/data-provider/node_modules - key: node-modules-frontend-${{ runner.os }}-20.19-${{ hashFiles('package-lock.json') }} + node-version: 20 + cache: 'npm' - name: Install dependencies - if: steps.cache-node-modules.outputs.cache-hit != 'true' run: npm ci - - name: Restore data-provider build cache - id: cache-data-provider - uses: actions/cache@v4 - with: - path: packages/data-provider/dist - key: build-data-provider-${{ runner.os }}-${{ hashFiles('packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/rollup.config.js', 'packages/data-provider/package.json') }} - - - name: Build data-provider - if: steps.cache-data-provider.outputs.cache-hit != 'true' - run: npm run build:data-provider - - - name: Restore client-package build cache - id: cache-client-package - uses: actions/cache@v4 - with: - path: packages/client/dist - key: build-client-package-${{ runner.os }}-${{ hashFiles('packages/client/src/**', 'packages/client/tsconfig*.json', 'packages/client/rollup.config.js', 'packages/client/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/rollup.config.js', 'packages/data-provider/package.json') }} - - - name: Build client-package - if: steps.cache-client-package.outputs.cache-hit != 'true' - run: npm run build:client-package - - - name: Upload data-provider build - uses: actions/upload-artifact@v4 - with: - name: build-data-provider - path: packages/data-provider/dist - retention-days: 2 - - - name: Upload client-package build - uses: actions/upload-artifact@v4 - with: - name: build-client-package - path: packages/client/dist - retention-days: 2 - - test-ubuntu: - name: 'Tests: Ubuntu' - needs: build - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - uses: actions/checkout@v4 - - - name: Use Node.js 20.19 - uses: actions/setup-node@v4 - with: - node-version: '20.19' - - - name: Restore node_modules cache - id: cache-node-modules - uses: actions/cache@v4 - with: - path: | - node_modules - client/node_modules - packages/client/node_modules - packages/data-provider/node_modules - key: node-modules-frontend-${{ runner.os }}-20.19-${{ hashFiles('package-lock.json') }} - - - name: Install dependencies - if: steps.cache-node-modules.outputs.cache-hit != 'true' - run: npm ci - - - name: Download data-provider build - uses: actions/download-artifact@v4 - with: - name: build-data-provider - path: packages/data-provider/dist - - - name: Download client-package build - uses: actions/download-artifact@v4 - with: - name: build-client-package - path: packages/client/dist + - name: Build Client + run: npm run frontend:ci - name: Run unit tests run: npm run test:ci --verbose working-directory: client - test-windows: - name: 'Tests: Windows' - needs: build + tests_frontend_windows: + name: Run frontend unit tests on Windows + timeout-minutes: 60 runs-on: windows-latest - timeout-minutes: 20 + env: + NODE_OPTIONS: '--max-old-space-size=${{ secrets.NODE_MAX_OLD_SPACE_SIZE || 6144 }}' steps: - uses: actions/checkout@v4 - - - name: Use Node.js 20.19 + - name: Use Node.js 20.x uses: actions/setup-node@v4 with: - node-version: '20.19' - - - name: Restore node_modules cache - id: cache-node-modules - uses: actions/cache@v4 - with: - path: | - node_modules - client/node_modules - packages/client/node_modules - packages/data-provider/node_modules - key: node-modules-frontend-${{ runner.os }}-20.19-${{ hashFiles('package-lock.json') }} + node-version: 20 + cache: 'npm' - name: Install dependencies - if: steps.cache-node-modules.outputs.cache-hit != 'true' run: npm ci - - name: Download data-provider build - uses: actions/download-artifact@v4 - with: - name: build-data-provider - path: packages/data-provider/dist - - - name: Download client-package build - uses: actions/download-artifact@v4 - with: - name: build-client-package - path: packages/client/dist + - name: Build Client + run: npm run frontend:ci - name: Run unit tests run: npm run test:ci --verbose - working-directory: client - - build-verify: - name: Vite build verification - needs: build - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - uses: actions/checkout@v4 - - - name: Use Node.js 20.19 - uses: actions/setup-node@v4 - with: - node-version: '20.19' - - - name: Restore node_modules cache - id: cache-node-modules - uses: actions/cache@v4 - with: - path: | - node_modules - client/node_modules - packages/client/node_modules - packages/data-provider/node_modules - key: node-modules-frontend-${{ runner.os }}-20.19-${{ hashFiles('package-lock.json') }} - - - name: Install dependencies - if: steps.cache-node-modules.outputs.cache-hit != 'true' - run: npm ci - - - name: Download data-provider build - uses: actions/download-artifact@v4 - with: - name: build-data-provider - path: packages/data-provider/dist - - - name: Download client-package build - uses: actions/download-artifact@v4 - with: - name: build-client-package - path: packages/client/dist - - - name: Build client - run: cd client && npm run build:ci + working-directory: client \ No newline at end of file diff --git a/.gitignore b/.gitignore index e302d15a46..86d4a3ddae 100644 --- a/.gitignore +++ b/.gitignore @@ -63,7 +63,6 @@ bower_components/ .clineignore .cursor .aider* -.bg-shell/ # Floobits .floo @@ -130,7 +129,6 @@ helm/**/charts/ helm/**/.values.yaml !/client/src/@types/i18next.d.ts -!/client/src/@types/react.d.ts # SAML Idp cert *.cert @@ -145,6 +143,7 @@ helm/**/.values.yaml /.codeium *.local.md + # Removed Windows wrapper files per user request hive-mind-prompt-*.txt @@ -155,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 @@ -172,8 +171,5 @@ claude-flow.config.json *.sqlite-journal *.sqlite-wal claude-flow -.playwright-mcp/* # Removed Windows wrapper files per user request hive-mind-prompt-*.txt -CLAUDE.md -.gsd diff --git a/.husky/pre-commit b/.husky/pre-commit index 70fef90065..23c736d1de 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,3 +1,2 @@ -#!/bin/sh [ -n "$CI" ] && exit 0 npx lint-staged --config ./.husky/lint-staged.config.js diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index ceb2b988dc..0000000000 --- a/AGENTS.md +++ /dev/null @@ -1 +0,0 @@ -CLAUDE.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..a8cb8282bd --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,236 @@ +# Changelog + +All notable changes to this project will be documented in this file. + + + + + + +## [Unreleased] + +### ✨ New Features + +- ✨ feat: implement search parameter updates by **@mawburn** in [#7151](https://github.com/danny-avila/LibreChat/pull/7151) +- 🎏 feat: Add MCP support for Streamable HTTP Transport by **@benverhees** in [#7353](https://github.com/danny-avila/LibreChat/pull/7353) +- 🔒 feat: Add Content Security Policy using Helmet middleware by **@rubentalstra** in [#7377](https://github.com/danny-avila/LibreChat/pull/7377) +- ✨ feat: Add Normalization for MCP Server Names by **@danny-avila** in [#7421](https://github.com/danny-avila/LibreChat/pull/7421) +- 📊 feat: Improve Helm Chart by **@hofq** in [#3638](https://github.com/danny-avila/LibreChat/pull/3638) +- 🦾 feat: Claude-4 Support by **@danny-avila** in [#7509](https://github.com/danny-avila/LibreChat/pull/7509) +- 🪨 feat: Bedrock Support for Claude-4 Reasoning by **@danny-avila** in [#7517](https://github.com/danny-avila/LibreChat/pull/7517) + +### 🌍 Internationalization + +- 🌍 i18n: Add `Danish` and `Czech` and `Catalan` localization support by **@rubentalstra** in [#7373](https://github.com/danny-avila/LibreChat/pull/7373) +- 🌍 i18n: Update translation.json with latest translations by **@github-actions[bot]** in [#7375](https://github.com/danny-avila/LibreChat/pull/7375) +- 🌍 i18n: Update translation.json with latest translations by **@github-actions[bot]** in [#7468](https://github.com/danny-avila/LibreChat/pull/7468) + +### 🔧 Fixes + +- 💬 fix: update aria-label for accessibility in ConvoLink component by **@berry-13** in [#7320](https://github.com/danny-avila/LibreChat/pull/7320) +- 🔑 fix: use `apiKey` instead of `openAIApiKey` in OpenAI-like Config by **@danny-avila** in [#7337](https://github.com/danny-avila/LibreChat/pull/7337) +- 🔄 fix: update navigation logic in `useFocusChatEffect` to ensure correct search parameters are used by **@mawburn** in [#7340](https://github.com/danny-avila/LibreChat/pull/7340) +- 🔄 fix: Improve MCP Connection Cleanup by **@danny-avila** in [#7400](https://github.com/danny-avila/LibreChat/pull/7400) +- 🛡️ fix: Preset and Validation Logic for URL Query Params by **@danny-avila** in [#7407](https://github.com/danny-avila/LibreChat/pull/7407) +- 🌘 fix: artifact of preview text is illegible in dark mode by **@nhtruong** in [#7405](https://github.com/danny-avila/LibreChat/pull/7405) +- 🛡️ fix: Temporarily Remove CSP until Configurable by **@danny-avila** in [#7419](https://github.com/danny-avila/LibreChat/pull/7419) +- 💽 fix: Exclude index page `/` from static cache settings by **@sbruel** in [#7382](https://github.com/danny-avila/LibreChat/pull/7382) + +### ⚙️ Other Changes + +- 📜 docs: CHANGELOG for release v0.7.8 by **@github-actions[bot]** in [#7290](https://github.com/danny-avila/LibreChat/pull/7290) +- 📦 chore: Update API Package Dependencies by **@danny-avila** in [#7359](https://github.com/danny-avila/LibreChat/pull/7359) +- 📜 docs: Unreleased Changelog by **@github-actions[bot]** in [#7321](https://github.com/danny-avila/LibreChat/pull/7321) +- 📜 docs: Unreleased Changelog by **@github-actions[bot]** in [#7434](https://github.com/danny-avila/LibreChat/pull/7434) +- 🛡️ chore: `multer` v2.0.0 for CVE-2025-47935 and CVE-2025-47944 by **@danny-avila** in [#7454](https://github.com/danny-avila/LibreChat/pull/7454) +- 📂 refactor: Improve `FileAttachment` & File Form Deletion by **@danny-avila** in [#7471](https://github.com/danny-avila/LibreChat/pull/7471) +- 📊 chore: Remove Old Helm Chart by **@hofq** in [#7512](https://github.com/danny-avila/LibreChat/pull/7512) +- 🪖 chore: bump helm app version to v0.7.8 by **@austin-barrington** in [#7524](https://github.com/danny-avila/LibreChat/pull/7524) + + + +--- +## [v0.7.8] - + +Changes from v0.7.8-rc1 to v0.7.8. + +### ✨ New Features + +- ✨ feat: Enhance form submission for touch screens by **@berry-13** in [#7198](https://github.com/danny-avila/LibreChat/pull/7198) +- 🔍 feat: Additional Tavily API Tool Parameters by **@glowforge-opensource** in [#7232](https://github.com/danny-avila/LibreChat/pull/7232) +- 🐋 feat: Add python to Dockerfile for increased MCP compatibility by **@technicalpickles** in [#7270](https://github.com/danny-avila/LibreChat/pull/7270) + +### 🔧 Fixes + +- 🔧 fix: Google Gemma Support & OpenAI Reasoning Instructions by **@danny-avila** in [#7196](https://github.com/danny-avila/LibreChat/pull/7196) +- 🛠️ fix: Conversation Navigation State by **@danny-avila** in [#7210](https://github.com/danny-avila/LibreChat/pull/7210) +- 🔄 fix: o-Series Model Regex for System Messages by **@danny-avila** in [#7245](https://github.com/danny-avila/LibreChat/pull/7245) +- 🔖 fix: Custom Headers for Initial MCP SSE Connection by **@danny-avila** in [#7246](https://github.com/danny-avila/LibreChat/pull/7246) +- 🛡️ fix: Deep Clone `MCPOptions` for User MCP Connections by **@danny-avila** in [#7247](https://github.com/danny-avila/LibreChat/pull/7247) +- 🔄 fix: URL Param Race Condition and File Draft Persistence by **@danny-avila** in [#7257](https://github.com/danny-avila/LibreChat/pull/7257) +- 🔄 fix: Assistants Endpoint & Minor Issues by **@danny-avila** in [#7274](https://github.com/danny-avila/LibreChat/pull/7274) +- 🔄 fix: Ollama Think Tag Edge Case with Tools by **@danny-avila** in [#7275](https://github.com/danny-avila/LibreChat/pull/7275) + +### ⚙️ Other Changes + +- 📜 docs: CHANGELOG for release v0.7.8-rc1 by **@github-actions[bot]** in [#7153](https://github.com/danny-avila/LibreChat/pull/7153) +- 🔄 refactor: Artifact Visibility Management by **@danny-avila** in [#7181](https://github.com/danny-avila/LibreChat/pull/7181) +- 📦 chore: Bump Package Security by **@danny-avila** in [#7183](https://github.com/danny-avila/LibreChat/pull/7183) +- 🌿 refactor: Unmount Fork Popover on Hide for Better Performance by **@danny-avila** in [#7189](https://github.com/danny-avila/LibreChat/pull/7189) +- 🧰 chore: ESLint configuration to enforce Prettier formatting rules by **@mawburn** in [#7186](https://github.com/danny-avila/LibreChat/pull/7186) +- 🎨 style: Improve KaTeX Rendering for LaTeX Equations by **@andresgit** in [#7223](https://github.com/danny-avila/LibreChat/pull/7223) +- 📝 docs: Update `.env.example` Google models by **@marlonka** in [#7254](https://github.com/danny-avila/LibreChat/pull/7254) +- 💬 refactor: MCP Chat Visibility Option, Google Rates, Remove OpenAPI Plugins by **@danny-avila** in [#7286](https://github.com/danny-avila/LibreChat/pull/7286) +- 📜 docs: Unreleased Changelog by **@github-actions[bot]** in [#7214](https://github.com/danny-avila/LibreChat/pull/7214) + + + +[See full release details][release-v0.7.8] + +[release-v0.7.8]: https://github.com/danny-avila/LibreChat/releases/tag/v0.7.8 + +--- +## [v0.7.8-rc1] - + +Changes from v0.7.7 to v0.7.8-rc1. + +### ✨ New Features + +- 🔍 feat: Mistral OCR API / Upload Files as Text by **@danny-avila** in [#6274](https://github.com/danny-avila/LibreChat/pull/6274) +- 🤖 feat: Support OpenAI Web Search models by **@danny-avila** in [#6313](https://github.com/danny-avila/LibreChat/pull/6313) +- 🔗 feat: Agent Chain (Mixture-of-Agents) by **@danny-avila** in [#6374](https://github.com/danny-avila/LibreChat/pull/6374) +- ⌛ feat: `initTimeout` for Slow Starting MCP Servers by **@perweij** in [#6383](https://github.com/danny-avila/LibreChat/pull/6383) +- 🚀 feat: `S3` Integration for File handling and Image uploads by **@rubentalstra** in [#6142](https://github.com/danny-avila/LibreChat/pull/6142) +- 🔒feat: Enable OpenID Auto-Redirect by **@leondape** in [#6066](https://github.com/danny-avila/LibreChat/pull/6066) +- 🚀 feat: Integrate `Azure Blob Storage` for file handling and image uploads by **@rubentalstra** in [#6153](https://github.com/danny-avila/LibreChat/pull/6153) +- 🚀 feat: Add support for custom `AWS` endpoint in `S3` by **@rubentalstra** in [#6431](https://github.com/danny-avila/LibreChat/pull/6431) +- 🚀 feat: Add support for LDAP STARTTLS in LDAP authentication by **@rubentalstra** in [#6438](https://github.com/danny-avila/LibreChat/pull/6438) +- 🚀 feat: Refactor schema exports and update package version to 0.0.4 by **@rubentalstra** in [#6455](https://github.com/danny-avila/LibreChat/pull/6455) +- 🔼 feat: Add Auto Submit For URL Query Params by **@mjaverto** in [#6440](https://github.com/danny-avila/LibreChat/pull/6440) +- 🛠 feat: Enhance Redis Integration, Rate Limiters & Log Headers by **@danny-avila** in [#6462](https://github.com/danny-avila/LibreChat/pull/6462) +- 💵 feat: Add Automatic Balance Refill by **@rubentalstra** in [#6452](https://github.com/danny-avila/LibreChat/pull/6452) +- 🗣️ feat: add support for gpt-4o-transcribe models by **@berry-13** in [#6483](https://github.com/danny-avila/LibreChat/pull/6483) +- 🎨 feat: UI Refresh for Enhanced UX by **@berry-13** in [#6346](https://github.com/danny-avila/LibreChat/pull/6346) +- 🌍 feat: Add support for Hungarian language localization by **@rubentalstra** in [#6508](https://github.com/danny-avila/LibreChat/pull/6508) +- 🚀 feat: Add Gemini 2.5 Token/Context Values, Increase Max Possible Output to 64k by **@danny-avila** in [#6563](https://github.com/danny-avila/LibreChat/pull/6563) +- 🚀 feat: Enhance MCP Connections For Multi-User Support by **@danny-avila** in [#6610](https://github.com/danny-avila/LibreChat/pull/6610) +- 🚀 feat: Enhance S3 URL Expiry with Refresh; fix: S3 File Deletion by **@danny-avila** in [#6647](https://github.com/danny-avila/LibreChat/pull/6647) +- 🚀 feat: enhance UI components and refactor settings by **@berry-13** in [#6625](https://github.com/danny-avila/LibreChat/pull/6625) +- 💬 feat: move TemporaryChat to the Header by **@berry-13** in [#6646](https://github.com/danny-avila/LibreChat/pull/6646) +- 🚀 feat: Use Model Specs + Specific Endpoints, Limit Providers for Agents by **@danny-avila** in [#6650](https://github.com/danny-avila/LibreChat/pull/6650) +- 🪙 feat: Sync Balance Config on Login by **@danny-avila** in [#6671](https://github.com/danny-avila/LibreChat/pull/6671) +- 🔦 feat: MCP Support for Non-Agent Endpoints by **@danny-avila** in [#6775](https://github.com/danny-avila/LibreChat/pull/6775) +- 🗃️ feat: Code Interpreter File Persistence between Sessions by **@danny-avila** in [#6790](https://github.com/danny-avila/LibreChat/pull/6790) +- 🖥️ feat: Code Interpreter API for Non-Agent Endpoints by **@danny-avila** in [#6803](https://github.com/danny-avila/LibreChat/pull/6803) +- ⚡ feat: Self-hosted Artifacts Static Bundler URL by **@danny-avila** in [#6827](https://github.com/danny-avila/LibreChat/pull/6827) +- 🐳 feat: Add Jemalloc and UV to Docker Builds by **@danny-avila** in [#6836](https://github.com/danny-avila/LibreChat/pull/6836) +- 🤖 feat: GPT-4.1 by **@danny-avila** in [#6880](https://github.com/danny-avila/LibreChat/pull/6880) +- 👋 feat: remove Edge TTS by **@berry-13** in [#6885](https://github.com/danny-avila/LibreChat/pull/6885) +- feat: nav optimization by **@berry-13** in [#5785](https://github.com/danny-avila/LibreChat/pull/5785) +- 🗺️ feat: Add Parameter Location Mapping for OpenAPI actions by **@peeeteeer** in [#6858](https://github.com/danny-avila/LibreChat/pull/6858) +- 🤖 feat: Support `o4-mini` and `o3` Models by **@danny-avila** in [#6928](https://github.com/danny-avila/LibreChat/pull/6928) +- 🎨 feat: OpenAI Image Tools (GPT-Image-1) by **@danny-avila** in [#7079](https://github.com/danny-avila/LibreChat/pull/7079) +- 🗓️ feat: Add Special Variables for Prompts & Agents, Prompt UI Improvements by **@danny-avila** in [#7123](https://github.com/danny-avila/LibreChat/pull/7123) + +### 🌍 Internationalization + +- 🌍 i18n: Add Thai Language Support and Update Translations by **@rubentalstra** in [#6219](https://github.com/danny-avila/LibreChat/pull/6219) +- 🌍 i18n: Update translation.json with latest translations by **@github-actions[bot]** in [#6220](https://github.com/danny-avila/LibreChat/pull/6220) +- 🌍 i18n: Update translation.json with latest translations by **@github-actions[bot]** in [#6240](https://github.com/danny-avila/LibreChat/pull/6240) +- 🌍 i18n: Update translation.json with latest translations by **@github-actions[bot]** in [#6241](https://github.com/danny-avila/LibreChat/pull/6241) +- 🌍 i18n: Update translation.json with latest translations by **@github-actions[bot]** in [#6277](https://github.com/danny-avila/LibreChat/pull/6277) +- 🌍 i18n: Update translation.json with latest translations by **@github-actions[bot]** in [#6414](https://github.com/danny-avila/LibreChat/pull/6414) +- 🌍 i18n: Update translation.json with latest translations by **@github-actions[bot]** in [#6505](https://github.com/danny-avila/LibreChat/pull/6505) +- 🌍 i18n: Update translation.json with latest translations by **@github-actions[bot]** in [#6530](https://github.com/danny-avila/LibreChat/pull/6530) +- 🌍 i18n: Add Persian Localization Support by **@rubentalstra** in [#6669](https://github.com/danny-avila/LibreChat/pull/6669) +- 🌍 i18n: Update translation.json with latest translations by **@github-actions[bot]** in [#6667](https://github.com/danny-avila/LibreChat/pull/6667) +- 🌍 i18n: Update translation.json with latest translations by **@github-actions[bot]** in [#7126](https://github.com/danny-avila/LibreChat/pull/7126) +- 🌍 i18n: Update translation.json with latest translations by **@github-actions[bot]** in [#7148](https://github.com/danny-avila/LibreChat/pull/7148) + +### 👐 Accessibility + +- 🎨 a11y: Update Model Spec Description Text by **@berry-13** in [#6294](https://github.com/danny-avila/LibreChat/pull/6294) +- 🗑️ a11y: Add Accessible Name to Button for File Attachment Removal by **@kangabell** in [#6709](https://github.com/danny-avila/LibreChat/pull/6709) +- ⌨️ a11y: enhance accessibility & visual consistency by **@berry-13** in [#6866](https://github.com/danny-avila/LibreChat/pull/6866) +- 🙌 a11y: Searchbar/Conversations List Focus by **@danny-avila** in [#7096](https://github.com/danny-avila/LibreChat/pull/7096) +- 👐 a11y: Improve Fork and SplitText Accessibility by **@danny-avila** in [#7147](https://github.com/danny-avila/LibreChat/pull/7147) + +### 🔧 Fixes + +- 🐛 fix: Avatar Type Definitions in Agent/Assistant Schemas by **@danny-avila** in [#6235](https://github.com/danny-avila/LibreChat/pull/6235) +- 🔧 fix: MeiliSearch Field Error and Patch Incorrect Import by #6210 by **@rubentalstra** in [#6245](https://github.com/danny-avila/LibreChat/pull/6245) +- 🔏 fix: Enhance Two-Factor Authentication by **@rubentalstra** in [#6247](https://github.com/danny-avila/LibreChat/pull/6247) +- 🐛 fix: Await saveMessage in abortMiddleware to ensure proper execution by **@sh4shii** in [#6248](https://github.com/danny-avila/LibreChat/pull/6248) +- 🔧 fix: Axios Proxy Usage And Bump `mongoose` by **@danny-avila** in [#6298](https://github.com/danny-avila/LibreChat/pull/6298) +- 🔧 fix: comment out MCP servers to resolve service run issues by **@KunalScriptz** in [#6316](https://github.com/danny-avila/LibreChat/pull/6316) +- 🔧 fix: Update Token Calculations and Mapping, MCP `env` Initialization by **@danny-avila** in [#6406](https://github.com/danny-avila/LibreChat/pull/6406) +- 🐞 fix: Agent "Resend" Message Attachments + Source Icon Styling by **@danny-avila** in [#6408](https://github.com/danny-avila/LibreChat/pull/6408) +- 🐛 fix: Prevent Crash on Duplicate Message ID by **@Odrec** in [#6392](https://github.com/danny-avila/LibreChat/pull/6392) +- 🔐 fix: Invalid Key Length in 2FA Encryption by **@rubentalstra** in [#6432](https://github.com/danny-avila/LibreChat/pull/6432) +- 🏗️ fix: Fix Agents Token Spend Race Conditions, Expand Test Coverage by **@danny-avila** in [#6480](https://github.com/danny-avila/LibreChat/pull/6480) +- 🔃 fix: Draft Clearing, Claude Titles, Remove Default Vision Max Tokens by **@danny-avila** in [#6501](https://github.com/danny-avila/LibreChat/pull/6501) +- 🔧 fix: Update username reference to use user.name in greeting display by **@rubentalstra** in [#6534](https://github.com/danny-avila/LibreChat/pull/6534) +- 🔧 fix: S3 Download Stream with Key Extraction and Blob Storage Encoding for Vision by **@danny-avila** in [#6557](https://github.com/danny-avila/LibreChat/pull/6557) +- 🔧 fix: Mistral type strictness for `usage` & update token values/windows by **@danny-avila** in [#6562](https://github.com/danny-avila/LibreChat/pull/6562) +- 🔧 fix: Consolidate Text Parsing and TTS Edge Initialization by **@danny-avila** in [#6582](https://github.com/danny-avila/LibreChat/pull/6582) +- 🔧 fix: Ensure continuation in image processing on base64 encoding from Blob Storage by **@danny-avila** in [#6619](https://github.com/danny-avila/LibreChat/pull/6619) +- ✉️ fix: Fallback For User Name In Email Templates by **@danny-avila** in [#6620](https://github.com/danny-avila/LibreChat/pull/6620) +- 🔧 fix: Azure Blob Integration and File Source References by **@rubentalstra** in [#6575](https://github.com/danny-avila/LibreChat/pull/6575) +- 🐛 fix: Safeguard against undefined addedEndpoints by **@wipash** in [#6654](https://github.com/danny-avila/LibreChat/pull/6654) +- 🤖 fix: Gemini 2.5 Vision Support by **@danny-avila** in [#6663](https://github.com/danny-avila/LibreChat/pull/6663) +- 🔄 fix: Avatar & Error Handling Enhancements by **@danny-avila** in [#6687](https://github.com/danny-avila/LibreChat/pull/6687) +- 🔧 fix: Chat Middleware, Zod Conversion, Auto-Save and S3 URL Refresh by **@danny-avila** in [#6720](https://github.com/danny-avila/LibreChat/pull/6720) +- 🔧 fix: Agent Capability Checks & DocumentDB Compatibility for Agent Resource Removal by **@danny-avila** in [#6726](https://github.com/danny-avila/LibreChat/pull/6726) +- 🔄 fix: Improve audio MIME type detection and handling by **@berry-13** in [#6707](https://github.com/danny-avila/LibreChat/pull/6707) +- 🪺 fix: Update Role Handling due to New Schema Shape by **@danny-avila** in [#6774](https://github.com/danny-avila/LibreChat/pull/6774) +- 🗨️ fix: Show ModelSpec Greeting by **@berry-13** in [#6770](https://github.com/danny-avila/LibreChat/pull/6770) +- 🔧 fix: Keyv and Proxy Issues, and More Memory Optimizations by **@danny-avila** in [#6867](https://github.com/danny-avila/LibreChat/pull/6867) +- ✨ fix: Implement dynamic text sizing for greeting and name display by **@berry-13** in [#6833](https://github.com/danny-avila/LibreChat/pull/6833) +- 📝 fix: Mistral OCR Image Support and Azure Agent Titles by **@danny-avila** in [#6901](https://github.com/danny-avila/LibreChat/pull/6901) +- 📢 fix: Invalid `engineTTS` and Conversation State on Navigation by **@berry-13** in [#6904](https://github.com/danny-avila/LibreChat/pull/6904) +- 🛠️ fix: Improve Accessibility and Display of Conversation Menu by **@danny-avila** in [#6913](https://github.com/danny-avila/LibreChat/pull/6913) +- 🔧 fix: Agent Resource Form, Convo Menu Style, Ensure Draft Clears on Submission by **@danny-avila** in [#6925](https://github.com/danny-avila/LibreChat/pull/6925) +- 🔀 fix: MCP Improvements, Auto-Save Drafts, Artifact Markup by **@danny-avila** in [#7040](https://github.com/danny-avila/LibreChat/pull/7040) +- 🐋 fix: Improve Deepseek Compatbility by **@danny-avila** in [#7132](https://github.com/danny-avila/LibreChat/pull/7132) +- 🐙 fix: Add Redis Ping Interval to Prevent Connection Drops by **@peeeteeer** in [#7127](https://github.com/danny-avila/LibreChat/pull/7127) + +### ⚙️ Other Changes + +- 📦 refactor: Move DB Models to `@librechat/data-schemas` by **@rubentalstra** in [#6210](https://github.com/danny-avila/LibreChat/pull/6210) +- 📦 chore: Patch `axios` to address CVE-2025-27152 by **@danny-avila** in [#6222](https://github.com/danny-avila/LibreChat/pull/6222) +- ⚠️ refactor: Use Error Content Part Instead Of Throwing Error for Agents by **@danny-avila** in [#6262](https://github.com/danny-avila/LibreChat/pull/6262) +- 🏃‍♂️ refactor: Improve Agent Run Context & Misc. Changes by **@danny-avila** in [#6448](https://github.com/danny-avila/LibreChat/pull/6448) +- 📝 docs: librechat.example.yaml by **@ineiti** in [#6442](https://github.com/danny-avila/LibreChat/pull/6442) +- 🏃‍♂️ refactor: More Agent Context Improvements during Run by **@danny-avila** in [#6477](https://github.com/danny-avila/LibreChat/pull/6477) +- 🔃 refactor: Allow streaming for `o1` models by **@danny-avila** in [#6509](https://github.com/danny-avila/LibreChat/pull/6509) +- 🔧 chore: `Vite` Plugin Upgrades & Config Optimizations by **@rubentalstra** in [#6547](https://github.com/danny-avila/LibreChat/pull/6547) +- 🔧 refactor: Consolidate Logging, Model Selection & Actions Optimizations, Minor Fixes by **@danny-avila** in [#6553](https://github.com/danny-avila/LibreChat/pull/6553) +- 🎨 style: Address Minor UI Refresh Issues by **@berry-13** in [#6552](https://github.com/danny-avila/LibreChat/pull/6552) +- 🔧 refactor: Enhance Model & Endpoint Configurations with Global Indicators 🌍 by **@berry-13** in [#6578](https://github.com/danny-avila/LibreChat/pull/6578) +- 💬 style: Chat UI, Greeting, and Message adjustments by **@berry-13** in [#6612](https://github.com/danny-avila/LibreChat/pull/6612) +- ⚡ refactor: DocumentDB Compatibility for Balance Updates by **@danny-avila** in [#6673](https://github.com/danny-avila/LibreChat/pull/6673) +- 🧹 chore: Update ESLint rules for React hooks by **@rubentalstra** in [#6685](https://github.com/danny-avila/LibreChat/pull/6685) +- 🪙 chore: Update Gemini Pricing by **@RedwindA** in [#6731](https://github.com/danny-avila/LibreChat/pull/6731) +- 🪺 refactor: Nest Permission fields for Roles by **@rubentalstra** in [#6487](https://github.com/danny-avila/LibreChat/pull/6487) +- 📦 chore: Update `caniuse-lite` dependency to version 1.0.30001706 by **@rubentalstra** in [#6482](https://github.com/danny-avila/LibreChat/pull/6482) +- ⚙️ refactor: OAuth Flow Signal, Type Safety, Tool Progress & Updated Packages by **@danny-avila** in [#6752](https://github.com/danny-avila/LibreChat/pull/6752) +- 📦 chore: bump vite from 6.2.3 to 6.2.5 by **@dependabot[bot]** in [#6745](https://github.com/danny-avila/LibreChat/pull/6745) +- 💾 chore: Enhance Local Storage Handling and Update MCP SDK by **@danny-avila** in [#6809](https://github.com/danny-avila/LibreChat/pull/6809) +- 🤖 refactor: Improve Agents Memory Usage, Bump Keyv, Grok 3 by **@danny-avila** in [#6850](https://github.com/danny-avila/LibreChat/pull/6850) +- 💾 refactor: Enhance Memory In Image Encodings & Client Disposal by **@danny-avila** in [#6852](https://github.com/danny-avila/LibreChat/pull/6852) +- 🔁 refactor: Token Event Handler and Standardize `maxTokens` Key by **@danny-avila** in [#6886](https://github.com/danny-avila/LibreChat/pull/6886) +- 🔍 refactor: Search & Message Retrieval by **@berry-13** in [#6903](https://github.com/danny-avila/LibreChat/pull/6903) +- 🎨 style: standardize dropdown styling & fix z-Index layering by **@berry-13** in [#6939](https://github.com/danny-avila/LibreChat/pull/6939) +- 📙 docs: CONTRIBUTING.md by **@dblock** in [#6831](https://github.com/danny-avila/LibreChat/pull/6831) +- 🧭 refactor: Modernize Nav/Header by **@danny-avila** in [#7094](https://github.com/danny-avila/LibreChat/pull/7094) +- 🪶 refactor: Chat Input Focus for Conversation Navigations & ChatForm Optimizations by **@danny-avila** in [#7100](https://github.com/danny-avila/LibreChat/pull/7100) +- 🔃 refactor: Streamline Navigation, Message Loading UX by **@danny-avila** in [#7118](https://github.com/danny-avila/LibreChat/pull/7118) +- 📜 docs: Unreleased changelog by **@github-actions[bot]** in [#6265](https://github.com/danny-avila/LibreChat/pull/6265) + + + +[See full release details][release-v0.7.8-rc1] + +[release-v0.7.8-rc1]: https://github.com/danny-avila/LibreChat/releases/tag/v0.7.8-rc1 + +--- diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 81362cfc57..0000000000 --- a/CLAUDE.md +++ /dev/null @@ -1,172 +0,0 @@ -# LibreChat - -## Project Overview - -LibreChat is a monorepo with the following key workspaces: - -| Workspace | Language | Side | Dependency | Purpose | -|---|---|---|---|---| -| `/api` | JS (legacy) | Backend | `packages/api`, `packages/data-schemas`, `packages/data-provider`, `@librechat/agents` | Express server — minimize changes here | -| `/packages/api` | **TypeScript** | Backend | `packages/data-schemas`, `packages/data-provider` | New backend code lives here (TS only, consumed by `/api`) | -| `/packages/data-schemas` | TypeScript | Backend | `packages/data-provider` | Database models/schemas, shareable across backend projects | -| `/packages/data-provider` | TypeScript | Shared | — | Shared API types, endpoints, data-service — used by both frontend and backend | -| `/client` | TypeScript/React | Frontend | `packages/data-provider`, `packages/client` | Frontend SPA | -| `/packages/client` | TypeScript | Frontend | `packages/data-provider` | Shared frontend utilities | - -The source code for `@librechat/agents` (major backend dependency, same team) is at `/home/danny/agentus`. - ---- - -## Workspace Boundaries - -- **All new backend code must be TypeScript** in `/packages/api`. -- Keep `/api` changes to the absolute minimum (thin JS wrappers calling into `/packages/api`). -- Database-specific shared logic goes in `/packages/data-schemas`. -- Frontend/backend shared API logic (endpoints, types, data-service) goes in `/packages/data-provider`. -- Build data-provider from project root: `npm run build:data-provider`. - ---- - -## Code Style - -### Naming and File Organization - -- **Single-word file names** whenever possible (e.g., `permissions.ts`, `capabilities.ts`, `service.ts`). -- When multiple words are needed, prefer grouping related modules under a **single-word directory** rather than using multi-word file names (e.g., `admin/capabilities.ts` not `adminCapabilities.ts`). -- The directory already provides context — `app/service.ts` not `app/appConfigService.ts`. - -### Structure and Clarity - -- **Never-nesting**: early returns, flat code, minimal indentation. Break complex operations into well-named helpers. -- **Functional first**: pure functions, immutable data, `map`/`filter`/`reduce` over imperative loops. Only reach for OOP when it clearly improves domain modeling or state encapsulation. -- **No dynamic imports** unless absolutely necessary. - -### DRY - -- Extract repeated logic into utility functions. -- Reusable hooks / higher-order components for UI patterns. -- Parameterized helpers instead of near-duplicate functions. -- Constants for repeated values; configuration objects over duplicated init code. -- Shared validators, centralized error handling, single source of truth for business rules. -- Shared typing system with interfaces/types extending common base definitions. -- Abstraction layers for external API interactions. - -### Iteration and Performance - -- **Minimize looping** — especially over shared data structures like message arrays, which are iterated frequently throughout the codebase. Every additional pass adds up at scale. -- Consolidate sequential O(n) operations into a single pass whenever possible; never loop over the same collection twice if the work can be combined. -- Choose data structures that reduce the need to iterate (e.g., `Map`/`Set` for lookups instead of `Array.find`/`Array.includes`). -- Avoid unnecessary object creation; consider space-time tradeoffs. -- Prevent memory leaks: careful with closures, dispose resources/event listeners, no circular references. - -### Type Safety - -- **Never use `any`**. Explicit types for all parameters, return values, and variables. -- **Limit `unknown`** — avoid `unknown`, `Record`, and `as unknown as T` assertions. A `Record` almost always signals a missing explicit type definition. -- **Don't duplicate types** — before defining a new type, check whether it already exists in the project (especially `packages/data-provider`). Reuse and extend existing types rather than creating redundant definitions. -- Use union types, generics, and interfaces appropriately. -- All TypeScript and ESLint warnings/errors must be addressed — do not leave unresolved diagnostics. - -### Comments and Documentation - -- Write self-documenting code; no inline comments narrating what code does. -- JSDoc only for complex/non-obvious logic or intellisense on public APIs. -- Single-line JSDoc for brief docs, multi-line for complex cases. -- Avoid standalone `//` comments unless absolutely necessary. - -### Import Order - -Imports are organized into three sections: - -1. **Package imports** — sorted shortest to longest line length (`react` always first). -2. **`import type` imports** — sorted longest to shortest (package types first, then local types; length resets between sub-groups). -3. **Local/project imports** — sorted longest to shortest. - -Multi-line imports count total character length across all lines. Consolidate value imports from the same module. Always use standalone `import type { ... }` — never inline `type` inside value imports. - -### JS/TS Loop Preferences - -- **Limit looping as much as possible.** Prefer single-pass transformations and avoid re-iterating the same data. -- `for (let i = 0; ...)` for performance-critical or index-dependent operations. -- `for...of` for simple array iteration. -- `for...in` only for object property enumeration. - ---- - -## Frontend Rules (`client/src/**/*`) - -### Localization - -- All user-facing text must use `useLocalize()`. -- Only update English keys in `client/src/locales/en/translation.json` (other languages are automated externally). -- Semantic key prefixes: `com_ui_`, `com_assistants_`, etc. - -### Components - -- TypeScript for all React components with proper type imports. -- Semantic HTML with ARIA labels (`role`, `aria-label`) for accessibility. -- Group related components in feature directories (e.g., `SidePanel/Memories/`). -- Use index files for clean exports. - -### Data Management - -- Feature hooks: `client/src/data-provider/[Feature]/queries.ts` → `[Feature]/index.ts` → `client/src/data-provider/index.ts`. -- React Query (`@tanstack/react-query`) for all API interactions; proper query invalidation on mutations. -- QueryKeys and MutationKeys in `packages/data-provider/src/keys.ts`. - -### Data-Provider Integration - -- Endpoints: `packages/data-provider/src/api-endpoints.ts` -- Data service: `packages/data-provider/src/data-service.ts` -- Types: `packages/data-provider/src/types/queries.ts` -- Use `encodeURIComponent` for dynamic URL parameters. - -### Performance - -- Prioritize memory and speed efficiency at scale. -- Cursor pagination for large datasets. -- Proper dependency arrays to avoid unnecessary re-renders. -- Leverage React Query caching and background refetching. - ---- - -## Development Commands - -| Command | Purpose | -|---|---| -| `npm run smart-reinstall` | Install deps (if lockfile changed) + build via Turborepo | -| `npm run reinstall` | Clean install — wipe `node_modules` and reinstall from scratch | -| `npm run backend` | Start the backend server | -| `npm run backend:dev` | Start backend with file watching (development) | -| `npm run build` | Build all compiled code via Turborepo (parallel, cached) | -| `npm run frontend` | Build all compiled code sequentially (legacy fallback) | -| `npm run frontend:dev` | Start frontend dev server with HMR (port 3090, requires backend running) | -| `npm run build:data-provider` | Rebuild `packages/data-provider` after changes | - -- Node.js: v20.19.0+ or ^22.12.0 or >= 23.0.0 -- Database: MongoDB -- Backend runs on `http://localhost:3080/`; frontend dev server on `http://localhost:3090/` - ---- - -## Testing - -- Framework: **Jest**, run per-workspace. -- Run tests from their workspace directory: `cd api && npx jest `, `cd packages/api && npx jest `, etc. -- Frontend tests: `__tests__` directories alongside components; use `test/layout-test-utils` for rendering. -- Cover loading, success, and error states for UI/data flows. - -### Philosophy - -- **Real logic over mocks.** Exercise actual code paths with real dependencies. Mocking is a last resort. -- **Spies over mocks.** Assert that real functions are called with expected arguments and frequency without replacing underlying logic. -- **MongoDB**: use `mongodb-memory-server` for a real in-memory MongoDB instance. Test actual queries and schema validation, not mocked DB calls. -- **MCP**: use real `@modelcontextprotocol/sdk` exports for servers, transports, and tool definitions. Mirror real scenarios, don't stub SDK internals. -- Only mock what you cannot control: external HTTP APIs, rate-limited services, non-deterministic system calls. -- Heavy mocking is a code smell, not a testing strategy. - ---- - -## Formatting - -Fix all formatting lint errors (trailing spaces, tabs, newlines, indentation) using auto-fix when available. All TypeScript/ESLint warnings and errors **must** be resolved. diff --git a/Dockerfile b/Dockerfile index 19d275eb31..d45844c4a6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,9 @@ -# v0.8.4 +# v0.8.3-rc1 # Base node image FROM node:20-alpine AS node -RUN apk upgrade --no-cache +# Install jemalloc RUN apk add --no-cache jemalloc RUN apk add --no-cache python3 py3-pip uv diff --git a/Dockerfile.multi b/Dockerfile.multi index bf5570f386..5a610725d5 100644 --- a/Dockerfile.multi +++ b/Dockerfile.multi @@ -1,12 +1,12 @@ # Dockerfile.multi -# v0.8.4 +# v0.8.3-rc1 # Set configurable max-old-space-size with default ARG NODE_MAX_OLD_SPACE_SIZE=6144 # Base for all builds FROM node:20-alpine AS base-min -RUN apk upgrade --no-cache +# Install jemalloc RUN apk add --no-cache jemalloc # Set environment variable to use jemalloc ENV LD_PRELOAD=/usr/lib/libjemalloc.so.2 diff --git a/README.md b/README.md index a7f68d9a92..6e04396637 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,6 @@

-

- English · - 中文 -

-

- - Deploy on Railway + + Deploy on Railway Deploy on Zeabur diff --git a/README.zh.md b/README.zh.md deleted file mode 100644 index 7f74057413..0000000000 --- a/README.zh.md +++ /dev/null @@ -1,227 +0,0 @@ - - -

- - - -

- LibreChat -

-

- -

- English · - 中文 -

- -

- - - - - - - - - - - - -

- -

- - Deploy on Railway - - - Deploy on Zeabur - - - Deploy on Sealos - -

- -

- - 翻译进度 - -

- - -# ✨ 功能 - -- 🖥️ **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 历史 - -

- - Star History Chart - -

-

- - danny-avila%2FLibreChat | Trendshift - - - ROSS Index - 2024年第一季度增长最快的开源初创公司 | Runa Capital - -

- ---- - -## ✨ 贡献 - -欢迎任何形式的贡献、建议、错误报告和修复! - -对于新功能、组件或扩展,请在发送 PR 前开启 issue 进行讨论。 - -如果您想帮助我们将 LibreChat 翻译成您的母语,我们非常欢迎!改进翻译不仅能让全球用户更轻松地使用 LibreChat,还能提升整体用户体验。请查看我们的[翻译指南](https://www.librechat.ai/docs/translation)。 - ---- - -## 💖 感谢所有贡献者 - - - - - ---- - -## 🎉 特别鸣谢 - -感谢 [Locize](https://locize.com) 提供的翻译管理工具,支持 LibreChat 的多语言功能。 - -

- - Locize Logo - -

diff --git a/api/app/clients/BaseClient.js b/api/app/clients/BaseClient.js index 905cadfd23..a2dfaf9907 100644 --- a/api/app/clients/BaseClient.js +++ b/api/app/clients/BaseClient.js @@ -3,9 +3,7 @@ const fetch = require('node-fetch'); const { logger } = require('@librechat/data-schemas'); const { countTokens, - checkBalance, getBalanceConfig, - buildMessageFiles, extractFileContext, encodeAndFormatAudios, encodeAndFormatVideos, @@ -13,27 +11,34 @@ const { } = require('@librechat/api'); const { Constants, + ErrorTypes, FileSources, ContentTypes, excludedKeys, EModelEndpoint, - mergeFileConfig, isParamEndpoint, isAgentsEndpoint, isEphemeralAgentId, supportsBalanceCheck, - isBedrockDocumentType, - getEndpointFileConfig, } = require('librechat-data-provider'); +const { + updateMessage, + getMessages, + saveMessage, + saveConvo, + getConvo, + getFiles, +} = require('~/models'); const { getStrategyFunctions } = require('~/server/services/Files/strategies'); -const { logViolation } = require('~/cache'); +const { checkBalance } = require('~/models/balanceMethods'); +const { truncateToolCallOutputs } = require('./prompts'); const TextStream = require('./TextStream'); -const db = require('~/models'); class BaseClient { constructor(apiKey, options = {}) { this.apiKey = apiKey; this.sender = options.sender ?? 'AI'; + this.contextStrategy = null; this.currentDateString = new Date().toLocaleDateString('en-us', { year: 'numeric', month: 'long', @@ -73,10 +78,6 @@ class BaseClient { this.currentMessages = []; /** @type {import('librechat-data-provider').VisionModes | undefined} */ this.visionMode; - /** @type {import('librechat-data-provider').FileConfig | undefined} */ - this._mergedFileConfig; - /** @type {import('librechat-data-provider').EndpointFileConfig | undefined} */ - this._endpointFileConfig; } setOptions() { @@ -121,9 +122,7 @@ class BaseClient { * @returns {number} */ getTokenCountForResponse(responseMessage) { - logger.debug('[BaseClient] `recordTokenUsage` not implemented.', { - messageId: responseMessage?.messageId, - }); + logger.debug('[BaseClient] `recordTokenUsage` not implemented.', responseMessage); } /** @@ -134,14 +133,12 @@ class BaseClient { * @param {AppConfig['balance']} [balance] * @param {number} promptTokens * @param {number} completionTokens - * @param {string} [messageId] * @returns {Promise} */ - async recordTokenUsage({ model, balance, promptTokens, completionTokens, messageId }) { + async recordTokenUsage({ model, balance, promptTokens, completionTokens }) { logger.debug('[BaseClient] `recordTokenUsage` not implemented.', { model, balance, - messageId, promptTokens, completionTokens, }); @@ -336,6 +333,45 @@ class BaseClient { return payload; } + async handleTokenCountMap(tokenCountMap) { + if (this.clientName === EModelEndpoint.agents) { + return; + } + if (this.currentMessages.length === 0) { + return; + } + + for (let i = 0; i < this.currentMessages.length; i++) { + // Skip the last message, which is the user message. + if (i === this.currentMessages.length - 1) { + break; + } + + const message = this.currentMessages[i]; + const { messageId } = message; + const update = {}; + + if (messageId === tokenCountMap.summaryMessage?.messageId) { + logger.debug(`[BaseClient] Adding summary props to ${messageId}.`); + + update.summary = tokenCountMap.summaryMessage.content; + update.summaryTokenCount = tokenCountMap.summaryMessage.tokenCount; + } + + if (message.tokenCount && !update.summaryTokenCount) { + logger.debug(`[BaseClient] Skipping ${messageId}: already had a token count.`); + continue; + } + + const tokenCount = tokenCountMap[messageId]; + if (tokenCount) { + message.tokenCount = tokenCount; + update.tokenCount = tokenCount; + await this.updateMessageInDatabase({ messageId, ...update }); + } + } + } + concatenateMessages(messages) { return messages.reduce((acc, message) => { const nameOrRole = message.name ?? message.role; @@ -406,6 +442,154 @@ class BaseClient { }; } + async handleContextStrategy({ + instructions, + orderedMessages, + formattedMessages, + buildTokenMap = true, + }) { + let _instructions; + let tokenCount; + + if (instructions) { + ({ tokenCount, ..._instructions } = instructions); + } + + _instructions && logger.debug('[BaseClient] instructions tokenCount: ' + tokenCount); + if (tokenCount && tokenCount > this.maxContextTokens) { + const info = `${tokenCount} / ${this.maxContextTokens}`; + const errorMessage = `{ "type": "${ErrorTypes.INPUT_LENGTH}", "info": "${info}" }`; + logger.warn(`Instructions token count exceeds max token count (${info}).`); + throw new Error(errorMessage); + } + + if (this.clientName === EModelEndpoint.agents) { + const { dbMessages, editedIndices } = truncateToolCallOutputs( + orderedMessages, + this.maxContextTokens, + this.getTokenCountForMessage.bind(this), + ); + + if (editedIndices.length > 0) { + logger.debug('[BaseClient] Truncated tool call outputs:', editedIndices); + for (const index of editedIndices) { + formattedMessages[index].content = dbMessages[index].content; + } + orderedMessages = dbMessages; + } + } + + let orderedWithInstructions = this.addInstructions(orderedMessages, instructions); + + let { context, remainingContextTokens, messagesToRefine } = + await this.getMessagesWithinTokenLimit({ + messages: orderedWithInstructions, + instructions, + }); + + logger.debug('[BaseClient] Context Count (1/2)', { + remainingContextTokens, + maxContextTokens: this.maxContextTokens, + }); + + let summaryMessage; + let summaryTokenCount; + let { shouldSummarize } = this; + + // Calculate the difference in length to determine how many messages were discarded if any + let payload; + let { length } = formattedMessages; + length += instructions != null ? 1 : 0; + const diff = length - context.length; + const firstMessage = orderedWithInstructions[0]; + const usePrevSummary = + shouldSummarize && + diff === 1 && + firstMessage?.summary && + this.previous_summary.messageId === firstMessage.messageId; + + if (diff > 0) { + payload = formattedMessages.slice(diff); + logger.debug( + `[BaseClient] Difference between original payload (${length}) and context (${context.length}): ${diff}`, + ); + } + + payload = this.addInstructions(payload ?? formattedMessages, _instructions); + + const latestMessage = orderedWithInstructions[orderedWithInstructions.length - 1]; + if (payload.length === 0 && !shouldSummarize && latestMessage) { + const info = `${latestMessage.tokenCount} / ${this.maxContextTokens}`; + const errorMessage = `{ "type": "${ErrorTypes.INPUT_LENGTH}", "info": "${info}" }`; + logger.warn(`Prompt token count exceeds max token count (${info}).`); + throw new Error(errorMessage); + } else if ( + _instructions && + payload.length === 1 && + payload[0].content === _instructions.content + ) { + const info = `${tokenCount + 3} / ${this.maxContextTokens}`; + const errorMessage = `{ "type": "${ErrorTypes.INPUT_LENGTH}", "info": "${info}" }`; + logger.warn( + `Including instructions, the prompt token count exceeds remaining max token count (${info}).`, + ); + throw new Error(errorMessage); + } + + if (usePrevSummary) { + summaryMessage = { role: 'system', content: firstMessage.summary }; + summaryTokenCount = firstMessage.summaryTokenCount; + payload.unshift(summaryMessage); + remainingContextTokens -= summaryTokenCount; + } else if (shouldSummarize && messagesToRefine.length > 0) { + ({ summaryMessage, summaryTokenCount } = await this.summarizeMessages({ + messagesToRefine, + remainingContextTokens, + })); + summaryMessage && payload.unshift(summaryMessage); + remainingContextTokens -= summaryTokenCount; + } + + // Make sure to only continue summarization logic if the summary message was generated + shouldSummarize = summaryMessage != null && shouldSummarize === true; + + logger.debug('[BaseClient] Context Count (2/2)', { + remainingContextTokens, + maxContextTokens: this.maxContextTokens, + }); + + /** @type {Record | undefined} */ + let tokenCountMap; + if (buildTokenMap) { + const currentPayload = shouldSummarize ? orderedWithInstructions : context; + tokenCountMap = currentPayload.reduce((map, message, index) => { + const { messageId } = message; + if (!messageId) { + return map; + } + + if (shouldSummarize && index === messagesToRefine.length - 1 && !usePrevSummary) { + map.summaryMessage = { ...summaryMessage, messageId, tokenCount: summaryTokenCount }; + } + + map[messageId] = currentPayload[index].tokenCount; + return map; + }, {}); + } + + const promptTokens = this.maxContextTokens - remainingContextTokens; + + logger.debug('[BaseClient] tokenCountMap:', tokenCountMap); + logger.debug('[BaseClient]', { + promptTokens, + remainingContextTokens, + payloadSize: payload.length, + maxContextTokens: this.maxContextTokens, + }); + + return { payload, tokenCountMap, promptTokens, messages: orderedWithInstructions }; + } + async sendMessage(message, opts = {}) { const appConfig = this.options.req?.config; /** @type {Promise} */ @@ -474,30 +658,18 @@ class BaseClient { opts, ); - if (tokenCountMap && tokenCountMap[userMessage.messageId]) { - userMessage.tokenCount = tokenCountMap[userMessage.messageId]; - logger.debug('[BaseClient] userMessage', { - messageId: userMessage.messageId, - tokenCount: userMessage.tokenCount, - conversationId: userMessage.conversationId, - }); + if (tokenCountMap) { + logger.debug('[BaseClient] tokenCountMap', tokenCountMap); + if (tokenCountMap[userMessage.messageId]) { + userMessage.tokenCount = tokenCountMap[userMessage.messageId]; + logger.debug('[BaseClient] userMessage', userMessage); + } + + this.handleTokenCountMap(tokenCountMap); } if (!isEdited && !this.skipSaveUserMessage) { - const reqFiles = this.options.req?.body?.files; - if (reqFiles && Array.isArray(this.options.attachments)) { - const files = buildMessageFiles(reqFiles, this.options.attachments); - if (files.length > 0) { - userMessage.files = files; - } - delete userMessage.image_urls; - } - userMessagePromise = this.saveMessageToDatabase(userMessage, saveOptions, user).catch( - (err) => { - logger.error('[BaseClient] Failed to save user message:', err); - return {}; - }, - ); + userMessagePromise = this.saveMessageToDatabase(userMessage, saveOptions, user); this.savedMessageIds.add(userMessage.messageId); if (typeof opts?.getReqData === 'function') { opts.getReqData({ @@ -511,28 +683,18 @@ class BaseClient { balanceConfig?.enabled && supportsBalanceCheck[this.options.endpointType ?? this.options.endpoint] ) { - await checkBalance( - { - req: this.options.req, - res: this.options.res, - txData: { - user: this.user, - tokenType: 'prompt', - amount: promptTokens, - endpoint: this.options.endpoint, - model: this.modelOptions?.model ?? this.model, - endpointTokenConfig: this.options.endpointTokenConfig, - }, + await checkBalance({ + req: this.options.req, + res: this.options.res, + txData: { + user: this.user, + tokenType: 'prompt', + amount: promptTokens, + endpoint: this.options.endpoint, + model: this.modelOptions?.model ?? this.model, + endpointTokenConfig: this.options.endpointTokenConfig, }, - { - logViolation, - getMultiplier: db.getMultiplier, - findBalanceByUser: db.findBalanceByUser, - createAutoRefillTransaction: db.createAutoRefillTransaction, - balanceConfig, - upsertBalanceFields: db.upsertBalanceFields, - }, - ); + }); } const { completion, metadata } = await this.sendCompletion(payload, opts); @@ -585,7 +747,12 @@ class BaseClient { responseMessage.text = completion.join(''); } - if (tokenCountMap && this.recordTokenUsage && this.getTokenCountForResponse) { + if ( + tokenCountMap && + this.recordTokenUsage && + this.getTokenCountForResponse && + this.getTokenCount + ) { let completionTokens; /** @@ -598,6 +765,13 @@ class BaseClient { if (usage != null && Number(usage[this.outputTokensKey]) > 0) { responseMessage.tokenCount = usage[this.outputTokensKey]; completionTokens = responseMessage.tokenCount; + await this.updateUserMessageTokenCount({ + usage, + tokenCountMap, + userMessage, + userMessagePromise, + opts, + }); } else { responseMessage.tokenCount = this.getTokenCountForResponse(responseMessage); completionTokens = responseMessage.tokenCount; @@ -606,45 +780,15 @@ class BaseClient { promptTokens, completionTokens, balance: balanceConfig, - /** Note: When using agents, responseMessage.model is the agent ID, not the model */ - model: this.model, - messageId: this.responseMessageId, + model: responseMessage.model, }); } - - logger.debug('[BaseClient] Response token usage', { - messageId: responseMessage.messageId, - model: responseMessage.model, - promptTokens, - completionTokens, - }); } if (userMessagePromise) { await userMessagePromise; } - if ( - this.contextMeta?.calibrationRatio > 0 && - this.contextMeta.calibrationRatio !== 1 && - userMessage.tokenCount > 0 - ) { - const calibrated = Math.round(userMessage.tokenCount * this.contextMeta.calibrationRatio); - if (calibrated !== userMessage.tokenCount) { - logger.debug('[BaseClient] Calibrated user message tokenCount', { - messageId: userMessage.messageId, - raw: userMessage.tokenCount, - calibrated, - ratio: this.contextMeta.calibrationRatio, - }); - userMessage.tokenCount = calibrated; - await this.updateMessageInDatabase({ - messageId: userMessage.messageId, - tokenCount: calibrated, - }); - } - } - if (this.artifactPromises) { responseMessage.attachments = (await Promise.all(this.artifactPromises)).filter((a) => a); } @@ -657,10 +801,6 @@ class BaseClient { } } - if (this.contextMeta) { - responseMessage.contextMeta = this.contextMeta; - } - responseMessage.databasePromise = this.saveMessageToDatabase( responseMessage, saveOptions, @@ -671,10 +811,79 @@ class BaseClient { return responseMessage; } + /** + * Stream usage should only be used for user message token count re-calculation if: + * - The stream usage is available, with input tokens greater than 0, + * - the client provides a function to calculate the current token count, + * - files are being resent with every message (default behavior; or if `false`, with no attachments), + * - the `promptPrefix` (custom instructions) is not set. + * + * In these cases, the legacy token estimations would be more accurate. + * + * TODO: included system messages in the `orderedMessages` accounting, potentially as a + * separate message in the UI. ChatGPT does this through "hidden" system messages. + * @param {object} params + * @param {StreamUsage} params.usage + * @param {Record} params.tokenCountMap + * @param {TMessage} params.userMessage + * @param {Promise} params.userMessagePromise + * @param {object} params.opts + */ + async updateUserMessageTokenCount({ + usage, + tokenCountMap, + userMessage, + userMessagePromise, + opts, + }) { + /** @type {boolean} */ + const shouldUpdateCount = + this.calculateCurrentTokenCount != null && + Number(usage[this.inputTokensKey]) > 0 && + (this.options.resendFiles || + (!this.options.resendFiles && !this.options.attachments?.length)) && + !this.options.promptPrefix; + + if (!shouldUpdateCount) { + return; + } + + const userMessageTokenCount = this.calculateCurrentTokenCount({ + currentMessageId: userMessage.messageId, + tokenCountMap, + usage, + }); + + if (userMessageTokenCount === userMessage.tokenCount) { + return; + } + + userMessage.tokenCount = userMessageTokenCount; + /* + Note: `AgentController` saves the user message if not saved here + (noted by `savedMessageIds`), so we update the count of its `userMessage` reference + */ + if (typeof opts?.getReqData === 'function') { + opts.getReqData({ + userMessage, + }); + } + /* + Note: we update the user message to be sure it gets the calculated token count; + though `AgentController` saves the user message if not saved here + (noted by `savedMessageIds`), EditController does not + */ + await userMessagePromise; + await this.updateMessageInDatabase({ + messageId: userMessage.messageId, + tokenCount: userMessageTokenCount, + }); + } + async loadHistory(conversationId, parentMessageId = null) { logger.debug('[BaseClient] Loading history:', { conversationId, parentMessageId }); - const messages = (await db.getMessages({ conversationId })) ?? []; + const messages = (await getMessages({ conversationId })) ?? []; if (messages.length === 0) { return []; @@ -697,24 +906,10 @@ class BaseClient { return _messages; } + // Find the latest message with a 'summary' property for (let i = _messages.length - 1; i >= 0; i--) { - const msg = _messages[i]; - if (!msg) { - continue; - } - - const summaryBlock = BaseClient.findSummaryContentBlock(msg); - if (summaryBlock) { - this.previous_summary = { - ...msg, - summary: BaseClient.getSummaryText(summaryBlock), - summaryTokenCount: summaryBlock.tokenCount, - }; - break; - } - - if (msg.summary) { - this.previous_summary = msg; + if (_messages[i]?.summary) { + this.previous_summary = _messages[i]; break; } } @@ -739,30 +934,16 @@ class BaseClient { * @param {string | null} user */ async saveMessageToDatabase(message, endpointOptions, user = null) { - // Snapshot options before any await; disposeClient may set client.options = null - // while this method is suspended at an I/O boundary, but the local reference - // remains valid (disposeClient nulls the property, not the object itself). - const options = this.options; - if (!options) { - logger.error('[BaseClient] saveMessageToDatabase: client disposed before save, skipping'); - return {}; - } - if (this.user && user !== this.user) { throw new Error('User mismatch.'); } - const hasAddedConvo = options?.req?.body?.addedConvo != null; - const reqCtx = { - userId: options?.req?.user?.id, - isTemporary: options?.req?.body?.isTemporary, - interfaceConfig: options?.req?.config?.interfaceConfig, - }; - const savedMessage = await db.saveMessage( - reqCtx, + const hasAddedConvo = this.options?.req?.body?.addedConvo != null; + const savedMessage = await saveMessage( + this.options?.req, { ...message, - endpoint: options.endpoint, + endpoint: this.options.endpoint, unfinished: false, user, ...(hasAddedConvo && { addedConvo: true }), @@ -776,20 +957,20 @@ class BaseClient { const fieldsToKeep = { conversationId: message.conversationId, - endpoint: options.endpoint, - endpointType: options.endpointType, + endpoint: this.options.endpoint, + endpointType: this.options.endpointType, ...endpointOptions, }; const existingConvo = this.fetchedConvo === true ? null - : await db.getConvo(options?.req?.user?.id, message.conversationId); + : await getConvo(this.options?.req?.user?.id, message.conversationId); const unsetFields = {}; const exceptions = new Set(['spec', 'iconURL']); const hasNonEphemeralAgent = - isAgentsEndpoint(options.endpoint) && + isAgentsEndpoint(this.options.endpoint) && endpointOptions?.agent_id && !isEphemeralAgentId(endpointOptions.agent_id); if (hasNonEphemeralAgent) { @@ -811,7 +992,7 @@ class BaseClient { } } - const conversation = await db.saveConvo(reqCtx, fieldsToKeep, { + const conversation = await saveConvo(this.options?.req, fieldsToKeep, { context: 'api/app/clients/BaseClient.js - saveMessageToDatabase #saveConvo', unsetFields, }); @@ -824,35 +1005,7 @@ class BaseClient { * @param {Partial} message */ async updateMessageInDatabase(message) { - await db.updateMessage(this.options?.req?.user?.id, message); - } - - /** Extracts text from a summary block (handles both legacy `text` field and new `content` array format). */ - static getSummaryText(summaryBlock) { - if (Array.isArray(summaryBlock.content)) { - return summaryBlock.content.map((b) => b.text ?? '').join(''); - } - if (typeof summaryBlock.content === 'string') { - return summaryBlock.content; - } - return summaryBlock.text ?? ''; - } - - /** Finds the last summary content block in a message's content array (last-summary-wins). */ - static findSummaryContentBlock(message) { - if (!Array.isArray(message?.content)) { - return null; - } - let lastSummary = null; - for (const part of message.content) { - if ( - part?.type === ContentTypes.SUMMARY && - BaseClient.getSummaryText(part).trim().length > 0 - ) { - lastSummary = part; - } - } - return lastSummary; + await updateMessage(this.options.req, message); } /** @@ -909,35 +1062,20 @@ class BaseClient { break; } - let resolved = message; - let hasSummary = false; - if (summary) { - const summaryBlock = BaseClient.findSummaryContentBlock(message); - if (summaryBlock) { - const summaryText = BaseClient.getSummaryText(summaryBlock); - resolved = { - ...message, - role: 'system', - content: [{ type: ContentTypes.TEXT, text: summaryText }], - tokenCount: summaryBlock.tokenCount, - }; - hasSummary = true; - } else if (message.summary) { - resolved = { - ...message, - role: 'system', - content: [{ type: ContentTypes.TEXT, text: message.summary }], - tokenCount: message.summaryTokenCount ?? message.tokenCount, - }; - hasSummary = true; - } + if (summary && message.summary) { + message.role = 'system'; + message.text = message.summary; } - const shouldMap = mapMethod != null && (mapCondition != null ? mapCondition(resolved) : true); - const processedMessage = shouldMap ? mapMethod(resolved) : resolved; + if (summary && message.summaryTokenCount) { + message.tokenCount = message.summaryTokenCount; + } + + const shouldMap = mapMethod != null && (mapCondition != null ? mapCondition(message) : true); + const processedMessage = shouldMap ? mapMethod(message) : message; orderedMessages.push(processedMessage); - if (hasSummary) { + if (summary && message.summary) { break; } @@ -1093,7 +1231,6 @@ class BaseClient { provider: this.options.agent?.provider ?? this.options.endpoint, endpoint: this.options.agent?.endpoint ?? this.options.endpoint, useResponsesApi: this.options.agent?.model_parameters?.useResponsesApi, - model: this.modelOptions?.model ?? this.model, }, getStrategyFunctions, ); @@ -1163,19 +1300,6 @@ class BaseClient { const allFiles = []; - const provider = this.options.agent?.provider ?? this.options.endpoint; - const isBedrock = provider === EModelEndpoint.bedrock; - - if (!this._mergedFileConfig && this.options.req?.config?.fileConfig) { - this._mergedFileConfig = mergeFileConfig(this.options.req.config.fileConfig); - const endpoint = this.options.agent?.endpoint ?? this.options.endpoint; - this._endpointFileConfig = getEndpointFileConfig({ - fileConfig: this._mergedFileConfig, - endpoint, - endpointType: this.options.endpointType, - }); - } - for (const file of attachments) { /** @type {FileSources} */ const source = file.source ?? FileSources.local; @@ -1193,23 +1317,12 @@ class BaseClient { } else if (file.type === 'application/pdf') { categorizedAttachments.documents.push(file); allFiles.push(file); - } else if (isBedrock && isBedrockDocumentType(file.type)) { - categorizedAttachments.documents.push(file); - allFiles.push(file); } else if (file.type.startsWith('video/')) { categorizedAttachments.videos.push(file); allFiles.push(file); } else if (file.type.startsWith('audio/')) { categorizedAttachments.audios.push(file); allFiles.push(file); - } else if ( - file.type && - this._mergedFileConfig && - this._endpointFileConfig?.supportedMimeTypes && - this._mergedFileConfig.checkType(file.type, this._endpointFileConfig.supportedMimeTypes) - ) { - categorizedAttachments.documents.push(file); - allFiles.push(file); } } @@ -1286,7 +1399,7 @@ class BaseClient { return message; } - const files = await db.getFiles( + const files = await getFiles( { file_id: { $in: fileIds }, }, diff --git a/api/app/clients/prompts/truncate.js b/api/app/clients/prompts/truncate.js index e744b40daa..564b39efeb 100644 --- a/api/app/clients/prompts/truncate.js +++ b/api/app/clients/prompts/truncate.js @@ -37,4 +37,79 @@ function smartTruncateText(text, maxLength = MAX_CHAR) { return text; } -module.exports = { truncateText, smartTruncateText }; +/** + * @param {TMessage[]} _messages + * @param {number} maxContextTokens + * @param {function({role: string, content: TMessageContent[]}): number} getTokenCountForMessage + * + * @returns {{ + * dbMessages: TMessage[], + * editedIndices: number[] + * }} + */ +function truncateToolCallOutputs(_messages, maxContextTokens, getTokenCountForMessage) { + const THRESHOLD_PERCENTAGE = 0.5; + const targetTokenLimit = maxContextTokens * THRESHOLD_PERCENTAGE; + + let currentTokenCount = 3; + const messages = [..._messages]; + const processedMessages = []; + let currentIndex = messages.length; + const editedIndices = new Set(); + while (messages.length > 0) { + currentIndex--; + const message = messages.pop(); + currentTokenCount += message.tokenCount; + if (currentTokenCount < targetTokenLimit) { + processedMessages.push(message); + continue; + } + + if (!message.content || !Array.isArray(message.content)) { + processedMessages.push(message); + continue; + } + + const toolCallIndices = message.content + .map((item, index) => (item.type === 'tool_call' ? index : -1)) + .filter((index) => index !== -1) + .reverse(); + + if (toolCallIndices.length === 0) { + processedMessages.push(message); + continue; + } + + const newContent = [...message.content]; + + // Truncate all tool outputs since we're over threshold + for (const index of toolCallIndices) { + const toolCall = newContent[index].tool_call; + if (!toolCall || !toolCall.output) { + continue; + } + + editedIndices.add(currentIndex); + + newContent[index] = { + ...newContent[index], + tool_call: { + ...toolCall, + output: '[OUTPUT_OMITTED_FOR_BREVITY]', + }, + }; + } + + const truncatedMessage = { + ...message, + content: newContent, + tokenCount: getTokenCountForMessage({ role: 'assistant', content: newContent }), + }; + + processedMessages.push(truncatedMessage); + } + + return { dbMessages: processedMessages.reverse(), editedIndices: Array.from(editedIndices) }; +} + +module.exports = { truncateText, smartTruncateText, truncateToolCallOutputs }; diff --git a/api/app/clients/specs/BaseClient.test.js b/api/app/clients/specs/BaseClient.test.js index 3ce910948c..fed80de28c 100644 --- a/api/app/clients/specs/BaseClient.test.js +++ b/api/app/clients/specs/BaseClient.test.js @@ -38,7 +38,7 @@ jest.mock('~/models', () => ({ updateFileUsage: jest.fn(), })); -const { getConvo, saveConvo, saveMessage } = require('~/models'); +const { getConvo, saveConvo } = require('~/models'); jest.mock('@librechat/agents', () => { const actual = jest.requireActual('@librechat/agents'); @@ -355,8 +355,7 @@ describe('BaseClient', () => { id: '3', parentMessageId: '2', role: 'system', - text: 'Message 3', - content: [{ type: 'text', text: 'Summary for Message 3' }], + text: 'Summary for Message 3', summary: 'Summary for Message 3', }, { id: '4', parentMessageId: '3', text: 'Message 4' }, @@ -381,8 +380,7 @@ describe('BaseClient', () => { id: '4', parentMessageId: '3', role: 'system', - text: 'Message 4', - content: [{ type: 'text', text: 'Summary for Message 4' }], + text: 'Summary for Message 4', summary: 'Summary for Message 4', }, { id: '5', parentMessageId: '4', text: 'Message 5' }, @@ -407,123 +405,12 @@ describe('BaseClient', () => { id: '4', parentMessageId: '3', role: 'system', - text: 'Message 4', - content: [{ type: 'text', text: 'Summary for Message 4' }], + text: 'Summary for Message 4', summary: 'Summary for Message 4', }, { id: '5', parentMessageId: '4', text: 'Message 5' }, ]); }); - - it('should detect summary content block and use it over legacy fields (summary mode)', () => { - const messagesWithContentBlock = [ - { id: '3', parentMessageId: '2', text: 'Message 3' }, - { - id: '2', - parentMessageId: '1', - text: 'Message 2', - content: [ - { type: 'text', text: 'Original text' }, - { type: 'summary', text: 'Content block summary', tokenCount: 42 }, - ], - }, - { id: '1', parentMessageId: null, text: 'Message 1' }, - ]; - const result = TestClient.constructor.getMessagesForConversation({ - messages: messagesWithContentBlock, - parentMessageId: '3', - summary: true, - }); - expect(result).toHaveLength(2); - expect(result[0].role).toBe('system'); - expect(result[0].content).toEqual([{ type: 'text', text: 'Content block summary' }]); - expect(result[0].tokenCount).toBe(42); - }); - - it('should prefer content block summary over legacy summary field', () => { - const messagesWithBoth = [ - { id: '2', parentMessageId: '1', text: 'Message 2' }, - { - id: '1', - parentMessageId: null, - text: 'Message 1', - summary: 'Legacy summary', - summaryTokenCount: 10, - content: [{ type: 'summary', text: 'Content block summary', tokenCount: 20 }], - }, - ]; - const result = TestClient.constructor.getMessagesForConversation({ - messages: messagesWithBoth, - parentMessageId: '2', - summary: true, - }); - expect(result).toHaveLength(2); - expect(result[0].content).toEqual([{ type: 'text', text: 'Content block summary' }]); - expect(result[0].tokenCount).toBe(20); - }); - - it('should fallback to legacy summary when no content block exists', () => { - const messagesWithLegacy = [ - { id: '2', parentMessageId: '1', text: 'Message 2' }, - { - id: '1', - parentMessageId: null, - text: 'Message 1', - summary: 'Legacy summary only', - summaryTokenCount: 15, - }, - ]; - const result = TestClient.constructor.getMessagesForConversation({ - messages: messagesWithLegacy, - parentMessageId: '2', - summary: true, - }); - expect(result).toHaveLength(2); - expect(result[0].content).toEqual([{ type: 'text', text: 'Legacy summary only' }]); - expect(result[0].tokenCount).toBe(15); - }); - }); - - describe('findSummaryContentBlock', () => { - it('should find a summary block in the content array', () => { - const message = { - content: [ - { type: 'text', text: 'some text' }, - { type: 'summary', text: 'Summary of conversation', tokenCount: 50 }, - ], - }; - const result = TestClient.constructor.findSummaryContentBlock(message); - expect(result).toBeTruthy(); - expect(result.text).toBe('Summary of conversation'); - expect(result.tokenCount).toBe(50); - }); - - it('should return null when no summary block exists', () => { - const message = { - content: [ - { type: 'text', text: 'some text' }, - { type: 'tool_call', tool_call: {} }, - ], - }; - expect(TestClient.constructor.findSummaryContentBlock(message)).toBeNull(); - }); - - it('should return null for string content', () => { - const message = { content: 'just a string' }; - expect(TestClient.constructor.findSummaryContentBlock(message)).toBeNull(); - }); - - it('should return null for missing content', () => { - expect(TestClient.constructor.findSummaryContentBlock({})).toBeNull(); - expect(TestClient.constructor.findSummaryContentBlock(null)).toBeNull(); - }); - - it('should skip summary blocks with no text', () => { - const message = { - content: [{ type: 'summary', tokenCount: 10 }], - }; - expect(TestClient.constructor.findSummaryContentBlock(message)).toBeNull(); - }); }); describe('sendMessage', () => { @@ -906,52 +793,6 @@ describe('BaseClient', () => { ); }); - test('saveMessageToDatabase returns early when this.options is null (client disposed)', async () => { - const savedOptions = TestClient.options; - TestClient.options = null; - saveMessage.mockClear(); - - const result = await TestClient.saveMessageToDatabase( - { messageId: 'msg-1', conversationId: 'conv-1', isCreatedByUser: true, text: 'hi' }, - {}, - null, - ); - - expect(result).toEqual({}); - expect(saveMessage).not.toHaveBeenCalled(); - - TestClient.options = savedOptions; - }); - - test('saveMessageToDatabase uses snapshot of options, immune to mid-await disposal', async () => { - const savedOptions = TestClient.options; - saveMessage.mockClear(); - saveConvo.mockClear(); - - // Make db.saveMessage yield, simulating I/O suspension during which disposal occurs - saveMessage.mockImplementation(async (_reqCtx, msgData) => { - // Simulate disposeClient nullifying client.options while awaiting - TestClient.options = null; - return msgData; - }); - saveConvo.mockResolvedValue({ conversationId: 'conv-1' }); - - const result = await TestClient.saveMessageToDatabase( - { messageId: 'msg-1', conversationId: 'conv-1', isCreatedByUser: true, text: 'hi' }, - { endpoint: 'openAI' }, - null, - ); - - // Should complete without TypeError, using the snapshotted options - expect(result).toHaveProperty('message'); - expect(result).toHaveProperty('conversation'); - expect(saveMessage).toHaveBeenCalled(); - - TestClient.options = savedOptions; - saveMessage.mockReset(); - saveConvo.mockReset(); - }); - test('userMessagePromise is awaited before saving response message', async () => { // Mock the saveMessageToDatabase method TestClient.saveMessageToDatabase = jest.fn().mockImplementation(() => { @@ -980,56 +821,6 @@ describe('BaseClient', () => { }); }); - describe('recordTokenUsage model assignment', () => { - test('should pass this.model to recordTokenUsage, not the agent ID from responseMessage.model', async () => { - const actualModel = 'claude-opus-4-5'; - const agentId = 'agent_p5Z_IU6EIxBoqn1BoqLBp'; - - TestClient.model = actualModel; - TestClient.options.endpoint = 'agents'; - TestClient.options.agent = { id: agentId }; - - TestClient.getTokenCountForResponse = jest.fn().mockReturnValue(50); - TestClient.recordTokenUsage = jest.fn().mockResolvedValue(undefined); - TestClient.buildMessages.mockReturnValue({ - prompt: [], - tokenCountMap: { res: 50 }, - }); - - await TestClient.sendMessage('Hello', {}); - - expect(TestClient.recordTokenUsage).toHaveBeenCalledWith( - expect.objectContaining({ - model: actualModel, - }), - ); - - const callArgs = TestClient.recordTokenUsage.mock.calls[0][0]; - expect(callArgs.model).not.toBe(agentId); - }); - - test('should pass this.model even when this.model differs from modelOptions.model', async () => { - const instanceModel = 'gpt-4o'; - TestClient.model = instanceModel; - TestClient.modelOptions = { model: 'gpt-4o-mini' }; - - TestClient.getTokenCountForResponse = jest.fn().mockReturnValue(50); - TestClient.recordTokenUsage = jest.fn().mockResolvedValue(undefined); - TestClient.buildMessages.mockReturnValue({ - prompt: [], - tokenCountMap: { res: 50 }, - }); - - await TestClient.sendMessage('Hello', {}); - - expect(TestClient.recordTokenUsage).toHaveBeenCalledWith( - expect.objectContaining({ - model: instanceModel, - }), - ); - }); - }); - describe('getMessagesWithinTokenLimit with instructions', () => { test('should always include instructions when present', async () => { TestClient.maxContextTokens = 50; @@ -1137,123 +928,4 @@ describe('BaseClient', () => { expect(result.remainingContextTokens).toBe(2); // 25 - 20 - 3(assistant label) }); }); - - describe('sendMessage file population', () => { - const attachment = { - file_id: 'file-abc', - filename: 'image.png', - filepath: '/uploads/image.png', - type: 'image/png', - bytes: 1024, - object: 'file', - user: 'user-1', - embedded: false, - usage: 0, - text: 'large ocr blob that should be stripped', - _id: 'mongo-id-1', - }; - - beforeEach(() => { - TestClient.options.req = { body: { files: [{ file_id: 'file-abc' }] } }; - TestClient.options.attachments = [attachment]; - }); - - test('populates userMessage.files before saveMessageToDatabase is called', async () => { - TestClient.saveMessageToDatabase = jest.fn().mockImplementation((msg) => { - return Promise.resolve({ message: msg }); - }); - - await TestClient.sendMessage('Hello'); - - const userSave = TestClient.saveMessageToDatabase.mock.calls.find( - ([msg]) => msg.isCreatedByUser, - ); - expect(userSave).toBeDefined(); - expect(userSave[0].files).toBeDefined(); - expect(userSave[0].files).toHaveLength(1); - expect(userSave[0].files[0].file_id).toBe('file-abc'); - }); - - test('strips text and _id from files before saving', async () => { - TestClient.saveMessageToDatabase = jest.fn().mockResolvedValue({ message: {} }); - - await TestClient.sendMessage('Hello'); - - const userSave = TestClient.saveMessageToDatabase.mock.calls.find( - ([msg]) => msg.isCreatedByUser, - ); - expect(userSave[0].files[0].text).toBeUndefined(); - expect(userSave[0].files[0]._id).toBeUndefined(); - expect(userSave[0].files[0].filename).toBe('image.png'); - }); - - test('deletes image_urls from userMessage when files are present', async () => { - TestClient.saveMessageToDatabase = jest.fn().mockResolvedValue({ message: {} }); - TestClient.options.attachments = [ - { ...attachment, image_urls: ['data:image/png;base64,...'] }, - ]; - - await TestClient.sendMessage('Hello'); - - const userSave = TestClient.saveMessageToDatabase.mock.calls.find( - ([msg]) => msg.isCreatedByUser, - ); - expect(userSave[0].image_urls).toBeUndefined(); - }); - - test('does not set files when no attachments match request file IDs', async () => { - TestClient.options.req = { body: { files: [{ file_id: 'file-nomatch' }] } }; - TestClient.saveMessageToDatabase = jest.fn().mockResolvedValue({ message: {} }); - - await TestClient.sendMessage('Hello'); - - const userSave = TestClient.saveMessageToDatabase.mock.calls.find( - ([msg]) => msg.isCreatedByUser, - ); - expect(userSave[0].files).toBeUndefined(); - }); - - test('skips file population when attachments is not an array (Promise case)', async () => { - TestClient.options.attachments = Promise.resolve([attachment]); - TestClient.saveMessageToDatabase = jest.fn().mockResolvedValue({ message: {} }); - - await TestClient.sendMessage('Hello'); - - const userSave = TestClient.saveMessageToDatabase.mock.calls.find( - ([msg]) => msg.isCreatedByUser, - ); - expect(userSave[0].files).toBeUndefined(); - }); - - test('skips file population when skipSaveUserMessage is true', async () => { - TestClient.skipSaveUserMessage = true; - TestClient.saveMessageToDatabase = jest.fn().mockResolvedValue({ message: {} }); - - await TestClient.sendMessage('Hello'); - - const userSave = TestClient.saveMessageToDatabase.mock.calls.find( - ([msg]) => msg?.isCreatedByUser, - ); - expect(userSave).toBeUndefined(); - }); - - test('ignores file_id: undefined entries in req.body.files (no set poisoning)', async () => { - TestClient.options.req = { - body: { files: [{ file_id: undefined }, { file_id: 'file-abc' }] }, - }; - TestClient.options.attachments = [ - { ...attachment, file_id: undefined }, - { ...attachment, file_id: 'file-abc' }, - ]; - TestClient.saveMessageToDatabase = jest.fn().mockResolvedValue({ message: {} }); - - await TestClient.sendMessage('Hello'); - - const userSave = TestClient.saveMessageToDatabase.mock.calls.find( - ([msg]) => msg.isCreatedByUser, - ); - expect(userSave[0].files).toHaveLength(1); - expect(userSave[0].files[0].file_id).toBe('file-abc'); - }); - }); }); diff --git a/api/app/clients/tools/manifest.json b/api/app/clients/tools/manifest.json index 9637c20867..7930e67ac9 100644 --- a/api/app/clients/tools/manifest.json +++ b/api/app/clients/tools/manifest.json @@ -16,7 +16,7 @@ "name": "Google", "pluginKey": "google", "description": "Use Google Search to find information about the weather, news, sports, and more.", - "icon": "assets/google-search.svg", + "icon": "https://i.imgur.com/SMmVkNB.png", "authConfig": [ { "authField": "GOOGLE_CSE_ID", @@ -61,7 +61,7 @@ "name": "DALL-E-3", "pluginKey": "dalle", "description": "[DALL-E-3] Create realistic images and art from a description in natural language", - "icon": "assets/openai.svg", + "icon": "https://i.imgur.com/u2TzXzH.png", "authConfig": [ { "authField": "DALLE3_API_KEY||DALLE_API_KEY", @@ -74,7 +74,7 @@ "name": "Tavily Search", "pluginKey": "tavily_search_results_json", "description": "Tavily Search is a robust search API tailored for LLM Agents. It seamlessly integrates with diverse data sources to ensure a superior, relevant search experience.", - "icon": "assets/tavily.svg", + "icon": "https://tavily.com/favicon.ico", "authConfig": [ { "authField": "TAVILY_API_KEY", @@ -87,14 +87,14 @@ "name": "Calculator", "pluginKey": "calculator", "description": "Perform simple and complex mathematical calculations.", - "icon": "assets/calculator.svg", + "icon": "https://i.imgur.com/RHsSG5h.png", "authConfig": [] }, { "name": "Stable Diffusion", "pluginKey": "stable-diffusion", "description": "Generate photo-realistic images given any text input.", - "icon": "assets/stability-ai.svg", + "icon": "https://i.imgur.com/Yr466dp.png", "authConfig": [ { "authField": "SD_WEBUI_URL", @@ -107,7 +107,7 @@ "name": "Azure AI Search", "pluginKey": "azure-ai-search", "description": "Use Azure AI Search to find information", - "icon": "assets/azure-ai-search.svg", + "icon": "https://i.imgur.com/E7crPze.png", "authConfig": [ { "authField": "AZURE_AI_SEARCH_SERVICE_ENDPOINT", @@ -143,7 +143,7 @@ "name": "Flux", "pluginKey": "flux", "description": "Generate images using text with the Flux API.", - "icon": "assets/bfl-ai.svg", + "icon": "https://blackforestlabs.ai/wp-content/uploads/2024/07/bfl_logo_retraced_blk.png", "isAuthRequired": "true", "authConfig": [ { @@ -156,14 +156,14 @@ { "name": "Gemini Image Tools", "pluginKey": "gemini_image_gen", + "toolkit": true, "description": "Generate high-quality images using Google's Gemini Image Models. Supports Gemini API or Vertex AI.", "icon": "assets/gemini_image_gen.svg", "authConfig": [ { - "authField": "GEMINI_API_KEY||GOOGLE_KEY||GOOGLE_SERVICE_KEY_FILE", - "label": "Gemini API Key (optional)", - "description": "Your Google Gemini API Key from Google AI Studio. Leave blank to use Vertex AI with a service account (GOOGLE_SERVICE_KEY_FILE or api/data/auth.json).", - "optional": true + "authField": "GEMINI_API_KEY||GOOGLE_KEY||GEMINI_VERTEX_ENABLED", + "label": "Gemini API Key (Optional if Vertex AI is configured)", + "description": "Your Google Gemini API Key from Google AI Studio. Leave blank if using Vertex AI with service account." } ] } diff --git a/api/app/clients/tools/structured/DALLE3.js b/api/app/clients/tools/structured/DALLE3.js index c48db1d764..26610f73ba 100644 --- a/api/app/clients/tools/structured/DALLE3.js +++ b/api/app/clients/tools/structured/DALLE3.js @@ -51,10 +51,6 @@ 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 f8341f7904..56f86a707d 100644 --- a/api/app/clients/tools/structured/FluxAPI.js +++ b/api/app/clients/tools/structured/FluxAPI.js @@ -113,10 +113,6 @@ 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) { @@ -528,40 +524,10 @@ 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({ @@ -575,6 +541,12 @@ 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/GeminiImageGen.js b/api/app/clients/tools/structured/GeminiImageGen.js index f197f1d41b..c0e5a0ce1d 100644 --- a/api/app/clients/tools/structured/GeminiImageGen.js +++ b/api/app/clients/tools/structured/GeminiImageGen.js @@ -1,3 +1,4 @@ +const fs = require('fs'); const path = require('path'); const sharp = require('sharp'); const { v4 } = require('uuid'); @@ -5,7 +6,12 @@ const { ProxyAgent } = require('undici'); const { GoogleGenAI } = require('@google/genai'); const { tool } = require('@langchain/core/tools'); const { logger } = require('@librechat/data-schemas'); -const { ContentTypes, EImageOutputType } = require('librechat-data-provider'); +const { + FileContext, + ContentTypes, + FileSources, + EImageOutputType, +} = require('librechat-data-provider'); const { geminiToolkit, loadServiceKey, @@ -13,7 +19,8 @@ const { getTransactionsConfig, } = require('@librechat/api'); const { getStrategyFunctions } = require('~/server/services/Files/strategies'); -const { spendTokens, getFiles } = require('~/models'); +const { spendTokens } = require('~/models/spendTokens'); +const { getFiles } = require('~/models/File'); /** * Configure proxy support for Google APIs @@ -52,12 +59,17 @@ const displayMessage = * @returns {string} - The processed string */ function replaceUnwantedChars(inputString) { - return ( - inputString - ?.replace(/\r\n|\r|\n/g, ' ') - .replace(/"/g, '') - .trim() || '' - ); + return inputString?.replace(/[^\w\s\-_.,!?()]/g, '') || ''; +} + +/** + * Validate and sanitize image format + * @param {string} format - The format to validate + * @returns {string} - Safe format + */ +function getSafeFormat(format) { + const allowedFormats = ['png', 'jpg', 'jpeg', 'webp', 'gif']; + return allowedFormats.includes(format?.toLowerCase()) ? format.toLowerCase() : 'png'; } /** @@ -105,8 +117,11 @@ async function initializeGeminiClient(options = {}) { return new GoogleGenAI({ apiKey: googleKey }); } + // Fall back to Vertex AI with service account logger.debug('[GeminiImageGen] Using Vertex AI with service account'); const credentialsPath = getDefaultServiceKeyPath(); + + // Use loadServiceKey for consistent loading (supports file paths, JSON strings, base64) const serviceKey = await loadServiceKey(credentialsPath); if (!serviceKey || !serviceKey.project_id) { @@ -116,14 +131,75 @@ async function initializeGeminiClient(options = {}) { ); } + // Set GOOGLE_APPLICATION_CREDENTIALS for any Google Cloud SDK dependencies + try { + await fs.promises.access(credentialsPath); + process.env.GOOGLE_APPLICATION_CREDENTIALS = credentialsPath; + } catch { + // File doesn't exist, skip setting env var + } + return new GoogleGenAI({ vertexai: true, project: serviceKey.project_id, location: process.env.GOOGLE_LOC || process.env.GOOGLE_CLOUD_LOCATION || 'global', - googleAuthOptions: { credentials: serviceKey }, }); } +/** + * Save image to local filesystem + * @param {string} base64Data - Base64 encoded image data + * @param {string} format - Image format + * @param {string} userId - User ID + * @returns {Promise} - The relative URL + */ +async function saveImageLocally(base64Data, format, userId) { + const safeFormat = getSafeFormat(format); + const safeUserId = userId ? path.basename(userId) : 'default'; + const imageName = `gemini-img-${v4()}.${safeFormat}`; + const userDir = path.join(process.cwd(), 'client/public/images', safeUserId); + + await fs.promises.mkdir(userDir, { recursive: true }); + + const filePath = path.join(userDir, imageName); + await fs.promises.writeFile(filePath, Buffer.from(base64Data, 'base64')); + + logger.debug('[GeminiImageGen] Image saved locally to:', filePath); + return `/images/${safeUserId}/${imageName}`; +} + +/** + * Save image to cloud storage + * @param {Object} params - Parameters + * @returns {Promise} - The storage URL or null + */ +async function saveToCloudStorage({ base64Data, format, processFileURL, fileStrategy, userId }) { + if (!processFileURL || !fileStrategy || !userId) { + return null; + } + + try { + const safeFormat = getSafeFormat(format); + const safeUserId = path.basename(userId); + const dataURL = `data:image/${safeFormat};base64,${base64Data}`; + const imageName = `gemini-img-${v4()}.${safeFormat}`; + + const result = await processFileURL({ + URL: dataURL, + basePath: 'images', + userId: safeUserId, + fileName: imageName, + fileStrategy, + context: FileContext.image_generation, + }); + + return result.filepath; + } catch (error) { + logger.error('[GeminiImageGen] Error saving to cloud storage:', error); + return null; + } +} + /** * Convert image files to Gemini inline data format * @param {Object} params - Parameters @@ -250,9 +326,8 @@ function checkForSafetyBlock(response) { * @param {string} params.userId - The user ID * @param {string} params.conversationId - The conversation ID * @param {string} params.model - The model name - * @param {string} [params.messageId] - The response message ID for transaction correlation */ -async function recordTokenUsage({ usageMetadata, req, userId, conversationId, model, messageId }) { +async function recordTokenUsage({ usageMetadata, req, userId, conversationId, model }) { if (!usageMetadata) { logger.debug('[GeminiImageGen] No usage metadata available for balance tracking'); return; @@ -288,7 +363,6 @@ async function recordTokenUsage({ usageMetadata, req, userId, conversationId, mo { user: userId, model, - messageId, conversationId, context: 'image_generation', balance, @@ -316,18 +390,34 @@ function createGeminiImageTool(fields = {}) { throw new Error('This tool is only available for agents.'); } - const { req, imageFiles = [], userId, fileStrategy, GEMINI_API_KEY, GOOGLE_KEY } = fields; + // Skip validation during tool creation - validation happens at runtime in initializeGeminiClient + // This allows the tool to be added to agents when using Vertex AI without requiring API keys + // The actual credentials check happens when the tool is invoked + + const { + req, + imageFiles = [], + processFileURL, + userId, + fileStrategy, + GEMINI_API_KEY, + GOOGLE_KEY, + // GEMINI_VERTEX_ENABLED is used for auth validation only (not used in code) + // When set as env var, it signals Vertex AI is configured and bypasses API key requirement + } = fields; const imageOutputType = fields.imageOutputType || EImageOutputType.PNG; const geminiImageGenTool = tool( - async ({ prompt, image_ids, aspectRatio, imageSize }, runnableConfig) => { + async ({ prompt, image_ids, aspectRatio, imageSize }, _runnableConfig) => { if (!prompt) { throw new Error('Missing required field: prompt'); } - logger.debug('[GeminiImageGen] Generating image', { aspectRatio, imageSize }); + logger.debug('[GeminiImageGen] Generating image with prompt:', prompt?.substring(0, 100)); + logger.debug('[GeminiImageGen] Options:', { aspectRatio, imageSize }); + // Initialize Gemini client with user-provided credentials let ai; try { ai = await initializeGeminiClient({ @@ -342,8 +432,10 @@ function createGeminiImageTool(fields = {}) { ]; } + // Build request contents const contents = [{ text: replaceUnwantedChars(prompt) }]; + // Add context images if provided if (image_ids?.length > 0) { const contextImages = await convertImagesToInlineData({ imageFiles, @@ -355,34 +447,28 @@ function createGeminiImageTool(fields = {}) { logger.debug('[GeminiImageGen] Added', contextImages.length, 'context images'); } + // Generate image let apiResponse; const geminiModel = process.env.GEMINI_IMAGE_MODEL || 'gemini-2.5-flash-image'; - const config = { - responseModalities: ['TEXT', 'IMAGE'], - }; - - const supportsImageSize = !geminiModel.includes('gemini-2.5-flash-image'); - if (aspectRatio || (imageSize && supportsImageSize)) { - config.imageConfig = {}; - if (aspectRatio) { - config.imageConfig.aspectRatio = aspectRatio; - } - if (imageSize && supportsImageSize) { - config.imageConfig.imageSize = imageSize; - } - } - - let derivedSignal = null; - let abortHandler = null; - - if (runnableConfig?.signal) { - derivedSignal = AbortSignal.any([runnableConfig.signal]); - abortHandler = () => logger.debug('[GeminiImageGen] Image generation aborted'); - derivedSignal.addEventListener('abort', abortHandler, { once: true }); - config.abortSignal = derivedSignal; - } - try { + // Build config with optional imageConfig + const config = { + responseModalities: ['TEXT', 'IMAGE'], + }; + + // Add imageConfig if aspectRatio or imageSize is specified + // Note: gemini-2.5-flash-image doesn't support imageSize + const supportsImageSize = !geminiModel.includes('gemini-2.5-flash-image'); + if (aspectRatio || (imageSize && supportsImageSize)) { + config.imageConfig = {}; + if (aspectRatio) { + config.imageConfig.aspectRatio = aspectRatio; + } + if (imageSize && supportsImageSize) { + config.imageConfig.imageSize = imageSize; + } + } + apiResponse = await ai.models.generateContent({ model: geminiModel, contents, @@ -394,12 +480,9 @@ function createGeminiImageTool(fields = {}) { [{ type: ContentTypes.TEXT, text: `Image generation failed: ${error.message}` }], { content: [], file_ids: [] }, ]; - } finally { - if (abortHandler && derivedSignal) { - derivedSignal.removeEventListener('abort', abortHandler); - } } + // Check for safety blocks const safetyBlock = checkForSafetyBlock(apiResponse); if (safetyBlock) { logger.warn('[GeminiImageGen] Safety block:', safetyBlock); @@ -426,7 +509,46 @@ function createGeminiImageTool(fields = {}) { const imageData = convertedBuffer.toString('base64'); const mimeType = outputFormat === 'jpeg' ? 'image/jpeg' : `image/${outputFormat}`; + logger.debug('[GeminiImageGen] Image format:', { outputFormat, mimeType }); + + let imageUrl; + const useLocalStorage = !fileStrategy || fileStrategy === FileSources.local; + + if (useLocalStorage) { + try { + imageUrl = await saveImageLocally(imageData, outputFormat, userId); + } catch (error) { + logger.error('[GeminiImageGen] Local save failed:', error); + imageUrl = `data:${mimeType};base64,${imageData}`; + } + } else { + const cloudUrl = await saveToCloudStorage({ + base64Data: imageData, + format: outputFormat, + processFileURL, + fileStrategy, + userId, + }); + + if (cloudUrl) { + imageUrl = cloudUrl; + } else { + // Fallback to local + try { + imageUrl = await saveImageLocally(imageData, outputFormat, userId); + } catch (_error) { + imageUrl = `data:${mimeType};base64,${imageData}`; + } + } + } + + logger.debug('[GeminiImageGen] Image URL:', imageUrl); + + // For the artifact, we need a data URL (same as OpenAI) + // The local file save is for persistence, but the response needs a data URL const dataUrl = `data:${mimeType};base64,${imageData}`; + + // Return in content_and_artifact format (same as OpenAI) const file_ids = [v4()]; const content = [ { @@ -445,15 +567,12 @@ function createGeminiImageTool(fields = {}) { }, ]; - const conversationId = runnableConfig?.configurable?.thread_id; - const messageId = - runnableConfig?.configurable?.run_id ?? - runnableConfig?.configurable?.requestBody?.messageId; + // Record token usage for balance tracking (don't await to avoid blocking response) + const conversationId = _runnableConfig?.configurable?.thread_id; recordTokenUsage({ usageMetadata: apiResponse.usageMetadata, req, userId, - messageId, conversationId, model: geminiModel, }).catch((error) => { diff --git a/api/app/clients/tools/structured/StableDiffusion.js b/api/app/clients/tools/structured/StableDiffusion.js index 8cf4b141bb..d7a7a4d96b 100644 --- a/api/app/clients/tools/structured/StableDiffusion.js +++ b/api/app/clients/tools/structured/StableDiffusion.js @@ -43,10 +43,6 @@ 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); @@ -119,7 +115,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 this.returnValue('Error making API request.'); + return 'Error making API request.'; } const image = generationResponse.data.images[0]; diff --git a/api/app/clients/tools/structured/specs/DALLE3-proxy.spec.js b/api/app/clients/tools/structured/specs/DALLE3-proxy.spec.js index 262842b3c2..4481a7d70f 100644 --- a/api/app/clients/tools/structured/specs/DALLE3-proxy.spec.js +++ b/api/app/clients/tools/structured/specs/DALLE3-proxy.spec.js @@ -1,6 +1,7 @@ const DALLE3 = require('../DALLE3'); const { ProxyAgent } = require('undici'); +jest.mock('tiktoken'); const processFileURL = jest.fn(); describe('DALLE3 Proxy Configuration', () => { diff --git a/api/app/clients/tools/structured/specs/DALLE3.spec.js b/api/app/clients/tools/structured/specs/DALLE3.spec.js index 6071929bfc..d2040989f9 100644 --- a/api/app/clients/tools/structured/specs/DALLE3.spec.js +++ b/api/app/clients/tools/structured/specs/DALLE3.spec.js @@ -14,6 +14,15 @@ jest.mock('@librechat/data-schemas', () => { }; }); +jest.mock('tiktoken', () => { + return { + encoding_for_model: jest.fn().mockReturnValue({ + encode: jest.fn(), + decode: jest.fn(), + }), + }; +}); + const processFileURL = jest.fn(); const generate = jest.fn(); diff --git a/api/app/clients/tools/structured/specs/imageTools-agent.spec.js b/api/app/clients/tools/structured/specs/imageTools-agent.spec.js deleted file mode 100644 index b82dd87b3f..0000000000 --- a/api/app/clients/tools/structured/specs/imageTools-agent.spec.js +++ /dev/null @@ -1,294 +0,0 @@ -/** - * 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'); - }); - }); -}); diff --git a/api/app/clients/tools/util/handleTools.js b/api/app/clients/tools/util/handleTools.js index 8adb43f945..65c88ce83f 100644 --- a/api/app/clients/tools/util/handleTools.js +++ b/api/app/clients/tools/util/handleTools.js @@ -7,13 +7,13 @@ const { } = require('@librechat/agents'); const { checkAccess, - toolkitParent, createSafeUser, mcpToolPattern, loadWebSearchAuth, buildImageToolContext, buildWebSearchContext, } = require('@librechat/api'); +const { getMCPServersRegistry } = require('~/config'); const { Tools, Constants, @@ -38,14 +38,13 @@ const { createGeminiImageTool, createOpenAIImageTools, } = require('../'); -const { createMCPTool, createMCPTools, resolveConfigServers } = require('~/server/services/MCP'); -const { createFileSearchTool, primeFiles: primeSearchFiles } = require('./fileSearch'); const { primeFiles: primeCodeFiles } = require('~/server/services/Files/Code/process'); +const { createFileSearchTool, primeFiles: primeSearchFiles } = require('./fileSearch'); const { getUserPluginAuthValue } = require('~/server/services/PluginService'); +const { createMCPTool, createMCPTools } = require('~/server/services/MCP'); const { loadAuthValues } = require('~/server/services/Tools/credentials'); const { getMCPServerTools } = require('~/server/services/Config'); -const { getMCPServersRegistry } = require('~/config'); -const { getRoleByName } = require('~/models'); +const { getRoleByName } = require('~/models/Role'); /** * Validates the availability and authentication of tools for a user based on environment variables or user-specific plugin authentication values. @@ -208,7 +207,7 @@ const loadTools = async ({ }, gemini_image_gen: async (toolContextMap) => { const authFields = getAuthFields('gemini_image_gen'); - const authValues = await loadAuthValues({ userId: user, authFields, throwError: false }); + const authValues = await loadAuthValues({ userId: user, authFields }); const imageFiles = options.tool_resources?.[EToolResources.image_edit]?.files ?? []; const toolContext = buildImageToolContext({ imageFiles, @@ -223,6 +222,7 @@ const loadTools = async ({ isAgent: !!agent, req: options.req, imageFiles, + processFileURL: options.processFileURL, userId: user, fileStrategy, }); @@ -256,12 +256,6 @@ const loadTools = async ({ const toolContextMap = {}; const requestedMCPTools = {}; - /** Resolve config-source servers for the current user/tenant context */ - let configServers; - if (tools.some((tool) => tool && mcpToolPattern.test(tool))) { - configServers = await resolveConfigServers(options.req); - } - for (const tool of tools) { if (tool === Tools.execute_code) { requestedTools[tool] = async () => { @@ -347,7 +341,7 @@ const loadTools = async ({ continue; } const serverConfig = serverName - ? await getMCPServersRegistry().getServerConfig(serverName, user, configServers) + ? await getMCPServersRegistry().getServerConfig(serverName, user) : null; if (!serverConfig) { logger.warn( @@ -376,16 +370,8 @@ const loadTools = async ({ continue; } - const toolKey = customConstructors[tool] ? tool : toolkitParent[tool]; - if (toolKey && customConstructors[toolKey]) { - if (!requestedTools[toolKey]) { - let cached; - requestedTools[toolKey] = async () => { - cached ??= customConstructors[toolKey](toolContextMap); - return cached; - }; - } - requestedTools[tool] = requestedTools[toolKey]; + if (customConstructors[tool]) { + requestedTools[tool] = async () => customConstructors[tool](toolContextMap); continue; } @@ -425,7 +411,6 @@ const loadTools = async ({ let index = -1; const failedMCPServers = new Set(); const safeUser = createSafeUser(options.req?.user); - for (const [serverName, toolConfigs] of Object.entries(requestedMCPTools)) { index++; /** @type {LCAvailableTools} */ @@ -440,7 +425,6 @@ const loadTools = async ({ signal, user: safeUser, userMCPAuthMap, - configServers, res: options.res, streamId: options.req?._resumableStreamId || null, model: agent?.model ?? model, diff --git a/api/cache/banViolation.js b/api/cache/banViolation.js index 36945ca420..4d321889c1 100644 --- a/api/cache/banViolation.js +++ b/api/cache/banViolation.js @@ -1,7 +1,8 @@ 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/cache/getLogStores.js b/api/cache/getLogStores.js index 70eb681e53..3089192196 100644 --- a/api/cache/getLogStores.js +++ b/api/cache/getLogStores.js @@ -47,7 +47,7 @@ const namespaces = { [CacheKeys.MODEL_QUERIES]: standardCache(CacheKeys.MODEL_QUERIES), [CacheKeys.AUDIO_RUNS]: standardCache(CacheKeys.AUDIO_RUNS, Time.TEN_MINUTES), [CacheKeys.MESSAGES]: standardCache(CacheKeys.MESSAGES, Time.ONE_MINUTE), - [CacheKeys.FLOWS]: standardCache(CacheKeys.FLOWS, Time.ONE_MINUTE * 10), + [CacheKeys.FLOWS]: standardCache(CacheKeys.FLOWS, Time.ONE_MINUTE * 3), [CacheKeys.OPENID_EXCHANGED_TOKENS]: standardCache( CacheKeys.OPENID_EXCHANGED_TOKENS, Time.TEN_MINUTES, diff --git a/api/db/index.js b/api/db/index.js index f4359c8adf..5c29902f69 100644 --- a/api/db/index.js +++ b/api/db/index.js @@ -1,13 +1,8 @@ const mongoose = require('mongoose'); const { createModels } = require('@librechat/data-schemas'); const { connectDb } = require('./connect'); - -// createModels MUST run before requiring indexSync. -// indexSync.js captures mongoose.models.Message and mongoose.models.Conversation -// at module load time. If those models are not registered first, all MeiliSearch -// sync operations will silently fail on every startup. -createModels(mongoose); - const indexSync = require('./indexSync'); +createModels(mongoose); + module.exports = { connectDb, indexSync }; diff --git a/api/db/index.spec.js b/api/db/index.spec.js deleted file mode 100644 index e1ebe176dc..0000000000 --- a/api/db/index.spec.js +++ /dev/null @@ -1,26 +0,0 @@ -describe('api/db/index.js', () => { - test('createModels is called before indexSync is loaded', () => { - jest.resetModules(); - - const callOrder = []; - - jest.mock('@librechat/data-schemas', () => ({ - createModels: jest.fn((m) => { - callOrder.push('createModels'); - m.models.Message = { name: 'Message' }; - m.models.Conversation = { name: 'Conversation' }; - }), - })); - - jest.mock('./indexSync', () => { - callOrder.push('indexSync'); - return jest.fn(); - }); - - jest.mock('./connect', () => ({ connectDb: jest.fn() })); - - require('./index'); - - expect(callOrder).toEqual(['createModels', 'indexSync']); - }); -}); diff --git a/api/db/indexSync.js b/api/db/indexSync.js index 13059033fb..8e8e999d92 100644 --- a/api/db/indexSync.js +++ b/api/db/indexSync.js @@ -6,6 +6,9 @@ const { isEnabled, FlowStateManager } = require('@librechat/api'); const { getLogStores } = require('~/cache'); const { batchResetMeiliFlags } = require('./utils'); +const Conversation = mongoose.models.Conversation; +const Message = mongoose.models.Message; + const searchEnabled = isEnabled(process.env.SEARCH); const indexingDisabled = isEnabled(process.env.MEILI_NO_SYNC); let currentTimeout = null; @@ -197,14 +200,6 @@ async function performSync(flowManager, flowId, flowType) { return { messagesSync: false, convosSync: false }; } - const Message = mongoose.models.Message; - const Conversation = mongoose.models.Conversation; - if (!Message || !Conversation) { - throw new Error( - '[indexSync] Models not registered. Ensure createModels() has been called before indexSync.', - ); - } - const client = MeiliSearchClient.getInstance(); const { status } = await client.health(); @@ -241,12 +236,8 @@ async function performSync(flowManager, flowId, flowType) { const messageCount = messageProgress.totalDocuments; const messagesIndexed = messageProgress.totalProcessed; const unindexedMessages = messageCount - messagesIndexed; - const noneIndexed = messagesIndexed === 0 && unindexedMessages > 0; - if (settingsUpdated || noneIndexed || unindexedMessages > syncThreshold) { - if (noneIndexed && !settingsUpdated) { - logger.info('[indexSync] No messages marked as indexed, forcing full sync'); - } + if (settingsUpdated || unindexedMessages > syncThreshold) { logger.info(`[indexSync] Starting message sync (${unindexedMessages} unindexed)`); await Message.syncWithMeili(); messagesSync = true; @@ -270,13 +261,9 @@ async function performSync(flowManager, flowId, flowType) { const convoCount = convoProgress.totalDocuments; const convosIndexed = convoProgress.totalProcessed; - const unindexedConvos = convoCount - convosIndexed; - const noneConvosIndexed = convosIndexed === 0 && unindexedConvos > 0; - if (settingsUpdated || noneConvosIndexed || unindexedConvos > syncThreshold) { - if (noneConvosIndexed && !settingsUpdated) { - logger.info('[indexSync] No conversations marked as indexed, forcing full sync'); - } + const unindexedConvos = convoCount - convosIndexed; + if (settingsUpdated || unindexedConvos > syncThreshold) { logger.info(`[indexSync] Starting convos sync (${unindexedConvos} unindexed)`); await Conversation.syncWithMeili(); convosSync = true; @@ -354,13 +341,6 @@ async function indexSync() { logger.debug('[indexSync] Creating indices...'); currentTimeout = setTimeout(async () => { try { - const Message = mongoose.models.Message; - const Conversation = mongoose.models.Conversation; - if (!Message || !Conversation) { - throw new Error( - '[indexSync] Models not registered. Ensure createModels() has been called before indexSync.', - ); - } await Message.syncWithMeili(); await Conversation.syncWithMeili(); } catch (err) { diff --git a/api/db/indexSync.spec.js b/api/db/indexSync.spec.js index dbe07c7595..c2e5901d6a 100644 --- a/api/db/indexSync.spec.js +++ b/api/db/indexSync.spec.js @@ -462,69 +462,4 @@ describe('performSync() - syncThreshold logic', () => { ); expect(mockLogger.info).toHaveBeenCalledWith('[indexSync] Starting convos sync (50 unindexed)'); }); - - test('forces sync when zero documents indexed (reset scenario) even if below threshold', async () => { - Message.getSyncProgress.mockResolvedValue({ - totalProcessed: 0, - totalDocuments: 680, - isComplete: false, - }); - - Conversation.getSyncProgress.mockResolvedValue({ - totalProcessed: 0, - totalDocuments: 76, - isComplete: false, - }); - - Message.syncWithMeili.mockResolvedValue(undefined); - Conversation.syncWithMeili.mockResolvedValue(undefined); - - const indexSync = require('./indexSync'); - await indexSync(); - - expect(Message.syncWithMeili).toHaveBeenCalledTimes(1); - expect(Conversation.syncWithMeili).toHaveBeenCalledTimes(1); - expect(mockLogger.info).toHaveBeenCalledWith( - '[indexSync] No messages marked as indexed, forcing full sync', - ); - expect(mockLogger.info).toHaveBeenCalledWith( - '[indexSync] Starting message sync (680 unindexed)', - ); - expect(mockLogger.info).toHaveBeenCalledWith( - '[indexSync] No conversations marked as indexed, forcing full sync', - ); - expect(mockLogger.info).toHaveBeenCalledWith('[indexSync] Starting convos sync (76 unindexed)'); - }); - - test('does NOT force sync when some documents already indexed and below threshold', async () => { - Message.getSyncProgress.mockResolvedValue({ - totalProcessed: 630, - totalDocuments: 680, - isComplete: false, - }); - - Conversation.getSyncProgress.mockResolvedValue({ - totalProcessed: 70, - totalDocuments: 76, - isComplete: false, - }); - - const indexSync = require('./indexSync'); - await indexSync(); - - expect(Message.syncWithMeili).not.toHaveBeenCalled(); - expect(Conversation.syncWithMeili).not.toHaveBeenCalled(); - expect(mockLogger.info).not.toHaveBeenCalledWith( - '[indexSync] No messages marked as indexed, forcing full sync', - ); - expect(mockLogger.info).not.toHaveBeenCalledWith( - '[indexSync] No conversations marked as indexed, forcing full sync', - ); - expect(mockLogger.info).toHaveBeenCalledWith( - '[indexSync] 50 messages unindexed (below threshold: 1000, skipping)', - ); - expect(mockLogger.info).toHaveBeenCalledWith( - '[indexSync] 6 convos unindexed (below threshold: 1000, skipping)', - ); - }); }); diff --git a/api/jest.config.js b/api/jest.config.js index 47f8b7287b..20ee3c6aed 100644 --- a/api/jest.config.js +++ b/api/jest.config.js @@ -3,13 +3,12 @@ module.exports = { clearMocks: true, roots: [''], coverageDirectory: 'coverage', - maxWorkers: '50%', testTimeout: 30000, // 30 seconds timeout for all tests setupFiles: ['./test/jestSetup.js', './test/__mocks__/logger.js'], moduleNameMapper: { '~/(.*)': '/$1', '~/data/auth.json': '/__mocks__/auth.mock.json', - '^openid-client/passport$': '/test/__mocks__/openid-client-passport.js', + '^openid-client/passport$': '/test/__mocks__/openid-client-passport.js', // Mock for the passport strategy part '^openid-client$': '/test/__mocks__/openid-client.js', }, transformIgnorePatterns: ['/node_modules/(?!(openid-client|oauth4webapi|jose)/).*/'], diff --git a/api/models/Action.js b/api/models/Action.js new file mode 100644 index 0000000000..20aa20a7e4 --- /dev/null +++ b/api/models/Action.js @@ -0,0 +1,77 @@ +const { Action } = require('~/db/models'); + +/** + * Update an action with new data without overwriting existing properties, + * or create a new action if it doesn't exist. + * + * @param {Object} searchParams - The search parameters to find the action to update. + * @param {string} searchParams.action_id - The ID of the action to update. + * @param {string} searchParams.user - The user ID of the action's author. + * @param {Object} updateData - An object containing the properties to update. + * @returns {Promise} The updated or newly created action document as a plain object. + */ +const updateAction = async (searchParams, updateData) => { + const options = { new: true, upsert: true }; + return await Action.findOneAndUpdate(searchParams, updateData, options).lean(); +}; + +/** + * Retrieves all actions that match the given search parameters. + * + * @param {Object} searchParams - The search parameters to find matching actions. + * @param {boolean} includeSensitive - Flag to include sensitive data in the metadata. + * @returns {Promise>} A promise that resolves to an array of action documents as plain objects. + */ +const getActions = async (searchParams, includeSensitive = false) => { + const actions = await Action.find(searchParams).lean(); + + if (!includeSensitive) { + for (let i = 0; i < actions.length; i++) { + const metadata = actions[i].metadata; + if (!metadata) { + continue; + } + + const sensitiveFields = ['api_key', 'oauth_client_id', 'oauth_client_secret']; + for (let field of sensitiveFields) { + if (metadata[field]) { + delete metadata[field]; + } + } + } + } + + return actions; +}; + +/** + * Deletes an action by params. + * + * @param {Object} searchParams - The search parameters to find the action to delete. + * @param {string} searchParams.action_id - The ID of the action to delete. + * @param {string} searchParams.user - The user ID of the action's author. + * @returns {Promise} A promise that resolves to the deleted action document as a plain object, or null if no document was found. + */ +const deleteAction = async (searchParams) => { + return await Action.findOneAndDelete(searchParams).lean(); +}; + +/** + * Deletes actions by params. + * + * @param {Object} searchParams - The search parameters to find the actions to delete. + * @param {string} searchParams.action_id - The ID of the action(s) to delete. + * @param {string} searchParams.user - The user ID of the action's author. + * @returns {Promise} A promise that resolves to the number of deleted action documents. + */ +const deleteActions = async (searchParams) => { + const result = await Action.deleteMany(searchParams); + return result.deletedCount; +}; + +module.exports = { + getActions, + updateAction, + deleteAction, + deleteActions, +}; diff --git a/api/models/Agent.js b/api/models/Agent.js new file mode 100644 index 0000000000..663285183a --- /dev/null +++ b/api/models/Agent.js @@ -0,0 +1,931 @@ +const mongoose = require('mongoose'); +const crypto = require('node:crypto'); +const { logger } = require('@librechat/data-schemas'); +const { getCustomEndpointConfig } = require('@librechat/api'); +const { + Tools, + SystemRoles, + ResourceType, + actionDelimiter, + isAgentsEndpoint, + isEphemeralAgentId, + encodeEphemeralAgentId, +} = require('librechat-data-provider'); +const { mcp_all, mcp_delimiter } = require('librechat-data-provider').Constants; +const { + removeAgentFromAllProjects, + removeAgentIdsFromProject, + addAgentIdsToProject, +} = require('./Project'); +const { removeAllPermissions } = require('~/server/services/PermissionService'); +const { getMCPServerTools } = require('~/server/services/Config'); +const { Agent, AclEntry, User } = require('~/db/models'); +const { getActions } = require('./Action'); + +/** + * Extracts unique MCP server names from tools array + * Tools format: "toolName_mcp_serverName" or "sys__server__sys_mcp_serverName" + * @param {string[]} tools - Array of tool identifiers + * @returns {string[]} Array of unique MCP server names + */ +const extractMCPServerNames = (tools) => { + if (!tools || !Array.isArray(tools)) { + return []; + } + const serverNames = new Set(); + for (const tool of tools) { + if (!tool || !tool.includes(mcp_delimiter)) { + continue; + } + const parts = tool.split(mcp_delimiter); + if (parts.length >= 2) { + serverNames.add(parts[parts.length - 1]); + } + } + return Array.from(serverNames); +}; + +/** + * Create an agent with the provided data. + * @param {Object} agentData - The agent data to create. + * @returns {Promise} The created agent document as a plain object. + * @throws {Error} If the agent creation fails. + */ +const createAgent = async (agentData) => { + const { author: _author, ...versionData } = agentData; + const timestamp = new Date(); + const initialAgentData = { + ...agentData, + versions: [ + { + ...versionData, + createdAt: timestamp, + updatedAt: timestamp, + }, + ], + category: agentData.category || 'general', + mcpServerNames: extractMCPServerNames(agentData.tools), + }; + + return (await Agent.create(initialAgentData)).toObject(); +}; + +/** + * Get an agent document based on the provided ID. + * + * @param {Object} searchParameter - The search parameters to find the agent to update. + * @param {string} searchParameter.id - The ID of the agent to update. + * @param {string} searchParameter.author - The user ID of the agent's author. + * @returns {Promise} The agent document as a plain object, or null if not found. + */ +const getAgent = async (searchParameter) => await Agent.findOne(searchParameter).lean(); + +/** + * Get multiple agent documents based on the provided search parameters. + * + * @param {Object} searchParameter - The search parameters to find agents. + * @returns {Promise} Array of agent documents as plain objects. + */ +const getAgents = async (searchParameter) => await Agent.find(searchParameter).lean(); + +/** + * Load an agent based on the provided ID + * + * @param {Object} params + * @param {ServerRequest} params.req + * @param {string} params.spec + * @param {string} params.agent_id + * @param {string} params.endpoint + * @param {import('@librechat/agents').ClientOptions} [params.model_parameters] + * @returns {Promise} The agent document as a plain object, or null if not found. + */ +const loadEphemeralAgent = async ({ req, spec, endpoint, model_parameters: _m }) => { + const { model, ...model_parameters } = _m; + const modelSpecs = req.config?.modelSpecs?.list; + /** @type {TModelSpec | null} */ + let modelSpec = null; + if (spec != null && spec !== '') { + modelSpec = modelSpecs?.find((s) => s.name === spec) || null; + } + /** @type {TEphemeralAgent | null} */ + const ephemeralAgent = req.body.ephemeralAgent; + const mcpServers = new Set(ephemeralAgent?.mcp); + const userId = req.user?.id; // note: userId cannot be undefined at runtime + if (modelSpec?.mcpServers) { + for (const mcpServer of modelSpec.mcpServers) { + mcpServers.add(mcpServer); + } + } + /** @type {string[]} */ + const tools = []; + if (ephemeralAgent?.execute_code === true || modelSpec?.executeCode === true) { + tools.push(Tools.execute_code); + } + if (ephemeralAgent?.file_search === true || modelSpec?.fileSearch === true) { + tools.push(Tools.file_search); + } + if (ephemeralAgent?.web_search === true || modelSpec?.webSearch === true) { + tools.push(Tools.web_search); + } + + const addedServers = new Set(); + if (mcpServers.size > 0) { + for (const mcpServer of mcpServers) { + if (addedServers.has(mcpServer)) { + continue; + } + const serverTools = await getMCPServerTools(userId, mcpServer); + if (!serverTools) { + tools.push(`${mcp_all}${mcp_delimiter}${mcpServer}`); + addedServers.add(mcpServer); + continue; + } + tools.push(...Object.keys(serverTools)); + addedServers.add(mcpServer); + } + } + + const instructions = req.body.promptPrefix; + + // Get endpoint config for modelDisplayLabel fallback + const appConfig = req.config; + let endpointConfig = appConfig?.endpoints?.[endpoint]; + if (!isAgentsEndpoint(endpoint) && !endpointConfig) { + try { + endpointConfig = getCustomEndpointConfig({ endpoint, appConfig }); + } catch (err) { + logger.error('[loadEphemeralAgent] Error getting custom endpoint config', err); + } + } + + // For ephemeral agents, use modelLabel if provided, then model spec's label, + // then modelDisplayLabel from endpoint config, otherwise empty string to show model name + const sender = + model_parameters?.modelLabel ?? modelSpec?.label ?? endpointConfig?.modelDisplayLabel ?? ''; + + // Encode ephemeral agent ID with endpoint, model, and computed sender for display + const ephemeralId = encodeEphemeralAgentId({ endpoint, model, sender }); + + const result = { + id: ephemeralId, + instructions, + provider: endpoint, + model_parameters, + model, + tools, + }; + + if (ephemeralAgent?.artifacts != null && ephemeralAgent.artifacts) { + result.artifacts = ephemeralAgent.artifacts; + } + return result; +}; + +/** + * Load an agent based on the provided ID + * + * @param {Object} params + * @param {ServerRequest} params.req + * @param {string} params.spec + * @param {string} params.agent_id + * @param {string} params.endpoint + * @param {import('@librechat/agents').ClientOptions} [params.model_parameters] + * @returns {Promise} The agent document as a plain object, or null if not found. + */ +const loadAgent = async ({ req, spec, agent_id, endpoint, model_parameters }) => { + if (!agent_id) { + return null; + } + if (isEphemeralAgentId(agent_id)) { + return await loadEphemeralAgent({ req, spec, endpoint, model_parameters }); + } + const agent = await getAgent({ + id: agent_id, + }); + + if (!agent) { + return null; + } + + agent.version = agent.versions ? agent.versions.length : 0; + return agent; +}; + +/** + * Check if a version already exists in the versions array, excluding timestamp and author fields + * @param {Object} updateData - The update data to compare + * @param {Object} currentData - The current agent data + * @param {Array} versions - The existing versions array + * @param {string} [actionsHash] - Hash of current action metadata + * @returns {Object|null} - The matching version if found, null otherwise + */ +const isDuplicateVersion = (updateData, currentData, versions, actionsHash = null) => { + if (!versions || versions.length === 0) { + return null; + } + + const excludeFields = [ + '_id', + 'id', + 'createdAt', + 'updatedAt', + 'author', + 'updatedBy', + 'created_at', + 'updated_at', + '__v', + 'versions', + 'actionsHash', // Exclude actionsHash from direct comparison + ]; + + const { $push: _$push, $pull: _$pull, $addToSet: _$addToSet, ...directUpdates } = updateData; + + if (Object.keys(directUpdates).length === 0 && !actionsHash) { + return null; + } + + const wouldBeVersion = { ...currentData, ...directUpdates }; + const lastVersion = versions[versions.length - 1]; + + if (actionsHash && lastVersion.actionsHash !== actionsHash) { + return null; + } + + const allFields = new Set([...Object.keys(wouldBeVersion), ...Object.keys(lastVersion)]); + + const importantFields = Array.from(allFields).filter((field) => !excludeFields.includes(field)); + + let isMatch = true; + for (const field of importantFields) { + const wouldBeValue = wouldBeVersion[field]; + const lastVersionValue = lastVersion[field]; + + // Skip if both are undefined/null + if (!wouldBeValue && !lastVersionValue) { + continue; + } + + // Handle arrays + if (Array.isArray(wouldBeValue) || Array.isArray(lastVersionValue)) { + // Normalize: treat undefined/null as empty array for comparison + let wouldBeArr; + if (Array.isArray(wouldBeValue)) { + wouldBeArr = wouldBeValue; + } else if (wouldBeValue == null) { + wouldBeArr = []; + } else { + wouldBeArr = [wouldBeValue]; + } + + let lastVersionArr; + if (Array.isArray(lastVersionValue)) { + lastVersionArr = lastVersionValue; + } else if (lastVersionValue == null) { + lastVersionArr = []; + } else { + lastVersionArr = [lastVersionValue]; + } + + if (wouldBeArr.length !== lastVersionArr.length) { + isMatch = false; + break; + } + + // Special handling for projectIds (MongoDB ObjectIds) + if (field === 'projectIds') { + const wouldBeIds = wouldBeArr.map((id) => id.toString()).sort(); + const versionIds = lastVersionArr.map((id) => id.toString()).sort(); + + if (!wouldBeIds.every((id, i) => id === versionIds[i])) { + isMatch = false; + break; + } + } + // Handle arrays of objects + else if ( + wouldBeArr.length > 0 && + typeof wouldBeArr[0] === 'object' && + wouldBeArr[0] !== null + ) { + const sortedWouldBe = [...wouldBeArr].map((item) => JSON.stringify(item)).sort(); + const sortedVersion = [...lastVersionArr].map((item) => JSON.stringify(item)).sort(); + + if (!sortedWouldBe.every((item, i) => item === sortedVersion[i])) { + isMatch = false; + break; + } + } else { + const sortedWouldBe = [...wouldBeArr].sort(); + const sortedVersion = [...lastVersionArr].sort(); + + if (!sortedWouldBe.every((item, i) => item === sortedVersion[i])) { + isMatch = false; + break; + } + } + } + // Handle objects + else if (typeof wouldBeValue === 'object' && wouldBeValue !== null) { + const lastVersionObj = + typeof lastVersionValue === 'object' && lastVersionValue !== null ? lastVersionValue : {}; + + // For empty objects, normalize the comparison + const wouldBeKeys = Object.keys(wouldBeValue); + const lastVersionKeys = Object.keys(lastVersionObj); + + // If both are empty objects, they're equal + if (wouldBeKeys.length === 0 && lastVersionKeys.length === 0) { + continue; + } + + // Otherwise do a deep comparison + if (JSON.stringify(wouldBeValue) !== JSON.stringify(lastVersionObj)) { + isMatch = false; + break; + } + } + // Handle primitive values + else { + // For primitives, handle the case where one is undefined and the other is a default value + if (wouldBeValue !== lastVersionValue) { + // Special handling for boolean false vs undefined + if ( + typeof wouldBeValue === 'boolean' && + wouldBeValue === false && + lastVersionValue === undefined + ) { + continue; + } + // Special handling for empty string vs undefined + if ( + typeof wouldBeValue === 'string' && + wouldBeValue === '' && + lastVersionValue === undefined + ) { + continue; + } + isMatch = false; + break; + } + } + } + + return isMatch ? lastVersion : null; +}; + +/** + * Update an agent with new data without overwriting existing + * properties, or create a new agent if it doesn't exist. + * When an agent is updated, a copy of the current state will be saved to the versions array. + * + * @param {Object} searchParameter - The search parameters to find the agent to update. + * @param {string} searchParameter.id - The ID of the agent to update. + * @param {string} [searchParameter.author] - The user ID of the agent's author. + * @param {Object} updateData - An object containing the properties to update. + * @param {Object} [options] - Optional configuration object. + * @param {string} [options.updatingUserId] - The ID of the user performing the update (used for tracking non-author updates). + * @param {boolean} [options.forceVersion] - Force creation of a new version even if no fields changed. + * @param {boolean} [options.skipVersioning] - Skip version creation entirely (useful for isolated operations like sharing). + * @returns {Promise} The updated or newly created agent document as a plain object. + * @throws {Error} If the update would create a duplicate version + */ +const updateAgent = async (searchParameter, updateData, options = {}) => { + const { updatingUserId = null, forceVersion = false, skipVersioning = false } = options; + const mongoOptions = { new: true, upsert: false }; + + const currentAgent = await Agent.findOne(searchParameter); + if (currentAgent) { + const { + __v, + _id, + id: __id, + versions, + author: _author, + ...versionData + } = currentAgent.toObject(); + const { $push, $pull, $addToSet, ...directUpdates } = updateData; + + // Sync mcpServerNames when tools are updated + if (directUpdates.tools !== undefined) { + const mcpServerNames = extractMCPServerNames(directUpdates.tools); + directUpdates.mcpServerNames = mcpServerNames; + updateData.mcpServerNames = mcpServerNames; // Also update the original updateData + } + + let actionsHash = null; + + // Generate actions hash if agent has actions + if (currentAgent.actions && currentAgent.actions.length > 0) { + // Extract action IDs from the format "domain_action_id" + const actionIds = currentAgent.actions + .map((action) => { + const parts = action.split(actionDelimiter); + return parts[1]; // Get just the action ID part + }) + .filter(Boolean); + + if (actionIds.length > 0) { + try { + const actions = await getActions( + { + action_id: { $in: actionIds }, + }, + true, + ); // Include sensitive data for hash + + actionsHash = await generateActionMetadataHash(currentAgent.actions, actions); + } catch (error) { + logger.error('Error fetching actions for hash generation:', error); + } + } + } + + const shouldCreateVersion = + !skipVersioning && + (forceVersion || Object.keys(directUpdates).length > 0 || $push || $pull || $addToSet); + + if (shouldCreateVersion) { + const duplicateVersion = isDuplicateVersion(updateData, versionData, versions, actionsHash); + if (duplicateVersion && !forceVersion) { + // No changes detected, return the current agent without creating a new version + const agentObj = currentAgent.toObject(); + agentObj.version = versions.length; + return agentObj; + } + } + + const versionEntry = { + ...versionData, + ...directUpdates, + updatedAt: new Date(), + }; + + // Include actions hash in version if available + if (actionsHash) { + versionEntry.actionsHash = actionsHash; + } + + // Always store updatedBy field to track who made the change + if (updatingUserId) { + versionEntry.updatedBy = new mongoose.Types.ObjectId(updatingUserId); + } + + if (shouldCreateVersion) { + updateData.$push = { + ...($push || {}), + versions: versionEntry, + }; + } + } + + return Agent.findOneAndUpdate(searchParameter, updateData, mongoOptions).lean(); +}; + +/** + * Modifies an agent with the resource file id. + * @param {object} params + * @param {ServerRequest} params.req + * @param {string} params.agent_id + * @param {string} params.tool_resource + * @param {string} params.file_id + * @returns {Promise} The updated agent. + */ +const addAgentResourceFile = async ({ req, agent_id, tool_resource, file_id }) => { + const searchParameter = { id: agent_id }; + let agent = await getAgent(searchParameter); + if (!agent) { + throw new Error('Agent not found for adding resource file'); + } + const fileIdsPath = `tool_resources.${tool_resource}.file_ids`; + await Agent.updateOne( + { + id: agent_id, + [`${fileIdsPath}`]: { $exists: false }, + }, + { + $set: { + [`${fileIdsPath}`]: [], + }, + }, + ); + + const updateData = { + $addToSet: { + tools: tool_resource, + [fileIdsPath]: file_id, + }, + }; + + const updatedAgent = await updateAgent(searchParameter, updateData, { + updatingUserId: req?.user?.id, + }); + if (updatedAgent) { + return updatedAgent; + } else { + throw new Error('Agent not found for adding resource file'); + } +}; + +/** + * Removes multiple resource files from an agent using atomic operations. + * @param {object} params + * @param {string} params.agent_id + * @param {Array<{tool_resource: string, file_id: string}>} params.files + * @returns {Promise} The updated agent. + * @throws {Error} If the agent is not found or update fails. + */ +const removeAgentResourceFiles = async ({ agent_id, files }) => { + const searchParameter = { id: agent_id }; + + // Group files to remove by resource + const filesByResource = files.reduce((acc, { tool_resource, file_id }) => { + if (!acc[tool_resource]) { + acc[tool_resource] = []; + } + acc[tool_resource].push(file_id); + return acc; + }, {}); + + // Step 1: Atomically remove file IDs using $pull + const pullOps = {}; + const resourcesToCheck = new Set(); + for (const [resource, fileIds] of Object.entries(filesByResource)) { + const fileIdsPath = `tool_resources.${resource}.file_ids`; + pullOps[fileIdsPath] = { $in: fileIds }; + resourcesToCheck.add(resource); + } + + const updatePullData = { $pull: pullOps }; + const agentAfterPull = await Agent.findOneAndUpdate(searchParameter, updatePullData, { + new: true, + }).lean(); + + if (!agentAfterPull) { + // Agent might have been deleted concurrently, or never existed. + // Check if it existed before trying to throw. + const agentExists = await getAgent(searchParameter); + if (!agentExists) { + throw new Error('Agent not found for removing resource files'); + } + // If it existed but findOneAndUpdate returned null, something else went wrong. + throw new Error('Failed to update agent during file removal (pull step)'); + } + + // Return the agent state directly after the $pull operation. + // Skipping the $unset step for now to simplify and test core $pull atomicity. + // Empty arrays might remain, but the removal itself should be correct. + return agentAfterPull; +}; + +/** + * Deletes an agent based on the provided ID. + * + * @param {Object} searchParameter - The search parameters to find the agent to delete. + * @param {string} searchParameter.id - The ID of the agent to delete. + * @param {string} [searchParameter.author] - The user ID of the agent's author. + * @returns {Promise} Resolves when the agent has been successfully deleted. + */ +const deleteAgent = async (searchParameter) => { + const agent = await Agent.findOneAndDelete(searchParameter); + if (agent) { + await removeAgentFromAllProjects(agent.id); + await Promise.all([ + removeAllPermissions({ + resourceType: ResourceType.AGENT, + resourceId: agent._id, + }), + removeAllPermissions({ + resourceType: ResourceType.REMOTE_AGENT, + resourceId: agent._id, + }), + ]); + try { + await Agent.updateMany({ 'edges.to': agent.id }, { $pull: { edges: { to: agent.id } } }); + } catch (error) { + logger.error('[deleteAgent] Error removing agent from handoff edges', error); + } + try { + await User.updateMany( + { 'favorites.agentId': agent.id }, + { $pull: { favorites: { agentId: agent.id } } }, + ); + } catch (error) { + logger.error('[deleteAgent] Error removing agent from user favorites', error); + } + } + return agent; +}; + +/** + * Deletes all agents created by a specific user. + * @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. + */ +const deleteUserAgents = async (userId) => { + try { + const userAgents = await getAgents({ author: userId }); + + if (userAgents.length === 0) { + return; + } + + const agentIds = userAgents.map((agent) => agent.id); + const agentObjectIds = userAgents.map((agent) => agent._id); + + for (const agentId of agentIds) { + await removeAgentFromAllProjects(agentId); + } + + await AclEntry.deleteMany({ + resourceType: { $in: [ResourceType.AGENT, ResourceType.REMOTE_AGENT] }, + resourceId: { $in: agentObjectIds }, + }); + + try { + await User.updateMany( + { 'favorites.agentId': { $in: agentIds } }, + { $pull: { favorites: { agentId: { $in: agentIds } } } }, + ); + } catch (error) { + logger.error('[deleteUserAgents] Error removing agents from user favorites', error); + } + + await Agent.deleteMany({ author: userId }); + } catch (error) { + logger.error('[deleteUserAgents] General error:', error); + } +}; + +/** + * Get agents by accessible IDs with optional cursor-based pagination. + * @param {Object} params - The parameters for getting accessible agents. + * @param {Array} [params.accessibleIds] - Array of agent ObjectIds the user has ACL access to. + * @param {Object} [params.otherParams] - Additional query parameters (including author filter). + * @param {number} [params.limit] - Number of agents to return (max 100). If not provided, returns all agents. + * @param {string} [params.after] - Cursor for pagination - get agents after this cursor. // base64 encoded JSON string with updatedAt and _id. + * @returns {Promise} A promise that resolves to an object containing the agents data and pagination info. + */ +const getListAgentsByAccess = async ({ + accessibleIds = [], + otherParams = {}, + limit = null, + after = null, +}) => { + const isPaginated = limit !== null && limit !== undefined; + const normalizedLimit = isPaginated ? Math.min(Math.max(1, parseInt(limit) || 20), 100) : null; + + // Build base query combining ACL accessible agents with other filters + const baseQuery = { ...otherParams, _id: { $in: accessibleIds } }; + + // Add cursor condition + if (after) { + try { + const cursor = JSON.parse(Buffer.from(after, 'base64').toString('utf8')); + const { updatedAt, _id } = cursor; + + const cursorCondition = { + $or: [ + { updatedAt: { $lt: new Date(updatedAt) } }, + { updatedAt: new Date(updatedAt), _id: { $gt: new mongoose.Types.ObjectId(_id) } }, + ], + }; + + // Merge cursor condition with base query + if (Object.keys(baseQuery).length > 0) { + baseQuery.$and = [{ ...baseQuery }, cursorCondition]; + // Remove the original conditions from baseQuery to avoid duplication + Object.keys(baseQuery).forEach((key) => { + if (key !== '$and') delete baseQuery[key]; + }); + } else { + Object.assign(baseQuery, cursorCondition); + } + } catch (error) { + logger.warn('Invalid cursor:', error.message); + } + } + + let query = Agent.find(baseQuery, { + id: 1, + _id: 1, + name: 1, + avatar: 1, + author: 1, + projectIds: 1, + description: 1, + updatedAt: 1, + category: 1, + support_contact: 1, + is_promoted: 1, + }).sort({ updatedAt: -1, _id: 1 }); + + // Only apply limit if pagination is requested + if (isPaginated) { + query = query.limit(normalizedLimit + 1); + } + + const agents = await query.lean(); + + const hasMore = isPaginated ? agents.length > normalizedLimit : false; + const data = (isPaginated ? agents.slice(0, normalizedLimit) : agents).map((agent) => { + if (agent.author) { + agent.author = agent.author.toString(); + } + return agent; + }); + + // Generate next cursor only if paginated + let nextCursor = null; + if (isPaginated && hasMore && data.length > 0) { + const lastAgent = agents[normalizedLimit - 1]; + nextCursor = Buffer.from( + JSON.stringify({ + updatedAt: lastAgent.updatedAt.toISOString(), + _id: lastAgent._id.toString(), + }), + ).toString('base64'); + } + + return { + object: 'list', + data, + first_id: data.length > 0 ? data[0].id : null, + last_id: data.length > 0 ? data[data.length - 1].id : null, + has_more: hasMore, + after: nextCursor, + }; +}; + +/** + * Updates the projects associated with an agent, adding and removing project IDs as specified. + * This function also updates the corresponding projects to include or exclude the agent ID. + * + * @param {Object} params - Parameters for updating the agent's projects. + * @param {IUser} params.user - Parameters for updating the agent's projects. + * @param {string} params.agentId - The ID of the agent to update. + * @param {string[]} [params.projectIds] - Array of project IDs to add to the agent. + * @param {string[]} [params.removeProjectIds] - Array of project IDs to remove from the agent. + * @returns {Promise} The updated agent document. + * @throws {Error} If there's an error updating the agent or projects. + */ +const updateAgentProjects = async ({ user, agentId, projectIds, removeProjectIds }) => { + const updateOps = {}; + + if (removeProjectIds && removeProjectIds.length > 0) { + for (const projectId of removeProjectIds) { + await removeAgentIdsFromProject(projectId, [agentId]); + } + updateOps.$pull = { projectIds: { $in: removeProjectIds } }; + } + + if (projectIds && projectIds.length > 0) { + for (const projectId of projectIds) { + await addAgentIdsToProject(projectId, [agentId]); + } + updateOps.$addToSet = { projectIds: { $each: projectIds } }; + } + + if (Object.keys(updateOps).length === 0) { + return await getAgent({ id: agentId }); + } + + const updateQuery = { id: agentId, author: user.id }; + if (user.role === SystemRoles.ADMIN) { + delete updateQuery.author; + } + + const updatedAgent = await updateAgent(updateQuery, updateOps, { + updatingUserId: user.id, + skipVersioning: true, + }); + if (updatedAgent) { + return updatedAgent; + } + if (updateOps.$addToSet) { + for (const projectId of projectIds) { + await removeAgentIdsFromProject(projectId, [agentId]); + } + } else if (updateOps.$pull) { + for (const projectId of removeProjectIds) { + await addAgentIdsToProject(projectId, [agentId]); + } + } + + return await getAgent({ id: agentId }); +}; + +/** + * Reverts an agent to a specific version in its version history. + * @param {Object} searchParameter - The search parameters to find the agent to revert. + * @param {string} searchParameter.id - The ID of the agent to revert. + * @param {string} [searchParameter.author] - The user ID of the agent's author. + * @param {number} versionIndex - The index of the version to revert to in the versions array. + * @returns {Promise} The updated agent document after reverting. + * @throws {Error} If the agent is not found or the specified version does not exist. + */ +const revertAgentVersion = async (searchParameter, versionIndex) => { + const agent = await Agent.findOne(searchParameter); + if (!agent) { + throw new Error('Agent not found'); + } + + if (!agent.versions || !agent.versions[versionIndex]) { + throw new Error(`Version ${versionIndex} not found`); + } + + const revertToVersion = agent.versions[versionIndex]; + + const updateData = { + ...revertToVersion, + }; + + delete updateData._id; + delete updateData.id; + delete updateData.versions; + delete updateData.author; + delete updateData.updatedBy; + + return Agent.findOneAndUpdate(searchParameter, updateData, { new: true }).lean(); +}; + +/** + * Generates a hash of action metadata for version comparison + * @param {string[]} actionIds - Array of action IDs in format "domain_action_id" + * @param {Action[]} actions - Array of action documents + * @returns {Promise} - SHA256 hash of the action metadata + */ +const generateActionMetadataHash = async (actionIds, actions) => { + if (!actionIds || actionIds.length === 0) { + return ''; + } + + // Create a map of action_id to metadata for quick lookup + const actionMap = new Map(); + actions.forEach((action) => { + actionMap.set(action.action_id, action.metadata); + }); + + // Sort action IDs for consistent hashing + const sortedActionIds = [...actionIds].sort(); + + // Build a deterministic string representation of all action metadata + const metadataString = sortedActionIds + .map((actionFullId) => { + // Extract just the action_id part (after the delimiter) + const parts = actionFullId.split(actionDelimiter); + const actionId = parts[1]; + + const metadata = actionMap.get(actionId); + if (!metadata) { + return `${actionId}:null`; + } + + // Sort metadata keys for deterministic output + const sortedKeys = Object.keys(metadata).sort(); + const metadataStr = sortedKeys + .map((key) => `${key}:${JSON.stringify(metadata[key])}`) + .join(','); + return `${actionId}:{${metadataStr}}`; + }) + .join(';'); + + // Use Web Crypto API to generate hash + const encoder = new TextEncoder(); + const data = encoder.encode(metadataString); + const hashBuffer = await crypto.webcrypto.subtle.digest('SHA-256', data); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join(''); + + return hashHex; +}; +/** + * Counts the number of promoted agents. + * @returns {Promise} - The count of promoted agents + */ +const countPromotedAgents = async () => { + const count = await Agent.countDocuments({ is_promoted: true }); + return count; +}; + +/** + * Load a default agent based on the endpoint + * @param {string} endpoint + * @returns {Agent | null} + */ + +module.exports = { + getAgent, + getAgents, + loadAgent, + createAgent, + updateAgent, + deleteAgent, + deleteUserAgents, + revertAgentVersion, + updateAgentProjects, + countPromotedAgents, + addAgentResourceFile, + getListAgentsByAccess, + removeAgentResourceFiles, + generateActionMetadataHash, +}; diff --git a/packages/data-schemas/src/methods/agent.spec.ts b/api/models/Agent.spec.js similarity index 66% rename from packages/data-schemas/src/methods/agent.spec.ts rename to api/models/Agent.spec.js index 3184f51fa1..baceb3e8f3 100644 --- a/packages/data-schemas/src/methods/agent.spec.ts +++ b/api/models/Agent.spec.js @@ -1,128 +1,62 @@ -import mongoose from 'mongoose'; -import { v4 as uuidv4 } from 'uuid'; -import { MongoMemoryServer } from 'mongodb-memory-server'; -import { - AccessRoleIds, - ResourceType, - PrincipalType, - PrincipalModel, - PermissionBits, - EToolResources, -} from 'librechat-data-provider'; -import type { - UpdateWithAggregationPipeline, - RootFilterQuery, - QueryOptions, - UpdateQuery, -} from 'mongoose'; -import type { IAgent, IAclEntry, IUser, IAccessRole } from '..'; -import { createAgentMethods, type AgentMethods } from './agent'; -import { createAclEntryMethods } from './aclEntry'; -import { createModels } from '~/models'; - -/** Version snapshot stored in `IAgent.versions[]`. Extends the base omit with runtime-only fields. */ -type VersionEntry = Omit & { - __v?: number; - versions?: unknown; - version?: number; - updatedBy?: mongoose.Types.ObjectId; +const originalEnv = { + CREDS_KEY: process.env.CREDS_KEY, + CREDS_IV: process.env.CREDS_IV, }; -jest.mock('~/config/winston', () => ({ - error: jest.fn(), - warn: jest.fn(), - info: jest.fn(), - debug: jest.fn(), +process.env.CREDS_KEY = '0123456789abcdef0123456789abcdef'; +process.env.CREDS_IV = '0123456789abcdef'; + +jest.mock('~/server/services/Config', () => ({ + getCachedTools: jest.fn(), + getMCPServerTools: jest.fn(), })); -let mongoServer: InstanceType; -let Agent: mongoose.Model; -let AclEntry: mongoose.Model; -let User: mongoose.Model; -let AccessRole: mongoose.Model; -let modelsToCleanup: string[] = []; -let methods: ReturnType; +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 { + getAgent, + loadAgent, + createAgent, + updateAgent, + deleteAgent, + deleteUserAgents, + revertAgentVersion, + updateAgentProjects, + addAgentResourceFile, + getListAgentsByAccess, + removeAgentResourceFiles, + generateActionMetadataHash, +} = require('./Agent'); +const permissionService = require('~/server/services/PermissionService'); +const { getCachedTools, getMCPServerTools } = require('~/server/services/Config'); +const { AclEntry, User } = require('~/db/models'); -let createAgent: AgentMethods['createAgent']; -let getAgent: AgentMethods['getAgent']; -let updateAgent: AgentMethods['updateAgent']; -let deleteAgent: AgentMethods['deleteAgent']; -let deleteUserAgents: AgentMethods['deleteUserAgents']; -let revertAgentVersion: AgentMethods['revertAgentVersion']; -let addAgentResourceFile: AgentMethods['addAgentResourceFile']; -let removeAgentResourceFiles: AgentMethods['removeAgentResourceFiles']; -let getListAgentsByAccess: AgentMethods['getListAgentsByAccess']; -let generateActionMetadataHash: AgentMethods['generateActionMetadataHash']; +/** + * @type {import('mongoose').Model} + */ +let Agent; -const getActions = jest.fn().mockResolvedValue([]); - -beforeAll(async () => { - mongoServer = await MongoMemoryServer.create(); - const mongoUri = mongoServer.getUri(); - - const models = createModels(mongoose); - modelsToCleanup = Object.keys(models); - Agent = mongoose.models.Agent as mongoose.Model; - AclEntry = mongoose.models.AclEntry as mongoose.Model; - User = mongoose.models.User as mongoose.Model; - AccessRole = mongoose.models.AccessRole as mongoose.Model; - - const removeAllPermissions = async ({ - resourceType, - resourceId, - }: { - resourceType: string; - resourceId: unknown; - }) => { - await AclEntry.deleteMany({ resourceType, resourceId }); - }; - - const aclEntryMethods = createAclEntryMethods(mongoose); - const { getSoleOwnedResourceIds } = aclEntryMethods; - - methods = createAgentMethods(mongoose, { - removeAllPermissions, - getActions, - getSoleOwnedResourceIds, - }); - createAgent = methods.createAgent; - getAgent = methods.getAgent; - updateAgent = methods.updateAgent; - deleteAgent = methods.deleteAgent; - deleteUserAgents = methods.deleteUserAgents; - revertAgentVersion = methods.revertAgentVersion; - addAgentResourceFile = methods.addAgentResourceFile; - removeAgentResourceFiles = methods.removeAgentResourceFiles; - getListAgentsByAccess = methods.getListAgentsByAccess; - generateActionMetadataHash = methods.generateActionMetadataHash; - - await mongoose.connect(mongoUri); - - await AccessRole.create({ - accessRoleId: AccessRoleIds.AGENT_OWNER, - name: 'Owner', - description: 'Full control over agents', - resourceType: ResourceType.AGENT, - permBits: 15, - }); -}, 30000); - -afterAll(async () => { - const collections = mongoose.connection.collections; - for (const key in collections) { - await collections[key].deleteMany({}); - } - for (const modelName of modelsToCleanup) { - if (mongoose.models[modelName]) { - delete (mongoose.models as Record)[modelName]; - } - } - await mongoose.disconnect(); - await mongoServer.stop(); -}); - -describe('Agent Methods', () => { +describe('models/Agent', () => { describe('Agent Resource File Operations', () => { + let mongoServer; + + beforeAll(async () => { + mongoServer = await MongoMemoryServer.create(); + const mongoUri = mongoServer.getUri(); + Agent = mongoose.models.Agent || mongoose.model('Agent', agentSchema); + await mongoose.connect(mongoUri); + }, 20000); + + afterAll(async () => { + await mongoose.disconnect(); + await mongoServer.stop(); + process.env.CREDS_KEY = originalEnv.CREDS_KEY; + process.env.CREDS_IV = originalEnv.CREDS_IV; + }); + beforeEach(async () => { await Agent.deleteMany({}); await User.deleteMany({}); @@ -139,10 +73,10 @@ describe('Agent Methods', () => { file_id: fileId, }); - expect(updatedAgent!.tools).toContain(toolResource); - expect(Array.isArray(updatedAgent!.tools)).toBe(true); + expect(updatedAgent.tools).toContain(toolResource); + expect(Array.isArray(updatedAgent.tools)).toBe(true); // Should not duplicate - const count = updatedAgent!.tools?.filter((t) => t === toolResource).length ?? 0; + const count = updatedAgent.tools.filter((t) => t === toolResource).length; expect(count).toBe(1); }); @@ -166,9 +100,9 @@ describe('Agent Methods', () => { file_id: fileId2, }); - expect(updatedAgent!.tools).toContain(toolResource); - expect(Array.isArray(updatedAgent!.tools)).toBe(true); - const count = updatedAgent!.tools?.filter((t) => t === toolResource).length ?? 0; + expect(updatedAgent.tools).toContain(toolResource); + expect(Array.isArray(updatedAgent.tools)).toBe(true); + const count = updatedAgent.tools.filter((t) => t === toolResource).length; expect(count).toBe(1); }); @@ -182,13 +116,9 @@ describe('Agent Methods', () => { await Promise.all(additionPromises); const updatedAgent = await Agent.findOne({ id: agent.id }); - expect(updatedAgent?.tool_resources?.[EToolResources.execute_code]?.file_ids).toBeDefined(); - expect(updatedAgent?.tool_resources?.[EToolResources.execute_code]?.file_ids).toHaveLength( - 10, - ); - expect( - new Set(updatedAgent?.tool_resources?.[EToolResources.execute_code]?.file_ids).size, - ).toBe(10); + expect(updatedAgent.tool_resources.test_tool.file_ids).toBeDefined(); + expect(updatedAgent.tool_resources.test_tool.file_ids).toHaveLength(10); + expect(new Set(updatedAgent.tool_resources.test_tool.file_ids).size).toBe(10); }); test('should handle concurrent additions and removals', async () => { @@ -198,18 +128,18 @@ describe('Agent Methods', () => { await Promise.all(createFileOperations(agent.id, initialFileIds, 'add')); const newFileIds = Array.from({ length: 5 }, () => uuidv4()); - const operations: Promise[] = [ + const operations = [ ...newFileIds.map((fileId) => addAgentResourceFile({ agent_id: agent.id, - tool_resource: EToolResources.execute_code, + tool_resource: 'test_tool', file_id: fileId, }), ), ...initialFileIds.map((fileId) => removeAgentResourceFiles({ agent_id: agent.id, - files: [{ tool_resource: EToolResources.execute_code, file_id: fileId }], + files: [{ tool_resource: 'test_tool', file_id: fileId }], }), ), ]; @@ -217,8 +147,8 @@ describe('Agent Methods', () => { await Promise.all(operations); const updatedAgent = await Agent.findOne({ id: agent.id }); - expect(updatedAgent?.tool_resources?.[EToolResources.execute_code]?.file_ids).toBeDefined(); - expect(updatedAgent?.tool_resources?.[EToolResources.execute_code]?.file_ids).toHaveLength(5); + expect(updatedAgent.tool_resources.test_tool.file_ids).toBeDefined(); + expect(updatedAgent.tool_resources.test_tool.file_ids).toHaveLength(5); }); test('should initialize array when adding to non-existent tool resource', async () => { @@ -227,13 +157,13 @@ describe('Agent Methods', () => { const updatedAgent = await addAgentResourceFile({ agent_id: agent.id, - tool_resource: EToolResources.context, + tool_resource: 'new_tool', file_id: fileId, }); - expect(updatedAgent?.tool_resources?.[EToolResources.context]?.file_ids).toBeDefined(); - expect(updatedAgent?.tool_resources?.[EToolResources.context]?.file_ids).toHaveLength(1); - expect(updatedAgent?.tool_resources?.[EToolResources.context]?.file_ids?.[0]).toBe(fileId); + expect(updatedAgent.tool_resources.new_tool.file_ids).toBeDefined(); + expect(updatedAgent.tool_resources.new_tool.file_ids).toHaveLength(1); + expect(updatedAgent.tool_resources.new_tool.file_ids[0]).toBe(fileId); }); test('should handle rapid sequential modifications to same tool resource', async () => { @@ -243,33 +173,27 @@ describe('Agent Methods', () => { for (let i = 0; i < 10; i++) { await addAgentResourceFile({ agent_id: agent.id, - tool_resource: EToolResources.execute_code, + tool_resource: 'test_tool', file_id: `${fileId}_${i}`, }); if (i % 2 === 0) { await removeAgentResourceFiles({ agent_id: agent.id, - files: [{ tool_resource: EToolResources.execute_code, file_id: `${fileId}_${i}` }], + files: [{ tool_resource: 'test_tool', file_id: `${fileId}_${i}` }], }); } } const updatedAgent = await Agent.findOne({ id: agent.id }); - expect(updatedAgent?.tool_resources?.[EToolResources.execute_code]?.file_ids).toBeDefined(); - expect( - Array.isArray(updatedAgent!.tool_resources![EToolResources.execute_code]!.file_ids), - ).toBe(true); + expect(updatedAgent.tool_resources.test_tool.file_ids).toBeDefined(); + expect(Array.isArray(updatedAgent.tool_resources.test_tool.file_ids)).toBe(true); }); test('should handle multiple tool resources concurrently', async () => { const agent = await createBasicAgent(); - const toolResources = [ - EToolResources.file_search, - EToolResources.execute_code, - EToolResources.image_edit, - ] as const; - const operations: Promise[] = []; + const toolResources = ['tool1', 'tool2', 'tool3']; + const operations = []; toolResources.forEach((tool) => { const fileIds = Array.from({ length: 5 }, () => uuidv4()); @@ -288,8 +212,8 @@ describe('Agent Methods', () => { const updatedAgent = await Agent.findOne({ id: agent.id }); toolResources.forEach((tool) => { - expect(updatedAgent!.tool_resources![tool]!.file_ids).toBeDefined(); - expect(updatedAgent!.tool_resources![tool]!.file_ids).toHaveLength(5); + expect(updatedAgent.tool_resources[tool].file_ids).toBeDefined(); + expect(updatedAgent.tool_resources[tool].file_ids).toHaveLength(5); }); }); @@ -318,7 +242,7 @@ describe('Agent Methods', () => { if (setupFile) { await addAgentResourceFile({ agent_id: agent.id, - tool_resource: EToolResources.execute_code, + tool_resource: 'test_tool', file_id: fileId, }); } @@ -327,19 +251,19 @@ describe('Agent Methods', () => { operation === 'add' ? addAgentResourceFile({ agent_id: agent.id, - tool_resource: EToolResources.execute_code, + tool_resource: 'test_tool', file_id: fileId, }) : removeAgentResourceFiles({ agent_id: agent.id, - files: [{ tool_resource: EToolResources.execute_code, file_id: fileId }], + files: [{ tool_resource: 'test_tool', file_id: fileId }], }), ); await Promise.all(promises); const updatedAgent = await Agent.findOne({ id: agent.id }); - const fileIds = updatedAgent?.tool_resources?.[EToolResources.execute_code]?.file_ids ?? []; + const fileIds = updatedAgent.tool_resources?.test_tool?.file_ids ?? []; expect(fileIds).toHaveLength(expectedLength); if (expectedContains) { @@ -356,27 +280,27 @@ describe('Agent Methods', () => { await addAgentResourceFile({ agent_id: agent.id, - tool_resource: EToolResources.execute_code, + tool_resource: 'test_tool', file_id: fileId, }); - const operations: Promise[] = [ + const operations = [ addAgentResourceFile({ agent_id: agent.id, - tool_resource: EToolResources.execute_code, + tool_resource: 'test_tool', file_id: fileId, }), removeAgentResourceFiles({ agent_id: agent.id, - files: [{ tool_resource: EToolResources.execute_code, file_id: fileId }], + files: [{ tool_resource: 'test_tool', file_id: fileId }], }), ]; await Promise.all(operations); const updatedAgent = await Agent.findOne({ id: agent.id }); - const finalFileIds = updatedAgent!.tool_resources![EToolResources.execute_code]!.file_ids!; - const count = finalFileIds.filter((id: string) => id === fileId).length; + const finalFileIds = updatedAgent.tool_resources.test_tool.file_ids; + const count = finalFileIds.filter((id) => id === fileId).length; expect(count).toBeLessThanOrEqual(1); if (count === 0) { @@ -396,7 +320,7 @@ describe('Agent Methods', () => { fileIds.map((fileId) => addAgentResourceFile({ agent_id: agent.id, - tool_resource: EToolResources.execute_code, + tool_resource: 'test_tool', file_id: fileId, }), ), @@ -406,7 +330,7 @@ describe('Agent Methods', () => { const removalPromises = fileIds.map((fileId) => removeAgentResourceFiles({ agent_id: agent.id, - files: [{ tool_resource: EToolResources.execute_code, file_id: fileId }], + files: [{ tool_resource: 'test_tool', file_id: fileId }], }), ); @@ -414,8 +338,7 @@ describe('Agent Methods', () => { const updatedAgent = await Agent.findOne({ id: agent.id }); // Check if the array is empty or the tool resource itself is removed - const finalFileIds = - updatedAgent?.tool_resources?.[EToolResources.execute_code]?.file_ids ?? []; + const finalFileIds = updatedAgent.tool_resources?.test_tool?.file_ids ?? []; expect(finalFileIds).toHaveLength(0); }); @@ -439,7 +362,7 @@ describe('Agent Methods', () => { ])('addAgentResourceFile with $name', ({ needsAgent, params, shouldResolve, error }) => { test(`should ${shouldResolve ? 'resolve' : 'reject'}`, async () => { const agent = needsAgent ? await createBasicAgent() : null; - const agent_id = needsAgent ? agent!.id : `agent_${uuidv4()}`; + const agent_id = needsAgent ? agent.id : `agent_${uuidv4()}`; if (shouldResolve) { await expect(addAgentResourceFile({ agent_id, ...params })).resolves.toBeDefined(); @@ -452,7 +375,7 @@ describe('Agent Methods', () => { describe.each([ { name: 'empty files array', - files: [] as { tool_resource: string; file_id: string }[], + files: [], needsAgent: true, shouldResolve: true, }, @@ -472,7 +395,7 @@ describe('Agent Methods', () => { ])('removeAgentResourceFiles with $name', ({ files, needsAgent, shouldResolve, error }) => { test(`should ${shouldResolve ? 'resolve' : 'reject'}`, async () => { const agent = needsAgent ? await createBasicAgent() : null; - const agent_id = needsAgent ? agent!.id : `agent_${uuidv4()}`; + const agent_id = needsAgent ? agent.id : `agent_${uuidv4()}`; if (shouldResolve) { const result = await removeAgentResourceFiles({ agent_id, files }); @@ -489,10 +412,36 @@ describe('Agent Methods', () => { }); describe('Agent CRUD Operations', () => { + let mongoServer; + let AccessRole; + + beforeAll(async () => { + mongoServer = await MongoMemoryServer.create(); + const mongoUri = mongoServer.getUri(); + Agent = mongoose.models.Agent || mongoose.model('Agent', agentSchema); + await mongoose.connect(mongoUri); + + // Initialize models + const dbModels = require('~/db/models'); + AccessRole = dbModels.AccessRole; + + // Create necessary access roles for agents + await AccessRole.create({ + accessRoleId: AccessRoleIds.AGENT_OWNER, + name: 'Owner', + description: 'Full control over agents', + resourceType: ResourceType.AGENT, + permBits: 15, // VIEW | EDIT | DELETE | SHARE + }); + }, 20000); + + afterAll(async () => { + await mongoose.disconnect(); + await mongoServer.stop(); + }); + beforeEach(async () => { await Agent.deleteMany({}); - await User.deleteMany({}); - await AclEntry.deleteMany({}); }); test('should create and get an agent', async () => { @@ -513,9 +462,9 @@ describe('Agent Methods', () => { const retrievedAgent = await getAgent({ id: agentId }); expect(retrievedAgent).toBeDefined(); - expect(retrievedAgent!.id).toBe(agentId); - expect(retrievedAgent!.name).toBe('Test Agent'); - expect(retrievedAgent!.description).toBe('Test description'); + expect(retrievedAgent.id).toBe(agentId); + expect(retrievedAgent.name).toBe('Test Agent'); + expect(retrievedAgent.description).toBe('Test description'); }); test('should delete an agent', async () => { @@ -553,9 +502,8 @@ describe('Agent Methods', () => { }); // Grant permissions (simulating sharing) - await AclEntry.create({ + await permissionService.grantPermission({ principalType: PrincipalType.USER, - principalModel: PrincipalModel.USER, principalId: authorId, resourceType: ResourceType.AGENT, resourceId: agent._id, @@ -617,15 +565,15 @@ describe('Agent Methods', () => { // Verify edge exists before deletion const sourceAgentBefore = await getAgent({ id: sourceAgentId }); - expect(sourceAgentBefore!.edges).toHaveLength(1); - expect(sourceAgentBefore!.edges![0].to).toBe(targetAgentId); + expect(sourceAgentBefore.edges).toHaveLength(1); + expect(sourceAgentBefore.edges[0].to).toBe(targetAgentId); // Delete the target agent await deleteAgent({ id: targetAgentId }); // Verify the edge is removed from source agent const sourceAgentAfter = await getAgent({ id: sourceAgentId }); - expect(sourceAgentAfter!.edges).toHaveLength(0); + expect(sourceAgentAfter.edges).toHaveLength(0); }); test('should remove agent from user favorites when agent is deleted', async () => { @@ -653,10 +601,8 @@ describe('Agent Methods', () => { // Verify user has agent in favorites const userBefore = await User.findById(userId); - expect(userBefore!.favorites).toHaveLength(2); - expect( - userBefore!.favorites!.some((f: Record) => f.agentId === agentId), - ).toBe(true); + expect(userBefore.favorites).toHaveLength(2); + expect(userBefore.favorites.some((f) => f.agentId === agentId)).toBe(true); // Delete the agent await deleteAgent({ id: agentId }); @@ -667,13 +613,9 @@ describe('Agent Methods', () => { // Verify agent is removed from user favorites const userAfter = await User.findById(userId); - expect(userAfter!.favorites).toHaveLength(1); - expect( - userAfter!.favorites!.some((f: Record) => f.agentId === agentId), - ).toBe(false); - expect(userAfter!.favorites!.some((f: Record) => f.model === 'gpt-4')).toBe( - true, - ); + expect(userAfter.favorites).toHaveLength(1); + expect(userAfter.favorites.some((f) => f.agentId === agentId)).toBe(false); + expect(userAfter.favorites.some((f) => f.model === 'gpt-4')).toBe(true); }); test('should remove agent from multiple users favorites when agent is deleted', async () => { @@ -715,11 +657,9 @@ describe('Agent Methods', () => { const user1After = await User.findById(user1Id); const user2After = await User.findById(user2Id); - expect(user1After!.favorites).toHaveLength(0); - expect(user2After!.favorites).toHaveLength(1); - expect( - user2After!.favorites!.some((f: Record) => f.agentId === agentId), - ).toBe(false); + expect(user1After.favorites).toHaveLength(0); + expect(user2After.favorites).toHaveLength(1); + expect(user2After.favorites.some((f) => f.agentId === agentId)).toBe(false); }); test('should preserve other agents in database when one agent is deleted', async () => { @@ -766,9 +706,9 @@ describe('Agent Methods', () => { const keptAgent1 = await getAgent({ id: agentToKeep1Id }); const keptAgent2 = await getAgent({ id: agentToKeep2Id }); expect(keptAgent1).not.toBeNull(); - expect(keptAgent1!.name).toBe('Agent To Keep 1'); + expect(keptAgent1.name).toBe('Agent To Keep 1'); expect(keptAgent2).not.toBeNull(); - expect(keptAgent2!.name).toBe('Agent To Keep 2'); + expect(keptAgent2.name).toBe('Agent To Keep 2'); }); test('should preserve other agents in user favorites when one agent is deleted', async () => { @@ -818,23 +758,17 @@ describe('Agent Methods', () => { // Verify user has all three agents in favorites const userBefore = await User.findById(userId); - expect(userBefore!.favorites).toHaveLength(3); + expect(userBefore.favorites).toHaveLength(3); // Delete one agent await deleteAgent({ id: agentToDeleteId }); // Verify only the deleted agent is removed from favorites const userAfter = await User.findById(userId); - expect(userAfter!.favorites).toHaveLength(2); - expect( - userAfter!.favorites?.some((f: Record) => f.agentId === agentToDeleteId), - ).toBe(false); - expect( - userAfter!.favorites?.some((f: Record) => f.agentId === agentToKeep1Id), - ).toBe(true); - expect( - userAfter!.favorites?.some((f: Record) => f.agentId === agentToKeep2Id), - ).toBe(true); + expect(userAfter.favorites).toHaveLength(2); + expect(userAfter.favorites.some((f) => f.agentId === agentToDeleteId)).toBe(false); + expect(userAfter.favorites.some((f) => f.agentId === agentToKeep1Id)).toBe(true); + expect(userAfter.favorites.some((f) => f.agentId === agentToKeep2Id)).toBe(true); }); test('should not affect users who do not have deleted agent in favorites', async () => { @@ -884,27 +818,15 @@ describe('Agent Methods', () => { // Verify user with deleted agent has it removed const userWithDeleted = await User.findById(userWithDeletedAgentId); - expect(userWithDeleted!.favorites).toHaveLength(1); - expect( - userWithDeleted!.favorites!.some( - (f: Record) => f.agentId === agentToDeleteId, - ), - ).toBe(false); - expect( - userWithDeleted!.favorites!.some((f: Record) => f.model === 'gpt-4'), - ).toBe(true); + expect(userWithDeleted.favorites).toHaveLength(1); + expect(userWithDeleted.favorites.some((f) => f.agentId === agentToDeleteId)).toBe(false); + expect(userWithDeleted.favorites.some((f) => f.model === 'gpt-4')).toBe(true); // Verify user without deleted agent is completely unaffected const userWithoutDeleted = await User.findById(userWithoutDeletedAgentId); - expect(userWithoutDeleted!.favorites).toHaveLength(2); - expect( - userWithoutDeleted!.favorites!.some( - (f: Record) => f.agentId === otherAgentId, - ), - ).toBe(true); - expect( - userWithoutDeleted!.favorites!.some((f: Record) => f.model === 'claude-3'), - ).toBe(true); + expect(userWithoutDeleted.favorites).toHaveLength(2); + expect(userWithoutDeleted.favorites.some((f) => f.agentId === otherAgentId)).toBe(true); + expect(userWithoutDeleted.favorites.some((f) => f.model === 'claude-3')).toBe(true); }); test('should remove all user agents from favorites when deleteUserAgents is called', async () => { @@ -916,7 +838,8 @@ describe('Agent Methods', () => { const agent2Id = `agent_${uuidv4()}`; const otherAuthorAgentId = `agent_${uuidv4()}`; - const agent1 = await createAgent({ + // Create agents by the author to be deleted + await createAgent({ id: agent1Id, name: 'Author Agent 1', provider: 'test', @@ -924,7 +847,7 @@ describe('Agent Methods', () => { author: authorId, }); - const agent2 = await createAgent({ + await createAgent({ id: agent2Id, name: 'Author Agent 2', provider: 'test', @@ -932,6 +855,7 @@ describe('Agent Methods', () => { author: authorId, }); + // Create agent by different author (should not be deleted) await createAgent({ id: otherAuthorAgentId, name: 'Other Author Agent', @@ -940,29 +864,7 @@ describe('Agent Methods', () => { author: otherAuthorId, }); - const ownerBits = - PermissionBits.VIEW | PermissionBits.EDIT | PermissionBits.DELETE | PermissionBits.SHARE; - await AclEntry.create({ - principalType: PrincipalType.USER, - principalId: authorId, - principalModel: PrincipalModel.USER, - resourceType: ResourceType.AGENT, - resourceId: agent1._id, - permBits: ownerBits, - grantedBy: authorId, - grantedAt: new Date(), - }); - await AclEntry.create({ - principalType: PrincipalType.USER, - principalId: authorId, - principalModel: PrincipalModel.USER, - resourceType: ResourceType.AGENT, - resourceId: agent2._id, - permBits: ownerBits, - grantedBy: authorId, - grantedAt: new Date(), - }); - + // Create user with all agents in favorites await User.create({ _id: userId, name: 'Test User', @@ -976,32 +878,27 @@ describe('Agent Methods', () => { ], }); + // Verify user has all favorites const userBefore = await User.findById(userId); - expect(userBefore!.favorites).toHaveLength(4); + 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: Record) => f.agentId === agent1Id), - ).toBe(false); - expect( - userAfter!.favorites!.some((f: Record) => f.agentId === agent2Id), - ).toBe(false); - expect( - userAfter!.favorites!.some( - (f: Record) => f.agentId === otherAuthorAgentId, - ), - ).toBe(true); - expect(userAfter!.favorites!.some((f: Record) => f.model === 'gpt-4')).toBe( - true, - ); + expect(userAfter.favorites).toHaveLength(2); + expect(userAfter.favorites.some((f) => f.agentId === agent1Id)).toBe(false); + expect(userAfter.favorites.some((f) => f.agentId === agent2Id)).toBe(false); + expect(userAfter.favorites.some((f) => f.agentId === otherAuthorAgentId)).toBe(true); + expect(userAfter.favorites.some((f) => f.model === 'gpt-4')).toBe(true); }); test('should handle deleteUserAgents when agents are in multiple users favorites', async () => { @@ -1014,7 +911,8 @@ describe('Agent Methods', () => { const agent2Id = `agent_${uuidv4()}`; const unrelatedAgentId = `agent_${uuidv4()}`; - const agent1 = await createAgent({ + // Create agents by the author + await createAgent({ id: agent1Id, name: 'Author Agent 1', provider: 'test', @@ -1022,7 +920,7 @@ describe('Agent Methods', () => { author: authorId, }); - const agent2 = await createAgent({ + await createAgent({ id: agent2Id, name: 'Author Agent 2', provider: 'test', @@ -1030,29 +928,7 @@ describe('Agent Methods', () => { author: authorId, }); - const ownerBits = - PermissionBits.VIEW | PermissionBits.EDIT | PermissionBits.DELETE | PermissionBits.SHARE; - await AclEntry.create({ - principalType: PrincipalType.USER, - principalId: authorId, - principalModel: PrincipalModel.USER, - resourceType: ResourceType.AGENT, - resourceId: agent1._id, - permBits: ownerBits, - grantedBy: authorId, - grantedAt: new Date(), - }); - await AclEntry.create({ - principalType: PrincipalType.USER, - principalId: authorId, - principalModel: PrincipalModel.USER, - resourceType: ResourceType.AGENT, - resourceId: agent2._id, - permBits: ownerBits, - grantedBy: authorId, - grantedAt: new Date(), - }); - + // Create users with various favorites configurations await User.create({ _id: user1Id, name: 'User 1', @@ -1077,28 +953,23 @@ describe('Agent Methods', () => { 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); + expect(user1After.favorites).toHaveLength(0); const user2After = await User.findById(user2Id); - expect(user2After!.favorites).toHaveLength(1); - expect( - user2After!.favorites!.some((f: Record) => f.agentId === agent1Id), - ).toBe(false); - expect( - user2After!.favorites!.some((f: Record) => f.model === 'claude-3'), - ).toBe(true); + expect(user2After.favorites).toHaveLength(1); + 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: Record) => f.agentId === unrelatedAgentId), - ).toBe(true); - expect(user3After!.favorites!.some((f: Record) => f.model === 'gpt-4')).toBe( - true, - ); + expect(user3After.favorites).toHaveLength(2); + expect(user3After.favorites.some((f) => f.agentId === unrelatedAgentId)).toBe(true); + expect(user3After.favorites.some((f) => f.model === 'gpt-4')).toBe(true); }); test('should handle deleteUserAgents when user has no agents', async () => { @@ -1108,7 +979,8 @@ describe('Agent Methods', () => { const existingAgentId = `agent_${uuidv4()}`; - const existingAgent = await createAgent({ + // Create agent by different author + await createAgent({ id: existingAgentId, name: 'Existing Agent', provider: 'test', @@ -1116,19 +988,7 @@ describe('Agent Methods', () => { author: otherAuthorId, }); - const ownerBits = - PermissionBits.VIEW | PermissionBits.EDIT | PermissionBits.DELETE | PermissionBits.SHARE; - await AclEntry.create({ - principalType: PrincipalType.USER, - principalId: otherAuthorId, - principalModel: PrincipalModel.USER, - resourceType: ResourceType.AGENT, - resourceId: existingAgent._id, - permBits: ownerBits, - grantedBy: otherAuthorId, - grantedAt: new Date(), - }); - + // Create user with favorites await User.create({ _id: userId, name: 'Test User', @@ -1137,18 +997,17 @@ describe('Agent Methods', () => { 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: Record) => f.agentId === existingAgentId), - ).toBe(true); - expect(userAfter!.favorites!.some((f: Record) => f.model === 'gpt-4')).toBe( - true, - ); + expect(userAfter.favorites).toHaveLength(2); + expect(userAfter.favorites.some((f) => f.agentId === existingAgentId)).toBe(true); + expect(userAfter.favorites.some((f) => f.model === 'gpt-4')).toBe(true); }); test('should handle deleteUserAgents when agents are not in any favorites', async () => { @@ -1158,7 +1017,8 @@ describe('Agent Methods', () => { const agent1Id = `agent_${uuidv4()}`; const agent2Id = `agent_${uuidv4()}`; - const agent1 = await createAgent({ + // Create agents by the author + await createAgent({ id: agent1Id, name: 'Agent 1', provider: 'test', @@ -1166,7 +1026,7 @@ describe('Agent Methods', () => { author: authorId, }); - const agent2 = await createAgent({ + await createAgent({ id: agent2Id, name: 'Agent 2', provider: 'test', @@ -1174,29 +1034,7 @@ describe('Agent Methods', () => { author: authorId, }); - const ownerBits = - PermissionBits.VIEW | PermissionBits.EDIT | PermissionBits.DELETE | PermissionBits.SHARE; - await AclEntry.create({ - principalType: PrincipalType.USER, - principalId: authorId, - principalModel: PrincipalModel.USER, - resourceType: ResourceType.AGENT, - resourceId: agent1._id, - permBits: ownerBits, - grantedBy: authorId, - grantedAt: new Date(), - }); - await AclEntry.create({ - principalType: PrincipalType.USER, - principalId: authorId, - principalModel: PrincipalModel.USER, - resourceType: ResourceType.AGENT, - resourceId: agent2._id, - permBits: ownerBits, - grantedBy: authorId, - grantedAt: new Date(), - }); - + // Create user with favorites that don't include these agents await User.create({ _id: userId, name: 'Test User', @@ -1205,119 +1043,130 @@ describe('Agent Methods', () => { 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: Record) => f.model === 'gpt-4')).toBe( - true, - ); + 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 AclEntry.create({ - principalType: PrincipalType.USER, - principalId: deletingUserId, - principalModel: PrincipalModel.USER, - resourceType: ResourceType.AGENT, - resourceId: (soleAgent as unknown as { _id: mongoose.Types.ObjectId })._id, - permBits: PermissionBits.DELETE | PermissionBits.VIEW | PermissionBits.EDIT, - }); - - await AclEntry.create({ - principalType: PrincipalType.USER, - principalId: deletingUserId, - principalModel: PrincipalModel.USER, - resourceType: ResourceType.AGENT, - resourceId: (multiAgent as unknown as { _id: mongoose.Types.ObjectId })._id, - permBits: PermissionBits.DELETE | PermissionBits.VIEW | PermissionBits.EDIT, - }); - await AclEntry.create({ - principalType: PrincipalType.USER, - principalId: otherOwnerId, - principalModel: PrincipalModel.USER, - resourceType: ResourceType.AGENT, - resourceId: (multiAgent as unknown as { _id: mongoose.Types.ObjectId })._id, - permBits: PermissionBits.DELETE | PermissionBits.VIEW | PermissionBits.EDIT, - }); - - 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 as unknown as { _id: mongoose.Types.ObjectId })._id, - }); - expect(soleAcl).toHaveLength(0); - - const multiAcl = await AclEntry.find({ - resourceType: ResourceType.AGENT, - resourceId: (multiAgent as unknown as { _id: mongoose.Types.ObjectId })._id, - principalId: otherOwnerId, - }); - expect(multiAcl).toHaveLength(1); - expect(multiAcl[0].permBits & PermissionBits.DELETE).toBeTruthy(); - - const deletingUserMultiAcl = await AclEntry.find({ - resourceType: ResourceType.AGENT, - resourceId: (multiAgent as unknown as { _id: mongoose.Types.ObjectId })._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()}`; + test('should update agent projects', async () => { + const agentId = `agent_${uuidv4()}`; + const authorId = new mongoose.Types.ObjectId(); + const projectId1 = new mongoose.Types.ObjectId(); + const projectId2 = new mongoose.Types.ObjectId(); + const projectId3 = new mongoose.Types.ObjectId(); await createAgent({ - id: legacyAgentId, - name: 'Legacy Agent (no ACL)', + id: agentId, + name: 'Project Test Agent', provider: 'test', model: 'test-model', - author: legacyUserId, + author: authorId, + projectIds: [projectId1], }); - await deleteUserAgents(legacyUserId.toString()); + await updateAgent( + { id: agentId }, + { $addToSet: { projectIds: { $each: [projectId2, projectId3] } } }, + ); - expect(await getAgent({ id: legacyAgentId })).toBeNull(); + await updateAgent({ id: agentId }, { $pull: { projectIds: projectId1 } }); + + await updateAgent({ id: agentId }, { projectIds: [projectId2, projectId3] }); + + const updatedAgent = await getAgent({ id: agentId }); + expect(updatedAgent.projectIds).toHaveLength(2); + expect(updatedAgent.projectIds.map((id) => id.toString())).toContain(projectId2.toString()); + expect(updatedAgent.projectIds.map((id) => id.toString())).toContain(projectId3.toString()); + expect(updatedAgent.projectIds.map((id) => id.toString())).not.toContain( + projectId1.toString(), + ); + + await updateAgent({ id: agentId }, { projectIds: [] }); + + const emptyProjectsAgent = await getAgent({ id: agentId }); + expect(emptyProjectsAgent.projectIds).toHaveLength(0); + + const nonExistentId = `agent_${uuidv4()}`; + await expect( + updateAgentProjects({ + id: nonExistentId, + projectIds: [projectId1], + }), + ).rejects.toThrow(); + }); + + test('should handle ephemeral agent loading', async () => { + const agentId = 'ephemeral_test'; + const endpoint = 'openai'; + + const originalModule = jest.requireActual('librechat-data-provider'); + + const mockDataProvider = { + ...originalModule, + Constants: { + ...originalModule.Constants, + EPHEMERAL_AGENT_ID: 'ephemeral_test', + }, + }; + + jest.doMock('librechat-data-provider', () => mockDataProvider); + + expect(agentId).toBeDefined(); + expect(endpoint).toBeDefined(); + + jest.dontMock('librechat-data-provider'); + }); + + test('should handle loadAgent functionality and errors', async () => { + const agentId = `agent_${uuidv4()}`; + const authorId = new mongoose.Types.ObjectId(); + + await createAgent({ + id: agentId, + name: 'Test Load Agent', + provider: 'test', + model: 'test-model', + author: authorId, + tools: ['tool1', 'tool2'], + }); + + const agent = await getAgent({ id: agentId }); + + expect(agent).toBeDefined(); + expect(agent.id).toBe(agentId); + expect(agent.name).toBe('Test Load Agent'); + expect(agent.tools).toEqual(expect.arrayContaining(['tool1', 'tool2'])); + + const mockLoadAgent = jest.fn().mockResolvedValue(agent); + const loadedAgent = await mockLoadAgent(); + expect(loadedAgent).toBeDefined(); + expect(loadedAgent.id).toBe(agentId); + + const nonExistentId = `agent_${uuidv4()}`; + const nonExistentAgent = await getAgent({ id: nonExistentId }); + expect(nonExistentAgent).toBeNull(); + + const mockLoadAgentError = jest.fn().mockRejectedValue(new Error('No agent found with ID')); + await expect(mockLoadAgentError()).rejects.toThrow('No agent found with ID'); }); describe('Edge Cases', () => { test.each([ { name: 'getAgent with undefined search parameters', - fn: () => getAgent(undefined as unknown as Parameters[0]), + fn: () => getAgent(undefined), expected: null, }, { @@ -1329,10 +1178,38 @@ describe('Agent Methods', () => { const result = await fn(); expect(result).toBe(expected); }); + + test('should handle updateAgentProjects with non-existent agent', async () => { + const nonExistentId = `agent_${uuidv4()}`; + const userId = new mongoose.Types.ObjectId(); + const projectId = new mongoose.Types.ObjectId(); + + const result = await updateAgentProjects({ + user: { id: userId.toString() }, + agentId: nonExistentId, + projectIds: [projectId.toString()], + }); + + expect(result).toBeNull(); + }); }); }); describe('Agent Version History', () => { + let mongoServer; + + beforeAll(async () => { + mongoServer = await MongoMemoryServer.create(); + const mongoUri = mongoServer.getUri(); + Agent = mongoose.models.Agent || mongoose.model('Agent', agentSchema); + await mongoose.connect(mongoUri); + }, 20000); + + afterAll(async () => { + await mongoose.disconnect(); + await mongoServer.stop(); + }); + beforeEach(async () => { await Agent.deleteMany({}); }); @@ -1340,12 +1217,12 @@ describe('Agent Methods', () => { test('should create an agent with a single entry in versions array', async () => { const agent = await createBasicAgent(); - expect(agent!.versions).toBeDefined(); + expect(agent.versions).toBeDefined(); expect(Array.isArray(agent.versions)).toBe(true); - expect(agent!.versions).toHaveLength(1); - expect(agent!.versions![0].name).toBe('Test Agent'); - expect(agent!.versions![0].provider).toBe('test'); - expect(agent!.versions![0].model).toBe('test-model'); + expect(agent.versions).toHaveLength(1); + expect(agent.versions[0].name).toBe('Test Agent'); + expect(agent.versions[0].provider).toBe('test'); + expect(agent.versions[0].model).toBe('test-model'); }); test('should accumulate version history across multiple updates', async () => { @@ -1367,29 +1244,29 @@ describe('Agent Methods', () => { await updateAgent({ id: agentId }, { name: 'Third Name', model: 'new-model' }); const finalAgent = await updateAgent({ id: agentId }, { description: 'Final description' }); - expect(finalAgent!.versions).toBeDefined(); - expect(Array.isArray(finalAgent!.versions)).toBe(true); - expect(finalAgent!.versions).toHaveLength(4); + expect(finalAgent.versions).toBeDefined(); + expect(Array.isArray(finalAgent.versions)).toBe(true); + expect(finalAgent.versions).toHaveLength(4); - expect(finalAgent!.versions![0].name).toBe('First Name'); - expect(finalAgent!.versions![0].description).toBe('First description'); - expect(finalAgent!.versions![0].model).toBe('test-model'); + expect(finalAgent.versions[0].name).toBe('First Name'); + expect(finalAgent.versions[0].description).toBe('First description'); + expect(finalAgent.versions[0].model).toBe('test-model'); - expect(finalAgent!.versions![1].name).toBe('Second Name'); - expect(finalAgent!.versions![1].description).toBe('Second description'); - expect(finalAgent!.versions![1].model).toBe('test-model'); + expect(finalAgent.versions[1].name).toBe('Second Name'); + expect(finalAgent.versions[1].description).toBe('Second description'); + expect(finalAgent.versions[1].model).toBe('test-model'); - expect(finalAgent!.versions![2].name).toBe('Third Name'); - expect(finalAgent!.versions![2].description).toBe('Second description'); - expect(finalAgent!.versions![2].model).toBe('new-model'); + expect(finalAgent.versions[2].name).toBe('Third Name'); + expect(finalAgent.versions[2].description).toBe('Second description'); + expect(finalAgent.versions[2].model).toBe('new-model'); - expect(finalAgent!.versions![3].name).toBe('Third Name'); - expect(finalAgent!.versions![3].description).toBe('Final description'); - expect(finalAgent!.versions![3].model).toBe('new-model'); + expect(finalAgent.versions[3].name).toBe('Third Name'); + expect(finalAgent.versions[3].description).toBe('Final description'); + expect(finalAgent.versions[3].model).toBe('new-model'); - expect(finalAgent!.name).toBe('Third Name'); - expect(finalAgent!.description).toBe('Final description'); - expect(finalAgent!.model).toBe('new-model'); + expect(finalAgent.name).toBe('Third Name'); + expect(finalAgent.description).toBe('Final description'); + expect(finalAgent.model).toBe('new-model'); }); test('should not include metadata fields in version history', async () => { @@ -1404,14 +1281,14 @@ describe('Agent Methods', () => { const updatedAgent = await updateAgent({ id: agentId }, { description: 'New description' }); - expect(updatedAgent!.versions).toHaveLength(2); - expect(updatedAgent!.versions![0]._id).toBeUndefined(); - expect((updatedAgent!.versions![0] as VersionEntry).__v).toBeUndefined(); - expect(updatedAgent!.versions![0].name).toBe('Test Agent'); - expect(updatedAgent!.versions![0].author).toBeUndefined(); + expect(updatedAgent.versions).toHaveLength(2); + expect(updatedAgent.versions[0]._id).toBeUndefined(); + expect(updatedAgent.versions[0].__v).toBeUndefined(); + expect(updatedAgent.versions[0].name).toBe('Test Agent'); + expect(updatedAgent.versions[0].author).toBeUndefined(); - expect(updatedAgent!.versions![1]._id).toBeUndefined(); - expect((updatedAgent!.versions![1] as VersionEntry).__v).toBeUndefined(); + expect(updatedAgent.versions[1]._id).toBeUndefined(); + expect(updatedAgent.versions[1].__v).toBeUndefined(); }); test('should not recursively include previous versions', async () => { @@ -1428,16 +1305,17 @@ describe('Agent Methods', () => { await updateAgent({ id: agentId }, { name: 'Updated Name 2' }); const finalAgent = await updateAgent({ id: agentId }, { name: 'Updated Name 3' }); - expect(finalAgent!.versions).toHaveLength(4); + expect(finalAgent.versions).toHaveLength(4); - finalAgent!.versions!.forEach((version) => { - expect((version as VersionEntry).versions).toBeUndefined(); + finalAgent.versions.forEach((version) => { + expect(version.versions).toBeUndefined(); }); }); test('should handle MongoDB operators and field updates correctly', async () => { const agentId = `agent_${uuidv4()}`; const authorId = new mongoose.Types.ObjectId(); + const projectId = new mongoose.Types.ObjectId(); await createAgent({ id: agentId, @@ -1453,14 +1331,16 @@ describe('Agent Methods', () => { { description: 'Updated description', $push: { tools: 'tool2' }, + $addToSet: { projectIds: projectId }, }, ); const firstUpdate = await getAgent({ id: agentId }); - expect(firstUpdate!.description).toBe('Updated description'); - expect(firstUpdate!.tools).toContain('tool1'); - expect(firstUpdate!.tools).toContain('tool2'); - expect(firstUpdate!.versions).toHaveLength(2); + expect(firstUpdate.description).toBe('Updated description'); + expect(firstUpdate.tools).toContain('tool1'); + expect(firstUpdate.tools).toContain('tool2'); + expect(firstUpdate.projectIds.map((id) => id.toString())).toContain(projectId.toString()); + expect(firstUpdate.versions).toHaveLength(2); await updateAgent( { id: agentId }, @@ -1470,11 +1350,11 @@ describe('Agent Methods', () => { ); const secondUpdate = await getAgent({ id: agentId }); - expect(secondUpdate!.tools).toHaveLength(2); - expect(secondUpdate!.tools).toContain('tool2'); - expect(secondUpdate!.tools).toContain('tool3'); - expect(secondUpdate!.tools).not.toContain('tool1'); - expect(secondUpdate!.versions).toHaveLength(3); + expect(secondUpdate.tools).toHaveLength(2); + expect(secondUpdate.tools).toContain('tool2'); + expect(secondUpdate.tools).toContain('tool3'); + expect(secondUpdate.tools).not.toContain('tool1'); + expect(secondUpdate.versions).toHaveLength(3); await updateAgent( { id: agentId }, @@ -1484,9 +1364,9 @@ describe('Agent Methods', () => { ); const thirdUpdate = await getAgent({ id: agentId }); - const toolCount = thirdUpdate!.tools!.filter((t) => t === 'tool3').length; + const toolCount = thirdUpdate.tools.filter((t) => t === 'tool3').length; expect(toolCount).toBe(2); - expect(thirdUpdate!.versions).toHaveLength(4); + expect(thirdUpdate.versions).toHaveLength(4); }); test('should handle parameter objects correctly', async () => { @@ -1507,8 +1387,8 @@ describe('Agent Methods', () => { { model_parameters: { temperature: 0.8 } }, ); - expect(updatedAgent!.versions).toHaveLength(2); - expect(updatedAgent!.model_parameters?.temperature).toBe(0.8); + expect(updatedAgent.versions).toHaveLength(2); + expect(updatedAgent.model_parameters.temperature).toBe(0.8); await updateAgent( { id: agentId }, @@ -1521,15 +1401,15 @@ describe('Agent Methods', () => { ); const complexAgent = await getAgent({ id: agentId }); - expect(complexAgent!.versions).toHaveLength(3); - expect(complexAgent!.model_parameters?.temperature).toBe(0.8); - expect(complexAgent!.model_parameters?.max_tokens).toBe(1000); + expect(complexAgent.versions).toHaveLength(3); + expect(complexAgent.model_parameters.temperature).toBe(0.8); + expect(complexAgent.model_parameters.max_tokens).toBe(1000); await updateAgent({ id: agentId }, { model_parameters: {} }); const emptyParamsAgent = await getAgent({ id: agentId }); - expect(emptyParamsAgent!.versions).toHaveLength(4); - expect(emptyParamsAgent!.model_parameters).toEqual({}); + expect(emptyParamsAgent.versions).toHaveLength(4); + expect(emptyParamsAgent.model_parameters).toEqual({}); }); test('should not create new version for duplicate updates', async () => { @@ -1548,15 +1428,15 @@ describe('Agent Methods', () => { }); const updatedAgent = await updateAgent({ id: testAgentId }, testCase.update); - expect(updatedAgent!.versions).toHaveLength(2); // No new version created + expect(updatedAgent.versions).toHaveLength(2); // No new version created // Update with duplicate data should succeed but not create a new version const duplicateUpdate = await updateAgent({ id: testAgentId }, testCase.duplicate); - expect(duplicateUpdate!.versions).toHaveLength(2); // No new version created + expect(duplicateUpdate.versions).toHaveLength(2); // No new version created const agent = await getAgent({ id: testAgentId }); - expect(agent!.versions).toHaveLength(2); + expect(agent.versions).toHaveLength(2); } }); @@ -1580,11 +1460,9 @@ describe('Agent Methods', () => { { updatingUserId: updatingUser.toString() }, ); - expect(updatedAgent!.versions).toHaveLength(2); - expect((updatedAgent!.versions![1] as VersionEntry)?.updatedBy?.toString()).toBe( - updatingUser.toString(), - ); - expect(updatedAgent!.author.toString()).toBe(originalAuthor.toString()); + expect(updatedAgent.versions).toHaveLength(2); + expect(updatedAgent.versions[1].updatedBy.toString()).toBe(updatingUser.toString()); + expect(updatedAgent.author.toString()).toBe(originalAuthor.toString()); }); test('should include updatedBy even when the original author updates the agent', async () => { @@ -1606,11 +1484,9 @@ describe('Agent Methods', () => { { updatingUserId: originalAuthor.toString() }, ); - expect(updatedAgent!.versions).toHaveLength(2); - expect((updatedAgent!.versions![1] as VersionEntry)?.updatedBy?.toString()).toBe( - originalAuthor.toString(), - ); - expect(updatedAgent!.author.toString()).toBe(originalAuthor.toString()); + expect(updatedAgent.versions).toHaveLength(2); + expect(updatedAgent.versions[1].updatedBy.toString()).toBe(originalAuthor.toString()); + expect(updatedAgent.author.toString()).toBe(originalAuthor.toString()); }); test('should track multiple different users updating the same agent', async () => { @@ -1657,21 +1533,20 @@ describe('Agent Methods', () => { { updatingUserId: user3.toString() }, ); - expect(finalAgent!.versions).toHaveLength(5); - expect(finalAgent!.author.toString()).toBe(originalAuthor.toString()); + expect(finalAgent.versions).toHaveLength(5); + expect(finalAgent.author.toString()).toBe(originalAuthor.toString()); // Check that each version has the correct updatedBy - const versions = finalAgent!.versions! as VersionEntry[]; - expect(versions[0]?.updatedBy).toBeUndefined(); // Initial creation has no updatedBy - expect(versions[1]?.updatedBy?.toString()).toBe(user1.toString()); - expect(versions[2]?.updatedBy?.toString()).toBe(originalAuthor.toString()); - expect(versions[3]?.updatedBy?.toString()).toBe(user2.toString()); - expect(versions[4]?.updatedBy?.toString()).toBe(user3.toString()); + expect(finalAgent.versions[0].updatedBy).toBeUndefined(); // Initial creation has no updatedBy + expect(finalAgent.versions[1].updatedBy.toString()).toBe(user1.toString()); + expect(finalAgent.versions[2].updatedBy.toString()).toBe(originalAuthor.toString()); + expect(finalAgent.versions[3].updatedBy.toString()).toBe(user2.toString()); + expect(finalAgent.versions[4].updatedBy.toString()).toBe(user3.toString()); // Verify the final state - expect(finalAgent!.name).toBe('Updated by User 2'); - expect(finalAgent!.description).toBe('Final update by User 3'); - expect(finalAgent!.model).toBe('new-model'); + expect(finalAgent.name).toBe('Updated by User 2'); + expect(finalAgent.description).toBe('Final update by User 3'); + expect(finalAgent.model).toBe('new-model'); }); test('should preserve original author during agent restoration', async () => { @@ -1694,6 +1569,7 @@ describe('Agent Methods', () => { { updatingUserId: updatingUser.toString() }, ); + const { revertAgentVersion } = require('./Agent'); const revertedAgent = await revertAgentVersion({ id: agentId }, 0); expect(revertedAgent.author.toString()).toBe(originalAuthor.toString()); @@ -1724,7 +1600,7 @@ describe('Agent Methods', () => { { updatingUserId: authorId.toString(), forceVersion: true }, ); - expect(firstUpdate!.versions).toHaveLength(2); + expect(firstUpdate.versions).toHaveLength(2); // Second update with same data but forceVersion should still create a version const secondUpdate = await updateAgent( @@ -1733,7 +1609,7 @@ describe('Agent Methods', () => { { updatingUserId: authorId.toString(), forceVersion: true }, ); - expect(secondUpdate!.versions).toHaveLength(3); + expect(secondUpdate.versions).toHaveLength(3); // Update without forceVersion and no changes should not create a version const duplicateUpdate = await updateAgent( @@ -1742,7 +1618,7 @@ describe('Agent Methods', () => { { updatingUserId: authorId.toString(), forceVersion: false }, ); - expect(duplicateUpdate!.versions).toHaveLength(3); // No new version created + expect(duplicateUpdate.versions).toHaveLength(3); // No new version created }); test('should handle isDuplicateVersion with arrays containing null/undefined values', async () => { @@ -1761,8 +1637,8 @@ describe('Agent Methods', () => { // Update with same array but different null/undefined arrangement const updatedAgent = await updateAgent({ id: agentId }, { tools: ['tool1', 'tool2'] }); - expect(updatedAgent!.versions).toHaveLength(2); - expect(updatedAgent!.tools).toEqual(['tool1', 'tool2']); + expect(updatedAgent.versions).toHaveLength(2); + expect(updatedAgent.tools).toEqual(['tool1', 'tool2']); }); test('should handle isDuplicateVersion with empty objects in tool_kwargs', async () => { @@ -1795,7 +1671,7 @@ describe('Agent Methods', () => { ); // Should create new version as order matters for arrays - expect(updatedAgent!.versions).toHaveLength(2); + expect(updatedAgent.versions).toHaveLength(2); }); test('should handle isDuplicateVersion with mixed primitive and object arrays', async () => { @@ -1818,7 +1694,7 @@ describe('Agent Methods', () => { ); // Should create new version as types differ - expect(updatedAgent!.versions).toHaveLength(2); + expect(updatedAgent.versions).toHaveLength(2); }); test('should handle isDuplicateVersion with deeply nested objects', async () => { @@ -1862,12 +1738,13 @@ describe('Agent Methods', () => { // Since we're updating back to the same model_parameters but with a different description, // it should create a new version const agent = await getAgent({ id: agentId }); - expect(agent!.versions).toHaveLength(3); + expect(agent.versions).toHaveLength(3); }); test('should handle version comparison with special field types', async () => { const agentId = `agent_${uuidv4()}`; const authorId = new mongoose.Types.ObjectId(); + const projectId = new mongoose.Types.ObjectId(); await createAgent({ id: agentId, @@ -1875,13 +1752,14 @@ describe('Agent Methods', () => { provider: 'test', model: 'test-model', author: authorId, + projectIds: [projectId], model_parameters: { temperature: 0.7 }, }); // Update with a real field change first const firstUpdate = await updateAgent({ id: agentId }, { description: 'New description' }); - expect(firstUpdate!.versions).toHaveLength(2); + expect(firstUpdate.versions).toHaveLength(2); // Update with model parameters change const secondUpdate = await updateAgent( @@ -1889,7 +1767,7 @@ describe('Agent Methods', () => { { model_parameters: { temperature: 0.8 } }, ); - expect(secondUpdate!.versions).toHaveLength(3); + expect(secondUpdate.versions).toHaveLength(3); }); test('should detect changes in support_contact fields', async () => { @@ -1920,9 +1798,9 @@ describe('Agent Methods', () => { }, ); - expect(firstUpdate!.versions).toHaveLength(2); - expect(firstUpdate!.support_contact?.name).toBe('Updated Support'); - expect(firstUpdate!.support_contact?.email).toBe('initial@support.com'); + expect(firstUpdate.versions).toHaveLength(2); + expect(firstUpdate.support_contact.name).toBe('Updated Support'); + expect(firstUpdate.support_contact.email).toBe('initial@support.com'); // Update support_contact email only const secondUpdate = await updateAgent( @@ -1935,8 +1813,8 @@ describe('Agent Methods', () => { }, ); - expect(secondUpdate!.versions).toHaveLength(3); - expect(secondUpdate!.support_contact?.email).toBe('updated@support.com'); + expect(secondUpdate.versions).toHaveLength(3); + expect(secondUpdate.support_contact.email).toBe('updated@support.com'); // Try to update with same support_contact - should be detected as duplicate but return successfully const duplicateUpdate = await updateAgent( @@ -1950,9 +1828,9 @@ describe('Agent Methods', () => { ); // Should not create a new version - expect(duplicateUpdate?.versions).toHaveLength(3); - expect((duplicateUpdate as IAgent & { version?: number })?.version).toBe(3); - expect(duplicateUpdate?.support_contact?.email).toBe('updated@support.com'); + expect(duplicateUpdate.versions).toHaveLength(3); + expect(duplicateUpdate.version).toBe(3); + expect(duplicateUpdate.support_contact.email).toBe('updated@support.com'); }); test('should handle support_contact from empty to populated', async () => { @@ -1982,9 +1860,9 @@ describe('Agent Methods', () => { }, ); - expect(updated?.versions).toHaveLength(2); - expect(updated?.support_contact?.name).toBe('New Support Team'); - expect(updated?.support_contact?.email).toBe('support@example.com'); + expect(updated.versions).toHaveLength(2); + expect(updated.support_contact.name).toBe('New Support Team'); + expect(updated.support_contact.email).toBe('support@example.com'); }); test('should handle support_contact edge cases in isDuplicateVersion', async () => { @@ -2012,8 +1890,8 @@ describe('Agent Methods', () => { }, ); - expect(emptyUpdate?.versions).toHaveLength(2); - expect(emptyUpdate?.support_contact).toEqual({}); + expect(emptyUpdate.versions).toHaveLength(2); + expect(emptyUpdate.support_contact).toEqual({}); // Update back to populated support_contact const repopulated = await updateAgent( @@ -2026,16 +1904,16 @@ describe('Agent Methods', () => { }, ); - expect(repopulated?.versions).toHaveLength(3); + expect(repopulated.versions).toHaveLength(3); // Verify all versions have correct support_contact const finalAgent = await getAgent({ id: agentId }); - expect(finalAgent!.versions![0]?.support_contact).toEqual({ + expect(finalAgent.versions[0].support_contact).toEqual({ name: 'Support', email: 'support@test.com', }); - expect(finalAgent!.versions![1]?.support_contact).toEqual({}); - expect(finalAgent!.versions![2]?.support_contact).toEqual({ + expect(finalAgent.versions[1].support_contact).toEqual({}); + expect(finalAgent.versions[2].support_contact).toEqual({ name: 'Support', email: 'support@test.com', }); @@ -2082,22 +1960,22 @@ describe('Agent Methods', () => { const finalAgent = await getAgent({ id: agentId }); // Verify version history - expect(finalAgent!.versions).toHaveLength(3); - expect(finalAgent!.versions![0]?.support_contact).toEqual({ + expect(finalAgent.versions).toHaveLength(3); + expect(finalAgent.versions[0].support_contact).toEqual({ name: 'Initial Contact', email: 'initial@test.com', }); - expect(finalAgent!.versions![1]?.support_contact).toEqual({ + expect(finalAgent.versions[1].support_contact).toEqual({ name: 'Second Contact', email: 'second@test.com', }); - expect(finalAgent!.versions![2]?.support_contact).toEqual({ + expect(finalAgent.versions[2].support_contact).toEqual({ name: 'Third Contact', email: 'third@test.com', }); // Current state should match last version - expect(finalAgent!.support_contact).toEqual({ + expect(finalAgent.support_contact).toEqual({ name: 'Third Contact', email: 'third@test.com', }); @@ -2132,9 +2010,9 @@ describe('Agent Methods', () => { }, ); - expect(updated?.versions).toHaveLength(2); - expect(updated?.support_contact?.name).toBe('New Name'); - expect(updated?.support_contact?.email).toBe(''); + expect(updated.versions).toHaveLength(2); + expect(updated.support_contact.name).toBe('New Name'); + expect(updated.support_contact.email).toBe(''); // Verify isDuplicateVersion works with partial changes - should return successfully without creating new version const duplicateUpdate = await updateAgent( @@ -2148,10 +2026,10 @@ describe('Agent Methods', () => { ); // Should not create a new version since content is the same - expect(duplicateUpdate?.versions).toHaveLength(2); - expect((duplicateUpdate as IAgent & { version?: number })?.version).toBe(2); - expect(duplicateUpdate?.support_contact?.name).toBe('New Name'); - expect(duplicateUpdate?.support_contact?.email).toBe(''); + expect(duplicateUpdate.versions).toHaveLength(2); + expect(duplicateUpdate.version).toBe(2); + expect(duplicateUpdate.support_contact.name).toBe('New Name'); + expect(duplicateUpdate.support_contact.email).toBe(''); }); // Edge Cases @@ -2174,7 +2052,7 @@ describe('Agent Methods', () => { ])('addAgentResourceFile with $name', ({ needsAgent, params, shouldResolve, error }) => { test(`should ${shouldResolve ? 'resolve' : 'reject'}`, async () => { const agent = needsAgent ? await createBasicAgent() : null; - const agent_id = needsAgent ? agent!.id : `agent_${uuidv4()}`; + const agent_id = needsAgent ? agent.id : `agent_${uuidv4()}`; if (shouldResolve) { await expect(addAgentResourceFile({ agent_id, ...params })).resolves.toBeDefined(); @@ -2207,7 +2085,7 @@ describe('Agent Methods', () => { ])('removeAgentResourceFiles with $name', ({ files, needsAgent, shouldResolve, error }) => { test(`should ${shouldResolve ? 'resolve' : 'reject'}`, async () => { const agent = needsAgent ? await createBasicAgent() : null; - const agent_id = needsAgent ? agent!.id : `agent_${uuidv4()}`; + const agent_id = needsAgent ? agent.id : `agent_${uuidv4()}`; if (shouldResolve) { const result = await removeAgentResourceFiles({ agent_id, files }); @@ -2239,8 +2117,8 @@ describe('Agent Methods', () => { } const agent = await getAgent({ id: agentId }); - expect(agent!.versions).toHaveLength(21); - expect(agent!.description).toBe('Version 19'); + expect(agent.versions).toHaveLength(21); + expect(agent.description).toBe('Version 19'); }); test('should handle revertAgentVersion with invalid version index', async () => { @@ -2281,13 +2159,27 @@ describe('Agent Methods', () => { const updatedAgent = await updateAgent({ id: agentId }, {}); expect(updatedAgent).toBeDefined(); - expect(updatedAgent!.name).toBe('Test Agent'); - expect(updatedAgent!.versions).toHaveLength(1); + expect(updatedAgent.name).toBe('Test Agent'); + expect(updatedAgent.versions).toHaveLength(1); }); }); }); describe('Action Metadata and Hash Generation', () => { + let mongoServer; + + beforeAll(async () => { + mongoServer = await MongoMemoryServer.create(); + const mongoUri = mongoServer.getUri(); + Agent = mongoose.models.Agent || mongoose.model('Agent', agentSchema); + await mongoose.connect(mongoUri); + }, 20000); + + afterAll(async () => { + await mongoose.disconnect(); + await mongoServer.stop(); + }); + beforeEach(async () => { await Agent.deleteMany({}); }); @@ -2455,9 +2347,332 @@ describe('Agent Methods', () => { }); }); - /* Load Agent Functionality tests moved to api/models/Agent.spec.js */ + describe('Load Agent Functionality', () => { + let mongoServer; + + beforeAll(async () => { + mongoServer = await MongoMemoryServer.create(); + const mongoUri = mongoServer.getUri(); + Agent = mongoose.models.Agent || mongoose.model('Agent', agentSchema); + await mongoose.connect(mongoUri); + }, 20000); + + afterAll(async () => { + await mongoose.disconnect(); + await mongoServer.stop(); + }); + + beforeEach(async () => { + await Agent.deleteMany({}); + }); + + test('should return null when agent_id is not provided', async () => { + const mockReq = { user: { id: 'user123' } }; + const result = await loadAgent({ + req: mockReq, + agent_id: null, + endpoint: 'openai', + model_parameters: { model: 'gpt-4' }, + }); + + expect(result).toBeNull(); + }); + + test('should return null when agent_id is empty string', async () => { + const mockReq = { user: { id: 'user123' } }; + const result = await loadAgent({ + req: mockReq, + agent_id: '', + endpoint: 'openai', + model_parameters: { model: 'gpt-4' }, + }); + + expect(result).toBeNull(); + }); + + test('should test ephemeral agent loading logic', async () => { + const { EPHEMERAL_AGENT_ID } = require('librechat-data-provider').Constants; + + getCachedTools.mockResolvedValue({ + tool1_mcp_server1: {}, + tool2_mcp_server2: {}, + another_tool: {}, + }); + + // Mock getMCPServerTools to return tools for each server + getMCPServerTools.mockImplementation(async (_userId, server) => { + if (server === 'server1') { + return { tool1_mcp_server1: {} }; + } else if (server === 'server2') { + return { tool2_mcp_server2: {} }; + } + return null; + }); + + const mockReq = { + user: { id: 'user123' }, + body: { + promptPrefix: 'Test instructions', + ephemeralAgent: { + execute_code: true, + web_search: true, + mcp: ['server1', 'server2'], + }, + }, + }; + + const result = await loadAgent({ + req: mockReq, + agent_id: EPHEMERAL_AGENT_ID, + endpoint: 'openai', + model_parameters: { model: 'gpt-4', temperature: 0.7 }, + }); + + if (result) { + // Ephemeral agent ID is encoded with endpoint and model + expect(result.id).toBe('openai__gpt-4'); + expect(result.instructions).toBe('Test instructions'); + expect(result.provider).toBe('openai'); + expect(result.model).toBe('gpt-4'); + expect(result.model_parameters.temperature).toBe(0.7); + expect(result.tools).toContain('execute_code'); + expect(result.tools).toContain('web_search'); + expect(result.tools).toContain('tool1_mcp_server1'); + expect(result.tools).toContain('tool2_mcp_server2'); + } else { + expect(result).toBeNull(); + } + }); + + test('should return null for non-existent agent', async () => { + const mockReq = { user: { id: 'user123' } }; + const result = await loadAgent({ + req: mockReq, + agent_id: 'agent_non_existent', + endpoint: 'openai', + model_parameters: { model: 'gpt-4' }, + }); + + expect(result).toBeNull(); + }); + + test('should load agent when user is the author', async () => { + const userId = new mongoose.Types.ObjectId(); + const agentId = `agent_${uuidv4()}`; + + await createAgent({ + id: agentId, + name: 'Test Agent', + provider: 'openai', + model: 'gpt-4', + author: userId, + description: 'Test description', + tools: ['web_search'], + }); + + const mockReq = { user: { id: userId.toString() } }; + const result = await loadAgent({ + req: mockReq, + agent_id: agentId, + endpoint: 'openai', + model_parameters: { model: 'gpt-4' }, + }); + + expect(result).toBeDefined(); + expect(result.id).toBe(agentId); + expect(result.name).toBe('Test Agent'); + expect(result.author.toString()).toBe(userId.toString()); + expect(result.version).toBe(1); + }); + + test('should return agent even when user is not author (permissions checked at route level)', async () => { + const authorId = new mongoose.Types.ObjectId(); + const userId = new mongoose.Types.ObjectId(); + const agentId = `agent_${uuidv4()}`; + + await createAgent({ + id: agentId, + name: 'Test Agent', + provider: 'openai', + model: 'gpt-4', + author: authorId, + }); + + const mockReq = { user: { id: userId.toString() } }; + const result = await loadAgent({ + req: mockReq, + agent_id: agentId, + endpoint: 'openai', + model_parameters: { model: 'gpt-4' }, + }); + + // With the new permission system, loadAgent returns the agent regardless of permissions + // Permission checks are handled at the route level via middleware + expect(result).toBeTruthy(); + expect(result.id).toBe(agentId); + expect(result.name).toBe('Test Agent'); + }); + + test('should handle ephemeral agent with no MCP servers', async () => { + const { EPHEMERAL_AGENT_ID } = require('librechat-data-provider').Constants; + + getCachedTools.mockResolvedValue({}); + + const mockReq = { + user: { id: 'user123' }, + body: { + promptPrefix: 'Simple instructions', + ephemeralAgent: { + execute_code: false, + web_search: false, + mcp: [], + }, + }, + }; + + const result = await loadAgent({ + req: mockReq, + agent_id: EPHEMERAL_AGENT_ID, + endpoint: 'openai', + model_parameters: { model: 'gpt-3.5-turbo' }, + }); + + if (result) { + expect(result.tools).toEqual([]); + expect(result.instructions).toBe('Simple instructions'); + } else { + expect(result).toBeFalsy(); + } + }); + + test('should handle ephemeral agent with undefined ephemeralAgent in body', async () => { + const { EPHEMERAL_AGENT_ID } = require('librechat-data-provider').Constants; + + getCachedTools.mockResolvedValue({}); + + const mockReq = { + user: { id: 'user123' }, + body: { + promptPrefix: 'Basic instructions', + }, + }; + + const result = await loadAgent({ + req: mockReq, + agent_id: EPHEMERAL_AGENT_ID, + endpoint: 'openai', + model_parameters: { model: 'gpt-4' }, + }); + + if (result) { + expect(result.tools).toEqual([]); + } else { + expect(result).toBeFalsy(); + } + }); + + describe('Edge Cases', () => { + test('should handle loadAgent with malformed req object', async () => { + const result = await loadAgent({ + req: null, + agent_id: 'agent_test', + endpoint: 'openai', + model_parameters: { model: 'gpt-4' }, + }); + + expect(result).toBeNull(); + }); + + test('should handle ephemeral agent with extremely large tool list', async () => { + const { EPHEMERAL_AGENT_ID } = require('librechat-data-provider').Constants; + + const largeToolList = Array.from({ length: 100 }, (_, i) => `tool_${i}_mcp_server1`); + const availableTools = largeToolList.reduce((acc, tool) => { + acc[tool] = {}; + return acc; + }, {}); + + getCachedTools.mockResolvedValue(availableTools); + + // Mock getMCPServerTools to return all tools for server1 + getMCPServerTools.mockImplementation(async (_userId, server) => { + if (server === 'server1') { + return availableTools; // All 100 tools belong to server1 + } + return null; + }); + + const mockReq = { + user: { id: 'user123' }, + body: { + promptPrefix: 'Test', + ephemeralAgent: { + execute_code: true, + web_search: true, + mcp: ['server1'], + }, + }, + }; + + const result = await loadAgent({ + req: mockReq, + agent_id: EPHEMERAL_AGENT_ID, + endpoint: 'openai', + model_parameters: { model: 'gpt-4' }, + }); + + if (result) { + expect(result.tools.length).toBeGreaterThan(100); + } + }); + + test('should return agent from different project (permissions checked at route level)', async () => { + const authorId = new mongoose.Types.ObjectId(); + const userId = new mongoose.Types.ObjectId(); + const agentId = `agent_${uuidv4()}`; + const projectId = new mongoose.Types.ObjectId(); + + await createAgent({ + id: agentId, + name: 'Project Agent', + provider: 'openai', + model: 'gpt-4', + author: authorId, + projectIds: [projectId], + }); + + const mockReq = { user: { id: userId.toString() } }; + const result = await loadAgent({ + req: mockReq, + agent_id: agentId, + endpoint: 'openai', + model_parameters: { model: 'gpt-4' }, + }); + + // With the new permission system, loadAgent returns the agent regardless of permissions + // Permission checks are handled at the route level via middleware + expect(result).toBeTruthy(); + expect(result.id).toBe(agentId); + expect(result.name).toBe('Project Agent'); + }); + }); + }); describe('Agent Edge Cases and Error Handling', () => { + let mongoServer; + + beforeAll(async () => { + mongoServer = await MongoMemoryServer.create(); + const mongoUri = mongoServer.getUri(); + Agent = mongoose.models.Agent || mongoose.model('Agent', agentSchema); + await mongoose.connect(mongoUri); + }, 20000); + + afterAll(async () => { + await mongoose.disconnect(); + await mongoServer.stop(); + }); + beforeEach(async () => { await Agent.deleteMany({}); }); @@ -2476,13 +2691,14 @@ describe('Agent Methods', () => { expect(agent).toBeDefined(); expect(agent.id).toBe(agentId); expect(agent.versions).toHaveLength(1); - expect(agent.versions![0]?.provider).toBe('test'); - expect(agent.versions![0]?.model).toBe('test-model'); + expect(agent.versions[0].provider).toBe('test'); + expect(agent.versions[0].model).toBe('test-model'); }); test('should handle agent creation with all optional fields', async () => { const agentId = `agent_${uuidv4()}`; const authorId = new mongoose.Types.ObjectId(); + const projectId = new mongoose.Types.ObjectId(); const agent = await createAgent({ id: agentId, @@ -2495,7 +2711,9 @@ describe('Agent Methods', () => { tools: ['tool1', 'tool2'], actions: ['action1', 'action2'], model_parameters: { temperature: 0.8, max_tokens: 1000 }, + projectIds: [projectId], avatar: 'https://example.com/avatar.png', + isCollaborative: true, tool_resources: { file_search: { file_ids: ['file1', 'file2'] }, }, @@ -2507,10 +2725,12 @@ describe('Agent Methods', () => { expect(agent.instructions).toBe('Complex instructions'); expect(agent.tools).toEqual(['tool1', 'tool2']); expect(agent.actions).toEqual(['action1', 'action2']); - expect(agent.model_parameters?.temperature).toBe(0.8); - expect(agent.model_parameters?.max_tokens).toBe(1000); + expect(agent.model_parameters.temperature).toBe(0.8); + expect(agent.model_parameters.max_tokens).toBe(1000); + expect(agent.projectIds.map((id) => id.toString())).toContain(projectId.toString()); expect(agent.avatar).toBe('https://example.com/avatar.png'); - expect(agent.tool_resources?.file_search?.file_ids).toEqual(['file1', 'file2']); + expect(agent.isCollaborative).toBe(true); + expect(agent.tool_resources.file_search.file_ids).toEqual(['file1', 'file2']); }); test('should handle updateAgent with empty update object', async () => { @@ -2528,8 +2748,8 @@ describe('Agent Methods', () => { const updatedAgent = await updateAgent({ id: agentId }, {}); expect(updatedAgent).toBeDefined(); - expect(updatedAgent!.name).toBe('Test Agent'); - expect(updatedAgent!.versions).toHaveLength(1); // No new version should be created + expect(updatedAgent.name).toBe('Test Agent'); + expect(updatedAgent.versions).toHaveLength(1); // No new version should be created }); test('should handle concurrent updates to different agents', async () => { @@ -2559,10 +2779,10 @@ describe('Agent Methods', () => { updateAgent({ id: agent2Id }, { description: 'Updated Agent 2' }), ]); - expect(updated1?.description).toBe('Updated Agent 1'); - expect(updated2?.description).toBe('Updated Agent 2'); - expect(updated1?.versions).toHaveLength(2); - expect(updated2?.versions).toHaveLength(2); + expect(updated1.description).toBe('Updated Agent 1'); + expect(updated2.description).toBe('Updated Agent 2'); + expect(updated1.versions).toHaveLength(2); + expect(updated2.versions).toHaveLength(2); }); test('should handle agent deletion with non-existent ID', async () => { @@ -2594,10 +2814,10 @@ describe('Agent Methods', () => { }, ); - expect(updatedAgent!.name).toBe('Updated Name'); - expect(updatedAgent!.tools).toContain('tool1'); - expect(updatedAgent!.tools).toContain('tool2'); - expect(updatedAgent!.versions).toHaveLength(2); + expect(updatedAgent.name).toBe('Updated Name'); + expect(updatedAgent.tools).toContain('tool1'); + expect(updatedAgent.tools).toContain('tool2'); + expect(updatedAgent.versions).toHaveLength(2); }); test('should handle revertAgentVersion with invalid version index', async () => { @@ -2624,9 +2844,11 @@ describe('Agent Methods', () => { test('should handle addAgentResourceFile with non-existent agent', async () => { const nonExistentId = `agent_${uuidv4()}`; + const mockReq = { user: { id: 'user123' } }; await expect( addAgentResourceFile({ + req: mockReq, agent_id: nonExistentId, tool_resource: 'file_search', file_id: 'file123', @@ -2667,8 +2889,8 @@ describe('Agent Methods', () => { }, ); - expect(firstUpdate!.tools).toContain('tool1'); - expect(firstUpdate!.tools).toContain('tool2'); + expect(firstUpdate.tools).toContain('tool1'); + expect(firstUpdate.tools).toContain('tool2'); // Second update with direct field update and $addToSet const secondUpdate = await updateAgent( @@ -2680,13 +2902,13 @@ describe('Agent Methods', () => { }, ); - expect(secondUpdate!.name).toBe('Updated Agent'); - expect(secondUpdate!.model_parameters?.temperature).toBe(0.8); - expect(secondUpdate!.model_parameters?.max_tokens).toBe(500); - expect(secondUpdate!.tools).toContain('tool1'); - expect(secondUpdate!.tools).toContain('tool2'); - expect(secondUpdate!.tools).toContain('tool3'); - expect(secondUpdate!.versions).toHaveLength(3); + expect(secondUpdate.name).toBe('Updated Agent'); + expect(secondUpdate.model_parameters.temperature).toBe(0.8); + expect(secondUpdate.model_parameters.max_tokens).toBe(500); + expect(secondUpdate.tools).toContain('tool1'); + expect(secondUpdate.tools).toContain('tool2'); + expect(secondUpdate.tools).toContain('tool3'); + expect(secondUpdate.versions).toHaveLength(3); }); test('should preserve version order in versions array', async () => { @@ -2705,12 +2927,27 @@ describe('Agent Methods', () => { await updateAgent({ id: agentId }, { name: 'Version 3' }); const finalAgent = await updateAgent({ id: agentId }, { name: 'Version 4' }); - expect(finalAgent!.versions).toHaveLength(4); - expect(finalAgent!.versions![0]?.name).toBe('Version 1'); - expect(finalAgent!.versions![1]?.name).toBe('Version 2'); - expect(finalAgent!.versions![2]?.name).toBe('Version 3'); - expect(finalAgent!.versions![3]?.name).toBe('Version 4'); - expect(finalAgent!.name).toBe('Version 4'); + expect(finalAgent.versions).toHaveLength(4); + expect(finalAgent.versions[0].name).toBe('Version 1'); + expect(finalAgent.versions[1].name).toBe('Version 2'); + expect(finalAgent.versions[2].name).toBe('Version 3'); + expect(finalAgent.versions[3].name).toBe('Version 4'); + expect(finalAgent.name).toBe('Version 4'); + }); + + test('should handle updateAgentProjects error scenarios', async () => { + const nonExistentId = `agent_${uuidv4()}`; + const userId = new mongoose.Types.ObjectId(); + const projectId = new mongoose.Types.ObjectId(); + + // Test with non-existent agent + const result = await updateAgentProjects({ + user: { id: userId.toString() }, + agentId: nonExistentId, + projectIds: [projectId.toString()], + }); + + expect(result).toBeNull(); }); test('should handle revertAgentVersion properly', async () => { @@ -2759,13 +2996,15 @@ describe('Agent Methods', () => { ); expect(updatedAgent).toBeDefined(); - expect(updatedAgent!.description).toBe('Updated description'); - expect(updatedAgent!.versions).toHaveLength(2); + expect(updatedAgent.description).toBe('Updated description'); + expect(updatedAgent.versions).toHaveLength(2); }); test('should handle updateAgent with combined MongoDB operators', async () => { const agentId = `agent_${uuidv4()}`; const authorId = new mongoose.Types.ObjectId(); + const projectId1 = new mongoose.Types.ObjectId(); + const projectId2 = new mongoose.Types.ObjectId(); await createAgent({ id: agentId, @@ -2774,6 +3013,7 @@ describe('Agent Methods', () => { model: 'test-model', author: authorId, tools: ['tool1'], + projectIds: [projectId1], }); // Use multiple operators in single update - but avoid conflicting operations on same field @@ -2782,14 +3022,26 @@ describe('Agent Methods', () => { { name: 'Updated Name', $push: { tools: 'tool2' }, + $addToSet: { projectIds: projectId2 }, + }, + ); + + const finalAgent = await updateAgent( + { id: agentId }, + { + $pull: { projectIds: projectId1 }, }, ); expect(updatedAgent).toBeDefined(); - expect(updatedAgent!.name).toBe('Updated Name'); - expect(updatedAgent!.tools).toContain('tool1'); - expect(updatedAgent!.tools).toContain('tool2'); - expect(updatedAgent!.versions).toHaveLength(2); + expect(updatedAgent.name).toBe('Updated Name'); + expect(updatedAgent.tools).toContain('tool1'); + expect(updatedAgent.tools).toContain('tool2'); + expect(updatedAgent.projectIds.map((id) => id.toString())).toContain(projectId2.toString()); + + expect(finalAgent).toBeDefined(); + expect(finalAgent.projectIds.map((id) => id.toString())).not.toContain(projectId1.toString()); + expect(finalAgent.versions).toHaveLength(3); }); test('should handle updateAgent when agent does not exist', async () => { @@ -2870,6 +3122,54 @@ describe('Agent Methods', () => { Agent.findOneAndUpdate = originalFindOneAndUpdate; }); + test('should handle loadEphemeralAgent with malformed MCP tool names', async () => { + const { EPHEMERAL_AGENT_ID } = require('librechat-data-provider').Constants; + + getCachedTools.mockResolvedValue({ + malformed_tool_name: {}, // No mcp delimiter + tool__server1: {}, // Wrong delimiter + tool_mcp_server1: {}, // Correct format + tool_mcp_server2: {}, // Different server + }); + + // Mock getMCPServerTools to return only tools matching the server + getMCPServerTools.mockImplementation(async (_userId, server) => { + if (server === 'server1') { + // Only return tool that correctly matches server1 format + return { tool_mcp_server1: {} }; + } else if (server === 'server2') { + return { tool_mcp_server2: {} }; + } + return null; + }); + + const mockReq = { + user: { id: 'user123' }, + body: { + promptPrefix: 'Test instructions', + ephemeralAgent: { + execute_code: false, + web_search: false, + mcp: ['server1'], + }, + }, + }; + + const result = await loadAgent({ + req: mockReq, + agent_id: EPHEMERAL_AGENT_ID, + endpoint: 'openai', + model_parameters: { model: 'gpt-4' }, + }); + + if (result) { + expect(result.tools).toEqual(['tool_mcp_server1']); + expect(result.tools).not.toContain('malformed_tool_name'); + expect(result.tools).not.toContain('tool__server1'); + expect(result.tools).not.toContain('tool_mcp_server2'); + } + }); + test('should handle addAgentResourceFile when array initialization fails', async () => { const agentId = `agent_${uuidv4()}`; const authorId = new mongoose.Types.ObjectId(); @@ -2890,10 +3190,7 @@ describe('Agent Methods', () => { updateOneCalled = true; return Promise.reject(new Error('Database error')); } - return originalUpdateOne.apply( - Agent, - args as [update: UpdateQuery | UpdateWithAggregationPipeline], - ); + return originalUpdateOne.apply(Agent, args); }); try { @@ -2905,8 +3202,8 @@ describe('Agent Methods', () => { expect(result).toBeDefined(); expect(result.tools).toContain('new_tool'); - } catch (error: unknown) { - expect((error as Error).message).toBe('Database error'); + } catch (error) { + expect(error.message).toBe('Database error'); } Agent.updateOne = originalUpdateOne; @@ -2914,6 +3211,20 @@ describe('Agent Methods', () => { }); describe('Agent IDs Field in Version Detection', () => { + let mongoServer; + + beforeAll(async () => { + mongoServer = await MongoMemoryServer.create(); + const mongoUri = mongoServer.getUri(); + Agent = mongoose.models.Agent || mongoose.model('Agent', agentSchema); + await mongoose.connect(mongoUri); + }, 20000); + + afterAll(async () => { + await mongoose.disconnect(); + await mongoServer.stop(); + }); + beforeEach(async () => { await Agent.deleteMany({}); }); @@ -2940,8 +3251,8 @@ describe('Agent Methods', () => { ); // Since agent_ids is no longer excluded, this should create a new version - expect(updated?.versions).toHaveLength(2); - expect(updated?.agent_ids).toEqual(['agent1', 'agent2', 'agent3']); + expect(updated.versions).toHaveLength(2); + expect(updated.agent_ids).toEqual(['agent1', 'agent2', 'agent3']); }); test('should detect duplicate version if agent_ids is updated to same value', async () => { @@ -2961,14 +3272,14 @@ describe('Agent Methods', () => { { id: agentId }, { agent_ids: ['agent1', 'agent2', 'agent3'] }, ); - expect(updatedAgent!.versions).toHaveLength(2); + expect(updatedAgent.versions).toHaveLength(2); // Update with same agent_ids should succeed but not create a new version const duplicateUpdate = await updateAgent( { id: agentId }, { agent_ids: ['agent1', 'agent2', 'agent3'] }, ); - expect(duplicateUpdate?.versions).toHaveLength(2); // No new version created + expect(duplicateUpdate.versions).toHaveLength(2); // No new version created }); test('should handle agent_ids field alongside other fields', async () => { @@ -2993,15 +3304,74 @@ describe('Agent Methods', () => { }, ); - expect(updated?.versions).toHaveLength(2); - expect(updated?.agent_ids).toEqual(['agent1', 'agent2']); - expect(updated?.description).toBe('Updated description'); + expect(updated.versions).toHaveLength(2); + expect(updated.agent_ids).toEqual(['agent1', 'agent2']); + expect(updated.description).toBe('Updated description'); const updated2 = await updateAgent({ id: agentId }, { description: 'Another description' }); - expect(updated2?.versions).toHaveLength(3); - expect(updated2?.agent_ids).toEqual(['agent1', 'agent2']); - expect(updated2?.description).toBe('Another description'); + expect(updated2.versions).toHaveLength(3); + expect(updated2.agent_ids).toEqual(['agent1', 'agent2']); + expect(updated2.description).toBe('Another description'); + }); + + test('should skip version creation when skipVersioning option is used', async () => { + const agentId = `agent_${uuidv4()}`; + const authorId = new mongoose.Types.ObjectId(); + const projectId1 = new mongoose.Types.ObjectId(); + const projectId2 = new mongoose.Types.ObjectId(); + + // Create agent with initial projectIds + await createAgent({ + id: agentId, + name: 'Test Agent', + provider: 'test', + model: 'test-model', + author: authorId, + projectIds: [projectId1], + }); + + // Share agent using updateAgentProjects (which uses skipVersioning) + const shared = await updateAgentProjects({ + user: { id: authorId.toString() }, // Use the same author ID + agentId: agentId, + projectIds: [projectId2.toString()], + }); + + // Should NOT create a new version due to skipVersioning + expect(shared.versions).toHaveLength(1); + expect(shared.projectIds.map((id) => id.toString())).toContain(projectId1.toString()); + expect(shared.projectIds.map((id) => id.toString())).toContain(projectId2.toString()); + + // Unshare agent using updateAgentProjects + const unshared = await updateAgentProjects({ + user: { id: authorId.toString() }, + agentId: agentId, + removeProjectIds: [projectId1.toString()], + }); + + // Still should NOT create a new version + expect(unshared.versions).toHaveLength(1); + expect(unshared.projectIds.map((id) => id.toString())).not.toContain(projectId1.toString()); + expect(unshared.projectIds.map((id) => id.toString())).toContain(projectId2.toString()); + + // Regular update without skipVersioning should create a version + const regularUpdate = await updateAgent( + { id: agentId }, + { description: 'Updated description' }, + ); + + expect(regularUpdate.versions).toHaveLength(2); + expect(regularUpdate.description).toBe('Updated description'); + + // Direct updateAgent with MongoDB operators should still create versions + const directUpdate = await updateAgent( + { id: agentId }, + { $addToSet: { projectIds: { $each: [projectId1] } } }, + ); + + expect(directUpdate.versions).toHaveLength(3); + expect(directUpdate.projectIds.length).toBe(2); }); test('should preserve agent_ids in version history', async () => { @@ -3023,11 +3393,11 @@ describe('Agent Methods', () => { const finalAgent = await getAgent({ id: agentId }); - expect(finalAgent!.versions).toHaveLength(3); - expect(finalAgent!.versions![0]?.agent_ids).toEqual(['agent1']); - expect(finalAgent!.versions![1]?.agent_ids).toEqual(['agent1', 'agent2']); - expect(finalAgent!.versions![2]?.agent_ids).toEqual(['agent3']); - expect(finalAgent!.agent_ids).toEqual(['agent3']); + expect(finalAgent.versions).toHaveLength(3); + expect(finalAgent.versions[0].agent_ids).toEqual(['agent1']); + expect(finalAgent.versions[1].agent_ids).toEqual(['agent1', 'agent2']); + expect(finalAgent.versions[2].agent_ids).toEqual(['agent3']); + expect(finalAgent.agent_ids).toEqual(['agent3']); }); test('should handle empty agent_ids arrays', async () => { @@ -3045,13 +3415,13 @@ describe('Agent Methods', () => { const updated = await updateAgent({ id: agentId }, { agent_ids: [] }); - expect(updated?.versions).toHaveLength(2); - expect(updated?.agent_ids).toEqual([]); + expect(updated.versions).toHaveLength(2); + expect(updated.agent_ids).toEqual([]); // Update with same empty agent_ids should succeed but not create a new version const duplicateUpdate = await updateAgent({ id: agentId }, { agent_ids: [] }); - expect(duplicateUpdate?.versions).toHaveLength(2); // No new version created - expect(duplicateUpdate?.agent_ids).toEqual([]); + expect(duplicateUpdate.versions).toHaveLength(2); // No new version created + expect(duplicateUpdate.agent_ids).toEqual([]); }); test('should handle agent without agent_ids field', async () => { @@ -3070,13 +3440,27 @@ describe('Agent Methods', () => { const updated = await updateAgent({ id: agentId }, { agent_ids: ['agent1'] }); - expect(updated?.versions).toHaveLength(2); - expect(updated?.agent_ids).toEqual(['agent1']); + expect(updated.versions).toHaveLength(2); + expect(updated.agent_ids).toEqual(['agent1']); }); }); }); describe('Support Contact Field', () => { + let mongoServer; + + beforeAll(async () => { + mongoServer = await MongoMemoryServer.create(); + const mongoUri = mongoServer.getUri(); + Agent = mongoose.models.Agent || mongoose.model('Agent', agentSchema); + await mongoose.connect(mongoUri); + }, 20000); + + afterAll(async () => { + await mongoose.disconnect(); + await mongoServer.stop(); + }); + beforeEach(async () => { await Agent.deleteMany({}); }); @@ -3100,18 +3484,18 @@ describe('Support Contact Field', () => { // Verify support_contact is stored correctly expect(agent.support_contact).toBeDefined(); - expect(agent.support_contact?.name).toBe('Support Team'); - expect(agent.support_contact?.email).toBe('support@example.com'); + expect(agent.support_contact.name).toBe('Support Team'); + expect(agent.support_contact.email).toBe('support@example.com'); // Verify no _id field is created in support_contact - expect((agent.support_contact as Record)?._id).toBeUndefined(); + expect(agent.support_contact._id).toBeUndefined(); // Fetch from database to double-check const dbAgent = await Agent.findOne({ id: agentData.id }); - expect(dbAgent?.support_contact).toBeDefined(); - expect(dbAgent?.support_contact?.name).toBe('Support Team'); - expect(dbAgent?.support_contact?.email).toBe('support@example.com'); - expect((dbAgent?.support_contact as Record)?._id).toBeUndefined(); + expect(dbAgent.support_contact).toBeDefined(); + expect(dbAgent.support_contact.name).toBe('Support Team'); + expect(dbAgent.support_contact.email).toBe('support@example.com'); + expect(dbAgent.support_contact._id).toBeUndefined(); }); it('should handle empty support_contact correctly', async () => { @@ -3129,7 +3513,7 @@ describe('Support Contact Field', () => { // Verify empty support_contact is stored as empty object expect(agent.support_contact).toEqual({}); - expect((agent.support_contact as Record)?._id).toBeUndefined(); + expect(agent.support_contact._id).toBeUndefined(); }); it('should handle missing support_contact correctly', async () => { @@ -3149,12 +3533,11 @@ describe('Support Contact Field', () => { }); describe('getListAgentsByAccess - Security Tests', () => { - let userA: mongoose.Types.ObjectId, userB: mongoose.Types.ObjectId; - let agentA1: Awaited>, - agentA2: Awaited>, - agentA3: Awaited>; + let userA, userB; + let agentA1, agentA2, agentA3; beforeEach(async () => { + Agent = mongoose.models.Agent || mongoose.model('Agent', agentSchema); await Agent.deleteMany({}); await AclEntry.deleteMany({}); @@ -3217,7 +3600,7 @@ describe('Support Contact Field', () => { test('should only return agents in accessibleIds list', async () => { // Give User B access to only one of User A's agents - const accessibleIds = [agentA1._id] as mongoose.Types.ObjectId[]; + const accessibleIds = [agentA1._id]; const result = await getListAgentsByAccess({ accessibleIds, @@ -3231,7 +3614,7 @@ describe('Support Contact Field', () => { test('should return multiple accessible agents when provided', async () => { // Give User B access to two of User A's agents - const accessibleIds = [agentA1._id, agentA3._id] as mongoose.Types.ObjectId[]; + const accessibleIds = [agentA1._id, agentA3._id]; const result = await getListAgentsByAccess({ accessibleIds, @@ -3247,7 +3630,7 @@ describe('Support Contact Field', () => { test('should respect other query parameters while enforcing accessibleIds', async () => { // Give access to all agents but filter by name - const accessibleIds = [agentA1._id, agentA2._id, agentA3._id] as mongoose.Types.ObjectId[]; + const accessibleIds = [agentA1._id, agentA2._id, agentA3._id]; const result = await getListAgentsByAccess({ accessibleIds, @@ -3274,9 +3657,7 @@ describe('Support Contact Field', () => { } // Give access to all agents - const allAgentIds = [agentA1, agentA2, agentA3, ...moreAgents].map( - (a) => a._id, - ) as mongoose.Types.ObjectId[]; + const allAgentIds = [agentA1, agentA2, agentA3, ...moreAgents].map((a) => a._id); // First page const page1 = await getListAgentsByAccess({ @@ -3343,7 +3724,7 @@ describe('Support Contact Field', () => { }); // Give User B access to one of User A's agents - const accessibleIds = [agentA1._id, agentB1._id] as mongoose.Types.ObjectId[]; + const accessibleIds = [agentA1._id, agentB1._id]; // Filter by author should further restrict the results const result = await getListAgentsByAccess({ @@ -3373,21 +3754,18 @@ function createTestIds() { return { agentId: `agent_${uuidv4()}`, authorId: new mongoose.Types.ObjectId(), + projectId: new mongoose.Types.ObjectId(), fileId: uuidv4(), }; } -function createFileOperations(agentId: string, fileIds: string[], operation = 'add') { +function createFileOperations(agentId, fileIds, operation = 'add') { return fileIds.map((fileId) => operation === 'add' - ? addAgentResourceFile({ - agent_id: agentId, - tool_resource: EToolResources.execute_code, - file_id: fileId, - }) + ? addAgentResourceFile({ agent_id: agentId, tool_resource: 'test_tool', file_id: fileId }) : removeAgentResourceFiles({ agent_id: agentId, - files: [{ tool_resource: EToolResources.execute_code, file_id: fileId }], + files: [{ tool_resource: 'test_tool', file_id: fileId }], }), ); } @@ -3401,14 +3779,7 @@ function mockFindOneAndUpdateError(errorOnCall = 1) { if (callCount === errorOnCall) { throw new Error('Database connection lost'); } - return original.apply( - Agent, - args as [ - filter?: RootFilterQuery | undefined, - update?: UpdateQuery | undefined, - options?: QueryOptions | null | undefined, - ], - ); + return original.apply(Agent, args); }); return () => { @@ -3417,6 +3788,9 @@ function mockFindOneAndUpdateError(errorOnCall = 1) { } function generateVersionTestCases() { + const projectId1 = new mongoose.Types.ObjectId(); + const projectId2 = new mongoose.Types.ObjectId(); + return [ { name: 'simple field update', @@ -3443,5 +3817,13 @@ function generateVersionTestCases() { update: { tools: ['tool2', 'tool3'] }, duplicate: { tools: ['tool2', 'tool3'] }, }, + { + name: 'projectIds update', + initial: { + projectIds: [projectId1], + }, + update: { projectIds: [projectId1, projectId2] }, + duplicate: { projectIds: [projectId2, projectId1] }, + }, ]; } diff --git a/api/models/Assistant.js b/api/models/Assistant.js new file mode 100644 index 0000000000..be94d35d7d --- /dev/null +++ b/api/models/Assistant.js @@ -0,0 +1,62 @@ +const { Assistant } = require('~/db/models'); + +/** + * Update an assistant with new data without overwriting existing properties, + * or create a new assistant if it doesn't exist. + * + * @param {Object} searchParams - The search parameters to find the assistant to update. + * @param {string} searchParams.assistant_id - The ID of the assistant to update. + * @param {string} searchParams.user - The user ID of the assistant's author. + * @param {Object} updateData - An object containing the properties to update. + * @returns {Promise} The updated or newly created assistant document as a plain object. + */ +const updateAssistantDoc = async (searchParams, updateData) => { + const options = { new: true, upsert: true }; + return await Assistant.findOneAndUpdate(searchParams, updateData, options).lean(); +}; + +/** + * Retrieves an assistant document based on the provided ID. + * + * @param {Object} searchParams - The search parameters to find the assistant to update. + * @param {string} searchParams.assistant_id - The ID of the assistant to update. + * @param {string} searchParams.user - The user ID of the assistant's author. + * @returns {Promise} The assistant document as a plain object, or null if not found. + */ +const getAssistant = async (searchParams) => await Assistant.findOne(searchParams).lean(); + +/** + * Retrieves all assistants that match the given search parameters. + * + * @param {Object} searchParams - The search parameters to find matching assistants. + * @param {Object} [select] - Optional. Specifies which document fields to include or exclude. + * @returns {Promise>} A promise that resolves to an array of assistant documents as plain objects. + */ +const getAssistants = async (searchParams, select = null) => { + let query = Assistant.find(searchParams); + + if (select) { + query = query.select(select); + } + + return await query.lean(); +}; + +/** + * Deletes an assistant based on the provided ID. + * + * @param {Object} searchParams - The search parameters to find the assistant to delete. + * @param {string} searchParams.assistant_id - The ID of the assistant to delete. + * @param {string} searchParams.user - The user ID of the assistant's author. + * @returns {Promise} Resolves when the assistant has been successfully deleted. + */ +const deleteAssistant = async (searchParams) => { + return await Assistant.findOneAndDelete(searchParams); +}; + +module.exports = { + updateAssistantDoc, + deleteAssistant, + getAssistants, + getAssistant, +}; diff --git a/api/models/Banner.js b/api/models/Banner.js new file mode 100644 index 0000000000..42ad1599ed --- /dev/null +++ b/api/models/Banner.js @@ -0,0 +1,28 @@ +const { logger } = require('@librechat/data-schemas'); +const { Banner } = require('~/db/models'); + +/** + * Retrieves the current active banner. + * @returns {Promise} The active banner object or null if no active banner is found. + */ +const getBanner = async (user) => { + try { + const now = new Date(); + const banner = await Banner.findOne({ + displayFrom: { $lte: now }, + $or: [{ displayTo: { $gte: now } }, { displayTo: null }], + type: 'banner', + }).lean(); + + if (!banner || banner.isPublic || user) { + return banner; + } + + return null; + } catch (error) { + logger.error('[getBanners] Error getting banners', error); + throw new Error('Error getting banners'); + } +}; + +module.exports = { getBanner }; diff --git a/api/models/Categories.js b/api/models/Categories.js new file mode 100644 index 0000000000..34bd2d8ed2 --- /dev/null +++ b/api/models/Categories.js @@ -0,0 +1,57 @@ +const { logger } = require('@librechat/data-schemas'); + +const options = [ + { + label: 'com_ui_idea', + value: 'idea', + }, + { + label: 'com_ui_travel', + value: 'travel', + }, + { + label: 'com_ui_teach_or_explain', + value: 'teach_or_explain', + }, + { + label: 'com_ui_write', + value: 'write', + }, + { + label: 'com_ui_shop', + value: 'shop', + }, + { + label: 'com_ui_code', + value: 'code', + }, + { + label: 'com_ui_misc', + value: 'misc', + }, + { + label: 'com_ui_roleplay', + value: 'roleplay', + }, + { + label: 'com_ui_finance', + value: 'finance', + }, +]; + +module.exports = { + /** + * Retrieves the categories asynchronously. + * @returns {Promise} An array of category objects. + * @throws {Error} If there is an error retrieving the categories. + */ + getCategories: async () => { + try { + // const categories = await Categories.find(); + return options; + } catch (error) { + logger.error('Error getting categories', error); + return []; + } + }, +}; diff --git a/api/models/Conversation.js b/api/models/Conversation.js new file mode 100644 index 0000000000..32eac1a764 --- /dev/null +++ b/api/models/Conversation.js @@ -0,0 +1,372 @@ +const { logger } = require('@librechat/data-schemas'); +const { createTempChatExpirationDate } = require('@librechat/api'); +const { getMessages, deleteMessages } = require('./Message'); +const { Conversation } = require('~/db/models'); + +/** + * Searches for a conversation by conversationId and returns a lean document with only conversationId and user. + * @param {string} conversationId - The conversation's ID. + * @returns {Promise<{conversationId: string, user: string} | null>} The conversation object with selected fields or null if not found. + */ +const searchConversation = async (conversationId) => { + try { + return await Conversation.findOne({ conversationId }, 'conversationId user').lean(); + } catch (error) { + logger.error('[searchConversation] Error searching conversation', error); + throw new Error('Error searching conversation'); + } +}; + +/** + * Retrieves a single conversation for a given user and conversation ID. + * @param {string} user - The user's ID. + * @param {string} conversationId - The conversation's ID. + * @returns {Promise} The conversation object. + */ +const getConvo = async (user, conversationId) => { + try { + return await Conversation.findOne({ user, conversationId }).lean(); + } catch (error) { + logger.error('[getConvo] Error getting single conversation', error); + throw new Error('Error getting single conversation'); + } +}; + +const deleteNullOrEmptyConversations = async () => { + try { + const filter = { + $or: [ + { conversationId: null }, + { conversationId: '' }, + { conversationId: { $exists: false } }, + ], + }; + + const result = await Conversation.deleteMany(filter); + + // Delete associated messages + const messageDeleteResult = await deleteMessages(filter); + + logger.info( + `[deleteNullOrEmptyConversations] Deleted ${result.deletedCount} conversations and ${messageDeleteResult.deletedCount} messages`, + ); + + return { + conversations: result, + messages: messageDeleteResult, + }; + } catch (error) { + logger.error('[deleteNullOrEmptyConversations] Error deleting conversations', error); + throw new Error('Error deleting conversations with null or empty conversationId'); + } +}; + +/** + * Searches for a conversation by conversationId and returns associated file ids. + * @param {string} conversationId - The conversation's ID. + * @returns {Promise} + */ +const getConvoFiles = async (conversationId) => { + try { + return (await Conversation.findOne({ conversationId }, 'files').lean())?.files ?? []; + } catch (error) { + logger.error('[getConvoFiles] Error getting conversation files', error); + throw new Error('Error getting conversation files'); + } +}; + +module.exports = { + getConvoFiles, + searchConversation, + deleteNullOrEmptyConversations, + /** + * Saves a conversation to the database. + * @param {Object} req - The request object. + * @param {string} conversationId - The conversation's ID. + * @param {Object} metadata - Additional metadata to log for operation. + * @returns {Promise} The conversation object. + */ + saveConvo: async (req, { conversationId, newConversationId, ...convo }, metadata) => { + try { + if (metadata?.context) { + logger.debug(`[saveConvo] ${metadata.context}`); + } + + const messages = await getMessages({ conversationId }, '_id'); + const update = { ...convo, messages, user: req.user.id }; + + if (newConversationId) { + update.conversationId = newConversationId; + } + + if (req?.body?.isTemporary) { + try { + const appConfig = req.config; + update.expiredAt = createTempChatExpirationDate(appConfig?.interfaceConfig); + } catch (err) { + logger.error('Error creating temporary chat expiration date:', err); + logger.info(`---\`saveConvo\` context: ${metadata?.context}`); + update.expiredAt = null; + } + } else { + update.expiredAt = null; + } + + /** @type {{ $set: Partial; $unset?: Record }} */ + const updateOperation = { $set: update }; + if (metadata && metadata.unsetFields && Object.keys(metadata.unsetFields).length > 0) { + updateOperation.$unset = metadata.unsetFields; + } + + /** Note: the resulting Model object is necessary for Meilisearch operations */ + const conversation = await Conversation.findOneAndUpdate( + { conversationId, user: req.user.id }, + updateOperation, + { + new: true, + upsert: metadata?.noUpsert !== true, + }, + ); + + if (!conversation) { + logger.debug('[saveConvo] Conversation not found, skipping update'); + return null; + } + + return conversation.toObject(); + } catch (error) { + logger.error('[saveConvo] Error saving conversation', error); + if (metadata && metadata?.context) { + logger.info(`[saveConvo] ${metadata.context}`); + } + return { message: 'Error saving conversation' }; + } + }, + bulkSaveConvos: async (conversations) => { + try { + const bulkOps = conversations.map((convo) => ({ + updateOne: { + filter: { conversationId: convo.conversationId, user: convo.user }, + update: convo, + upsert: true, + timestamps: false, + }, + })); + + const result = await Conversation.bulkWrite(bulkOps); + return result; + } catch (error) { + logger.error('[bulkSaveConvos] Error saving conversations in bulk', error); + throw new Error('Failed to save conversations in bulk.'); + } + }, + getConvosByCursor: async ( + user, + { + cursor, + limit = 25, + isArchived = false, + tags, + search, + sortBy = 'updatedAt', + sortDirection = 'desc', + } = {}, + ) => { + const filters = [{ user }]; + if (isArchived) { + filters.push({ isArchived: true }); + } else { + filters.push({ $or: [{ isArchived: false }, { isArchived: { $exists: false } }] }); + } + + if (Array.isArray(tags) && tags.length > 0) { + filters.push({ tags: { $in: tags } }); + } + + filters.push({ $or: [{ expiredAt: null }, { expiredAt: { $exists: false } }] }); + + if (search) { + try { + const meiliResults = await Conversation.meiliSearch(search, { filter: `user = "${user}"` }); + const matchingIds = Array.isArray(meiliResults.hits) + ? meiliResults.hits.map((result) => result.conversationId) + : []; + if (!matchingIds.length) { + return { conversations: [], nextCursor: null }; + } + filters.push({ conversationId: { $in: matchingIds } }); + } catch (error) { + logger.error('[getConvosByCursor] Error during meiliSearch', error); + throw new Error('Error during meiliSearch'); + } + } + + const validSortFields = ['title', 'createdAt', 'updatedAt']; + if (!validSortFields.includes(sortBy)) { + throw new Error( + `Invalid sortBy field: ${sortBy}. Must be one of ${validSortFields.join(', ')}`, + ); + } + const finalSortBy = sortBy; + const finalSortDirection = sortDirection === 'asc' ? 'asc' : 'desc'; + + let cursorFilter = null; + if (cursor) { + try { + const decoded = JSON.parse(Buffer.from(cursor, 'base64').toString()); + const { primary, secondary } = decoded; + const primaryValue = finalSortBy === 'title' ? primary : new Date(primary); + const secondaryValue = new Date(secondary); + const op = finalSortDirection === 'asc' ? '$gt' : '$lt'; + + cursorFilter = { + $or: [ + { [finalSortBy]: { [op]: primaryValue } }, + { + [finalSortBy]: primaryValue, + updatedAt: { [op]: secondaryValue }, + }, + ], + }; + } catch (err) { + logger.warn('[getConvosByCursor] Invalid cursor format, starting from beginning'); + } + if (cursorFilter) { + filters.push(cursorFilter); + } + } + + const query = filters.length === 1 ? filters[0] : { $and: filters }; + + try { + const sortOrder = finalSortDirection === 'asc' ? 1 : -1; + const sortObj = { [finalSortBy]: sortOrder }; + + if (finalSortBy !== 'updatedAt') { + sortObj.updatedAt = sortOrder; + } + + const convos = await Conversation.find(query) + .select( + 'conversationId endpoint title createdAt updatedAt user model agent_id assistant_id spec iconURL', + ) + .sort(sortObj) + .limit(limit + 1) + .lean(); + + let nextCursor = null; + if (convos.length > limit) { + convos.pop(); // Remove extra item used to detect next page + // Create cursor from the last RETURNED item (not the popped one) + const lastReturned = convos[convos.length - 1]; + const primaryValue = lastReturned[finalSortBy]; + const primaryStr = finalSortBy === 'title' ? primaryValue : primaryValue.toISOString(); + const secondaryStr = lastReturned.updatedAt.toISOString(); + const composite = { primary: primaryStr, secondary: secondaryStr }; + nextCursor = Buffer.from(JSON.stringify(composite)).toString('base64'); + } + + return { conversations: convos, nextCursor }; + } catch (error) { + logger.error('[getConvosByCursor] Error getting conversations', error); + throw new Error('Error getting conversations'); + } + }, + getConvosQueried: async (user, convoIds, cursor = null, limit = 25) => { + try { + if (!convoIds?.length) { + return { conversations: [], nextCursor: null, convoMap: {} }; + } + + const conversationIds = convoIds.map((convo) => convo.conversationId); + + const results = await Conversation.find({ + user, + conversationId: { $in: conversationIds }, + $or: [{ expiredAt: { $exists: false } }, { expiredAt: null }], + }).lean(); + + results.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt)); + + let filtered = results; + if (cursor && cursor !== 'start') { + const cursorDate = new Date(cursor); + filtered = results.filter((convo) => new Date(convo.updatedAt) < cursorDate); + } + + const limited = filtered.slice(0, limit + 1); + let nextCursor = null; + if (limited.length > limit) { + limited.pop(); // Remove extra item used to detect next page + // Create cursor from the last RETURNED item (not the popped one) + nextCursor = limited[limited.length - 1].updatedAt.toISOString(); + } + + const convoMap = {}; + limited.forEach((convo) => { + convoMap[convo.conversationId] = convo; + }); + + return { conversations: limited, nextCursor, convoMap }; + } catch (error) { + logger.error('[getConvosQueried] Error getting conversations', error); + throw new Error('Error fetching conversations'); + } + }, + getConvo, + /* chore: this method is not properly error handled */ + getConvoTitle: async (user, conversationId) => { + try { + const convo = await getConvo(user, conversationId); + /* ChatGPT Browser was triggering error here due to convo being saved later */ + if (convo && !convo.title) { + return null; + } else { + // TypeError: Cannot read properties of null (reading 'title') + return convo?.title || 'New Chat'; + } + } catch (error) { + logger.error('[getConvoTitle] Error getting conversation title', error); + throw new Error('Error getting conversation title'); + } + }, + /** + * Asynchronously deletes conversations and associated messages for a given user and filter. + * + * @async + * @function + * @param {string|ObjectId} user - The user's ID. + * @param {Object} filter - Additional filter criteria for the conversations to be deleted. + * @returns {Promise<{ n: number, ok: number, deletedCount: number, messages: { n: number, ok: number, deletedCount: number } }>} + * An object containing the count of deleted conversations and associated messages. + * @throws {Error} Throws an error if there's an issue with the database operations. + * + * @example + * const user = 'someUserId'; + * const filter = { someField: 'someValue' }; + * const result = await deleteConvos(user, filter); + * logger.error(result); // { n: 5, ok: 1, deletedCount: 5, messages: { n: 10, ok: 1, deletedCount: 10 } } + */ + deleteConvos: async (user, filter) => { + try { + const userFilter = { ...filter, user }; + const conversations = await Conversation.find(userFilter).select('conversationId'); + const conversationIds = conversations.map((c) => c.conversationId); + + if (!conversationIds.length) { + throw new Error('Conversation not found or already deleted.'); + } + + const deleteConvoResult = await Conversation.deleteMany(userFilter); + + const deleteMessagesResult = await deleteMessages({ + conversationId: { $in: conversationIds }, + }); + + return { ...deleteConvoResult, messages: deleteMessagesResult }; + } catch (error) { + logger.error('[deleteConvos] Error deleting conversations and messages', error); + throw error; + } + }, +}; diff --git a/packages/data-schemas/src/methods/conversation.spec.ts b/api/models/Conversation.spec.js similarity index 64% rename from packages/data-schemas/src/methods/conversation.spec.ts rename to api/models/Conversation.spec.js index 9e4c2d2f5d..bd415b4165 100644 --- a/packages/data-schemas/src/methods/conversation.spec.ts +++ b/api/models/Conversation.spec.js @@ -1,90 +1,39 @@ -import mongoose from 'mongoose'; -import { v4 as uuidv4 } from 'uuid'; -import { EModelEndpoint } from 'librechat-data-provider'; -import type { IConversation } from '../types'; -import { MongoMemoryServer } from 'mongodb-memory-server'; -import { ConversationMethods, createConversationMethods } from './conversation'; -import { tenantStorage, runAsSystem } from '~/config/tenantContext'; -import { createModels } from '../models'; +const mongoose = require('mongoose'); +const { v4: uuidv4 } = require('uuid'); +const { EModelEndpoint } = require('librechat-data-provider'); +const { MongoMemoryServer } = require('mongodb-memory-server'); +const { + deleteNullOrEmptyConversations, + searchConversation, + getConvosByCursor, + getConvosQueried, + getConvoFiles, + getConvoTitle, + deleteConvos, + saveConvo, + getConvo, +} = require('./Conversation'); +jest.mock('~/server/services/Config/app'); +jest.mock('./Message'); +const { getMessages, deleteMessages } = require('./Message'); -jest.mock('~/config/winston', () => ({ - error: jest.fn(), - warn: jest.fn(), - info: jest.fn(), - debug: jest.fn(), -})); - -let mongoServer: InstanceType; -let Conversation: mongoose.Model; -let modelsToCleanup: string[] = []; - -// Mock message methods (same as original test mocking ./Message) -const getMessages = jest.fn().mockResolvedValue([]); -const deleteMessages = jest.fn().mockResolvedValue({ deletedCount: 0 }); - -let methods: ConversationMethods; - -beforeAll(async () => { - mongoServer = await MongoMemoryServer.create(); - const mongoUri = mongoServer.getUri(); - - const models = createModels(mongoose); - modelsToCleanup = Object.keys(models); - Object.assign(mongoose.models, models); - Conversation = mongoose.models.Conversation as mongoose.Model; - - methods = createConversationMethods(mongoose, { getMessages, deleteMessages }); - - await mongoose.connect(mongoUri); -}); - -afterAll(async () => { - const collections = mongoose.connection.collections; - for (const key in collections) { - await collections[key].deleteMany({}); - } - - for (const modelName of modelsToCleanup) { - if (mongoose.models[modelName]) { - delete mongoose.models[modelName]; - } - } - - await mongoose.disconnect(); - await mongoServer.stop(); -}); - -const saveConvo = (...args: Parameters) => - methods.saveConvo(...args) as Promise; -const getConvo = (...args: Parameters) => - methods.getConvo(...args); -const getConvoTitle = (...args: Parameters) => - methods.getConvoTitle(...args); -const getConvoFiles = (...args: Parameters) => - methods.getConvoFiles(...args); -const deleteConvos = (...args: Parameters) => - methods.deleteConvos(...args); -const getConvosByCursor = (...args: Parameters) => - methods.getConvosByCursor(...args); -const getConvosQueried = (...args: Parameters) => - methods.getConvosQueried(...args); -const deleteNullOrEmptyConversations = ( - ...args: Parameters -) => methods.deleteNullOrEmptyConversations(...args); -const searchConversation = (...args: Parameters) => - methods.searchConversation(...args); +const { Conversation } = require('~/db/models'); describe('Conversation Operations', () => { - let mockCtx: { - userId: string; - isTemporary?: boolean; - interfaceConfig?: { temporaryChatRetention?: number }; - }; - let mockConversationData: { - conversationId: string; - title: string; - endpoint: string; - }; + let mongoServer; + let mockReq; + let mockConversationData; + + beforeAll(async () => { + mongoServer = await MongoMemoryServer.create(); + const mongoUri = mongoServer.getUri(); + await mongoose.connect(mongoUri); + }); + + afterAll(async () => { + await mongoose.disconnect(); + await mongoServer.stop(); + }); beforeEach(async () => { // Clear database @@ -92,13 +41,18 @@ describe('Conversation Operations', () => { // Reset mocks jest.clearAllMocks(); + + // Default mock implementations getMessages.mockResolvedValue([]); deleteMessages.mockResolvedValue({ deletedCount: 0 }); - mockCtx = { - userId: 'user123', - interfaceConfig: { - temporaryChatRetention: 24, // Default 24 hours + mockReq = { + user: { id: 'user123' }, + body: {}, + config: { + interfaceConfig: { + temporaryChatRetention: 24, // Default 24 hours + }, }, }; @@ -111,28 +65,29 @@ describe('Conversation Operations', () => { describe('saveConvo', () => { it('should save a conversation for an authenticated user', async () => { - const result = await saveConvo(mockCtx, mockConversationData); + const result = await saveConvo(mockReq, mockConversationData); - expect(result?.conversationId).toBe(mockConversationData.conversationId); - expect(result?.user).toBe('user123'); - expect(result?.title).toBe('Test Conversation'); - expect(result?.endpoint).toBe(EModelEndpoint.openAI); + expect(result.conversationId).toBe(mockConversationData.conversationId); + expect(result.user).toBe('user123'); + expect(result.title).toBe('Test Conversation'); + expect(result.endpoint).toBe(EModelEndpoint.openAI); // Verify the conversation was actually saved to the database - const savedConvo = await Conversation.findOne({ + const savedConvo = await Conversation.findOne({ conversationId: mockConversationData.conversationId, user: 'user123', }); expect(savedConvo).toBeTruthy(); - expect(savedConvo?.title).toBe('Test Conversation'); + expect(savedConvo.title).toBe('Test Conversation'); }); it('should query messages when saving a conversation', async () => { // Mock messages as ObjectIds + const mongoose = require('mongoose'); const mockMessages = [new mongoose.Types.ObjectId(), new mongoose.Types.ObjectId()]; getMessages.mockResolvedValue(mockMessages); - await saveConvo(mockCtx, mockConversationData); + await saveConvo(mockReq, mockConversationData); // Verify that getMessages was called with correct parameters expect(getMessages).toHaveBeenCalledWith( @@ -143,18 +98,18 @@ describe('Conversation Operations', () => { it('should handle newConversationId when provided', async () => { const newConversationId = uuidv4(); - const result = await saveConvo(mockCtx, { + const result = await saveConvo(mockReq, { ...mockConversationData, newConversationId, }); - expect(result?.conversationId).toBe(newConversationId); + expect(result.conversationId).toBe(newConversationId); }); it('should not create a conversation when noUpsert is true and conversation does not exist', async () => { const nonExistentId = uuidv4(); const result = await saveConvo( - mockCtx, + mockReq, { conversationId: nonExistentId, title: 'Ghost Title' }, { noUpsert: true }, ); @@ -166,30 +121,30 @@ describe('Conversation Operations', () => { }); it('should update an existing conversation when noUpsert is true', async () => { - await saveConvo(mockCtx, mockConversationData); + await saveConvo(mockReq, mockConversationData); const result = await saveConvo( - mockCtx, + mockReq, { conversationId: mockConversationData.conversationId, title: 'Updated Title' }, { noUpsert: true }, ); expect(result).not.toBeNull(); - expect(result?.title).toBe('Updated Title'); - expect(result?.conversationId).toBe(mockConversationData.conversationId); + expect(result.title).toBe('Updated Title'); + expect(result.conversationId).toBe(mockConversationData.conversationId); }); it('should still upsert by default when noUpsert is not provided', async () => { const newId = uuidv4(); - const result = await saveConvo(mockCtx, { + const result = await saveConvo(mockReq, { conversationId: newId, title: 'New Conversation', endpoint: EModelEndpoint.openAI, }); expect(result).not.toBeNull(); - expect(result?.conversationId).toBe(newId); - expect(result?.title).toBe('New Conversation'); + expect(result.conversationId).toBe(newId); + expect(result.title).toBe('New Conversation'); }); it('should handle unsetFields metadata', async () => { @@ -197,30 +152,31 @@ describe('Conversation Operations', () => { unsetFields: { someField: 1 }, }; - await saveConvo(mockCtx, mockConversationData, metadata); + await saveConvo(mockReq, mockConversationData, metadata); - const savedConvo = await Conversation.findOne({ + const savedConvo = await Conversation.findOne({ conversationId: mockConversationData.conversationId, }); - expect(savedConvo?.someField).toBeUndefined(); + expect(savedConvo.someField).toBeUndefined(); }); }); describe('isTemporary conversation handling', () => { it('should save a conversation with expiredAt when isTemporary is true', async () => { - mockCtx.interfaceConfig = { temporaryChatRetention: 24 }; - mockCtx.isTemporary = true; + mockReq.config.interfaceConfig.temporaryChatRetention = 24; + + mockReq.body = { isTemporary: true }; const beforeSave = new Date(); - const result = await saveConvo(mockCtx, mockConversationData); + const result = await saveConvo(mockReq, mockConversationData); const afterSave = new Date(); - expect(result?.conversationId).toBe(mockConversationData.conversationId); - expect(result?.expiredAt).toBeDefined(); - expect(result?.expiredAt).toBeInstanceOf(Date); + expect(result.conversationId).toBe(mockConversationData.conversationId); + expect(result.expiredAt).toBeDefined(); + expect(result.expiredAt).toBeInstanceOf(Date); const expectedExpirationTime = new Date(beforeSave.getTime() + 24 * 60 * 60 * 1000); - const actualExpirationTime = new Date(result?.expiredAt ?? 0); + const actualExpirationTime = new Date(result.expiredAt); expect(actualExpirationTime.getTime()).toBeGreaterThanOrEqual( expectedExpirationTime.getTime() - 1000, @@ -231,35 +187,36 @@ describe('Conversation Operations', () => { }); it('should save a conversation without expiredAt when isTemporary is false', async () => { - mockCtx.isTemporary = false; + mockReq.body = { isTemporary: false }; - const result = await saveConvo(mockCtx, mockConversationData); + const result = await saveConvo(mockReq, mockConversationData); - expect(result?.conversationId).toBe(mockConversationData.conversationId); - expect(result?.expiredAt).toBeNull(); + expect(result.conversationId).toBe(mockConversationData.conversationId); + expect(result.expiredAt).toBeNull(); }); it('should save a conversation without expiredAt when isTemporary is not provided', async () => { - mockCtx.isTemporary = undefined; + mockReq.body = {}; - const result = await saveConvo(mockCtx, mockConversationData); + const result = await saveConvo(mockReq, mockConversationData); - expect(result?.conversationId).toBe(mockConversationData.conversationId); - expect(result?.expiredAt).toBeNull(); + expect(result.conversationId).toBe(mockConversationData.conversationId); + expect(result.expiredAt).toBeNull(); }); it('should use custom retention period from config', async () => { - mockCtx.interfaceConfig = { temporaryChatRetention: 48 }; - mockCtx.isTemporary = true; + mockReq.config.interfaceConfig.temporaryChatRetention = 48; + + mockReq.body = { isTemporary: true }; const beforeSave = new Date(); - const result = await saveConvo(mockCtx, mockConversationData); + const result = await saveConvo(mockReq, mockConversationData); - expect(result?.expiredAt).toBeDefined(); + expect(result.expiredAt).toBeDefined(); // Verify expiredAt is approximately 48 hours in the future const expectedExpirationTime = new Date(beforeSave.getTime() + 48 * 60 * 60 * 1000); - const actualExpirationTime = new Date(result?.expiredAt ?? 0); + const actualExpirationTime = new Date(result.expiredAt); expect(actualExpirationTime.getTime()).toBeGreaterThanOrEqual( expectedExpirationTime.getTime() - 1000, @@ -271,17 +228,18 @@ describe('Conversation Operations', () => { it('should handle minimum retention period (1 hour)', async () => { // Mock app config with less than minimum retention - mockCtx.interfaceConfig = { temporaryChatRetention: 0.5 }; // Half hour - should be clamped to 1 hour - mockCtx.isTemporary = true; + mockReq.config.interfaceConfig.temporaryChatRetention = 0.5; // Half hour - should be clamped to 1 hour + + mockReq.body = { isTemporary: true }; const beforeSave = new Date(); - const result = await saveConvo(mockCtx, mockConversationData); + const result = await saveConvo(mockReq, mockConversationData); - expect(result?.expiredAt).toBeDefined(); + expect(result.expiredAt).toBeDefined(); // Verify expiredAt is approximately 1 hour in the future (minimum) const expectedExpirationTime = new Date(beforeSave.getTime() + 1 * 60 * 60 * 1000); - const actualExpirationTime = new Date(result?.expiredAt ?? 0); + const actualExpirationTime = new Date(result.expiredAt); expect(actualExpirationTime.getTime()).toBeGreaterThanOrEqual( expectedExpirationTime.getTime() - 1000, @@ -293,17 +251,18 @@ describe('Conversation Operations', () => { it('should handle maximum retention period (8760 hours)', async () => { // Mock app config with more than maximum retention - mockCtx.interfaceConfig = { temporaryChatRetention: 10000 }; // Should be clamped to 8760 hours - mockCtx.isTemporary = true; + mockReq.config.interfaceConfig.temporaryChatRetention = 10000; // Should be clamped to 8760 hours + + mockReq.body = { isTemporary: true }; const beforeSave = new Date(); - const result = await saveConvo(mockCtx, mockConversationData); + const result = await saveConvo(mockReq, mockConversationData); - expect(result?.expiredAt).toBeDefined(); + expect(result.expiredAt).toBeDefined(); // Verify expiredAt is approximately 8760 hours (1 year) in the future const expectedExpirationTime = new Date(beforeSave.getTime() + 8760 * 60 * 60 * 1000); - const actualExpirationTime = new Date(result?.expiredAt ?? 0); + const actualExpirationTime = new Date(result.expiredAt); expect(actualExpirationTime.getTime()).toBeGreaterThanOrEqual( expectedExpirationTime.getTime() - 1000, @@ -315,21 +274,22 @@ describe('Conversation Operations', () => { it('should handle missing config gracefully', async () => { // Simulate missing config - should use default retention period - mockCtx.interfaceConfig = undefined; - mockCtx.isTemporary = true; + delete mockReq.config; + + mockReq.body = { isTemporary: true }; const beforeSave = new Date(); - const result = await saveConvo(mockCtx, mockConversationData); + const result = await saveConvo(mockReq, mockConversationData); const afterSave = new Date(); // Should still save the conversation with default retention period (30 days) - expect(result?.conversationId).toBe(mockConversationData.conversationId); - expect(result?.expiredAt).toBeDefined(); - expect(result?.expiredAt).toBeInstanceOf(Date); + expect(result.conversationId).toBe(mockConversationData.conversationId); + expect(result.expiredAt).toBeDefined(); + expect(result.expiredAt).toBeInstanceOf(Date); // Verify expiredAt is approximately 30 days in the future (720 hours) const expectedExpirationTime = new Date(beforeSave.getTime() + 720 * 60 * 60 * 1000); - const actualExpirationTime = new Date(result?.expiredAt ?? 0); + const actualExpirationTime = new Date(result.expiredAt); expect(actualExpirationTime.getTime()).toBeGreaterThanOrEqual( expectedExpirationTime.getTime() - 1000, @@ -341,17 +301,18 @@ describe('Conversation Operations', () => { it('should use default retention when config is not provided', async () => { // Mock getAppConfig to return empty config - mockCtx.interfaceConfig = undefined; // Empty config - mockCtx.isTemporary = true; + mockReq.config = {}; // Empty config + + mockReq.body = { isTemporary: true }; const beforeSave = new Date(); - const result = await saveConvo(mockCtx, mockConversationData); + const result = await saveConvo(mockReq, mockConversationData); - expect(result?.expiredAt).toBeDefined(); + expect(result.expiredAt).toBeDefined(); // Default retention is 30 days (720 hours) const expectedExpirationTime = new Date(beforeSave.getTime() + 30 * 24 * 60 * 60 * 1000); - const actualExpirationTime = new Date(result?.expiredAt ?? 0); + const actualExpirationTime = new Date(result.expiredAt); expect(actualExpirationTime.getTime()).toBeGreaterThanOrEqual( expectedExpirationTime.getTime() - 1000, @@ -363,39 +324,40 @@ describe('Conversation Operations', () => { it('should update expiredAt when saving existing temporary conversation', async () => { // First save a temporary conversation - mockCtx.interfaceConfig = { temporaryChatRetention: 24 }; - mockCtx.isTemporary = true; - const firstSave = await saveConvo(mockCtx, mockConversationData); - const originalExpiredAt = firstSave?.expiredAt ?? new Date(0); + mockReq.config.interfaceConfig.temporaryChatRetention = 24; + + mockReq.body = { isTemporary: true }; + const firstSave = await saveConvo(mockReq, mockConversationData); + const originalExpiredAt = firstSave.expiredAt; // Wait a bit to ensure time difference await new Promise((resolve) => setTimeout(resolve, 100)); // Save again with same conversationId but different title const updatedData = { ...mockConversationData, title: 'Updated Title' }; - const secondSave = await saveConvo(mockCtx, updatedData); + const secondSave = await saveConvo(mockReq, updatedData); // Should update title and create new expiredAt - expect(secondSave?.title).toBe('Updated Title'); - expect(secondSave?.expiredAt).toBeDefined(); - expect(new Date(secondSave?.expiredAt ?? 0).getTime()).toBeGreaterThan( + expect(secondSave.title).toBe('Updated Title'); + expect(secondSave.expiredAt).toBeDefined(); + expect(new Date(secondSave.expiredAt).getTime()).toBeGreaterThan( new Date(originalExpiredAt).getTime(), ); }); it('should not set expiredAt when updating non-temporary conversation', async () => { // First save a non-temporary conversation - mockCtx.isTemporary = false; - const firstSave = await saveConvo(mockCtx, mockConversationData); - expect(firstSave?.expiredAt).toBeNull(); + mockReq.body = { isTemporary: false }; + const firstSave = await saveConvo(mockReq, mockConversationData); + expect(firstSave.expiredAt).toBeNull(); // Update without isTemporary flag - mockCtx.isTemporary = undefined; + mockReq.body = {}; const updatedData = { ...mockConversationData, title: 'Updated Title' }; - const secondSave = await saveConvo(mockCtx, updatedData); + const secondSave = await saveConvo(mockReq, updatedData); - expect(secondSave?.title).toBe('Updated Title'); - expect(secondSave?.expiredAt).toBeNull(); + expect(secondSave.title).toBe('Updated Title'); + expect(secondSave.expiredAt).toBeNull(); }); it('should filter out expired conversations in getConvosByCursor', async () => { @@ -419,13 +381,13 @@ describe('Conversation Operations', () => { }); // Mock Meili search - Object.assign(Conversation, { meiliSearch: jest.fn().mockResolvedValue({ hits: [] }) }); + Conversation.meiliSearch = jest.fn().mockResolvedValue({ hits: [] }); const result = await getConvosByCursor('user123'); // Should only return conversations with null or non-existent expiredAt - expect(result?.conversations).toHaveLength(1); - expect(result?.conversations[0]?.conversationId).toBe(nonExpiredConvo.conversationId); + expect(result.conversations).toHaveLength(1); + expect(result.conversations[0].conversationId).toBe(nonExpiredConvo.conversationId); }); it('should filter out expired conversations in getConvosQueried', async () => { @@ -454,10 +416,10 @@ describe('Conversation Operations', () => { const result = await getConvosQueried('user123', convoIds); // Should only return the non-expired conversation - expect(result?.conversations).toHaveLength(1); - expect(result?.conversations[0].conversationId).toBe(nonExpiredConvo.conversationId); - expect(result?.convoMap[nonExpiredConvo.conversationId]).toBeDefined(); - expect(result?.convoMap[expiredConvo.conversationId]).toBeUndefined(); + expect(result.conversations).toHaveLength(1); + expect(result.conversations[0].conversationId).toBe(nonExpiredConvo.conversationId); + expect(result.convoMap[nonExpiredConvo.conversationId]).toBeDefined(); + expect(result.convoMap[expiredConvo.conversationId]).toBeUndefined(); }); }); @@ -473,9 +435,9 @@ describe('Conversation Operations', () => { const result = await searchConversation(mockConversationData.conversationId); expect(result).toBeTruthy(); - expect(result!.conversationId).toBe(mockConversationData.conversationId); - expect(result!.user).toBe('user123'); - expect((result as unknown as { title?: string }).title).toBeUndefined(); // Only returns conversationId and user + expect(result.conversationId).toBe(mockConversationData.conversationId); + expect(result.user).toBe('user123'); + expect(result.title).toBeUndefined(); // Only returns conversationId and user }); it('should return null if conversation not found', async () => { @@ -495,9 +457,9 @@ describe('Conversation Operations', () => { const result = await getConvo('user123', mockConversationData.conversationId); - expect(result!.conversationId).toBe(mockConversationData.conversationId); - expect(result!.user).toBe('user123'); - expect(result!.title).toBe('Test Conversation'); + expect(result.conversationId).toBe(mockConversationData.conversationId); + expect(result.user).toBe('user123'); + expect(result.title).toBe('Test Conversation'); }); it('should return null if conversation not found', async () => { @@ -583,11 +545,10 @@ describe('Conversation Operations', () => { conversationId: mockConversationData.conversationId, }); - expect(result?.deletedCount).toBe(1); - expect(result?.messages.deletedCount).toBe(5); + expect(result.deletedCount).toBe(1); + expect(result.messages.deletedCount).toBe(5); expect(deleteMessages).toHaveBeenCalledWith({ conversationId: { $in: [mockConversationData.conversationId] }, - user: 'user123', }); // Verify conversation was deleted @@ -620,8 +581,8 @@ describe('Conversation Operations', () => { const result = await deleteNullOrEmptyConversations(); - expect(result?.conversations.deletedCount).toBe(0); // No invalid conversations to delete - expect(result?.messages.deletedCount).toBe(0); + expect(result.conversations.deletedCount).toBe(0); // No invalid conversations to delete + expect(result.messages.deletedCount).toBe(0); // Verify valid conversation remains const remainingConvos = await Conversation.find({}); @@ -635,7 +596,7 @@ describe('Conversation Operations', () => { // Force a database error by disconnecting await mongoose.disconnect(); - const result = await saveConvo(mockCtx, mockConversationData); + const result = await saveConvo(mockReq, mockConversationData); expect(result).toEqual({ message: 'Error saving conversation' }); @@ -649,7 +610,7 @@ describe('Conversation Operations', () => { * Helper to create conversations with specific timestamps * Uses collection.insertOne to bypass Mongoose timestamps entirely */ - const createConvoWithTimestamps = async (index: number, createdAt: Date, updatedAt: Date) => { + const createConvoWithTimestamps = async (index, createdAt, updatedAt) => { const conversationId = uuidv4(); // Use collection-level insert to bypass Mongoose timestamps await Conversation.collection.insertOne({ @@ -668,7 +629,7 @@ describe('Conversation Operations', () => { it('should not skip conversations at page boundaries', async () => { // Create 30 conversations to ensure pagination (limit is 25) const baseTime = new Date('2026-01-01T00:00:00.000Z'); - const convos: unknown[] = []; + const convos = []; for (let i = 0; i < 30; i++) { const updatedAt = new Date(baseTime.getTime() - i * 60000); // Each 1 minute apart @@ -694,8 +655,8 @@ describe('Conversation Operations', () => { // Verify no duplicates and no gaps const allIds = [ - ...page1.conversations.map((c: IConversation) => c.conversationId), - ...page2.conversations.map((c: IConversation) => c.conversationId), + ...page1.conversations.map((c) => c.conversationId), + ...page2.conversations.map((c) => c.conversationId), ]; const uniqueIds = new Set(allIds); @@ -710,7 +671,7 @@ describe('Conversation Operations', () => { const baseTime = new Date('2026-01-01T12:00:00.000Z'); // Create exactly 26 conversations - const convos: (IConversation | null)[] = []; + const convos = []; for (let i = 0; i < 26; i++) { const updatedAt = new Date(baseTime.getTime() - i * 60000); const convo = await createConvoWithTimestamps(i, updatedAt, updatedAt); @@ -727,8 +688,8 @@ describe('Conversation Operations', () => { expect(page1.nextCursor).toBeTruthy(); // Item 26 should NOT be in page 1 - const page1Ids = page1.conversations.map((c: IConversation) => c.conversationId); - expect(page1Ids).not.toContain(item26!.conversationId); + const page1Ids = page1.conversations.map((c) => c.conversationId); + expect(page1Ids).not.toContain(item26.conversationId); // Fetch second page const page2 = await getConvosByCursor('user123', { @@ -738,7 +699,7 @@ describe('Conversation Operations', () => { // Item 26 MUST be in page 2 (this was the bug - it was being skipped) expect(page2.conversations).toHaveLength(1); - expect(page2.conversations[0].conversationId).toBe(item26!.conversationId); + expect(page2.conversations[0].conversationId).toBe(item26.conversationId); }); it('should sort by updatedAt DESC by default', async () => { @@ -765,10 +726,10 @@ describe('Conversation Operations', () => { const result = await getConvosByCursor('user123'); // Should be sorted by updatedAt DESC (most recent first) - expect(result?.conversations).toHaveLength(3); - expect(result?.conversations[0].conversationId).toBe(convo1!.conversationId); // Jan 3 updatedAt - expect(result?.conversations[1].conversationId).toBe(convo2!.conversationId); // Jan 2 updatedAt - expect(result?.conversations[2].conversationId).toBe(convo3!.conversationId); // Jan 1 updatedAt + expect(result.conversations).toHaveLength(3); + expect(result.conversations[0].conversationId).toBe(convo1.conversationId); // Jan 3 updatedAt + expect(result.conversations[1].conversationId).toBe(convo2.conversationId); // Jan 2 updatedAt + expect(result.conversations[2].conversationId).toBe(convo3.conversationId); // Jan 1 updatedAt }); it('should handle conversations with same updatedAt (tie-breaker)', async () => { @@ -782,12 +743,12 @@ describe('Conversation Operations', () => { const result = await getConvosByCursor('user123'); // All 3 should be returned (no skipping due to same timestamps) - expect(result?.conversations).toHaveLength(3); + expect(result.conversations).toHaveLength(3); - const returnedIds = result?.conversations.map((c: IConversation) => c.conversationId); - expect(returnedIds).toContain(convo1!.conversationId); - expect(returnedIds).toContain(convo2!.conversationId); - expect(returnedIds).toContain(convo3!.conversationId); + const returnedIds = result.conversations.map((c) => c.conversationId); + expect(returnedIds).toContain(convo1.conversationId); + expect(returnedIds).toContain(convo2.conversationId); + expect(returnedIds).toContain(convo3.conversationId); }); it('should handle cursor pagination with conversations updated during pagination', async () => { @@ -844,15 +805,13 @@ describe('Conversation Operations', () => { const page1 = await getConvosByCursor('user123', { limit: 25 }); // Decode the cursor to verify it's based on the last RETURNED item - const decodedCursor = JSON.parse( - Buffer.from(page1.nextCursor as string, 'base64').toString(), - ); + const decodedCursor = JSON.parse(Buffer.from(page1.nextCursor, 'base64').toString()); // The cursor should match the last item in page1 (item at index 24) - const lastReturnedItem = page1.conversations[24] as IConversation; + const lastReturnedItem = page1.conversations[24]; expect(new Date(decodedCursor.primary).getTime()).toBe( - new Date(lastReturnedItem.updatedAt ?? 0).getTime(), + new Date(lastReturnedItem.updatedAt).getTime(), ); }); @@ -871,26 +830,26 @@ describe('Conversation Operations', () => { ); // Verify timestamps were set correctly - expect(new Date(convo1!.createdAt ?? 0).getTime()).toBe( + expect(new Date(convo1.createdAt).getTime()).toBe( new Date('2026-01-03T00:00:00.000Z').getTime(), ); - expect(new Date(convo2!.createdAt ?? 0).getTime()).toBe( + expect(new Date(convo2.createdAt).getTime()).toBe( new Date('2026-01-01T00:00:00.000Z').getTime(), ); const result = await getConvosByCursor('user123', { sortBy: 'createdAt' }); // Should be sorted by createdAt DESC - expect(result?.conversations).toHaveLength(2); - expect(result?.conversations[0].conversationId).toBe(convo1!.conversationId); // Jan 3 createdAt - expect(result?.conversations[1].conversationId).toBe(convo2!.conversationId); // Jan 1 createdAt + expect(result.conversations).toHaveLength(2); + expect(result.conversations[0].conversationId).toBe(convo1.conversationId); // Jan 3 createdAt + expect(result.conversations[1].conversationId).toBe(convo2.conversationId); // Jan 1 createdAt }); it('should handle empty result set gracefully', async () => { const result = await getConvosByCursor('user123'); - expect(result?.conversations).toHaveLength(0); - expect(result?.nextCursor).toBeNull(); + expect(result.conversations).toHaveLength(0); + expect(result.nextCursor).toBeNull(); }); it('should handle exactly limit number of conversations (no next page)', async () => { @@ -904,54 +863,8 @@ describe('Conversation Operations', () => { const result = await getConvosByCursor('user123', { limit: 25 }); - expect(result?.conversations).toHaveLength(25); - expect(result?.nextCursor).toBeNull(); // No next page - }); - }); - - describe('tenantId stripping', () => { - it('saveConvo should not write caller-supplied tenantId to the document', async () => { - const conversationId = uuidv4(); - const result = await saveConvo( - { userId: 'user123' }, - { conversationId, tenantId: 'malicious-tenant', title: 'Tenant Test' }, - ); - - expect(result).not.toBeNull(); - const doc = await Conversation.findOne({ conversationId }).lean(); - expect(doc).not.toBeNull(); - expect(doc?.title).toBe('Tenant Test'); - expect(doc?.tenantId).toBeUndefined(); - }); - - it('bulkSaveConvos should not overwrite tenantId via update payload', async () => { - const conversationId = uuidv4(); - - await tenantStorage.run({ tenantId: 'real-tenant' }, async () => { - await Conversation.create({ - conversationId, - user: 'user123', - title: 'Original', - endpoint: EModelEndpoint.openAI, - }); - }); - - await tenantStorage.run({ tenantId: 'real-tenant' }, async () => { - await methods.bulkSaveConvos([ - { - conversationId, - user: 'user123', - title: 'Updated', - tenantId: 'malicious-tenant', - endpoint: EModelEndpoint.openAI, - }, - ]); - }); - - const doc = await runAsSystem(async () => Conversation.findOne({ conversationId }).lean()); - expect(doc).not.toBeNull(); - expect(doc?.title).toBe('Updated'); - expect(doc?.tenantId).toBe('real-tenant'); + expect(result.conversations).toHaveLength(25); + expect(result.nextCursor).toBeNull(); // No next page }); }); }); diff --git a/api/models/ConversationTag.js b/api/models/ConversationTag.js new file mode 100644 index 0000000000..47a6c2bbf5 --- /dev/null +++ b/api/models/ConversationTag.js @@ -0,0 +1,284 @@ +const { logger } = require('@librechat/data-schemas'); +const { ConversationTag, Conversation } = require('~/db/models'); + +/** + * Retrieves all conversation tags for a user. + * @param {string} user - The user ID. + * @returns {Promise} An array of conversation tags. + */ +const getConversationTags = async (user) => { + try { + return await ConversationTag.find({ user }).sort({ position: 1 }).lean(); + } catch (error) { + logger.error('[getConversationTags] Error getting conversation tags', error); + throw new Error('Error getting conversation tags'); + } +}; + +/** + * Creates a new conversation tag. + * @param {string} user - The user ID. + * @param {Object} data - The tag data. + * @param {string} data.tag - The tag name. + * @param {string} [data.description] - The tag description. + * @param {boolean} [data.addToConversation] - Whether to add the tag to a conversation. + * @param {string} [data.conversationId] - The conversation ID to add the tag to. + * @returns {Promise} The created tag. + */ +const createConversationTag = async (user, data) => { + try { + const { tag, description, addToConversation, conversationId } = data; + + const existingTag = await ConversationTag.findOne({ user, tag }).lean(); + if (existingTag) { + return existingTag; + } + + const maxPosition = await ConversationTag.findOne({ user }).sort('-position').lean(); + const position = (maxPosition?.position || 0) + 1; + + const newTag = await ConversationTag.findOneAndUpdate( + { tag, user }, + { + tag, + user, + count: addToConversation ? 1 : 0, + position, + description, + $setOnInsert: { createdAt: new Date() }, + }, + { + new: true, + upsert: true, + lean: true, + }, + ); + + if (addToConversation && conversationId) { + await Conversation.findOneAndUpdate( + { user, conversationId }, + { $addToSet: { tags: tag } }, + { new: true }, + ); + } + + return newTag; + } catch (error) { + logger.error('[createConversationTag] Error creating conversation tag', error); + throw new Error('Error creating conversation tag'); + } +}; + +/** + * Updates an existing conversation tag. + * @param {string} user - The user ID. + * @param {string} oldTag - The current tag name. + * @param {Object} data - The updated tag data. + * @param {string} [data.tag] - The new tag name. + * @param {string} [data.description] - The updated description. + * @param {number} [data.position] - The new position. + * @returns {Promise} The updated tag. + */ +const updateConversationTag = async (user, oldTag, data) => { + try { + const { tag: newTag, description, position } = data; + + const existingTag = await ConversationTag.findOne({ user, tag: oldTag }).lean(); + if (!existingTag) { + return null; + } + + if (newTag && newTag !== oldTag) { + const tagAlreadyExists = await ConversationTag.findOne({ user, tag: newTag }).lean(); + if (tagAlreadyExists) { + throw new Error('Tag already exists'); + } + + await Conversation.updateMany({ user, tags: oldTag }, { $set: { 'tags.$': newTag } }); + } + + const updateData = {}; + if (newTag) { + updateData.tag = newTag; + } + if (description !== undefined) { + updateData.description = description; + } + if (position !== undefined) { + await adjustPositions(user, existingTag.position, position); + updateData.position = position; + } + + return await ConversationTag.findOneAndUpdate({ user, tag: oldTag }, updateData, { + new: true, + lean: true, + }); + } catch (error) { + logger.error('[updateConversationTag] Error updating conversation tag', error); + throw new Error('Error updating conversation tag'); + } +}; + +/** + * Adjusts positions of tags when a tag's position is changed. + * @param {string} user - The user ID. + * @param {number} oldPosition - The old position of the tag. + * @param {number} newPosition - The new position of the tag. + * @returns {Promise} + */ +const adjustPositions = async (user, oldPosition, newPosition) => { + if (oldPosition === newPosition) { + return; + } + + const update = oldPosition < newPosition ? { $inc: { position: -1 } } : { $inc: { position: 1 } }; + const position = + oldPosition < newPosition + ? { + $gt: Math.min(oldPosition, newPosition), + $lte: Math.max(oldPosition, newPosition), + } + : { + $gte: Math.min(oldPosition, newPosition), + $lt: Math.max(oldPosition, newPosition), + }; + + await ConversationTag.updateMany( + { + user, + position, + }, + update, + ); +}; + +/** + * Deletes a conversation tag. + * @param {string} user - The user ID. + * @param {string} tag - The tag to delete. + * @returns {Promise} The deleted tag. + */ +const deleteConversationTag = async (user, tag) => { + try { + const deletedTag = await ConversationTag.findOneAndDelete({ user, tag }).lean(); + if (!deletedTag) { + return null; + } + + await Conversation.updateMany({ user, tags: tag }, { $pull: { tags: tag } }); + + await ConversationTag.updateMany( + { user, position: { $gt: deletedTag.position } }, + { $inc: { position: -1 } }, + ); + + return deletedTag; + } catch (error) { + logger.error('[deleteConversationTag] Error deleting conversation tag', error); + throw new Error('Error deleting conversation tag'); + } +}; + +/** + * Updates tags for a specific conversation. + * @param {string} user - The user ID. + * @param {string} conversationId - The conversation ID. + * @param {string[]} tags - The new set of tags for the conversation. + * @returns {Promise} The updated list of tags for the conversation. + */ +const updateTagsForConversation = async (user, conversationId, tags) => { + try { + const conversation = await Conversation.findOne({ user, conversationId }).lean(); + if (!conversation) { + throw new Error('Conversation not found'); + } + + const oldTags = new Set(conversation.tags); + const newTags = new Set(tags); + + const addedTags = [...newTags].filter((tag) => !oldTags.has(tag)); + const removedTags = [...oldTags].filter((tag) => !newTags.has(tag)); + + const bulkOps = []; + + for (const tag of addedTags) { + bulkOps.push({ + updateOne: { + filter: { user, tag }, + update: { $inc: { count: 1 } }, + upsert: true, + }, + }); + } + + for (const tag of removedTags) { + bulkOps.push({ + updateOne: { + filter: { user, tag }, + update: { $inc: { count: -1 } }, + }, + }); + } + + if (bulkOps.length > 0) { + await ConversationTag.bulkWrite(bulkOps); + } + + const updatedConversation = ( + await Conversation.findOneAndUpdate( + { user, conversationId }, + { $set: { tags: [...newTags] } }, + { new: true }, + ) + ).toObject(); + + return updatedConversation.tags; + } catch (error) { + logger.error('[updateTagsForConversation] Error updating tags', error); + throw new Error('Error updating tags for conversation'); + } +}; + +/** + * Increments tag counts for existing tags only. + * @param {string} user - The user ID. + * @param {string[]} tags - Array of tag names to increment + * @returns {Promise} + */ +const bulkIncrementTagCounts = async (user, tags) => { + if (!tags || tags.length === 0) { + return; + } + + try { + const uniqueTags = [...new Set(tags.filter(Boolean))]; + if (uniqueTags.length === 0) { + return; + } + + const bulkOps = uniqueTags.map((tag) => ({ + updateOne: { + filter: { user, tag }, + update: { $inc: { count: 1 } }, + }, + })); + + const result = await ConversationTag.bulkWrite(bulkOps); + if (result && result.modifiedCount > 0) { + logger.debug( + `user: ${user} | Incremented tag counts - modified ${result.modifiedCount} tags`, + ); + } + } catch (error) { + logger.error('[bulkIncrementTagCounts] Error incrementing tag counts', error); + } +}; + +module.exports = { + getConversationTags, + createConversationTag, + updateConversationTag, + deleteConversationTag, + bulkIncrementTagCounts, + updateTagsForConversation, +}; diff --git a/api/models/File.js b/api/models/File.js new file mode 100644 index 0000000000..1a01ef12f9 --- /dev/null +++ b/api/models/File.js @@ -0,0 +1,250 @@ +const { logger } = require('@librechat/data-schemas'); +const { EToolResources, FileContext } = require('librechat-data-provider'); +const { File } = require('~/db/models'); + +/** + * Finds a file by its file_id with additional query options. + * @param {string} file_id - The unique identifier of the file. + * @param {object} options - Query options for filtering, projection, etc. + * @returns {Promise} A promise that resolves to the file document or null. + */ +const findFileById = async (file_id, options = {}) => { + return await File.findOne({ file_id, ...options }).lean(); +}; + +/** + * Retrieves files matching a given filter, sorted by the most recently updated. + * @param {Object} filter - The filter criteria to apply. + * @param {Object} [_sortOptions] - Optional sort parameters. + * @param {Object|String} [selectFields={ text: 0 }] - Fields to include/exclude in the query results. + * Default excludes the 'text' field. + * @returns {Promise>} A promise that resolves to an array of file documents. + */ +const getFiles = async (filter, _sortOptions, selectFields = { text: 0 }) => { + const sortOptions = { updatedAt: -1, ..._sortOptions }; + return await File.find(filter).select(selectFields).sort(sortOptions).lean(); +}; + +/** + * Retrieves tool files (files that are embedded or have a fileIdentifier) from an array of file IDs. + * Note: execute_code files are handled separately by getCodeGeneratedFiles. + * @param {string[]} fileIds - Array of file_id strings to search for + * @param {Set} toolResourceSet - Optional filter for tool resources + * @returns {Promise>} Files that match the criteria + */ +const getToolFilesByIds = async (fileIds, toolResourceSet) => { + if (!fileIds || !fileIds.length || !toolResourceSet?.size) { + return []; + } + + try { + const orConditions = []; + + if (toolResourceSet.has(EToolResources.context)) { + orConditions.push({ text: { $exists: true, $ne: null }, context: FileContext.agents }); + } + if (toolResourceSet.has(EToolResources.file_search)) { + orConditions.push({ embedded: true }); + } + + if (orConditions.length === 0) { + return []; + } + + const filter = { + file_id: { $in: fileIds }, + context: { $ne: FileContext.execute_code }, // Exclude code-generated files + $or: orConditions, + }; + + const selectFields = { text: 0 }; + const sortOptions = { updatedAt: -1 }; + + return await getFiles(filter, sortOptions, selectFields); + } catch (error) { + logger.error('[getToolFilesByIds] Error retrieving tool files:', error); + throw new Error('Error retrieving tool files'); + } +}; + +/** + * Retrieves files generated by code execution for a given conversation. + * These files are stored locally with fileIdentifier metadata for code env re-upload. + * @param {string} conversationId - The conversation ID to search for + * @param {string[]} [messageIds] - Optional array of messageIds to filter by (for linear thread filtering) + * @returns {Promise>} Files generated by code execution in the conversation + */ +const getCodeGeneratedFiles = async (conversationId, messageIds) => { + if (!conversationId) { + return []; + } + + /** messageIds are required for proper thread filtering of code-generated files */ + if (!messageIds || messageIds.length === 0) { + return []; + } + + try { + const filter = { + conversationId, + context: FileContext.execute_code, + messageId: { $exists: true, $in: messageIds }, + 'metadata.fileIdentifier': { $exists: true }, + }; + + const selectFields = { text: 0 }; + const sortOptions = { createdAt: 1 }; + + return await getFiles(filter, sortOptions, selectFields); + } catch (error) { + logger.error('[getCodeGeneratedFiles] Error retrieving code generated files:', error); + return []; + } +}; + +/** + * Retrieves user-uploaded execute_code files (not code-generated) by their file IDs. + * These are files with fileIdentifier metadata but context is NOT execute_code (e.g., agents or message_attachment). + * File IDs should be collected from message.files arrays in the current thread. + * @param {string[]} fileIds - Array of file IDs to fetch (from message.files in the thread) + * @returns {Promise>} User-uploaded execute_code files + */ +const getUserCodeFiles = async (fileIds) => { + if (!fileIds || fileIds.length === 0) { + return []; + } + + try { + const filter = { + file_id: { $in: fileIds }, + context: { $ne: FileContext.execute_code }, + 'metadata.fileIdentifier': { $exists: true }, + }; + + const selectFields = { text: 0 }; + const sortOptions = { createdAt: 1 }; + + return await getFiles(filter, sortOptions, selectFields); + } catch (error) { + logger.error('[getUserCodeFiles] Error retrieving user code files:', error); + return []; + } +}; + +/** + * Creates a new file with a TTL of 1 hour. + * @param {MongoFile} data - The file data to be created, must contain file_id. + * @param {boolean} disableTTL - Whether to disable the TTL. + * @returns {Promise} A promise that resolves to the created file document. + */ +const createFile = async (data, disableTTL) => { + const fileData = { + ...data, + expiresAt: new Date(Date.now() + 3600 * 1000), + }; + + if (disableTTL) { + delete fileData.expiresAt; + } + + return await File.findOneAndUpdate({ file_id: data.file_id }, fileData, { + new: true, + upsert: true, + }).lean(); +}; + +/** + * Updates a file identified by file_id with new data and removes the TTL. + * @param {MongoFile} data - The data to update, must contain file_id. + * @returns {Promise} A promise that resolves to the updated file document. + */ +const updateFile = async (data) => { + const { file_id, ...update } = data; + const updateOperation = { + $set: update, + $unset: { expiresAt: '' }, // Remove the expiresAt field to prevent TTL + }; + return await File.findOneAndUpdate({ file_id }, updateOperation, { new: true }).lean(); +}; + +/** + * Increments the usage of a file identified by file_id. + * @param {MongoFile} data - The data to update, must contain file_id and the increment value for usage. + * @returns {Promise} A promise that resolves to the updated file document. + */ +const updateFileUsage = async (data) => { + const { file_id, inc = 1 } = data; + const updateOperation = { + $inc: { usage: inc }, + $unset: { expiresAt: '', temp_file_id: '' }, + }; + return await File.findOneAndUpdate({ file_id }, updateOperation, { new: true }).lean(); +}; + +/** + * Deletes a file identified by file_id. + * @param {string} file_id - The unique identifier of the file to delete. + * @returns {Promise} A promise that resolves to the deleted file document or null. + */ +const deleteFile = async (file_id) => { + return await File.findOneAndDelete({ file_id }).lean(); +}; + +/** + * Deletes a file identified by a filter. + * @param {object} filter - The filter criteria to apply. + * @returns {Promise} A promise that resolves to the deleted file document or null. + */ +const deleteFileByFilter = async (filter) => { + return await File.findOneAndDelete(filter).lean(); +}; + +/** + * Deletes multiple files identified by an array of file_ids. + * @param {Array} file_ids - The unique identifiers of the files to delete. + * @returns {Promise} A promise that resolves to the result of the deletion operation. + */ +const deleteFiles = async (file_ids, user) => { + let deleteQuery = { file_id: { $in: file_ids } }; + if (user) { + deleteQuery = { user: user }; + } + return await File.deleteMany(deleteQuery); +}; + +/** + * Batch updates files with new signed URLs in MongoDB + * + * @param {MongoFile[]} updates - Array of updates in the format { file_id, filepath } + * @returns {Promise} + */ +async function batchUpdateFiles(updates) { + if (!updates || updates.length === 0) { + return; + } + + const bulkOperations = updates.map((update) => ({ + updateOne: { + filter: { file_id: update.file_id }, + update: { $set: { filepath: update.filepath } }, + }, + })); + + const result = await File.bulkWrite(bulkOperations); + logger.info(`Updated ${result.modifiedCount} files with new S3 URLs`); +} + +module.exports = { + findFileById, + getFiles, + getToolFilesByIds, + getCodeGeneratedFiles, + getUserCodeFiles, + createFile, + updateFile, + updateFileUsage, + deleteFile, + deleteFiles, + deleteFileByFilter, + batchUpdateFiles, +}; diff --git a/api/models/File.spec.js b/api/models/File.spec.js new file mode 100644 index 0000000000..2d4282cff7 --- /dev/null +++ b/api/models/File.spec.js @@ -0,0 +1,629 @@ +const mongoose = require('mongoose'); +const { v4: uuidv4 } = require('uuid'); +const { MongoMemoryServer } = require('mongodb-memory-server'); +const { createModels, createMethods } = require('@librechat/data-schemas'); +const { + SystemRoles, + ResourceType, + AccessRoleIds, + PrincipalType, +} = require('librechat-data-provider'); +const { grantPermission } = require('~/server/services/PermissionService'); +const { createAgent } = require('./Agent'); + +let File; +let Agent; +let AclEntry; +let User; +let modelsToCleanup = []; +let methods; +let getFiles; +let createFile; +let seedDefaultRoles; + +describe('File Access Control', () => { + let mongoServer; + + beforeAll(async () => { + mongoServer = await MongoMemoryServer.create(); + const mongoUri = mongoServer.getUri(); + await mongoose.connect(mongoUri); + + // Initialize all models + const models = createModels(mongoose); + + // Track which models we're adding + modelsToCleanup = Object.keys(models); + + // Register models on mongoose.models so methods can access them + const dbModels = require('~/db/models'); + Object.assign(mongoose.models, dbModels); + + File = dbModels.File; + Agent = dbModels.Agent; + AclEntry = dbModels.AclEntry; + User = dbModels.User; + + // Create methods from data-schemas (includes file methods) + methods = createMethods(mongoose); + getFiles = methods.getFiles; + createFile = methods.createFile; + seedDefaultRoles = methods.seedDefaultRoles; + + // Seed default roles + await seedDefaultRoles(); + }); + + afterAll(async () => { + // Clean up all collections before disconnecting + const collections = mongoose.connection.collections; + for (const key in collections) { + await collections[key].deleteMany({}); + } + + // Clear only the models we added + for (const modelName of modelsToCleanup) { + if (mongoose.models[modelName]) { + delete mongoose.models[modelName]; + } + } + + await mongoose.disconnect(); + await mongoServer.stop(); + }); + + beforeEach(async () => { + await File.deleteMany({}); + await Agent.deleteMany({}); + await AclEntry.deleteMany({}); + await User.deleteMany({}); + // Don't delete AccessRole as they are seeded defaults needed for tests + }); + + describe('hasAccessToFilesViaAgent', () => { + it('should efficiently check access for multiple files at once', async () => { + const userId = new mongoose.Types.ObjectId(); + const authorId = new mongoose.Types.ObjectId(); + const agentId = uuidv4(); + const fileIds = [uuidv4(), uuidv4(), uuidv4(), uuidv4()]; + + // Create users + await User.create({ + _id: userId, + email: 'user@example.com', + emailVerified: true, + provider: 'local', + }); + + await User.create({ + _id: authorId, + email: 'author@example.com', + emailVerified: true, + provider: 'local', + }); + + // Create files + for (const fileId of fileIds) { + await createFile({ + user: authorId, + file_id: fileId, + filename: `file-${fileId}.txt`, + filepath: `/uploads/${fileId}`, + }); + } + + // Create agent with only first two files attached + const agent = await createAgent({ + id: agentId, + name: 'Test Agent', + author: authorId, + model: 'gpt-4', + provider: 'openai', + tool_resources: { + file_search: { + file_ids: [fileIds[0], fileIds[1]], + }, + }, + }); + + // Grant EDIT permission to user on the agent + await grantPermission({ + principalType: PrincipalType.USER, + principalId: userId, + resourceType: ResourceType.AGENT, + resourceId: agent._id, + accessRoleId: AccessRoleIds.AGENT_EDITOR, + grantedBy: authorId, + }); + + // Check access for all files + const { hasAccessToFilesViaAgent } = require('~/server/services/Files/permissions'); + const accessMap = await hasAccessToFilesViaAgent({ + userId: userId, + role: SystemRoles.USER, + fileIds, + agentId: agent.id, // Use agent.id which is the custom UUID + }); + + // Should have access only to the first two files + expect(accessMap.get(fileIds[0])).toBe(true); + expect(accessMap.get(fileIds[1])).toBe(true); + expect(accessMap.get(fileIds[2])).toBe(false); + expect(accessMap.get(fileIds[3])).toBe(false); + }); + + it('should grant access to all files when user is the agent author', async () => { + const authorId = new mongoose.Types.ObjectId(); + const agentId = uuidv4(); + const fileIds = [uuidv4(), uuidv4(), uuidv4()]; + + // Create author user + await User.create({ + _id: authorId, + email: 'author@example.com', + emailVerified: true, + provider: 'local', + }); + + // Create agent + await createAgent({ + id: agentId, + name: 'Test Agent', + author: authorId, + model: 'gpt-4', + provider: 'openai', + tool_resources: { + file_search: { + file_ids: [fileIds[0]], // Only one file attached + }, + }, + }); + + // Check access as the author + const { hasAccessToFilesViaAgent } = require('~/server/services/Files/permissions'); + const accessMap = await hasAccessToFilesViaAgent({ + userId: authorId, + role: SystemRoles.USER, + fileIds, + agentId, + }); + + // Author should have access to all files + expect(accessMap.get(fileIds[0])).toBe(true); + expect(accessMap.get(fileIds[1])).toBe(true); + expect(accessMap.get(fileIds[2])).toBe(true); + }); + + it('should handle non-existent agent gracefully', async () => { + const userId = new mongoose.Types.ObjectId(); + const fileIds = [uuidv4(), uuidv4()]; + + // Create user + await User.create({ + _id: userId, + email: 'user@example.com', + emailVerified: true, + provider: 'local', + }); + + const { hasAccessToFilesViaAgent } = require('~/server/services/Files/permissions'); + const accessMap = await hasAccessToFilesViaAgent({ + userId: userId, + role: SystemRoles.USER, + fileIds, + agentId: 'non-existent-agent', + }); + + // Should have no access to any files + expect(accessMap.get(fileIds[0])).toBe(false); + expect(accessMap.get(fileIds[1])).toBe(false); + }); + + it('should deny access when user only has VIEW permission and needs access for deletion', async () => { + const userId = new mongoose.Types.ObjectId(); + const authorId = new mongoose.Types.ObjectId(); + const agentId = uuidv4(); + const fileIds = [uuidv4(), uuidv4()]; + + // Create users + await User.create({ + _id: userId, + email: 'user@example.com', + emailVerified: true, + provider: 'local', + }); + + await User.create({ + _id: authorId, + email: 'author@example.com', + emailVerified: true, + provider: 'local', + }); + + // Create agent with files + const agent = await createAgent({ + id: agentId, + name: 'View-Only Agent', + author: authorId, + model: 'gpt-4', + provider: 'openai', + tool_resources: { + file_search: { + file_ids: fileIds, + }, + }, + }); + + // Grant only VIEW permission to user on the agent + await grantPermission({ + principalType: PrincipalType.USER, + principalId: userId, + resourceType: ResourceType.AGENT, + resourceId: agent._id, + accessRoleId: AccessRoleIds.AGENT_VIEWER, + grantedBy: authorId, + }); + + // Check access for files + const { hasAccessToFilesViaAgent } = require('~/server/services/Files/permissions'); + const accessMap = await hasAccessToFilesViaAgent({ + userId: userId, + role: SystemRoles.USER, + fileIds, + agentId, + isDelete: true, + }); + + // Should have no access to any files when only VIEW permission + expect(accessMap.get(fileIds[0])).toBe(false); + expect(accessMap.get(fileIds[1])).toBe(false); + }); + + it('should grant access when user has VIEW permission', async () => { + const userId = new mongoose.Types.ObjectId(); + const authorId = new mongoose.Types.ObjectId(); + const agentId = uuidv4(); + const fileIds = [uuidv4(), uuidv4()]; + + // Create users + await User.create({ + _id: userId, + email: 'user@example.com', + emailVerified: true, + provider: 'local', + }); + + await User.create({ + _id: authorId, + email: 'author@example.com', + emailVerified: true, + provider: 'local', + }); + + // Create agent with files + const agent = await createAgent({ + id: agentId, + name: 'View-Only Agent', + author: authorId, + model: 'gpt-4', + provider: 'openai', + tool_resources: { + file_search: { + file_ids: fileIds, + }, + }, + }); + + // Grant only VIEW permission to user on the agent + await grantPermission({ + principalType: PrincipalType.USER, + principalId: userId, + resourceType: ResourceType.AGENT, + resourceId: agent._id, + accessRoleId: AccessRoleIds.AGENT_VIEWER, + grantedBy: authorId, + }); + + // Check access for files + const { hasAccessToFilesViaAgent } = require('~/server/services/Files/permissions'); + const accessMap = await hasAccessToFilesViaAgent({ + userId: userId, + role: SystemRoles.USER, + fileIds, + agentId, + }); + + expect(accessMap.get(fileIds[0])).toBe(true); + expect(accessMap.get(fileIds[1])).toBe(true); + }); + }); + + describe('getFiles with agent access control', () => { + test('should return files owned by user and files accessible through agent', async () => { + const authorId = new mongoose.Types.ObjectId(); + const userId = new mongoose.Types.ObjectId(); + const agentId = `agent_${uuidv4()}`; + const ownedFileId = `file_${uuidv4()}`; + const sharedFileId = `file_${uuidv4()}`; + const inaccessibleFileId = `file_${uuidv4()}`; + + // Create users + await User.create({ + _id: userId, + email: 'user@example.com', + emailVerified: true, + provider: 'local', + }); + + await User.create({ + _id: authorId, + email: 'author@example.com', + emailVerified: true, + provider: 'local', + }); + + // Create agent with shared file + const agent = await createAgent({ + id: agentId, + name: 'Shared Agent', + provider: 'test', + model: 'test-model', + author: authorId, + tool_resources: { + file_search: { + file_ids: [sharedFileId], + }, + }, + }); + + // Grant EDIT permission to user on the agent + await grantPermission({ + principalType: PrincipalType.USER, + principalId: userId, + resourceType: ResourceType.AGENT, + resourceId: agent._id, + accessRoleId: AccessRoleIds.AGENT_EDITOR, + grantedBy: authorId, + }); + + // Create files + await createFile({ + file_id: ownedFileId, + user: userId, + filename: 'owned.txt', + filepath: '/uploads/owned.txt', + type: 'text/plain', + bytes: 100, + }); + + await createFile({ + file_id: sharedFileId, + user: authorId, + filename: 'shared.txt', + filepath: '/uploads/shared.txt', + type: 'text/plain', + bytes: 200, + embedded: true, + }); + + await createFile({ + file_id: inaccessibleFileId, + user: authorId, + filename: 'inaccessible.txt', + filepath: '/uploads/inaccessible.txt', + type: 'text/plain', + bytes: 300, + }); + + // Get all files first + const allFiles = await getFiles( + { file_id: { $in: [ownedFileId, sharedFileId, inaccessibleFileId] } }, + null, + { text: 0 }, + ); + + // Then filter by access control + const { filterFilesByAgentAccess } = require('~/server/services/Files/permissions'); + const files = await filterFilesByAgentAccess({ + files: allFiles, + userId: userId, + role: SystemRoles.USER, + agentId, + }); + + expect(files).toHaveLength(2); + expect(files.map((f) => f.file_id)).toContain(ownedFileId); + expect(files.map((f) => f.file_id)).toContain(sharedFileId); + expect(files.map((f) => f.file_id)).not.toContain(inaccessibleFileId); + }); + + test('should return all files when no userId/agentId provided', async () => { + const userId = new mongoose.Types.ObjectId(); + const fileId1 = `file_${uuidv4()}`; + const fileId2 = `file_${uuidv4()}`; + + await createFile({ + file_id: fileId1, + user: userId, + filename: 'file1.txt', + filepath: '/uploads/file1.txt', + type: 'text/plain', + bytes: 100, + }); + + await createFile({ + file_id: fileId2, + user: new mongoose.Types.ObjectId(), + filename: 'file2.txt', + filepath: '/uploads/file2.txt', + type: 'text/plain', + bytes: 200, + }); + + const files = await getFiles({ file_id: { $in: [fileId1, fileId2] } }); + expect(files).toHaveLength(2); + }); + }); + + describe('Role-based file permissions', () => { + it('should optimize permission checks when role is provided', async () => { + const userId = new mongoose.Types.ObjectId(); + const authorId = new mongoose.Types.ObjectId(); + const agentId = uuidv4(); + const fileIds = [uuidv4(), uuidv4()]; + + // Create users + await User.create({ + _id: userId, + email: 'user@example.com', + emailVerified: true, + provider: 'local', + role: 'ADMIN', // User has ADMIN role + }); + + await User.create({ + _id: authorId, + email: 'author@example.com', + emailVerified: true, + provider: 'local', + }); + + // Create files + for (const fileId of fileIds) { + await createFile({ + file_id: fileId, + user: authorId, + filename: `${fileId}.txt`, + filepath: `/uploads/${fileId}.txt`, + type: 'text/plain', + bytes: 100, + }); + } + + // Create agent with files + const agent = await createAgent({ + id: agentId, + name: 'Test Agent', + author: authorId, + model: 'gpt-4', + provider: 'openai', + tool_resources: { + file_search: { + file_ids: fileIds, + }, + }, + }); + + // Grant permission to ADMIN role + await grantPermission({ + principalType: PrincipalType.ROLE, + principalId: 'ADMIN', + resourceType: ResourceType.AGENT, + resourceId: agent._id, + accessRoleId: AccessRoleIds.AGENT_EDITOR, + grantedBy: authorId, + }); + + // Check access with role provided (should avoid DB query) + const { hasAccessToFilesViaAgent } = require('~/server/services/Files/permissions'); + const accessMapWithRole = await hasAccessToFilesViaAgent({ + userId: userId, + role: 'ADMIN', + fileIds, + agentId: agent.id, + }); + + // User should have access through their ADMIN role + expect(accessMapWithRole.get(fileIds[0])).toBe(true); + expect(accessMapWithRole.get(fileIds[1])).toBe(true); + + // Check access without role (will query DB to get user's role) + const accessMapWithoutRole = await hasAccessToFilesViaAgent({ + userId: userId, + fileIds, + agentId: agent.id, + }); + + // Should have same result + expect(accessMapWithoutRole.get(fileIds[0])).toBe(true); + expect(accessMapWithoutRole.get(fileIds[1])).toBe(true); + }); + + it('should deny access when user role changes', async () => { + const userId = new mongoose.Types.ObjectId(); + const authorId = new mongoose.Types.ObjectId(); + const agentId = uuidv4(); + const fileId = uuidv4(); + + // Create users + await User.create({ + _id: userId, + email: 'user@example.com', + emailVerified: true, + provider: 'local', + role: 'EDITOR', + }); + + await User.create({ + _id: authorId, + email: 'author@example.com', + emailVerified: true, + provider: 'local', + }); + + // Create file + await createFile({ + file_id: fileId, + user: authorId, + filename: 'test.txt', + filepath: '/uploads/test.txt', + type: 'text/plain', + bytes: 100, + }); + + // Create agent + const agent = await createAgent({ + id: agentId, + name: 'Test Agent', + author: authorId, + model: 'gpt-4', + provider: 'openai', + tool_resources: { + file_search: { + file_ids: [fileId], + }, + }, + }); + + // Grant permission to EDITOR role only + await grantPermission({ + principalType: PrincipalType.ROLE, + principalId: 'EDITOR', + resourceType: ResourceType.AGENT, + resourceId: agent._id, + accessRoleId: AccessRoleIds.AGENT_EDITOR, + grantedBy: authorId, + }); + + const { hasAccessToFilesViaAgent } = require('~/server/services/Files/permissions'); + + // Check with EDITOR role - should have access + const accessAsEditor = await hasAccessToFilesViaAgent({ + userId: userId, + role: 'EDITOR', + fileIds: [fileId], + agentId: agent.id, + }); + expect(accessAsEditor.get(fileId)).toBe(true); + + // Simulate role change to USER - should lose access + const accessAsUser = await hasAccessToFilesViaAgent({ + userId: userId, + role: SystemRoles.USER, + fileIds: [fileId], + agentId: agent.id, + }); + expect(accessAsUser.get(fileId)).toBe(false); + }); + }); +}); diff --git a/api/models/Message.js b/api/models/Message.js new file mode 100644 index 0000000000..8fe04f6f54 --- /dev/null +++ b/api/models/Message.js @@ -0,0 +1,372 @@ +const { z } = require('zod'); +const { logger } = require('@librechat/data-schemas'); +const { createTempChatExpirationDate } = require('@librechat/api'); +const { Message } = require('~/db/models'); + +const idSchema = z.string().uuid(); + +/** + * Saves a message in the database. + * + * @async + * @function saveMessage + * @param {ServerRequest} req - The request object containing user information. + * @param {Object} params - The message data object. + * @param {string} params.endpoint - The endpoint where the message originated. + * @param {string} params.iconURL - The URL of the sender's icon. + * @param {string} params.messageId - The unique identifier for the message. + * @param {string} params.newMessageId - The new unique identifier for the message (if applicable). + * @param {string} params.conversationId - The identifier of the conversation. + * @param {string} [params.parentMessageId] - The identifier of the parent message, if any. + * @param {string} params.sender - The identifier of the sender. + * @param {string} params.text - The text content of the message. + * @param {boolean} params.isCreatedByUser - Indicates if the message was created by the user. + * @param {string} [params.error] - Any error associated with the message. + * @param {boolean} [params.unfinished] - Indicates if the message is unfinished. + * @param {Object[]} [params.files] - An array of files associated with the message. + * @param {string} [params.finish_reason] - Reason for finishing the message. + * @param {number} [params.tokenCount] - The number of tokens in the message. + * @param {string} [params.plugin] - Plugin associated with the message. + * @param {string[]} [params.plugins] - An array of plugins associated with the message. + * @param {string} [params.model] - The model used to generate the message. + * @param {Object} [metadata] - Additional metadata for this operation + * @param {string} [metadata.context] - The context of the operation + * @returns {Promise} The updated or newly inserted message document. + * @throws {Error} If there is an error in saving the message. + */ +async function saveMessage(req, params, metadata) { + if (!req?.user?.id) { + throw new Error('User not authenticated'); + } + + const validConvoId = idSchema.safeParse(params.conversationId); + if (!validConvoId.success) { + logger.warn(`Invalid conversation ID: ${params.conversationId}`); + logger.info(`---\`saveMessage\` context: ${metadata?.context}`); + logger.info(`---Invalid conversation ID Params: ${JSON.stringify(params, null, 2)}`); + return; + } + + try { + const update = { + ...params, + user: req.user.id, + messageId: params.newMessageId || params.messageId, + }; + + if (req?.body?.isTemporary) { + try { + const appConfig = req.config; + update.expiredAt = createTempChatExpirationDate(appConfig?.interfaceConfig); + } catch (err) { + logger.error('Error creating temporary chat expiration date:', err); + logger.info(`---\`saveMessage\` context: ${metadata?.context}`); + update.expiredAt = null; + } + } else { + update.expiredAt = null; + } + + if (update.tokenCount != null && isNaN(update.tokenCount)) { + logger.warn( + `Resetting invalid \`tokenCount\` for message \`${params.messageId}\`: ${update.tokenCount}`, + ); + logger.info(`---\`saveMessage\` context: ${metadata?.context}`); + update.tokenCount = 0; + } + const message = await Message.findOneAndUpdate( + { messageId: params.messageId, user: req.user.id }, + update, + { upsert: true, new: true }, + ); + + return message.toObject(); + } catch (err) { + logger.error('Error saving message:', err); + logger.info(`---\`saveMessage\` context: ${metadata?.context}`); + + // Check if this is a duplicate key error (MongoDB error code 11000) + if (err.code === 11000 && err.message.includes('duplicate key error')) { + // Log the duplicate key error but don't crash the application + logger.warn(`Duplicate messageId detected: ${params.messageId}. Continuing execution.`); + + try { + // Try to find the existing message with this ID + const existingMessage = await Message.findOne({ + messageId: params.messageId, + user: req.user.id, + }); + + // If we found it, return it + if (existingMessage) { + return existingMessage.toObject(); + } + + // If we can't find it (unlikely but possible in race conditions) + return { + ...params, + messageId: params.messageId, + user: req.user.id, + }; + } catch (findError) { + // If the findOne also fails, log it but don't crash + logger.warn( + `Could not retrieve existing message with ID ${params.messageId}: ${findError.message}`, + ); + return { + ...params, + messageId: params.messageId, + user: req.user.id, + }; + } + } + + throw err; // Re-throw other errors + } +} + +/** + * Saves multiple messages in the database in bulk. + * + * @async + * @function bulkSaveMessages + * @param {Object[]} messages - An array of message objects to save. + * @param {boolean} [overrideTimestamp=false] - Indicates whether to override the timestamps of the messages. Defaults to false. + * @returns {Promise} The result of the bulk write operation. + * @throws {Error} If there is an error in saving messages in bulk. + */ +async function bulkSaveMessages(messages, overrideTimestamp = false) { + try { + const bulkOps = messages.map((message) => ({ + updateOne: { + filter: { messageId: message.messageId }, + update: message, + timestamps: !overrideTimestamp, + upsert: true, + }, + })); + const result = await Message.bulkWrite(bulkOps); + return result; + } catch (err) { + logger.error('Error saving messages in bulk:', err); + throw err; + } +} + +/** + * Records a message in the database. + * + * @async + * @function recordMessage + * @param {Object} params - The message data object. + * @param {string} params.user - The identifier of the user. + * @param {string} params.endpoint - The endpoint where the message originated. + * @param {string} params.messageId - The unique identifier for the message. + * @param {string} params.conversationId - The identifier of the conversation. + * @param {string} [params.parentMessageId] - The identifier of the parent message, if any. + * @param {Partial} rest - Any additional properties from the TMessage typedef not explicitly listed. + * @returns {Promise} The updated or newly inserted message document. + * @throws {Error} If there is an error in saving the message. + */ +async function recordMessage({ + user, + endpoint, + messageId, + conversationId, + parentMessageId, + ...rest +}) { + try { + // No parsing of convoId as may use threadId + const message = { + user, + endpoint, + messageId, + conversationId, + parentMessageId, + ...rest, + }; + + return await Message.findOneAndUpdate({ user, messageId }, message, { + upsert: true, + new: true, + }); + } catch (err) { + logger.error('Error recording message:', err); + throw err; + } +} + +/** + * Updates the text of a message. + * + * @async + * @function updateMessageText + * @param {Object} params - The update data object. + * @param {Object} req - The request object. + * @param {string} params.messageId - The unique identifier for the message. + * @param {string} params.text - The new text content of the message. + * @returns {Promise} + * @throws {Error} If there is an error in updating the message text. + */ +async function updateMessageText(req, { messageId, text }) { + try { + await Message.updateOne({ messageId, user: req.user.id }, { text }); + } catch (err) { + logger.error('Error updating message text:', err); + throw err; + } +} + +/** + * Updates a message. + * + * @async + * @function updateMessage + * @param {Object} req - The request object. + * @param {Object} message - The message object containing update data. + * @param {string} message.messageId - The unique identifier for the message. + * @param {string} [message.text] - The new text content of the message. + * @param {Object[]} [message.files] - The files associated with the message. + * @param {boolean} [message.isCreatedByUser] - Indicates if the message was created by the user. + * @param {string} [message.sender] - The identifier of the sender. + * @param {number} [message.tokenCount] - The number of tokens in the message. + * @param {Object} [metadata] - The operation metadata + * @param {string} [metadata.context] - The operation metadata + * @returns {Promise} The updated message document. + * @throws {Error} If there is an error in updating the message or if the message is not found. + */ +async function updateMessage(req, message, metadata) { + try { + const { messageId, ...update } = message; + const updatedMessage = await Message.findOneAndUpdate( + { messageId, user: req.user.id }, + update, + { + new: true, + }, + ); + + if (!updatedMessage) { + throw new Error('Message not found or user not authorized.'); + } + + return { + messageId: updatedMessage.messageId, + conversationId: updatedMessage.conversationId, + parentMessageId: updatedMessage.parentMessageId, + sender: updatedMessage.sender, + text: updatedMessage.text, + isCreatedByUser: updatedMessage.isCreatedByUser, + tokenCount: updatedMessage.tokenCount, + feedback: updatedMessage.feedback, + }; + } catch (err) { + logger.error('Error updating message:', err); + if (metadata && metadata?.context) { + logger.info(`---\`updateMessage\` context: ${metadata.context}`); + } + throw err; + } +} + +/** + * Deletes messages in a conversation since a specific message. + * + * @async + * @function deleteMessagesSince + * @param {Object} params - The parameters object. + * @param {Object} req - The request object. + * @param {string} params.messageId - The unique identifier for the message. + * @param {string} params.conversationId - The identifier of the conversation. + * @returns {Promise} The number of deleted messages. + * @throws {Error} If there is an error in deleting messages. + */ +async function deleteMessagesSince(req, { messageId, conversationId }) { + try { + const message = await Message.findOne({ messageId, user: req.user.id }).lean(); + + if (message) { + const query = Message.find({ conversationId, user: req.user.id }); + return await query.deleteMany({ + createdAt: { $gt: message.createdAt }, + }); + } + return undefined; + } catch (err) { + logger.error('Error deleting messages:', err); + throw err; + } +} + +/** + * Retrieves messages from the database. + * @async + * @function getMessages + * @param {Record} filter - The filter criteria. + * @param {string | undefined} [select] - The fields to select. + * @returns {Promise} The messages that match the filter criteria. + * @throws {Error} If there is an error in retrieving messages. + */ +async function getMessages(filter, select) { + try { + if (select) { + return await Message.find(filter).select(select).sort({ createdAt: 1 }).lean(); + } + + return await Message.find(filter).sort({ createdAt: 1 }).lean(); + } catch (err) { + logger.error('Error getting messages:', err); + throw err; + } +} + +/** + * Retrieves a single message from the database. + * @async + * @function getMessage + * @param {{ user: string, messageId: string }} params - The search parameters + * @returns {Promise} The message that matches the criteria or null if not found + * @throws {Error} If there is an error in retrieving the message + */ +async function getMessage({ user, messageId }) { + try { + return await Message.findOne({ + user, + messageId, + }).lean(); + } catch (err) { + logger.error('Error getting message:', err); + throw err; + } +} + +/** + * Deletes messages from the database. + * + * @async + * @function deleteMessages + * @param {import('mongoose').FilterQuery} filter - The filter criteria to find messages to delete. + * @returns {Promise} The metadata with count of deleted messages. + * @throws {Error} If there is an error in deleting messages. + */ +async function deleteMessages(filter) { + try { + return await Message.deleteMany(filter); + } catch (err) { + logger.error('Error deleting messages:', err); + throw err; + } +} + +module.exports = { + saveMessage, + bulkSaveMessages, + recordMessage, + updateMessageText, + updateMessage, + deleteMessagesSince, + getMessages, + getMessage, + deleteMessages, +}; diff --git a/packages/data-schemas/src/methods/message.spec.ts b/api/models/Message.spec.js similarity index 61% rename from packages/data-schemas/src/methods/message.spec.ts rename to api/models/Message.spec.js index dfa34c0eec..39b5b4337c 100644 --- a/packages/data-schemas/src/methods/message.spec.ts +++ b/api/models/Message.spec.js @@ -1,76 +1,52 @@ -import mongoose from 'mongoose'; -import { v4 as uuidv4 } from 'uuid'; -import { MongoMemoryServer } from 'mongodb-memory-server'; -import type { IMessage } from '..'; -import { createMessageMethods } from './message'; -import { tenantStorage, runAsSystem } from '~/config/tenantContext'; -import { createModels } from '../models'; +const mongoose = require('mongoose'); +const { v4: uuidv4 } = require('uuid'); +const { messageSchema } = require('@librechat/data-schemas'); +const { MongoMemoryServer } = require('mongodb-memory-server'); -jest.mock('~/config/winston', () => ({ - error: jest.fn(), - warn: jest.fn(), - info: jest.fn(), - debug: jest.fn(), -})); +const { + saveMessage, + getMessages, + updateMessage, + deleteMessages, + bulkSaveMessages, + updateMessageText, + deleteMessagesSince, +} = require('./Message'); -let mongoServer: InstanceType; -let Message: mongoose.Model; -let saveMessage: ReturnType['saveMessage']; -let getMessages: ReturnType['getMessages']; -let updateMessage: ReturnType['updateMessage']; -let deleteMessages: ReturnType['deleteMessages']; -let bulkSaveMessages: ReturnType['bulkSaveMessages']; -let updateMessageText: ReturnType['updateMessageText']; -let deleteMessagesSince: ReturnType['deleteMessagesSince']; -let recordMessage: ReturnType['recordMessage']; +jest.mock('~/server/services/Config/app'); -beforeAll(async () => { - mongoServer = await MongoMemoryServer.create(); - const mongoUri = mongoServer.getUri(); - - const models = createModels(mongoose); - Object.assign(mongoose.models, models); - Message = mongoose.models.Message; - - const methods = createMessageMethods(mongoose); - saveMessage = methods.saveMessage; - getMessages = methods.getMessages; - updateMessage = methods.updateMessage; - deleteMessages = methods.deleteMessages; - bulkSaveMessages = methods.bulkSaveMessages; - updateMessageText = methods.updateMessageText; - deleteMessagesSince = methods.deleteMessagesSince; - recordMessage = methods.recordMessage; - - await mongoose.connect(mongoUri); -}); - -afterAll(async () => { - await mongoose.disconnect(); - await mongoServer.stop(); -}); +/** + * @type {import('mongoose').Model} + */ +let Message; describe('Message Operations', () => { - let mockCtx: { - userId: string; - isTemporary?: boolean; - interfaceConfig?: { temporaryChatRetention?: number }; - }; - let mockMessageData: Partial = { - messageId: 'msg123', - conversationId: uuidv4(), - text: 'Hello, world!', - user: 'user123', - }; + let mongoServer; + let mockReq; + let mockMessageData; + + beforeAll(async () => { + mongoServer = await MongoMemoryServer.create(); + const mongoUri = mongoServer.getUri(); + Message = mongoose.models.Message || mongoose.model('Message', messageSchema); + await mongoose.connect(mongoUri); + }); + + afterAll(async () => { + await mongoose.disconnect(); + await mongoServer.stop(); + }); beforeEach(async () => { // Clear database await Message.deleteMany({}); - mockCtx = { - userId: 'user123', - interfaceConfig: { - temporaryChatRetention: 24, // Default 24 hours + mockReq = { + user: { id: 'user123' }, + config: { + interfaceConfig: { + temporaryChatRetention: 24, // Default 24 hours + }, }, }; @@ -84,26 +60,26 @@ describe('Message Operations', () => { describe('saveMessage', () => { it('should save a message for an authenticated user', async () => { - const result = await saveMessage(mockCtx, mockMessageData); + const result = await saveMessage(mockReq, mockMessageData); - expect(result?.messageId).toBe('msg123'); - expect(result?.user).toBe('user123'); - expect(result?.text).toBe('Hello, world!'); + expect(result.messageId).toBe('msg123'); + expect(result.user).toBe('user123'); + expect(result.text).toBe('Hello, world!'); // Verify the message was actually saved to the database const savedMessage = await Message.findOne({ messageId: 'msg123', user: 'user123' }); expect(savedMessage).toBeTruthy(); - expect(savedMessage?.text).toBe('Hello, world!'); + expect(savedMessage.text).toBe('Hello, world!'); }); it('should throw an error for unauthenticated user', async () => { - mockCtx.userId = null as unknown as string; - await expect(saveMessage(mockCtx, mockMessageData)).rejects.toThrow('User not authenticated'); + mockReq.user = null; + await expect(saveMessage(mockReq, mockMessageData)).rejects.toThrow('User not authenticated'); }); it('should handle invalid conversation ID gracefully', async () => { mockMessageData.conversationId = 'invalid-id'; - const result = await saveMessage(mockCtx, mockMessageData); + const result = await saveMessage(mockReq, mockMessageData); expect(result).toBeUndefined(); }); }); @@ -111,38 +87,35 @@ describe('Message Operations', () => { describe('updateMessageText', () => { it('should update message text for the authenticated user', async () => { // First save a message - await saveMessage(mockCtx, mockMessageData); + await saveMessage(mockReq, mockMessageData); // Then update it - await updateMessageText(mockCtx.userId, { messageId: 'msg123', text: 'Updated text' }); + await updateMessageText(mockReq, { messageId: 'msg123', text: 'Updated text' }); // Verify the update const updatedMessage = await Message.findOne({ messageId: 'msg123', user: 'user123' }); - expect(updatedMessage?.text).toBe('Updated text'); + expect(updatedMessage.text).toBe('Updated text'); }); }); describe('updateMessage', () => { it('should update a message for the authenticated user', async () => { // First save a message - await saveMessage(mockCtx, mockMessageData); + await saveMessage(mockReq, mockMessageData); - const result = await updateMessage(mockCtx.userId, { - messageId: 'msg123', - text: 'Updated text', - }); + const result = await updateMessage(mockReq, { messageId: 'msg123', text: 'Updated text' }); - expect(result?.messageId).toBe('msg123'); - expect(result?.text).toBe('Updated text'); + expect(result.messageId).toBe('msg123'); + expect(result.text).toBe('Updated text'); // Verify in database const updatedMessage = await Message.findOne({ messageId: 'msg123', user: 'user123' }); - expect(updatedMessage?.text).toBe('Updated text'); + expect(updatedMessage.text).toBe('Updated text'); }); it('should throw an error if message is not found', async () => { await expect( - updateMessage(mockCtx.userId, { messageId: 'nonexistent', text: 'Test' }), + updateMessage(mockReq, { messageId: 'nonexistent', text: 'Test' }), ).rejects.toThrow('Message not found or user not authorized.'); }); }); @@ -152,21 +125,21 @@ describe('Message Operations', () => { const conversationId = uuidv4(); // Create multiple messages in the same conversation - await saveMessage(mockCtx, { + await saveMessage(mockReq, { messageId: 'msg1', conversationId, text: 'First message', user: 'user123', }); - await saveMessage(mockCtx, { + await saveMessage(mockReq, { messageId: 'msg2', conversationId, text: 'Second message', user: 'user123', }); - await saveMessage(mockCtx, { + await saveMessage(mockReq, { messageId: 'msg3', conversationId, text: 'Third message', @@ -174,7 +147,7 @@ describe('Message Operations', () => { }); // Delete messages since message2 (this should only delete messages created AFTER msg2) - await deleteMessagesSince(mockCtx.userId, { + await deleteMessagesSince(mockReq, { messageId: 'msg2', conversationId, }); @@ -188,7 +161,7 @@ describe('Message Operations', () => { }); it('should return undefined if no message is found', async () => { - const result = await deleteMessagesSince(mockCtx.userId, { + const result = await deleteMessagesSince(mockReq, { messageId: 'nonexistent', conversationId: 'convo123', }); @@ -201,14 +174,14 @@ describe('Message Operations', () => { const conversationId = uuidv4(); // Save some messages - await saveMessage(mockCtx, { + await saveMessage(mockReq, { messageId: 'msg1', conversationId, text: 'First message', user: 'user123', }); - await saveMessage(mockCtx, { + await saveMessage(mockReq, { messageId: 'msg2', conversationId, text: 'Second message', @@ -225,9 +198,9 @@ describe('Message Operations', () => { describe('deleteMessages', () => { it('should delete messages with the correct filter', async () => { // Save some messages for different users - await saveMessage(mockCtx, mockMessageData); + await saveMessage(mockReq, mockMessageData); await saveMessage( - { userId: 'user456' }, + { user: { id: 'user456' } }, { messageId: 'msg456', conversationId: uuidv4(), @@ -249,23 +222,22 @@ describe('Message Operations', () => { describe('Conversation Hijacking Prevention', () => { it("should not allow editing a message in another user's conversation", async () => { + const attackerReq = { user: { id: 'attacker123' } }; const victimConversationId = uuidv4(); const victimMessageId = 'victim-msg-123'; // First, save a message as the victim (but we'll try to edit as attacker) - await saveMessage( - { userId: 'victim123' }, - { - messageId: victimMessageId, - conversationId: victimConversationId, - text: 'Victim message', - user: 'victim123', - }, - ); + const victimReq = { user: { id: 'victim123' } }; + await saveMessage(victimReq, { + messageId: victimMessageId, + conversationId: victimConversationId, + text: 'Victim message', + user: 'victim123', + }); // Attacker tries to edit the victim's message await expect( - updateMessage('attacker123', { + updateMessage(attackerReq, { messageId: victimMessageId, conversationId: victimConversationId, text: 'Hacked message', @@ -277,26 +249,25 @@ describe('Message Operations', () => { messageId: victimMessageId, user: 'victim123', }); - expect(originalMessage?.text).toBe('Victim message'); + expect(originalMessage.text).toBe('Victim message'); }); it("should not allow deleting messages from another user's conversation", async () => { + const attackerReq = { user: { id: 'attacker123' } }; const victimConversationId = uuidv4(); const victimMessageId = 'victim-msg-123'; // Save a message as the victim - await saveMessage( - { userId: 'victim123' }, - { - messageId: victimMessageId, - conversationId: victimConversationId, - text: 'Victim message', - user: 'victim123', - }, - ); + const victimReq = { user: { id: 'victim123' } }; + await saveMessage(victimReq, { + messageId: victimMessageId, + conversationId: victimConversationId, + text: 'Victim message', + user: 'victim123', + }); // Attacker tries to delete from victim's conversation - const result = await deleteMessagesSince('attacker123', { + const result = await deleteMessagesSince(attackerReq, { messageId: victimMessageId, conversationId: victimConversationId, }); @@ -309,45 +280,41 @@ describe('Message Operations', () => { user: 'victim123', }); expect(victimMessage).toBeTruthy(); - expect(victimMessage?.text).toBe('Victim message'); + expect(victimMessage.text).toBe('Victim message'); }); it("should not allow inserting a new message into another user's conversation", async () => { + const attackerReq = { user: { id: 'attacker123' } }; const victimConversationId = uuidv4(); // Attacker tries to save a message - this should succeed but with attacker's user ID - const result = await saveMessage( - { userId: 'attacker123' }, - { - conversationId: victimConversationId, - text: 'Inserted malicious message', - messageId: 'new-msg-123', - user: 'attacker123', - }, - ); + const result = await saveMessage(attackerReq, { + conversationId: victimConversationId, + text: 'Inserted malicious message', + messageId: 'new-msg-123', + user: 'attacker123', + }); expect(result).toBeTruthy(); - expect(result?.user).toBe('attacker123'); + expect(result.user).toBe('attacker123'); // Verify the message was saved with the attacker's user ID, not as an anonymous message const savedMessage = await Message.findOne({ messageId: 'new-msg-123' }); - expect(savedMessage?.user).toBe('attacker123'); - expect(savedMessage?.conversationId).toBe(victimConversationId); + expect(savedMessage.user).toBe('attacker123'); + expect(savedMessage.conversationId).toBe(victimConversationId); }); it('should allow retrieving messages from any conversation', async () => { const victimConversationId = uuidv4(); // Save a message in the victim's conversation - await saveMessage( - { userId: 'victim123' }, - { - messageId: 'victim-msg', - conversationId: victimConversationId, - text: 'Victim message', - user: 'victim123', - }, - ); + const victimReq = { user: { id: 'victim123' } }; + await saveMessage(victimReq, { + messageId: 'victim-msg', + conversationId: victimConversationId, + text: 'Victim message', + user: 'victim123', + }); // Anyone should be able to retrieve messages by conversation ID const messages = await getMessages({ conversationId: victimConversationId }); @@ -364,21 +331,21 @@ describe('Message Operations', () => { it('should save a message with expiredAt when isTemporary is true', async () => { // Mock app config with 24 hour retention - mockCtx.interfaceConfig = { temporaryChatRetention: 24 }; + mockReq.config.interfaceConfig.temporaryChatRetention = 24; - mockCtx.isTemporary = true; + mockReq.body = { isTemporary: true }; const beforeSave = new Date(); - const result = await saveMessage(mockCtx, mockMessageData); + const result = await saveMessage(mockReq, mockMessageData); const afterSave = new Date(); - expect(result?.messageId).toBe('msg123'); - expect(result?.expiredAt).toBeDefined(); - expect(result?.expiredAt).toBeInstanceOf(Date); + expect(result.messageId).toBe('msg123'); + expect(result.expiredAt).toBeDefined(); + expect(result.expiredAt).toBeInstanceOf(Date); // Verify expiredAt is approximately 24 hours in the future const expectedExpirationTime = new Date(beforeSave.getTime() + 24 * 60 * 60 * 1000); - const actualExpirationTime = new Date(result?.expiredAt ?? 0); + const actualExpirationTime = new Date(result.expiredAt); expect(actualExpirationTime.getTime()).toBeGreaterThanOrEqual( expectedExpirationTime.getTime() - 1000, @@ -389,37 +356,38 @@ describe('Message Operations', () => { }); it('should save a message without expiredAt when isTemporary is false', async () => { - mockCtx.isTemporary = false; + mockReq.body = { isTemporary: false }; - const result = await saveMessage(mockCtx, mockMessageData); + const result = await saveMessage(mockReq, mockMessageData); - expect(result?.messageId).toBe('msg123'); - expect(result?.expiredAt).toBeNull(); + expect(result.messageId).toBe('msg123'); + expect(result.expiredAt).toBeNull(); }); it('should save a message without expiredAt when isTemporary is not provided', async () => { - // No isTemporary set + // No isTemporary in body + mockReq.body = {}; - const result = await saveMessage(mockCtx, mockMessageData); + const result = await saveMessage(mockReq, mockMessageData); - expect(result?.messageId).toBe('msg123'); - expect(result?.expiredAt).toBeNull(); + expect(result.messageId).toBe('msg123'); + expect(result.expiredAt).toBeNull(); }); it('should use custom retention period from config', async () => { // Mock app config with 48 hour retention - mockCtx.interfaceConfig = { temporaryChatRetention: 48 }; + mockReq.config.interfaceConfig.temporaryChatRetention = 48; - mockCtx.isTemporary = true; + mockReq.body = { isTemporary: true }; const beforeSave = new Date(); - const result = await saveMessage(mockCtx, mockMessageData); + const result = await saveMessage(mockReq, mockMessageData); - expect(result?.expiredAt).toBeDefined(); + expect(result.expiredAt).toBeDefined(); // Verify expiredAt is approximately 48 hours in the future const expectedExpirationTime = new Date(beforeSave.getTime() + 48 * 60 * 60 * 1000); - const actualExpirationTime = new Date(result?.expiredAt ?? 0); + const actualExpirationTime = new Date(result.expiredAt); expect(actualExpirationTime.getTime()).toBeGreaterThanOrEqual( expectedExpirationTime.getTime() - 1000, @@ -431,18 +399,18 @@ describe('Message Operations', () => { it('should handle minimum retention period (1 hour)', async () => { // Mock app config with less than minimum retention - mockCtx.interfaceConfig = { temporaryChatRetention: 0.5 }; // Half hour - should be clamped to 1 hour + mockReq.config.interfaceConfig.temporaryChatRetention = 0.5; // Half hour - should be clamped to 1 hour - mockCtx.isTemporary = true; + mockReq.body = { isTemporary: true }; const beforeSave = new Date(); - const result = await saveMessage(mockCtx, mockMessageData); + const result = await saveMessage(mockReq, mockMessageData); - expect(result?.expiredAt).toBeDefined(); + expect(result.expiredAt).toBeDefined(); // Verify expiredAt is approximately 1 hour in the future (minimum) const expectedExpirationTime = new Date(beforeSave.getTime() + 1 * 60 * 60 * 1000); - const actualExpirationTime = new Date(result?.expiredAt ?? 0); + const actualExpirationTime = new Date(result.expiredAt); expect(actualExpirationTime.getTime()).toBeGreaterThanOrEqual( expectedExpirationTime.getTime() - 1000, @@ -454,18 +422,18 @@ describe('Message Operations', () => { it('should handle maximum retention period (8760 hours)', async () => { // Mock app config with more than maximum retention - mockCtx.interfaceConfig = { temporaryChatRetention: 10000 }; // Should be clamped to 8760 hours + mockReq.config.interfaceConfig.temporaryChatRetention = 10000; // Should be clamped to 8760 hours - mockCtx.isTemporary = true; + mockReq.body = { isTemporary: true }; const beforeSave = new Date(); - const result = await saveMessage(mockCtx, mockMessageData); + const result = await saveMessage(mockReq, mockMessageData); - expect(result?.expiredAt).toBeDefined(); + expect(result.expiredAt).toBeDefined(); // Verify expiredAt is approximately 8760 hours (1 year) in the future const expectedExpirationTime = new Date(beforeSave.getTime() + 8760 * 60 * 60 * 1000); - const actualExpirationTime = new Date(result?.expiredAt ?? 0); + const actualExpirationTime = new Date(result.expiredAt); expect(actualExpirationTime.getTime()).toBeGreaterThanOrEqual( expectedExpirationTime.getTime() - 1000, @@ -477,22 +445,22 @@ describe('Message Operations', () => { it('should handle missing config gracefully', async () => { // Simulate missing config - should use default retention period - delete mockCtx.interfaceConfig; + delete mockReq.config; - mockCtx.isTemporary = true; + mockReq.body = { isTemporary: true }; const beforeSave = new Date(); - const result = await saveMessage(mockCtx, mockMessageData); + const result = await saveMessage(mockReq, mockMessageData); const afterSave = new Date(); // Should still save the message with default retention period (30 days) - expect(result?.messageId).toBe('msg123'); - expect(result?.expiredAt).toBeDefined(); - expect(result?.expiredAt).toBeInstanceOf(Date); + expect(result.messageId).toBe('msg123'); + expect(result.expiredAt).toBeDefined(); + expect(result.expiredAt).toBeInstanceOf(Date); // Verify expiredAt is approximately 30 days in the future (720 hours) const expectedExpirationTime = new Date(beforeSave.getTime() + 720 * 60 * 60 * 1000); - const actualExpirationTime = new Date(result?.expiredAt ?? 0); + const actualExpirationTime = new Date(result.expiredAt); expect(actualExpirationTime.getTime()).toBeGreaterThanOrEqual( expectedExpirationTime.getTime() - 1000, @@ -504,18 +472,18 @@ describe('Message Operations', () => { it('should use default retention when config is not provided', async () => { // Mock getAppConfig to return empty config - mockCtx.interfaceConfig = undefined; // Empty config + mockReq.config = {}; // Empty config - mockCtx.isTemporary = true; + mockReq.body = { isTemporary: true }; const beforeSave = new Date(); - const result = await saveMessage(mockCtx, mockMessageData); + const result = await saveMessage(mockReq, mockMessageData); - expect(result?.expiredAt).toBeDefined(); + expect(result.expiredAt).toBeDefined(); // Default retention is 30 days (720 hours) const expectedExpirationTime = new Date(beforeSave.getTime() + 30 * 24 * 60 * 60 * 1000); - const actualExpirationTime = new Date(result?.expiredAt ?? 0); + const actualExpirationTime = new Date(result.expiredAt); expect(actualExpirationTime.getTime()).toBeGreaterThanOrEqual( expectedExpirationTime.getTime() - 1000, @@ -527,47 +495,47 @@ describe('Message Operations', () => { it('should not update expiredAt on message update', async () => { // First save a temporary message - mockCtx.interfaceConfig = { temporaryChatRetention: 24 }; + mockReq.config.interfaceConfig.temporaryChatRetention = 24; - mockCtx.isTemporary = true; - const savedMessage = await saveMessage(mockCtx, mockMessageData); - const originalExpiredAt = savedMessage?.expiredAt; + mockReq.body = { isTemporary: true }; + const savedMessage = await saveMessage(mockReq, mockMessageData); + const originalExpiredAt = savedMessage.expiredAt; // Now update the message without isTemporary flag - mockCtx.isTemporary = undefined; - const updatedMessage = await updateMessage(mockCtx.userId, { + mockReq.body = {}; + const updatedMessage = await updateMessage(mockReq, { messageId: 'msg123', text: 'Updated text', }); // expiredAt should not be in the returned updated message object - expect(updatedMessage?.expiredAt).toBeUndefined(); + expect(updatedMessage.expiredAt).toBeUndefined(); // Verify in database that expiredAt wasn't changed const dbMessage = await Message.findOne({ messageId: 'msg123', user: 'user123' }); - expect(dbMessage?.expiredAt).toEqual(originalExpiredAt); + expect(dbMessage.expiredAt).toEqual(originalExpiredAt); }); it('should preserve expiredAt when saving existing temporary message', async () => { // First save a temporary message - mockCtx.interfaceConfig = { temporaryChatRetention: 24 }; + mockReq.config.interfaceConfig.temporaryChatRetention = 24; - mockCtx.isTemporary = true; - const firstSave = await saveMessage(mockCtx, mockMessageData); - const originalExpiredAt = firstSave?.expiredAt; + mockReq.body = { isTemporary: true }; + const firstSave = await saveMessage(mockReq, mockMessageData); + const originalExpiredAt = firstSave.expiredAt; // Wait a bit to ensure time difference await new Promise((resolve) => setTimeout(resolve, 100)); // Save again with same messageId but different text const updatedData = { ...mockMessageData, text: 'Updated text' }; - const secondSave = await saveMessage(mockCtx, updatedData); + const secondSave = await saveMessage(mockReq, updatedData); // Should update text but create new expiredAt - expect(secondSave?.text).toBe('Updated text'); - expect(secondSave?.expiredAt).toBeDefined(); - expect(new Date(secondSave?.expiredAt ?? 0).getTime()).toBeGreaterThan( - new Date(originalExpiredAt ?? 0).getTime(), + expect(secondSave.text).toBe('Updated text'); + expect(secondSave.expiredAt).toBeDefined(); + expect(new Date(secondSave.expiredAt).getTime()).toBeGreaterThan( + new Date(originalExpiredAt).getTime(), ); }); @@ -601,8 +569,8 @@ describe('Message Operations', () => { const bulk1 = savedMessages.find((m) => m.messageId === 'bulk1'); const bulk2 = savedMessages.find((m) => m.messageId === 'bulk2'); - expect(bulk1?.expiredAt).toBeDefined(); - expect(bulk2?.expiredAt).toBeNull(); + expect(bulk1.expiredAt).toBeDefined(); + expect(bulk2.expiredAt).toBeNull(); }); }); @@ -611,11 +579,7 @@ describe('Message Operations', () => { * Helper to create messages with specific timestamps * Uses collection.insertOne to bypass Mongoose timestamps */ - const createMessageWithTimestamp = async ( - index: number, - conversationId: string, - createdAt: Date, - ) => { + const createMessageWithTimestamp = async (index, conversationId, createdAt) => { const messageId = uuidv4(); await Message.collection.insertOne({ messageId, @@ -637,22 +601,15 @@ describe('Message Operations', () => { conversationId, user, pageSize = 25, - cursor = null as string | null, + cursor = null, sortBy = 'createdAt', sortDirection = 'desc', - }: { - conversationId: string; - user: string; - pageSize?: number; - cursor?: string | null; - sortBy?: string; - sortDirection?: string; }) => { const sortOrder = sortDirection === 'asc' ? 1 : -1; const sortField = ['createdAt', 'updatedAt'].includes(sortBy) ? sortBy : 'createdAt'; const cursorOperator = sortDirection === 'asc' ? '$gt' : '$lt'; - const filter: Record = { conversationId, user }; + const filter = { conversationId, user }; if (cursor) { filter[sortField] = { [cursorOperator]: new Date(cursor) }; } @@ -662,13 +619,11 @@ describe('Message Operations', () => { .limit(pageSize + 1) .lean(); - let nextCursor: string | null = null; + let nextCursor = null; if (messages.length > pageSize) { messages.pop(); // Remove extra item used to detect next page // Create cursor from the last RETURNED item (not the popped one) - nextCursor = (messages[messages.length - 1] as Record)[ - sortField - ] as string; + nextCursor = messages[messages.length - 1][sortField]; } return { messages, nextCursor }; @@ -722,7 +677,7 @@ describe('Message Operations', () => { const baseTime = new Date('2026-01-01T12:00:00.000Z'); // Create exactly 26 messages - const messages: (IMessage | null)[] = []; + const messages = []; for (let i = 0; i < 26; i++) { const createdAt = new Date(baseTime.getTime() - i * 60000); const msg = await createMessageWithTimestamp(i, conversationId, createdAt); @@ -744,7 +699,7 @@ describe('Message Operations', () => { // Item 26 should NOT be in page 1 const page1Ids = page1.messages.map((m) => m.messageId); - expect(page1Ids).not.toContain(item26!.messageId); + expect(page1Ids).not.toContain(item26.messageId); // Fetch second page const page2 = await getMessagesByCursor({ @@ -756,7 +711,7 @@ describe('Message Operations', () => { // Item 26 MUST be in page 2 (this was the bug - it was being skipped) expect(page2.messages).toHaveLength(1); - expect((page2.messages[0] as { messageId: string }).messageId).toBe(item26!.messageId); + expect(page2.messages[0].messageId).toBe(item26.messageId); }); it('should sort by createdAt DESC by default', async () => { @@ -785,10 +740,10 @@ describe('Message Operations', () => { }); // Should be sorted by createdAt DESC (newest first) by default - expect(result?.messages).toHaveLength(3); - expect((result?.messages[0] as { messageId: string }).messageId).toBe(msg3!.messageId); - expect((result?.messages[1] as { messageId: string }).messageId).toBe(msg2!.messageId); - expect((result?.messages[2] as { messageId: string }).messageId).toBe(msg1!.messageId); + expect(result.messages).toHaveLength(3); + expect(result.messages[0].messageId).toBe(msg3.messageId); + expect(result.messages[1].messageId).toBe(msg2.messageId); + expect(result.messages[2].messageId).toBe(msg1.messageId); }); it('should support ascending sort direction', async () => { @@ -812,9 +767,9 @@ describe('Message Operations', () => { }); // Should be sorted by createdAt ASC (oldest first) - expect(result?.messages).toHaveLength(2); - expect((result?.messages[0] as { messageId: string }).messageId).toBe(msg1!.messageId); - expect((result?.messages[1] as { messageId: string }).messageId).toBe(msg2!.messageId); + expect(result.messages).toHaveLength(2); + expect(result.messages[0].messageId).toBe(msg1.messageId); + expect(result.messages[1].messageId).toBe(msg2.messageId); }); it('should handle empty conversation', async () => { @@ -825,8 +780,8 @@ describe('Message Operations', () => { user: 'user123', }); - expect(result?.messages).toHaveLength(0); - expect(result?.nextCursor).toBeNull(); + expect(result.messages).toHaveLength(0); + expect(result.nextCursor).toBeNull(); }); it('should only return messages for the specified user', async () => { @@ -859,8 +814,8 @@ describe('Message Operations', () => { }); // Should only return user123's message - expect(result?.messages).toHaveLength(1); - expect((result?.messages[0] as { user: string }).user).toBe('user123'); + expect(result.messages).toHaveLength(1); + expect(result.messages[0].user).toBe('user123'); }); it('should handle exactly pageSize number of messages (no next page)', async () => { @@ -879,8 +834,8 @@ describe('Message Operations', () => { pageSize: 25, }); - expect(result?.messages).toHaveLength(25); - expect(result?.nextCursor).toBeNull(); // No next page + expect(result.messages).toHaveLength(25); + expect(result.nextCursor).toBeNull(); // No next page }); it('should handle pageSize of 1', async () => { @@ -894,8 +849,8 @@ describe('Message Operations', () => { } // Fetch with pageSize 1 - let cursor: string | null = null; - const allMessages: unknown[] = []; + let cursor = null; + const allMessages = []; for (let page = 0; page < 5; page++) { const result = await getMessagesByCursor({ @@ -905,8 +860,8 @@ describe('Message Operations', () => { cursor, }); - allMessages.push(...(result?.messages ?? [])); - cursor = result?.nextCursor; + allMessages.push(...result.messages); + cursor = result.nextCursor; if (!cursor) { break; @@ -915,7 +870,7 @@ describe('Message Operations', () => { // Should get all 3 messages without duplicates expect(allMessages).toHaveLength(3); - const uniqueIds = new Set(allMessages.map((m) => (m as { messageId: string }).messageId)); + const uniqueIds = new Set(allMessages.map((m) => m.messageId)); expect(uniqueIds.size).toBe(3); }); @@ -924,7 +879,7 @@ describe('Message Operations', () => { const sameTime = new Date('2026-01-01T12:00:00.000Z'); // Create multiple messages with the exact same timestamp - const messages: (IMessage | null)[] = []; + const messages = []; for (let i = 0; i < 5; i++) { const msg = await createMessageWithTimestamp(i, conversationId, sameTime); messages.push(msg); @@ -937,89 +892,7 @@ describe('Message Operations', () => { }); // All messages should be returned - expect(result?.messages).toHaveLength(5); - }); - }); - - describe('tenantId stripping', () => { - it('saveMessage should not write caller-supplied tenantId to the document', async () => { - const messageId = uuidv4(); - const conversationId = uuidv4(); - const result = await saveMessage( - { userId: 'user123' }, - { messageId, conversationId, text: 'Tenant test', tenantId: 'malicious-tenant' }, - ); - - expect(result).not.toBeNull(); - expect(result).toBeDefined(); - const doc = await Message.findOne({ messageId }).lean(); - expect(doc).not.toBeNull(); - expect(doc?.text).toBe('Tenant test'); - expect(doc?.tenantId).toBeUndefined(); - }); - - it('bulkSaveMessages should not overwrite tenantId via update payload', async () => { - const messageId = uuidv4(); - const conversationId = uuidv4(); - - await tenantStorage.run({ tenantId: 'real-tenant' }, async () => { - await Message.create({ - messageId, - conversationId, - user: 'user123', - text: 'Original', - }); - }); - - await tenantStorage.run({ tenantId: 'real-tenant' }, async () => { - await bulkSaveMessages([ - { - messageId, - conversationId, - user: 'user123', - text: 'Updated', - tenantId: 'malicious-tenant', - }, - ]); - }); - - const doc = await runAsSystem(async () => Message.findOne({ messageId }).lean()); - expect(doc).not.toBeNull(); - expect(doc?.text).toBe('Updated'); - expect(doc?.tenantId).toBe('real-tenant'); - }); - - it('recordMessage should not write caller-supplied tenantId to the document', async () => { - const messageId = uuidv4(); - const conversationId = uuidv4(); - await recordMessage({ - user: 'user123', - messageId, - conversationId, - text: 'Record tenant test', - tenantId: 'malicious-tenant', - }); - - const doc = await Message.findOne({ messageId }).lean(); - expect(doc).not.toBeNull(); - expect(doc?.text).toBe('Record tenant test'); - expect(doc?.tenantId).toBeUndefined(); - }); - - it('updateMessage should not write caller-supplied tenantId to the document', async () => { - const messageId = uuidv4(); - const conversationId = uuidv4(); - await saveMessage({ userId: 'user123' }, { messageId, conversationId, text: 'Original' }); - - await updateMessage('user123', { - messageId, - text: 'Updated', - tenantId: 'malicious-tenant', - }); - - const doc = await Message.findOne({ messageId }).lean(); - expect(doc?.text).toBe('Updated'); - expect(doc?.tenantId).toBeUndefined(); + expect(result.messages).toHaveLength(5); }); }); }); diff --git a/api/models/Preset.js b/api/models/Preset.js new file mode 100644 index 0000000000..4db3d59066 --- /dev/null +++ b/api/models/Preset.js @@ -0,0 +1,82 @@ +const { logger } = require('@librechat/data-schemas'); +const { Preset } = require('~/db/models'); + +const getPreset = async (user, presetId) => { + try { + return await Preset.findOne({ user, presetId }).lean(); + } catch (error) { + logger.error('[getPreset] Error getting single preset', error); + return { message: 'Error getting single preset' }; + } +}; + +module.exports = { + getPreset, + getPresets: async (user, filter) => { + try { + const presets = await Preset.find({ ...filter, user }).lean(); + const defaultValue = 10000; + + presets.sort((a, b) => { + let orderA = a.order !== undefined ? a.order : defaultValue; + let orderB = b.order !== undefined ? b.order : defaultValue; + + if (orderA !== orderB) { + return orderA - orderB; + } + + return b.updatedAt - a.updatedAt; + }); + + return presets; + } catch (error) { + logger.error('[getPresets] Error getting presets', error); + return { message: 'Error retrieving presets' }; + } + }, + savePreset: async (user, { presetId, newPresetId, defaultPreset, ...preset }) => { + try { + const setter = { $set: {} }; + const { user: _, ...cleanPreset } = preset; + const update = { presetId, ...cleanPreset }; + if (preset.tools && Array.isArray(preset.tools)) { + update.tools = + preset.tools + .map((tool) => tool?.pluginKey ?? tool) + .filter((toolName) => typeof toolName === 'string') ?? []; + } + if (newPresetId) { + update.presetId = newPresetId; + } + + if (defaultPreset) { + update.defaultPreset = defaultPreset; + update.order = 0; + + const currentDefault = await Preset.findOne({ defaultPreset: true, user }); + + if (currentDefault && currentDefault.presetId !== presetId) { + await Preset.findByIdAndUpdate(currentDefault._id, { + $unset: { defaultPreset: '', order: '' }, + }); + } + } else if (defaultPreset === false) { + update.defaultPreset = undefined; + update.order = undefined; + setter['$unset'] = { defaultPreset: '', order: '' }; + } + + setter.$set = update; + return await Preset.findOneAndUpdate({ presetId, user }, setter, { new: true, upsert: true }); + } catch (error) { + logger.error('[savePreset] Error saving preset', error); + return { message: 'Error saving preset' }; + } + }, + deletePresets: async (user, filter) => { + // let toRemove = await Preset.find({ ...filter, user }).select('presetId'); + // const ids = toRemove.map((instance) => instance.presetId); + let deleteCount = await Preset.deleteMany({ ...filter, user }); + return deleteCount; + }, +}; diff --git a/api/models/Project.js b/api/models/Project.js new file mode 100644 index 0000000000..8fd1e556f9 --- /dev/null +++ b/api/models/Project.js @@ -0,0 +1,133 @@ +const { GLOBAL_PROJECT_NAME } = require('librechat-data-provider').Constants; +const { Project } = require('~/db/models'); + +/** + * Retrieve a project by ID and convert the found project document to a plain object. + * + * @param {string} projectId - The ID of the project to find and return as a plain object. + * @param {string|string[]} [fieldsToSelect] - The fields to include or exclude in the returned document. + * @returns {Promise} A plain object representing the project document, or `null` if no project is found. + */ +const getProjectById = async function (projectId, fieldsToSelect = null) { + const query = Project.findById(projectId); + + if (fieldsToSelect) { + query.select(fieldsToSelect); + } + + return await query.lean(); +}; + +/** + * Retrieve a project by name and convert the found project document to a plain object. + * If the project with the given name doesn't exist and the name is "instance", create it and return the lean version. + * + * @param {string} projectName - The name of the project to find or create. + * @param {string|string[]} [fieldsToSelect] - The fields to include or exclude in the returned document. + * @returns {Promise} A plain object representing the project document. + */ +const getProjectByName = async function (projectName, fieldsToSelect = null) { + const query = { name: projectName }; + const update = { $setOnInsert: { name: projectName } }; + const options = { + new: true, + upsert: projectName === GLOBAL_PROJECT_NAME, + lean: true, + select: fieldsToSelect, + }; + + return await Project.findOneAndUpdate(query, update, options); +}; + +/** + * Add an array of prompt group IDs to a project's promptGroupIds array, ensuring uniqueness. + * + * @param {string} projectId - The ID of the project to update. + * @param {string[]} promptGroupIds - The array of prompt group IDs to add to the project. + * @returns {Promise} The updated project document. + */ +const addGroupIdsToProject = async function (projectId, promptGroupIds) { + return await Project.findByIdAndUpdate( + projectId, + { $addToSet: { promptGroupIds: { $each: promptGroupIds } } }, + { new: true }, + ); +}; + +/** + * Remove an array of prompt group IDs from a project's promptGroupIds array. + * + * @param {string} projectId - The ID of the project to update. + * @param {string[]} promptGroupIds - The array of prompt group IDs to remove from the project. + * @returns {Promise} The updated project document. + */ +const removeGroupIdsFromProject = async function (projectId, promptGroupIds) { + return await Project.findByIdAndUpdate( + projectId, + { $pull: { promptGroupIds: { $in: promptGroupIds } } }, + { new: true }, + ); +}; + +/** + * Remove a prompt group ID from all projects. + * + * @param {string} promptGroupId - The ID of the prompt group to remove from projects. + * @returns {Promise} + */ +const removeGroupFromAllProjects = async (promptGroupId) => { + await Project.updateMany({}, { $pull: { promptGroupIds: promptGroupId } }); +}; + +/** + * Add an array of agent IDs to a project's agentIds array, ensuring uniqueness. + * + * @param {string} projectId - The ID of the project to update. + * @param {string[]} agentIds - The array of agent IDs to add to the project. + * @returns {Promise} The updated project document. + */ +const addAgentIdsToProject = async function (projectId, agentIds) { + return await Project.findByIdAndUpdate( + projectId, + { $addToSet: { agentIds: { $each: agentIds } } }, + { new: true }, + ); +}; + +/** + * Remove an array of agent IDs from a project's agentIds array. + * + * @param {string} projectId - The ID of the project to update. + * @param {string[]} agentIds - The array of agent IDs to remove from the project. + * @returns {Promise} The updated project document. + */ +const removeAgentIdsFromProject = async function (projectId, agentIds) { + return await Project.findByIdAndUpdate( + projectId, + { $pull: { agentIds: { $in: agentIds } } }, + { new: true }, + ); +}; + +/** + * Remove an agent ID from all projects. + * + * @param {string} agentId - The ID of the agent to remove from projects. + * @returns {Promise} + */ +const removeAgentFromAllProjects = async (agentId) => { + await Project.updateMany({}, { $pull: { agentIds: agentId } }); +}; + +module.exports = { + getProjectById, + getProjectByName, + /* prompts */ + addGroupIdsToProject, + removeGroupIdsFromProject, + removeGroupFromAllProjects, + /* agents */ + addAgentIdsToProject, + removeAgentIdsFromProject, + removeAgentFromAllProjects, +}; diff --git a/api/models/Prompt.js b/api/models/Prompt.js new file mode 100644 index 0000000000..bde911b23a --- /dev/null +++ b/api/models/Prompt.js @@ -0,0 +1,708 @@ +const { ObjectId } = require('mongodb'); +const { escapeRegExp } = require('@librechat/api'); +const { logger } = require('@librechat/data-schemas'); +const { + Constants, + SystemRoles, + ResourceType, + SystemCategories, +} = require('librechat-data-provider'); +const { + removeGroupFromAllProjects, + removeGroupIdsFromProject, + addGroupIdsToProject, + getProjectByName, +} = require('./Project'); +const { removeAllPermissions } = require('~/server/services/PermissionService'); +const { PromptGroup, Prompt, AclEntry } = require('~/db/models'); + +/** + * Create a pipeline for the aggregation to get prompt groups + * @param {Object} query + * @param {number} skip + * @param {number} limit + * @returns {[Object]} - The pipeline for the aggregation + */ +const createGroupPipeline = (query, skip, limit) => { + return [ + { $match: query }, + { $sort: { createdAt: -1 } }, + { $skip: skip }, + { $limit: limit }, + { + $lookup: { + from: 'prompts', + localField: 'productionId', + foreignField: '_id', + as: 'productionPrompt', + }, + }, + { $unwind: { path: '$productionPrompt', preserveNullAndEmptyArrays: true } }, + { + $project: { + name: 1, + numberOfGenerations: 1, + oneliner: 1, + category: 1, + projectIds: 1, + productionId: 1, + author: 1, + authorName: 1, + createdAt: 1, + updatedAt: 1, + 'productionPrompt.prompt': 1, + // 'productionPrompt._id': 1, + // 'productionPrompt.type': 1, + }, + }, + ]; +}; + +/** + * Create a pipeline for the aggregation to get all prompt groups + * @param {Object} query + * @param {Partial} $project + * @returns {[Object]} - The pipeline for the aggregation + */ +const createAllGroupsPipeline = ( + query, + $project = { + name: 1, + oneliner: 1, + category: 1, + author: 1, + authorName: 1, + createdAt: 1, + updatedAt: 1, + command: 1, + 'productionPrompt.prompt': 1, + }, +) => { + return [ + { $match: query }, + { $sort: { createdAt: -1 } }, + { + $lookup: { + from: 'prompts', + localField: 'productionId', + foreignField: '_id', + as: 'productionPrompt', + }, + }, + { $unwind: { path: '$productionPrompt', preserveNullAndEmptyArrays: true } }, + { + $project, + }, + ]; +}; + +/** + * Get all prompt groups with filters + * @param {ServerRequest} req + * @param {TPromptGroupsWithFilterRequest} filter + * @returns {Promise} + */ +const getAllPromptGroups = async (req, filter) => { + try { + const { name, ...query } = filter; + + let searchShared = true; + let searchSharedOnly = false; + if (name) { + query.name = new RegExp(escapeRegExp(name), 'i'); + } + if (!query.category) { + delete query.category; + } else if (query.category === SystemCategories.MY_PROMPTS) { + searchShared = false; + delete query.category; + } else if (query.category === SystemCategories.NO_CATEGORY) { + query.category = ''; + } else if (query.category === SystemCategories.SHARED_PROMPTS) { + searchSharedOnly = true; + delete query.category; + } + + let combinedQuery = query; + + if (searchShared) { + const project = await getProjectByName(Constants.GLOBAL_PROJECT_NAME, 'promptGroupIds'); + if (project && project.promptGroupIds && project.promptGroupIds.length > 0) { + const projectQuery = { _id: { $in: project.promptGroupIds }, ...query }; + delete projectQuery.author; + combinedQuery = searchSharedOnly ? projectQuery : { $or: [projectQuery, query] }; + } + } + + const promptGroupsPipeline = createAllGroupsPipeline(combinedQuery); + return await PromptGroup.aggregate(promptGroupsPipeline).exec(); + } catch (error) { + console.error('Error getting all prompt groups', error); + return { message: 'Error getting all prompt groups' }; + } +}; + +/** + * Get prompt groups with filters + * @param {ServerRequest} req + * @param {TPromptGroupsWithFilterRequest} filter + * @returns {Promise} + */ +const getPromptGroups = async (req, filter) => { + try { + const { pageNumber = 1, pageSize = 10, name, ...query } = filter; + + const validatedPageNumber = Math.max(parseInt(pageNumber, 10), 1); + const validatedPageSize = Math.max(parseInt(pageSize, 10), 1); + + let searchShared = true; + let searchSharedOnly = false; + if (name) { + query.name = new RegExp(escapeRegExp(name), 'i'); + } + if (!query.category) { + delete query.category; + } else if (query.category === SystemCategories.MY_PROMPTS) { + searchShared = false; + delete query.category; + } else if (query.category === SystemCategories.NO_CATEGORY) { + query.category = ''; + } else if (query.category === SystemCategories.SHARED_PROMPTS) { + searchSharedOnly = true; + delete query.category; + } + + let combinedQuery = query; + + if (searchShared) { + // const projects = req.user.projects || []; // TODO: handle multiple projects + const project = await getProjectByName(Constants.GLOBAL_PROJECT_NAME, 'promptGroupIds'); + if (project && project.promptGroupIds && project.promptGroupIds.length > 0) { + const projectQuery = { _id: { $in: project.promptGroupIds }, ...query }; + delete projectQuery.author; + combinedQuery = searchSharedOnly ? projectQuery : { $or: [projectQuery, query] }; + } + } + + const skip = (validatedPageNumber - 1) * validatedPageSize; + const limit = validatedPageSize; + + const promptGroupsPipeline = createGroupPipeline(combinedQuery, skip, limit); + const totalPromptGroupsPipeline = [{ $match: combinedQuery }, { $count: 'total' }]; + + const [promptGroupsResults, totalPromptGroupsResults] = await Promise.all([ + PromptGroup.aggregate(promptGroupsPipeline).exec(), + PromptGroup.aggregate(totalPromptGroupsPipeline).exec(), + ]); + + const promptGroups = promptGroupsResults; + const totalPromptGroups = + totalPromptGroupsResults.length > 0 ? totalPromptGroupsResults[0].total : 0; + + return { + promptGroups, + pageNumber: validatedPageNumber.toString(), + pageSize: validatedPageSize.toString(), + pages: Math.ceil(totalPromptGroups / validatedPageSize).toString(), + }; + } catch (error) { + console.error('Error getting prompt groups', error); + return { message: 'Error getting prompt groups' }; + } +}; + +/** + * @param {Object} fields + * @param {string} fields._id + * @param {string} fields.author + * @param {string} fields.role + * @returns {Promise} + */ +const deletePromptGroup = async ({ _id, author, role }) => { + // Build query - with ACL, author is optional + const query = { _id }; + const groupQuery = { groupId: new ObjectId(_id) }; + + // Legacy: Add author filter if provided (backward compatibility) + if (author && role !== SystemRoles.ADMIN) { + query.author = author; + groupQuery.author = author; + } + + const response = await PromptGroup.deleteOne(query); + + if (!response || response.deletedCount === 0) { + throw new Error('Prompt group not found'); + } + + await Prompt.deleteMany(groupQuery); + await removeGroupFromAllProjects(_id); + + try { + await removeAllPermissions({ resourceType: ResourceType.PROMPTGROUP, resourceId: _id }); + } catch (error) { + logger.error('Error removing promptGroup permissions:', error); + } + + return { message: 'Prompt group deleted successfully' }; +}; + +/** + * Get prompt groups by accessible IDs with optional cursor-based pagination. + * @param {Object} params - The parameters for getting accessible prompt groups. + * @param {Array} [params.accessibleIds] - Array of prompt group ObjectIds the user has ACL access to. + * @param {Object} [params.otherParams] - Additional query parameters (including author filter). + * @param {number} [params.limit] - Number of prompt groups to return (max 100). If not provided, returns all prompt groups. + * @param {string} [params.after] - Cursor for pagination - get prompt groups after this cursor. // base64 encoded JSON string with updatedAt and _id. + * @returns {Promise} A promise that resolves to an object containing the prompt groups data and pagination info. + */ +async function getListPromptGroupsByAccess({ + accessibleIds = [], + otherParams = {}, + limit = null, + after = null, +}) { + const isPaginated = limit !== null && limit !== undefined; + const normalizedLimit = isPaginated ? Math.min(Math.max(1, parseInt(limit) || 20), 100) : null; + + // Build base query combining ACL accessible prompt groups with other filters + const baseQuery = { ...otherParams, _id: { $in: accessibleIds } }; + + // Add cursor condition + if (after && typeof after === 'string' && after !== 'undefined' && after !== 'null') { + try { + const cursor = JSON.parse(Buffer.from(after, 'base64').toString('utf8')); + const { updatedAt, _id } = cursor; + + const cursorCondition = { + $or: [ + { updatedAt: { $lt: new Date(updatedAt) } }, + { updatedAt: new Date(updatedAt), _id: { $gt: new ObjectId(_id) } }, + ], + }; + + // Merge cursor condition with base query + if (Object.keys(baseQuery).length > 0) { + baseQuery.$and = [{ ...baseQuery }, cursorCondition]; + // Remove the original conditions from baseQuery to avoid duplication + Object.keys(baseQuery).forEach((key) => { + if (key !== '$and') delete baseQuery[key]; + }); + } else { + Object.assign(baseQuery, cursorCondition); + } + } catch (error) { + logger.warn('Invalid cursor:', error.message); + } + } + + // Build aggregation pipeline + const pipeline = [{ $match: baseQuery }, { $sort: { updatedAt: -1, _id: 1 } }]; + + // Only apply limit if pagination is requested + if (isPaginated) { + pipeline.push({ $limit: normalizedLimit + 1 }); + } + + // Add lookup for production prompt + pipeline.push( + { + $lookup: { + from: 'prompts', + localField: 'productionId', + foreignField: '_id', + as: 'productionPrompt', + }, + }, + { $unwind: { path: '$productionPrompt', preserveNullAndEmptyArrays: true } }, + { + $project: { + name: 1, + numberOfGenerations: 1, + oneliner: 1, + category: 1, + projectIds: 1, + productionId: 1, + author: 1, + authorName: 1, + createdAt: 1, + updatedAt: 1, + 'productionPrompt.prompt': 1, + }, + }, + ); + + const promptGroups = await PromptGroup.aggregate(pipeline).exec(); + + const hasMore = isPaginated ? promptGroups.length > normalizedLimit : false; + const data = (isPaginated ? promptGroups.slice(0, normalizedLimit) : promptGroups).map( + (group) => { + if (group.author) { + group.author = group.author.toString(); + } + return group; + }, + ); + + // Generate next cursor only if paginated + let nextCursor = null; + if (isPaginated && hasMore && data.length > 0) { + const lastGroup = promptGroups[normalizedLimit - 1]; + nextCursor = Buffer.from( + JSON.stringify({ + updatedAt: lastGroup.updatedAt.toISOString(), + _id: lastGroup._id.toString(), + }), + ).toString('base64'); + } + + return { + object: 'list', + data, + first_id: data.length > 0 ? data[0]._id.toString() : null, + last_id: data.length > 0 ? data[data.length - 1]._id.toString() : null, + has_more: hasMore, + after: nextCursor, + }; +} + +module.exports = { + getPromptGroups, + deletePromptGroup, + getAllPromptGroups, + getListPromptGroupsByAccess, + /** + * Create a prompt and its respective group + * @param {TCreatePromptRecord} saveData + * @returns {Promise} + */ + createPromptGroup: async (saveData) => { + try { + const { prompt, group, author, authorName } = saveData; + + let newPromptGroup = await PromptGroup.findOneAndUpdate( + { ...group, author, authorName, productionId: null }, + { $setOnInsert: { ...group, author, authorName, productionId: null } }, + { new: true, upsert: true }, + ) + .lean() + .select('-__v') + .exec(); + + const newPrompt = await Prompt.findOneAndUpdate( + { ...prompt, author, groupId: newPromptGroup._id }, + { $setOnInsert: { ...prompt, author, groupId: newPromptGroup._id } }, + { new: true, upsert: true }, + ) + .lean() + .select('-__v') + .exec(); + + newPromptGroup = await PromptGroup.findByIdAndUpdate( + newPromptGroup._id, + { productionId: newPrompt._id }, + { new: true }, + ) + .lean() + .select('-__v') + .exec(); + + return { + prompt: newPrompt, + group: { + ...newPromptGroup, + productionPrompt: { prompt: newPrompt.prompt }, + }, + }; + } catch (error) { + logger.error('Error saving prompt group', error); + throw new Error('Error saving prompt group'); + } + }, + /** + * Save a prompt + * @param {TCreatePromptRecord} saveData + * @returns {Promise} + */ + savePrompt: async (saveData) => { + try { + const { prompt, author } = saveData; + const newPromptData = { + ...prompt, + author, + }; + + /** @type {TPrompt} */ + let newPrompt; + try { + newPrompt = await Prompt.create(newPromptData); + } catch (error) { + if (error?.message?.includes('groupId_1_version_1')) { + await Prompt.db.collection('prompts').dropIndex('groupId_1_version_1'); + } else { + throw error; + } + newPrompt = await Prompt.create(newPromptData); + } + + return { prompt: newPrompt }; + } catch (error) { + logger.error('Error saving prompt', error); + return { message: 'Error saving prompt' }; + } + }, + getPrompts: async (filter) => { + try { + return await Prompt.find(filter).sort({ createdAt: -1 }).lean(); + } catch (error) { + logger.error('Error getting prompts', error); + return { message: 'Error getting prompts' }; + } + }, + getPrompt: async (filter) => { + try { + if (filter.groupId) { + filter.groupId = new ObjectId(filter.groupId); + } + return await Prompt.findOne(filter).lean(); + } catch (error) { + logger.error('Error getting prompt', error); + return { message: 'Error getting prompt' }; + } + }, + /** + * Get prompt groups with filters + * @param {TGetRandomPromptsRequest} filter + * @returns {Promise} + */ + getRandomPromptGroups: async (filter) => { + try { + const result = await PromptGroup.aggregate([ + { + $match: { + category: { $ne: '' }, + }, + }, + { + $group: { + _id: '$category', + promptGroup: { $first: '$$ROOT' }, + }, + }, + { + $replaceRoot: { newRoot: '$promptGroup' }, + }, + { + $sample: { size: +filter.limit + +filter.skip }, + }, + { + $skip: +filter.skip, + }, + { + $limit: +filter.limit, + }, + ]); + return { prompts: result }; + } catch (error) { + logger.error('Error getting prompt groups', error); + return { message: 'Error getting prompt groups' }; + } + }, + getPromptGroupsWithPrompts: async (filter) => { + try { + return await PromptGroup.findOne(filter) + .populate({ + path: 'prompts', + select: '-_id -__v -user', + }) + .select('-_id -__v -user') + .lean(); + } catch (error) { + logger.error('Error getting prompt groups', error); + return { message: 'Error getting prompt groups' }; + } + }, + getPromptGroup: async (filter) => { + try { + return await PromptGroup.findOne(filter).lean(); + } catch (error) { + logger.error('Error getting prompt group', error); + return { message: 'Error getting prompt group' }; + } + }, + /** + * Deletes a prompt and its corresponding prompt group if it is the last prompt in the group. + * + * @param {Object} options - The options for deleting the prompt. + * @param {ObjectId|string} options.promptId - The ID of the prompt to delete. + * @param {ObjectId|string} options.groupId - The ID of the prompt's group. + * @param {ObjectId|string} options.author - The ID of the prompt's author. + * @param {string} options.role - The role of the prompt's author. + * @return {Promise} An object containing the result of the deletion. + * If the prompt was deleted successfully, the object will have a property 'prompt' with the value 'Prompt deleted successfully'. + * If the prompt group was deleted successfully, the object will have a property 'promptGroup' with the message 'Prompt group deleted successfully' and id of the deleted group. + * If there was an error deleting the prompt, the object will have a property 'message' with the value 'Error deleting prompt'. + */ + deletePrompt: async ({ promptId, groupId, author, role }) => { + const query = { _id: promptId, groupId, author }; + if (role === SystemRoles.ADMIN) { + delete query.author; + } + const { deletedCount } = await Prompt.deleteOne(query); + if (deletedCount === 0) { + throw new Error('Failed to delete the prompt'); + } + + const remainingPrompts = await Prompt.find({ groupId }) + .select('_id') + .sort({ createdAt: 1 }) + .lean(); + + if (remainingPrompts.length === 0) { + // Remove all ACL entries for the promptGroup when deleting the last prompt + try { + await removeAllPermissions({ + resourceType: ResourceType.PROMPTGROUP, + resourceId: groupId, + }); + } catch (error) { + logger.error('Error removing promptGroup permissions:', error); + } + + await PromptGroup.deleteOne({ _id: groupId }); + await removeGroupFromAllProjects(groupId); + + return { + prompt: 'Prompt deleted successfully', + promptGroup: { + message: 'Prompt group deleted successfully', + id: groupId, + }, + }; + } else { + const promptGroup = await PromptGroup.findById(groupId).lean(); + if (promptGroup.productionId.toString() === promptId.toString()) { + await PromptGroup.updateOne( + { _id: groupId }, + { productionId: remainingPrompts[remainingPrompts.length - 1]._id }, + ); + } + + return { prompt: 'Prompt deleted successfully' }; + } + }, + /** + * Delete all prompts and prompt groups created by a specific user. + * @param {ServerRequest} req - The server request object. + * @param {string} userId - The ID of the user whose prompts and prompt groups are to be deleted. + */ + deleteUserPrompts: async (req, userId) => { + try { + const promptGroups = await getAllPromptGroups(req, { author: new ObjectId(userId) }); + + if (promptGroups.length === 0) { + return; + } + + const groupIds = promptGroups.map((group) => group._id); + + for (const groupId of groupIds) { + await removeGroupFromAllProjects(groupId); + } + + await AclEntry.deleteMany({ + resourceType: ResourceType.PROMPTGROUP, + resourceId: { $in: groupIds }, + }); + + await PromptGroup.deleteMany({ author: new ObjectId(userId) }); + await Prompt.deleteMany({ author: new ObjectId(userId) }); + } catch (error) { + logger.error('[deleteUserPrompts] General error:', error); + } + }, + /** + * Update prompt group + * @param {Partial} filter - Filter to find prompt group + * @param {Partial} data - Data to update + * @returns {Promise} + */ + updatePromptGroup: async (filter, data) => { + try { + const updateOps = {}; + if (data.removeProjectIds) { + for (const projectId of data.removeProjectIds) { + await removeGroupIdsFromProject(projectId, [filter._id]); + } + + updateOps.$pull = { projectIds: { $in: data.removeProjectIds } }; + delete data.removeProjectIds; + } + + if (data.projectIds) { + for (const projectId of data.projectIds) { + await addGroupIdsToProject(projectId, [filter._id]); + } + + updateOps.$addToSet = { projectIds: { $each: data.projectIds } }; + delete data.projectIds; + } + + const updateData = { ...data, ...updateOps }; + const updatedDoc = await PromptGroup.findOneAndUpdate(filter, updateData, { + new: true, + upsert: false, + }); + + if (!updatedDoc) { + throw new Error('Prompt group not found'); + } + + return updatedDoc; + } catch (error) { + logger.error('Error updating prompt group', error); + return { message: 'Error updating prompt group' }; + } + }, + /** + * Function to make a prompt production based on its ID. + * @param {String} promptId - The ID of the prompt to make production. + * @returns {Object} The result of the production operation. + */ + makePromptProduction: async (promptId) => { + try { + const prompt = await Prompt.findById(promptId).lean(); + + if (!prompt) { + throw new Error('Prompt not found'); + } + + await PromptGroup.findByIdAndUpdate( + prompt.groupId, + { productionId: prompt._id }, + { new: true }, + ) + .lean() + .exec(); + + return { + message: 'Prompt production made successfully', + }; + } catch (error) { + logger.error('Error making prompt production', error); + return { message: 'Error making prompt production' }; + } + }, + updatePromptLabels: async (_id, labels) => { + try { + const response = await Prompt.updateOne({ _id }, { $set: { labels } }); + if (response.matchedCount === 0) { + return { message: 'Prompt not found' }; + } + return { message: 'Prompt labels updated successfully' }; + } catch (error) { + logger.error('Error updating prompt labels', error); + return { message: 'Error updating prompt labels' }; + } + }, +}; diff --git a/api/models/Prompt.spec.js b/api/models/Prompt.spec.js new file mode 100644 index 0000000000..e00a1a518c --- /dev/null +++ b/api/models/Prompt.spec.js @@ -0,0 +1,564 @@ +const mongoose = require('mongoose'); +const { ObjectId } = require('mongodb'); +const { logger } = require('@librechat/data-schemas'); +const { MongoMemoryServer } = require('mongodb-memory-server'); +const { + SystemRoles, + ResourceType, + AccessRoleIds, + PrincipalType, + PermissionBits, +} = require('librechat-data-provider'); + +// Mock the config/connect module to prevent connection attempts during tests +jest.mock('../../config/connect', () => jest.fn().mockResolvedValue(true)); + +const dbModels = require('~/db/models'); + +// Disable console for tests +logger.silent = true; + +let mongoServer; +let Prompt, PromptGroup, AclEntry, AccessRole, User, Group, Project; +let promptFns, permissionService; +let testUsers, testGroups, testRoles; + +beforeAll(async () => { + // Set up MongoDB memory server + mongoServer = await MongoMemoryServer.create(); + const mongoUri = mongoServer.getUri(); + await mongoose.connect(mongoUri); + + // Initialize models + Prompt = dbModels.Prompt; + PromptGroup = dbModels.PromptGroup; + AclEntry = dbModels.AclEntry; + AccessRole = dbModels.AccessRole; + User = dbModels.User; + Group = dbModels.Group; + Project = dbModels.Project; + + promptFns = require('~/models/Prompt'); + permissionService = require('~/server/services/PermissionService'); + + // Create test data + await setupTestData(); +}); + +afterAll(async () => { + await mongoose.disconnect(); + await mongoServer.stop(); + jest.clearAllMocks(); +}); + +async function setupTestData() { + // Create access roles for promptGroups + testRoles = { + viewer: await AccessRole.create({ + accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER, + name: 'Viewer', + description: 'Can view promptGroups', + resourceType: ResourceType.PROMPTGROUP, + permBits: PermissionBits.VIEW, + }), + editor: await AccessRole.create({ + accessRoleId: AccessRoleIds.PROMPTGROUP_EDITOR, + name: 'Editor', + description: 'Can view and edit promptGroups', + resourceType: ResourceType.PROMPTGROUP, + permBits: PermissionBits.VIEW | PermissionBits.EDIT, + }), + owner: await AccessRole.create({ + accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER, + name: 'Owner', + description: 'Full control over promptGroups', + resourceType: ResourceType.PROMPTGROUP, + permBits: + PermissionBits.VIEW | PermissionBits.EDIT | PermissionBits.DELETE | PermissionBits.SHARE, + }), + }; + + // Create test users + testUsers = { + owner: await User.create({ + name: 'Prompt Owner', + email: 'owner@example.com', + role: SystemRoles.USER, + }), + editor: await User.create({ + name: 'Prompt Editor', + email: 'editor@example.com', + role: SystemRoles.USER, + }), + viewer: await User.create({ + name: 'Prompt Viewer', + email: 'viewer@example.com', + role: SystemRoles.USER, + }), + admin: await User.create({ + name: 'Admin User', + email: 'admin@example.com', + role: SystemRoles.ADMIN, + }), + noAccess: await User.create({ + name: 'No Access User', + email: 'noaccess@example.com', + role: SystemRoles.USER, + }), + }; + + // Create test groups + testGroups = { + editors: await Group.create({ + name: 'Prompt Editors', + description: 'Group with editor access', + }), + viewers: await Group.create({ + name: 'Prompt Viewers', + description: 'Group with viewer access', + }), + }; + + await Project.create({ + name: 'Global', + description: 'Global project', + promptGroupIds: [], + }); +} + +describe('Prompt ACL Permissions', () => { + describe('Creating Prompts with Permissions', () => { + it('should grant owner permissions when creating a prompt', async () => { + // First create a group + const testGroup = await PromptGroup.create({ + name: 'Test Group', + category: 'testing', + author: testUsers.owner._id, + authorName: testUsers.owner.name, + productionId: new mongoose.Types.ObjectId(), + }); + + const promptData = { + prompt: { + prompt: 'Test prompt content', + name: 'Test Prompt', + type: 'text', + groupId: testGroup._id, + }, + author: testUsers.owner._id, + }; + + await promptFns.savePrompt(promptData); + + // Manually grant permissions as would happen in the route + await permissionService.grantPermission({ + principalType: PrincipalType.USER, + principalId: testUsers.owner._id, + resourceType: ResourceType.PROMPTGROUP, + resourceId: testGroup._id, + accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER, + grantedBy: testUsers.owner._id, + }); + + // Check ACL entry + const aclEntry = await AclEntry.findOne({ + resourceType: ResourceType.PROMPTGROUP, + resourceId: testGroup._id, + principalType: PrincipalType.USER, + principalId: testUsers.owner._id, + }); + + expect(aclEntry).toBeTruthy(); + expect(aclEntry.permBits).toBe(testRoles.owner.permBits); + }); + }); + + describe('Accessing Prompts', () => { + let testPromptGroup; + + beforeEach(async () => { + // Create a prompt group + testPromptGroup = await PromptGroup.create({ + name: 'Test Group', + author: testUsers.owner._id, + authorName: testUsers.owner.name, + productionId: new ObjectId(), + }); + + // Create a prompt + await Prompt.create({ + prompt: 'Test prompt for access control', + name: 'Access Test Prompt', + author: testUsers.owner._id, + groupId: testPromptGroup._id, + type: 'text', + }); + + // Grant owner permissions + await permissionService.grantPermission({ + principalType: PrincipalType.USER, + principalId: testUsers.owner._id, + resourceType: ResourceType.PROMPTGROUP, + resourceId: testPromptGroup._id, + accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER, + grantedBy: testUsers.owner._id, + }); + }); + + afterEach(async () => { + await Prompt.deleteMany({}); + await PromptGroup.deleteMany({}); + await AclEntry.deleteMany({}); + }); + + it('owner should have full access to their prompt', async () => { + const hasAccess = await permissionService.checkPermission({ + userId: testUsers.owner._id, + resourceType: ResourceType.PROMPTGROUP, + resourceId: testPromptGroup._id, + requiredPermission: PermissionBits.VIEW, + }); + + expect(hasAccess).toBe(true); + + const canEdit = await permissionService.checkPermission({ + userId: testUsers.owner._id, + resourceType: ResourceType.PROMPTGROUP, + resourceId: testPromptGroup._id, + requiredPermission: PermissionBits.EDIT, + }); + + expect(canEdit).toBe(true); + }); + + it('user with viewer role should only have view access', async () => { + // Grant viewer permissions + await permissionService.grantPermission({ + principalType: PrincipalType.USER, + principalId: testUsers.viewer._id, + resourceType: ResourceType.PROMPTGROUP, + resourceId: testPromptGroup._id, + accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER, + grantedBy: testUsers.owner._id, + }); + + const canView = await permissionService.checkPermission({ + userId: testUsers.viewer._id, + resourceType: ResourceType.PROMPTGROUP, + resourceId: testPromptGroup._id, + requiredPermission: PermissionBits.VIEW, + }); + + const canEdit = await permissionService.checkPermission({ + userId: testUsers.viewer._id, + resourceType: ResourceType.PROMPTGROUP, + resourceId: testPromptGroup._id, + requiredPermission: PermissionBits.EDIT, + }); + + expect(canView).toBe(true); + expect(canEdit).toBe(false); + }); + + it('user without permissions should have no access', async () => { + const hasAccess = await permissionService.checkPermission({ + userId: testUsers.noAccess._id, + resourceType: ResourceType.PROMPTGROUP, + resourceId: testPromptGroup._id, + requiredPermission: PermissionBits.VIEW, + }); + + expect(hasAccess).toBe(false); + }); + + it('admin should have access regardless of permissions', async () => { + // Admin users should work through normal permission system + // The middleware layer handles admin bypass, not the permission service + const hasAccess = await permissionService.checkPermission({ + userId: testUsers.admin._id, + resourceType: ResourceType.PROMPTGROUP, + resourceId: testPromptGroup._id, + requiredPermission: PermissionBits.VIEW, + }); + + // Without explicit permissions, even admin won't have access at this layer + expect(hasAccess).toBe(false); + + // The actual admin bypass happens in the middleware layer (`canAccessPromptViaGroup`/`canAccessPromptGroupResource`) + // which checks req.user.role === SystemRoles.ADMIN + }); + }); + + describe('Group-based Access', () => { + let testPromptGroup; + + beforeEach(async () => { + // Create a prompt group first + testPromptGroup = await PromptGroup.create({ + name: 'Group Access Test Group', + author: testUsers.owner._id, + authorName: testUsers.owner.name, + productionId: new ObjectId(), + }); + + await Prompt.create({ + prompt: 'Group access test prompt', + name: 'Group Test', + author: testUsers.owner._id, + groupId: testPromptGroup._id, + type: 'text', + }); + + // Add users to groups + await User.findByIdAndUpdate(testUsers.editor._id, { + $push: { groups: testGroups.editors._id }, + }); + + await User.findByIdAndUpdate(testUsers.viewer._id, { + $push: { groups: testGroups.viewers._id }, + }); + }); + + afterEach(async () => { + await Prompt.deleteMany({}); + await AclEntry.deleteMany({}); + await User.updateMany({}, { $set: { groups: [] } }); + }); + + it('group members should inherit group permissions', async () => { + // Create a prompt group + const testPromptGroup = await PromptGroup.create({ + name: 'Group Test Group', + author: testUsers.owner._id, + authorName: testUsers.owner.name, + productionId: new ObjectId(), + }); + + const { addUserToGroup } = require('~/models'); + await addUserToGroup(testUsers.editor._id, testGroups.editors._id); + + const prompt = await promptFns.savePrompt({ + author: testUsers.owner._id, + prompt: { + prompt: 'Group test prompt', + name: 'Group Test', + groupId: testPromptGroup._id, + type: 'text', + }, + }); + + // Check if savePrompt returned an error + if (!prompt || !prompt.prompt) { + throw new Error(`Failed to save prompt: ${prompt?.message || 'Unknown error'}`); + } + + // Grant edit permissions to the group + await permissionService.grantPermission({ + principalType: PrincipalType.GROUP, + principalId: testGroups.editors._id, + resourceType: ResourceType.PROMPTGROUP, + resourceId: testPromptGroup._id, + accessRoleId: AccessRoleIds.PROMPTGROUP_EDITOR, + grantedBy: testUsers.owner._id, + }); + + // Check if group member has access + const hasAccess = await permissionService.checkPermission({ + userId: testUsers.editor._id, + resourceType: ResourceType.PROMPTGROUP, + resourceId: testPromptGroup._id, + requiredPermission: PermissionBits.EDIT, + }); + + expect(hasAccess).toBe(true); + + // Check that non-member doesn't have access + const nonMemberAccess = await permissionService.checkPermission({ + userId: testUsers.viewer._id, + resourceType: ResourceType.PROMPTGROUP, + resourceId: testPromptGroup._id, + requiredPermission: PermissionBits.EDIT, + }); + + expect(nonMemberAccess).toBe(false); + }); + }); + + describe('Public Access', () => { + let publicPromptGroup, privatePromptGroup; + + beforeEach(async () => { + // Create separate prompt groups for public and private access + publicPromptGroup = await PromptGroup.create({ + name: 'Public Access Test Group', + author: testUsers.owner._id, + authorName: testUsers.owner.name, + productionId: new ObjectId(), + }); + + privatePromptGroup = await PromptGroup.create({ + name: 'Private Access Test Group', + author: testUsers.owner._id, + authorName: testUsers.owner.name, + productionId: new ObjectId(), + }); + + // Create prompts in their respective groups + await Prompt.create({ + prompt: 'Public prompt', + name: 'Public', + author: testUsers.owner._id, + groupId: publicPromptGroup._id, + type: 'text', + }); + + await Prompt.create({ + prompt: 'Private prompt', + name: 'Private', + author: testUsers.owner._id, + groupId: privatePromptGroup._id, + type: 'text', + }); + + // Grant public view access to publicPromptGroup + await permissionService.grantPermission({ + principalType: PrincipalType.PUBLIC, + principalId: null, + resourceType: ResourceType.PROMPTGROUP, + resourceId: publicPromptGroup._id, + accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER, + grantedBy: testUsers.owner._id, + }); + + // Grant only owner access to privatePromptGroup + await permissionService.grantPermission({ + principalType: PrincipalType.USER, + principalId: testUsers.owner._id, + resourceType: ResourceType.PROMPTGROUP, + resourceId: privatePromptGroup._id, + accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER, + grantedBy: testUsers.owner._id, + }); + }); + + afterEach(async () => { + await Prompt.deleteMany({}); + await PromptGroup.deleteMany({}); + await AclEntry.deleteMany({}); + }); + + it('public prompt should be accessible to any user', async () => { + const hasAccess = await permissionService.checkPermission({ + userId: testUsers.noAccess._id, + resourceType: ResourceType.PROMPTGROUP, + resourceId: publicPromptGroup._id, + requiredPermission: PermissionBits.VIEW, + includePublic: true, + }); + + expect(hasAccess).toBe(true); + }); + + it('private prompt should not be accessible to unauthorized users', async () => { + const hasAccess = await permissionService.checkPermission({ + userId: testUsers.noAccess._id, + resourceType: ResourceType.PROMPTGROUP, + resourceId: privatePromptGroup._id, + requiredPermission: PermissionBits.VIEW, + includePublic: true, + }); + + expect(hasAccess).toBe(false); + }); + }); + + describe('Prompt Deletion', () => { + let testPromptGroup; + + it('should remove ACL entries when prompt is deleted', async () => { + testPromptGroup = await PromptGroup.create({ + name: 'Deletion Test Group', + author: testUsers.owner._id, + authorName: testUsers.owner.name, + productionId: new ObjectId(), + }); + + const prompt = await promptFns.savePrompt({ + author: testUsers.owner._id, + prompt: { + prompt: 'To be deleted', + name: 'Delete Test', + groupId: testPromptGroup._id, + type: 'text', + }, + }); + + // Check if savePrompt returned an error + if (!prompt || !prompt.prompt) { + throw new Error(`Failed to save prompt: ${prompt?.message || 'Unknown error'}`); + } + + const testPromptId = prompt.prompt._id; + const promptGroupId = testPromptGroup._id; + + // Grant permission + await permissionService.grantPermission({ + principalType: PrincipalType.USER, + principalId: testUsers.owner._id, + resourceType: ResourceType.PROMPTGROUP, + resourceId: testPromptGroup._id, + accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER, + grantedBy: testUsers.owner._id, + }); + + // Verify ACL entry exists + const beforeDelete = await AclEntry.find({ + resourceType: ResourceType.PROMPTGROUP, + resourceId: testPromptGroup._id, + }); + expect(beforeDelete).toHaveLength(1); + + // Delete the prompt + await promptFns.deletePrompt({ + promptId: testPromptId, + groupId: promptGroupId, + author: testUsers.owner._id, + role: SystemRoles.USER, + }); + + // Verify ACL entries are removed + const aclEntries = await AclEntry.find({ + resourceType: ResourceType.PROMPTGROUP, + resourceId: testPromptGroup._id, + }); + + expect(aclEntries).toHaveLength(0); + }); + }); + + describe('Backwards Compatibility', () => { + it('should handle prompts without ACL entries gracefully', async () => { + // Create a prompt group first + const promptGroup = await PromptGroup.create({ + name: 'Legacy Test Group', + author: testUsers.owner._id, + authorName: testUsers.owner.name, + productionId: new ObjectId(), + }); + + // Create a prompt without ACL entries (legacy prompt) + const legacyPrompt = await Prompt.create({ + prompt: 'Legacy prompt without ACL', + name: 'Legacy', + author: testUsers.owner._id, + groupId: promptGroup._id, + type: 'text', + }); + + // The system should handle this gracefully + const prompt = await promptFns.getPrompt({ _id: legacyPrompt._id }); + expect(prompt).toBeTruthy(); + expect(prompt._id.toString()).toBe(legacyPrompt._id.toString()); + }); + }); +}); diff --git a/config/__tests__/migrate-prompt-permissions.spec.js b/api/models/PromptGroupMigration.spec.js similarity index 90% rename from config/__tests__/migrate-prompt-permissions.spec.js rename to api/models/PromptGroupMigration.spec.js index 2d5b2cb4b0..f568012cb3 100644 --- a/config/__tests__/migrate-prompt-permissions.spec.js +++ b/api/models/PromptGroupMigration.spec.js @@ -3,6 +3,7 @@ const { ObjectId } = require('mongodb'); const { logger } = require('@librechat/data-schemas'); const { MongoMemoryServer } = require('mongodb-memory-server'); const { + Constants, ResourceType, AccessRoleIds, PrincipalType, @@ -11,16 +12,16 @@ const { } = require('librechat-data-provider'); // Mock the config/connect module to prevent connection attempts during tests -jest.mock('../connect', () => jest.fn().mockResolvedValue(true)); +jest.mock('../../config/connect', () => jest.fn().mockResolvedValue(true)); // Disable console for tests logger.silent = true; describe('PromptGroup Migration Script', () => { let mongoServer; - let Prompt, PromptGroup, AclEntry, AccessRole, User; + let Prompt, PromptGroup, AclEntry, AccessRole, User, Project; let migrateToPromptGroupPermissions; - let testOwner; + let testOwner, testProject; let ownerRole, viewerRole; beforeAll(async () => { @@ -36,6 +37,7 @@ describe('PromptGroup Migration Script', () => { AclEntry = dbModels.AclEntry; AccessRole = dbModels.AccessRole; User = dbModels.User; + Project = dbModels.Project; // Create test user testOwner = await User.create({ @@ -44,10 +46,11 @@ describe('PromptGroup Migration Script', () => { role: 'USER', }); - // Create test project document in the raw `projects` collection - const projectName = 'instance'; - await mongoose.connection.db.collection('projects').insertOne({ + // Create test project with the proper name + const projectName = Constants.GLOBAL_PROJECT_NAME || 'instance'; + testProject = await Project.create({ name: projectName, + description: 'Global project', promptGroupIds: [], }); @@ -78,7 +81,7 @@ describe('PromptGroup Migration Script', () => { }); // Import migration function - const migration = require('../migrate-prompt-permissions'); + const migration = require('../../config/migrate-prompt-permissions'); migrateToPromptGroupPermissions = migration.migrateToPromptGroupPermissions; }); @@ -92,9 +95,9 @@ describe('PromptGroup Migration Script', () => { await Prompt.deleteMany({}); await PromptGroup.deleteMany({}); await AclEntry.deleteMany({}); - await mongoose.connection.db - .collection('projects') - .updateOne({ name: 'instance' }, { $set: { promptGroupIds: [] } }); + // Reset the project's promptGroupIds array + testProject.promptGroupIds = []; + await testProject.save(); }); it('should categorize promptGroups correctly in dry run', async () => { @@ -115,9 +118,8 @@ describe('PromptGroup Migration Script', () => { }); // Add global group to project's promptGroupIds array - await mongoose.connection.db - .collection('projects') - .updateOne({ name: 'instance' }, { $set: { promptGroupIds: [globalPromptGroup._id] } }); + testProject.promptGroupIds = [globalPromptGroup._id]; + await testProject.save(); const result = await migrateToPromptGroupPermissions({ dryRun: true }); @@ -144,9 +146,8 @@ describe('PromptGroup Migration Script', () => { }); // Add global group to project's promptGroupIds array - await mongoose.connection.db - .collection('projects') - .updateOne({ name: 'instance' }, { $set: { promptGroupIds: [globalPromptGroup._id] } }); + testProject.promptGroupIds = [globalPromptGroup._id]; + await testProject.save(); const result = await migrateToPromptGroupPermissions({ dryRun: false }); diff --git a/api/models/Role.js b/api/models/Role.js new file mode 100644 index 0000000000..b7f806f3b6 --- /dev/null +++ b/api/models/Role.js @@ -0,0 +1,304 @@ +const { + CacheKeys, + SystemRoles, + roleDefaults, + permissionsSchema, + removeNullishValues, +} = require('librechat-data-provider'); +const { logger } = require('@librechat/data-schemas'); +const getLogStores = require('~/cache/getLogStores'); +const { Role } = require('~/db/models'); + +/** + * Retrieve a role by name and convert the found role document to a plain object. + * If the role with the given name doesn't exist and the name is a system defined role, + * create it and return the lean version. + * + * @param {string} roleName - The name of the role to find or create. + * @param {string|string[]} [fieldsToSelect] - The fields to include or exclude in the returned document. + * @returns {Promise} Role document. + */ +const getRoleByName = async function (roleName, fieldsToSelect = null) { + const cache = getLogStores(CacheKeys.ROLES); + try { + const cachedRole = await cache.get(roleName); + if (cachedRole) { + return cachedRole; + } + let query = Role.findOne({ name: roleName }); + if (fieldsToSelect) { + query = query.select(fieldsToSelect); + } + let role = await query.lean().exec(); + + if (!role && SystemRoles[roleName]) { + role = await new Role(roleDefaults[roleName]).save(); + await cache.set(roleName, role); + return role.toObject(); + } + await cache.set(roleName, role); + return role; + } catch (error) { + throw new Error(`Failed to retrieve or create role: ${error.message}`); + } +}; + +/** + * Update role values by name. + * + * @param {string} roleName - The name of the role to update. + * @param {Partial} updates - The fields to update. + * @returns {Promise} Updated role document. + */ +const updateRoleByName = async function (roleName, updates) { + const cache = getLogStores(CacheKeys.ROLES); + try { + const role = await Role.findOneAndUpdate( + { name: roleName }, + { $set: updates }, + { new: true, lean: true }, + ) + .select('-__v') + .lean() + .exec(); + await cache.set(roleName, role); + return role; + } catch (error) { + throw new Error(`Failed to update role: ${error.message}`); + } +}; + +/** + * Updates access permissions for a specific role and multiple permission types. + * @param {string} roleName - The role to update. + * @param {Object.>} permissionsUpdate - Permissions to update and their values. + * @param {IRole} [roleData] - Optional role data to use instead of fetching from the database. + */ +async function updateAccessPermissions(roleName, permissionsUpdate, roleData) { + // Filter and clean the permission updates based on our schema definition. + const updates = {}; + for (const [permissionType, permissions] of Object.entries(permissionsUpdate)) { + if (permissionsSchema.shape && permissionsSchema.shape[permissionType]) { + updates[permissionType] = removeNullishValues(permissions); + } + } + if (!Object.keys(updates).length) { + return; + } + + try { + const role = roleData ?? (await getRoleByName(roleName)); + if (!role) { + return; + } + + const currentPermissions = role.permissions || {}; + const updatedPermissions = { ...currentPermissions }; + let hasChanges = false; + + const unsetFields = {}; + const permissionTypes = Object.keys(permissionsSchema.shape || {}); + for (const permType of permissionTypes) { + if (role[permType] && typeof role[permType] === 'object') { + logger.info( + `Migrating '${roleName}' role from old schema: found '${permType}' at top level`, + ); + + updatedPermissions[permType] = { + ...updatedPermissions[permType], + ...role[permType], + }; + + unsetFields[permType] = 1; + hasChanges = true; + } + } + + // Migrate legacy SHARED_GLOBAL → SHARE for PROMPTS and AGENTS. + // SHARED_GLOBAL was removed in favour of SHARE in PR #11283. If the DB still has + // SHARED_GLOBAL but not SHARE, inherit the value so sharing intent is preserved. + const legacySharedGlobalTypes = ['PROMPTS', 'AGENTS']; + for (const legacyPermType of legacySharedGlobalTypes) { + const existingTypePerms = currentPermissions[legacyPermType]; + if ( + existingTypePerms && + 'SHARED_GLOBAL' in existingTypePerms && + !('SHARE' in existingTypePerms) && + updates[legacyPermType] && + // Don't override an explicit SHARE value the caller already provided + !('SHARE' in updates[legacyPermType]) + ) { + const inheritedValue = existingTypePerms['SHARED_GLOBAL']; + updates[legacyPermType]['SHARE'] = inheritedValue; + logger.info( + `Migrating '${roleName}' role ${legacyPermType}.SHARED_GLOBAL=${inheritedValue} → SHARE`, + ); + } + } + + for (const [permissionType, permissions] of Object.entries(updates)) { + const currentTypePermissions = currentPermissions[permissionType] || {}; + updatedPermissions[permissionType] = { ...currentTypePermissions }; + + for (const [permission, value] of Object.entries(permissions)) { + if (currentTypePermissions[permission] !== value) { + updatedPermissions[permissionType][permission] = value; + hasChanges = true; + logger.info( + `Updating '${roleName}' role permission '${permissionType}' '${permission}' from ${currentTypePermissions[permission]} to: ${value}`, + ); + } + } + } + + // Clean up orphaned SHARED_GLOBAL fields left in DB after the schema rename. + // Since we $set the full permissions object, deleting from updatedPermissions + // is sufficient to remove the field from MongoDB. + for (const legacyPermType of legacySharedGlobalTypes) { + const existingTypePerms = currentPermissions[legacyPermType]; + if (existingTypePerms && 'SHARED_GLOBAL' in existingTypePerms) { + if (!updates[legacyPermType]) { + // permType wasn't in the update payload so the migration block above didn't run. + // Create a writable copy and handle the SHARED_GLOBAL → SHARE inheritance here + // to avoid removing SHARED_GLOBAL without writing SHARE (data loss). + updatedPermissions[legacyPermType] = { ...existingTypePerms }; + if (!('SHARE' in existingTypePerms)) { + updatedPermissions[legacyPermType]['SHARE'] = existingTypePerms['SHARED_GLOBAL']; + logger.info( + `Migrating '${roleName}' role ${legacyPermType}.SHARED_GLOBAL=${existingTypePerms['SHARED_GLOBAL']} → SHARE`, + ); + } + } + delete updatedPermissions[legacyPermType]['SHARED_GLOBAL']; + hasChanges = true; + logger.info( + `Removed legacy SHARED_GLOBAL field from '${roleName}' role ${legacyPermType} permissions`, + ); + } + } + + if (hasChanges) { + const updateObj = { permissions: updatedPermissions }; + + if (Object.keys(unsetFields).length > 0) { + logger.info( + `Unsetting old schema fields for '${roleName}' role: ${Object.keys(unsetFields).join(', ')}`, + ); + + try { + await Role.updateOne( + { name: roleName }, + { + $set: updateObj, + $unset: unsetFields, + }, + ); + + const cache = getLogStores(CacheKeys.ROLES); + const updatedRole = await Role.findOne({ name: roleName }).select('-__v').lean().exec(); + await cache.set(roleName, updatedRole); + + logger.info(`Updated role '${roleName}' and removed old schema fields`); + } catch (updateError) { + logger.error(`Error during role migration update: ${updateError.message}`); + throw updateError; + } + } else { + // Standard update if no migration needed + await updateRoleByName(roleName, updateObj); + } + + logger.info(`Updated '${roleName}' role permissions`); + } else { + logger.info(`No changes needed for '${roleName}' role permissions`); + } + } catch (error) { + logger.error(`Failed to update ${roleName} role permissions:`, error); + } +} + +/** + * Migrates roles from old schema to new schema structure. + * This can be called directly to fix existing roles. + * + * @param {string} [roleName] - Optional specific role to migrate. If not provided, migrates all roles. + * @returns {Promise} Number of roles migrated. + */ +const migrateRoleSchema = async function (roleName) { + try { + // Get roles to migrate + let roles; + if (roleName) { + const role = await Role.findOne({ name: roleName }); + roles = role ? [role] : []; + } else { + roles = await Role.find({}); + } + + logger.info(`Migrating ${roles.length} roles to new schema structure`); + let migratedCount = 0; + + for (const role of roles) { + const permissionTypes = Object.keys(permissionsSchema.shape || {}); + const unsetFields = {}; + let hasOldSchema = false; + + // Check for old schema fields + for (const permType of permissionTypes) { + if (role[permType] && typeof role[permType] === 'object') { + hasOldSchema = true; + + // Ensure permissions object exists + role.permissions = role.permissions || {}; + + // Migrate permissions from old location to new + role.permissions[permType] = { + ...role.permissions[permType], + ...role[permType], + }; + + // Mark field for removal + unsetFields[permType] = 1; + } + } + + if (hasOldSchema) { + try { + logger.info(`Migrating role '${role.name}' from old schema structure`); + + // Simple update operation + await Role.updateOne( + { _id: role._id }, + { + $set: { permissions: role.permissions }, + $unset: unsetFields, + }, + ); + + // Refresh cache + const cache = getLogStores(CacheKeys.ROLES); + const updatedRole = await Role.findById(role._id).lean().exec(); + await cache.set(role.name, updatedRole); + + migratedCount++; + logger.info(`Migrated role '${role.name}'`); + } catch (error) { + logger.error(`Failed to migrate role '${role.name}': ${error.message}`); + } + } + } + + logger.info(`Migration complete: ${migratedCount} roles migrated`); + return migratedCount; + } catch (error) { + logger.error(`Role schema migration failed: ${error.message}`); + throw error; + } +}; + +module.exports = { + getRoleByName, + updateRoleByName, + migrateRoleSchema, + updateAccessPermissions, +}; diff --git a/api/models/Role.spec.js b/api/models/Role.spec.js new file mode 100644 index 0000000000..0ec2f831e2 --- /dev/null +++ b/api/models/Role.spec.js @@ -0,0 +1,511 @@ +const mongoose = require('mongoose'); +const { MongoMemoryServer } = require('mongodb-memory-server'); +const { + SystemRoles, + Permissions, + roleDefaults, + PermissionTypes, +} = require('librechat-data-provider'); +const { getRoleByName, updateAccessPermissions } = require('~/models/Role'); +const getLogStores = require('~/cache/getLogStores'); +const { initializeRoles } = require('~/models'); +const { Role } = require('~/db/models'); + +// Mock the cache +jest.mock('~/cache/getLogStores', () => + jest.fn().mockReturnValue({ + get: jest.fn(), + set: jest.fn(), + del: jest.fn(), + }), +); + +let mongoServer; + +beforeAll(async () => { + mongoServer = await MongoMemoryServer.create(); + const mongoUri = mongoServer.getUri(); + await mongoose.connect(mongoUri); +}); + +afterAll(async () => { + await mongoose.disconnect(); + await mongoServer.stop(); +}); + +beforeEach(async () => { + await Role.deleteMany({}); + getLogStores.mockClear(); +}); + +describe('updateAccessPermissions', () => { + it('should update permissions when changes are needed', async () => { + await new Role({ + name: SystemRoles.USER, + permissions: { + [PermissionTypes.PROMPTS]: { + CREATE: true, + USE: true, + SHARE: false, + }, + }, + }).save(); + + await updateAccessPermissions(SystemRoles.USER, { + [PermissionTypes.PROMPTS]: { + CREATE: true, + USE: true, + SHARE: true, + }, + }); + + const updatedRole = await getRoleByName(SystemRoles.USER); + expect(updatedRole.permissions[PermissionTypes.PROMPTS]).toEqual({ + CREATE: true, + USE: true, + SHARE: true, + }); + }); + + it('should not update permissions when no changes are needed', async () => { + await new Role({ + name: SystemRoles.USER, + permissions: { + [PermissionTypes.PROMPTS]: { + CREATE: true, + USE: true, + SHARE: false, + }, + }, + }).save(); + + await updateAccessPermissions(SystemRoles.USER, { + [PermissionTypes.PROMPTS]: { + CREATE: true, + USE: true, + SHARE: false, + }, + }); + + const updatedRole = await getRoleByName(SystemRoles.USER); + expect(updatedRole.permissions[PermissionTypes.PROMPTS]).toEqual({ + CREATE: true, + USE: true, + SHARE: false, + }); + }); + + it('should handle non-existent roles', async () => { + await updateAccessPermissions('NON_EXISTENT_ROLE', { + [PermissionTypes.PROMPTS]: { CREATE: true }, + }); + const role = await Role.findOne({ name: 'NON_EXISTENT_ROLE' }); + expect(role).toBeNull(); + }); + + it('should update only specified permissions', async () => { + await new Role({ + name: SystemRoles.USER, + permissions: { + [PermissionTypes.PROMPTS]: { + CREATE: true, + USE: true, + SHARE: false, + }, + }, + }).save(); + + await updateAccessPermissions(SystemRoles.USER, { + [PermissionTypes.PROMPTS]: { SHARE: true }, + }); + + const updatedRole = await getRoleByName(SystemRoles.USER); + expect(updatedRole.permissions[PermissionTypes.PROMPTS]).toEqual({ + CREATE: true, + USE: true, + SHARE: true, + }); + }); + + it('should handle partial updates', async () => { + await new Role({ + name: SystemRoles.USER, + permissions: { + [PermissionTypes.PROMPTS]: { + CREATE: true, + USE: true, + SHARE: false, + }, + }, + }).save(); + + await updateAccessPermissions(SystemRoles.USER, { + [PermissionTypes.PROMPTS]: { USE: false }, + }); + + const updatedRole = await getRoleByName(SystemRoles.USER); + expect(updatedRole.permissions[PermissionTypes.PROMPTS]).toEqual({ + CREATE: true, + USE: false, + SHARE: false, + }); + }); + + it('should update multiple permission types at once', async () => { + await new Role({ + name: SystemRoles.USER, + permissions: { + [PermissionTypes.PROMPTS]: { CREATE: true, USE: true, SHARE: false }, + [PermissionTypes.BOOKMARKS]: { USE: true }, + }, + }).save(); + + await updateAccessPermissions(SystemRoles.USER, { + [PermissionTypes.PROMPTS]: { USE: false, SHARE: true }, + [PermissionTypes.BOOKMARKS]: { USE: false }, + }); + + const updatedRole = await getRoleByName(SystemRoles.USER); + expect(updatedRole.permissions[PermissionTypes.PROMPTS]).toEqual({ + CREATE: true, + USE: false, + SHARE: true, + }); + expect(updatedRole.permissions[PermissionTypes.BOOKMARKS]).toEqual({ USE: false }); + }); + + it('should handle updates for a single permission type', async () => { + await new Role({ + name: SystemRoles.USER, + permissions: { + [PermissionTypes.PROMPTS]: { CREATE: true, USE: true, SHARE: false }, + }, + }).save(); + + await updateAccessPermissions(SystemRoles.USER, { + [PermissionTypes.PROMPTS]: { USE: false, SHARE: true }, + }); + + const updatedRole = await getRoleByName(SystemRoles.USER); + expect(updatedRole.permissions[PermissionTypes.PROMPTS]).toEqual({ + CREATE: true, + USE: false, + SHARE: true, + }); + }); + + it('should update MULTI_CONVO permissions', async () => { + await new Role({ + name: SystemRoles.USER, + permissions: { + [PermissionTypes.MULTI_CONVO]: { USE: false }, + }, + }).save(); + + await updateAccessPermissions(SystemRoles.USER, { + [PermissionTypes.MULTI_CONVO]: { USE: true }, + }); + + const updatedRole = await getRoleByName(SystemRoles.USER); + expect(updatedRole.permissions[PermissionTypes.MULTI_CONVO]).toEqual({ USE: true }); + }); + + it('should update MULTI_CONVO permissions along with other permission types', async () => { + await new Role({ + name: SystemRoles.USER, + permissions: { + [PermissionTypes.PROMPTS]: { CREATE: true, USE: true, SHARE: false }, + [PermissionTypes.MULTI_CONVO]: { USE: false }, + }, + }).save(); + + await updateAccessPermissions(SystemRoles.USER, { + [PermissionTypes.PROMPTS]: { SHARE: true }, + [PermissionTypes.MULTI_CONVO]: { USE: true }, + }); + + const updatedRole = await getRoleByName(SystemRoles.USER); + expect(updatedRole.permissions[PermissionTypes.PROMPTS]).toEqual({ + CREATE: true, + USE: true, + SHARE: true, + }); + expect(updatedRole.permissions[PermissionTypes.MULTI_CONVO]).toEqual({ USE: true }); + }); + + it('should inherit SHARED_GLOBAL value into SHARE when SHARE is absent from both DB and update', async () => { + // Simulates the startup backfill path: caller sends SHARE_PUBLIC but not SHARE; + // migration should inherit SHARED_GLOBAL to preserve the deployment's sharing intent. + await Role.collection.insertOne({ + name: SystemRoles.USER, + permissions: { + [PermissionTypes.PROMPTS]: { USE: true, CREATE: true, SHARED_GLOBAL: true }, + [PermissionTypes.AGENTS]: { USE: true, CREATE: true, SHARED_GLOBAL: false }, + }, + }); + + await updateAccessPermissions(SystemRoles.USER, { + // No explicit SHARE — migration should inherit from SHARED_GLOBAL + [PermissionTypes.PROMPTS]: { SHARE_PUBLIC: false }, + [PermissionTypes.AGENTS]: { SHARE_PUBLIC: false }, + }); + + const updatedRole = await getRoleByName(SystemRoles.USER); + + // SHARED_GLOBAL=true → SHARE=true (inherited) + expect(updatedRole.permissions[PermissionTypes.PROMPTS].SHARE).toBe(true); + // SHARED_GLOBAL=false → SHARE=false (inherited) + expect(updatedRole.permissions[PermissionTypes.AGENTS].SHARE).toBe(false); + // SHARED_GLOBAL cleaned up + expect(updatedRole.permissions[PermissionTypes.PROMPTS].SHARED_GLOBAL).toBeUndefined(); + expect(updatedRole.permissions[PermissionTypes.AGENTS].SHARED_GLOBAL).toBeUndefined(); + }); + + it('should respect explicit SHARE in update payload and not override it with SHARED_GLOBAL', async () => { + // Caller explicitly passes SHARE: false even though SHARED_GLOBAL=true in DB. + // The explicit intent must win; migration must not silently overwrite it. + await Role.collection.insertOne({ + name: SystemRoles.USER, + permissions: { + [PermissionTypes.PROMPTS]: { USE: true, SHARED_GLOBAL: true }, + }, + }); + + await updateAccessPermissions(SystemRoles.USER, { + [PermissionTypes.PROMPTS]: { SHARE: false }, // explicit false — should be preserved + }); + + const updatedRole = await getRoleByName(SystemRoles.USER); + + expect(updatedRole.permissions[PermissionTypes.PROMPTS].SHARE).toBe(false); + expect(updatedRole.permissions[PermissionTypes.PROMPTS].SHARED_GLOBAL).toBeUndefined(); + }); + + it('should migrate SHARED_GLOBAL to SHARE even when the permType is not in the update payload', async () => { + // Bug #2 regression: cleanup block removes SHARED_GLOBAL but migration block only + // runs when the permType is in the update payload. Without the fix, SHARE would be + // lost when any other permType (e.g. MULTI_CONVO) is the only thing being updated. + await Role.collection.insertOne({ + name: SystemRoles.USER, + permissions: { + [PermissionTypes.PROMPTS]: { + USE: true, + SHARED_GLOBAL: true, // legacy — NO SHARE present + }, + [PermissionTypes.MULTI_CONVO]: { USE: false }, + }, + }); + + // Only update MULTI_CONVO — PROMPTS is intentionally absent from the payload + await updateAccessPermissions(SystemRoles.USER, { + [PermissionTypes.MULTI_CONVO]: { USE: true }, + }); + + const updatedRole = await getRoleByName(SystemRoles.USER); + + // SHARE should have been inherited from SHARED_GLOBAL, not silently dropped + expect(updatedRole.permissions[PermissionTypes.PROMPTS].SHARE).toBe(true); + // SHARED_GLOBAL should be removed + expect(updatedRole.permissions[PermissionTypes.PROMPTS].SHARED_GLOBAL).toBeUndefined(); + // Original USE should be untouched + expect(updatedRole.permissions[PermissionTypes.PROMPTS].USE).toBe(true); + // The actual update should have applied + expect(updatedRole.permissions[PermissionTypes.MULTI_CONVO].USE).toBe(true); + }); + + it('should remove orphaned SHARED_GLOBAL when SHARE already exists and permType is not in update', async () => { + // Safe cleanup case: SHARE already set, SHARED_GLOBAL is just orphaned noise. + // SHARE must not be changed; SHARED_GLOBAL must be removed. + await Role.collection.insertOne({ + name: SystemRoles.USER, + permissions: { + [PermissionTypes.PROMPTS]: { + USE: true, + SHARE: true, // already migrated + SHARED_GLOBAL: true, // orphaned + }, + [PermissionTypes.MULTI_CONVO]: { USE: false }, + }, + }); + + await updateAccessPermissions(SystemRoles.USER, { + [PermissionTypes.MULTI_CONVO]: { USE: true }, + }); + + const updatedRole = await getRoleByName(SystemRoles.USER); + + expect(updatedRole.permissions[PermissionTypes.PROMPTS].SHARED_GLOBAL).toBeUndefined(); + expect(updatedRole.permissions[PermissionTypes.PROMPTS].SHARE).toBe(true); + expect(updatedRole.permissions[PermissionTypes.MULTI_CONVO].USE).toBe(true); + }); + + it('should not update MULTI_CONVO permissions when no changes are needed', async () => { + await new Role({ + name: SystemRoles.USER, + permissions: { + [PermissionTypes.MULTI_CONVO]: { USE: true }, + }, + }).save(); + + await updateAccessPermissions(SystemRoles.USER, { + [PermissionTypes.MULTI_CONVO]: { USE: true }, + }); + + const updatedRole = await getRoleByName(SystemRoles.USER); + expect(updatedRole.permissions[PermissionTypes.MULTI_CONVO]).toEqual({ USE: true }); + }); +}); + +describe('initializeRoles', () => { + beforeEach(async () => { + await Role.deleteMany({}); + }); + + it('should create default roles if they do not exist', async () => { + await initializeRoles(); + + const adminRole = await getRoleByName(SystemRoles.ADMIN); + const userRole = await getRoleByName(SystemRoles.USER); + + expect(adminRole).toBeTruthy(); + expect(userRole).toBeTruthy(); + + // Check if all permission types exist in the permissions field + Object.values(PermissionTypes).forEach((permType) => { + expect(adminRole.permissions[permType]).toBeDefined(); + expect(userRole.permissions[permType]).toBeDefined(); + }); + + // Example: Check default values for ADMIN role + expect(adminRole.permissions[PermissionTypes.PROMPTS].SHARE).toBe(true); + expect(adminRole.permissions[PermissionTypes.BOOKMARKS].USE).toBe(true); + expect(adminRole.permissions[PermissionTypes.AGENTS].CREATE).toBe(true); + }); + + it('should not modify existing permissions for existing roles', async () => { + const customUserRole = { + name: SystemRoles.USER, + permissions: { + [PermissionTypes.PROMPTS]: { + [Permissions.USE]: false, + [Permissions.CREATE]: true, + [Permissions.SHARE]: true, + }, + [PermissionTypes.BOOKMARKS]: { [Permissions.USE]: false }, + }, + }; + + await new Role(customUserRole).save(); + await initializeRoles(); + + const userRole = await getRoleByName(SystemRoles.USER); + expect(userRole.permissions[PermissionTypes.PROMPTS]).toEqual( + customUserRole.permissions[PermissionTypes.PROMPTS], + ); + expect(userRole.permissions[PermissionTypes.BOOKMARKS]).toEqual( + customUserRole.permissions[PermissionTypes.BOOKMARKS], + ); + expect(userRole.permissions[PermissionTypes.AGENTS]).toBeDefined(); + }); + + it('should add new permission types to existing roles', async () => { + const partialUserRole = { + name: SystemRoles.USER, + permissions: { + [PermissionTypes.PROMPTS]: + roleDefaults[SystemRoles.USER].permissions[PermissionTypes.PROMPTS], + [PermissionTypes.BOOKMARKS]: + roleDefaults[SystemRoles.USER].permissions[PermissionTypes.BOOKMARKS], + }, + }; + + await new Role(partialUserRole).save(); + await initializeRoles(); + + const userRole = await getRoleByName(SystemRoles.USER); + expect(userRole.permissions[PermissionTypes.AGENTS]).toBeDefined(); + expect(userRole.permissions[PermissionTypes.AGENTS].CREATE).toBeDefined(); + expect(userRole.permissions[PermissionTypes.AGENTS].USE).toBeDefined(); + expect(userRole.permissions[PermissionTypes.AGENTS].SHARE).toBeDefined(); + }); + + it('should handle multiple runs without duplicating or modifying data', async () => { + await initializeRoles(); + await initializeRoles(); + + const adminRoles = await Role.find({ name: SystemRoles.ADMIN }); + const userRoles = await Role.find({ name: SystemRoles.USER }); + + expect(adminRoles).toHaveLength(1); + expect(userRoles).toHaveLength(1); + + const adminPerms = adminRoles[0].toObject().permissions; + const userPerms = userRoles[0].toObject().permissions; + Object.values(PermissionTypes).forEach((permType) => { + expect(adminPerms[permType]).toBeDefined(); + expect(userPerms[permType]).toBeDefined(); + }); + }); + + it('should update roles with missing permission types from roleDefaults', async () => { + const partialAdminRole = { + name: SystemRoles.ADMIN, + permissions: { + [PermissionTypes.PROMPTS]: { + [Permissions.USE]: false, + [Permissions.CREATE]: false, + [Permissions.SHARE]: false, + }, + [PermissionTypes.BOOKMARKS]: + roleDefaults[SystemRoles.ADMIN].permissions[PermissionTypes.BOOKMARKS], + }, + }; + + await new Role(partialAdminRole).save(); + await initializeRoles(); + + const adminRole = await getRoleByName(SystemRoles.ADMIN); + expect(adminRole.permissions[PermissionTypes.PROMPTS]).toEqual( + partialAdminRole.permissions[PermissionTypes.PROMPTS], + ); + expect(adminRole.permissions[PermissionTypes.AGENTS]).toBeDefined(); + expect(adminRole.permissions[PermissionTypes.AGENTS].CREATE).toBeDefined(); + expect(adminRole.permissions[PermissionTypes.AGENTS].USE).toBeDefined(); + expect(adminRole.permissions[PermissionTypes.AGENTS].SHARE).toBeDefined(); + }); + + it('should include MULTI_CONVO permissions when creating default roles', async () => { + await initializeRoles(); + + const adminRole = await getRoleByName(SystemRoles.ADMIN); + const userRole = await getRoleByName(SystemRoles.USER); + + expect(adminRole.permissions[PermissionTypes.MULTI_CONVO]).toBeDefined(); + expect(userRole.permissions[PermissionTypes.MULTI_CONVO]).toBeDefined(); + expect(adminRole.permissions[PermissionTypes.MULTI_CONVO].USE).toBe( + roleDefaults[SystemRoles.ADMIN].permissions[PermissionTypes.MULTI_CONVO].USE, + ); + expect(userRole.permissions[PermissionTypes.MULTI_CONVO].USE).toBe( + roleDefaults[SystemRoles.USER].permissions[PermissionTypes.MULTI_CONVO].USE, + ); + }); + + it('should add MULTI_CONVO permissions to existing roles without them', async () => { + const partialUserRole = { + name: SystemRoles.USER, + permissions: { + [PermissionTypes.PROMPTS]: + roleDefaults[SystemRoles.USER].permissions[PermissionTypes.PROMPTS], + [PermissionTypes.BOOKMARKS]: + roleDefaults[SystemRoles.USER].permissions[PermissionTypes.BOOKMARKS], + }, + }; + + await new Role(partialUserRole).save(); + await initializeRoles(); + + const userRole = await getRoleByName(SystemRoles.USER); + expect(userRole.permissions[PermissionTypes.MULTI_CONVO]).toBeDefined(); + expect(userRole.permissions[PermissionTypes.MULTI_CONVO].USE).toBeDefined(); + }); +}); diff --git a/api/models/ToolCall.js b/api/models/ToolCall.js new file mode 100644 index 0000000000..689386114b --- /dev/null +++ b/api/models/ToolCall.js @@ -0,0 +1,96 @@ +const { ToolCall } = require('~/db/models'); + +/** + * Create a new tool call + * @param {IToolCallData} toolCallData - The tool call data + * @returns {Promise} The created tool call document + */ +async function createToolCall(toolCallData) { + try { + return await ToolCall.create(toolCallData); + } catch (error) { + throw new Error(`Error creating tool call: ${error.message}`); + } +} + +/** + * Get a tool call by ID + * @param {string} id - The tool call document ID + * @returns {Promise} The tool call document or null if not found + */ +async function getToolCallById(id) { + try { + return await ToolCall.findById(id).lean(); + } catch (error) { + throw new Error(`Error fetching tool call: ${error.message}`); + } +} + +/** + * Get tool calls by message ID and user + * @param {string} messageId - The message ID + * @param {string} userId - The user's ObjectId + * @returns {Promise} Array of tool call documents + */ +async function getToolCallsByMessage(messageId, userId) { + try { + return await ToolCall.find({ messageId, user: userId }).lean(); + } catch (error) { + throw new Error(`Error fetching tool calls: ${error.message}`); + } +} + +/** + * Get tool calls by conversation ID and user + * @param {string} conversationId - The conversation ID + * @param {string} userId - The user's ObjectId + * @returns {Promise} Array of tool call documents + */ +async function getToolCallsByConvo(conversationId, userId) { + try { + return await ToolCall.find({ conversationId, user: userId }).lean(); + } catch (error) { + throw new Error(`Error fetching tool calls: ${error.message}`); + } +} + +/** + * Update a tool call + * @param {string} id - The tool call document ID + * @param {Partial} updateData - The data to update + * @returns {Promise} The updated tool call document or null if not found + */ +async function updateToolCall(id, updateData) { + try { + return await ToolCall.findByIdAndUpdate(id, updateData, { new: true }).lean(); + } catch (error) { + throw new Error(`Error updating tool call: ${error.message}`); + } +} + +/** + * Delete a tool call + * @param {string} userId - The related user's ObjectId + * @param {string} [conversationId] - The tool call conversation ID + * @returns {Promise<{ ok?: number; n?: number; deletedCount?: number }>} The result of the delete operation + */ +async function deleteToolCalls(userId, conversationId) { + try { + const query = { user: userId }; + if (conversationId) { + query.conversationId = conversationId; + } + return await ToolCall.deleteMany(query); + } catch (error) { + throw new Error(`Error deleting tool call: ${error.message}`); + } +} + +module.exports = { + createToolCall, + updateToolCall, + deleteToolCalls, + getToolCallById, + getToolCallsByConvo, + getToolCallsByMessage, +}; diff --git a/api/models/Transaction.js b/api/models/Transaction.js new file mode 100644 index 0000000000..e553e2bb3b --- /dev/null +++ b/api/models/Transaction.js @@ -0,0 +1,356 @@ +const { logger } = require('@librechat/data-schemas'); +const { getMultiplier, getCacheMultiplier } = require('./tx'); +const { Transaction, Balance } = require('~/db/models'); + +const cancelRate = 1.15; + +/** + * Updates a user's token balance based on a transaction using optimistic concurrency control + * without schema changes. Compatible with DocumentDB. + * @async + * @function + * @param {Object} params - The function parameters. + * @param {string|mongoose.Types.ObjectId} params.user - The user ID. + * @param {number} params.incrementValue - The value to increment the balance by (can be negative). + * @param {import('mongoose').UpdateQuery['$set']} [params.setValues] - Optional additional fields to set. + * @returns {Promise} Returns the updated balance document (lean). + * @throws {Error} Throws an error if the update fails after multiple retries. + */ +const updateBalance = async ({ user, incrementValue, setValues }) => { + let maxRetries = 10; // Number of times to retry on conflict + let delay = 50; // Initial retry delay in ms + let lastError = null; + + for (let attempt = 1; attempt <= maxRetries; attempt++) { + let currentBalanceDoc; + try { + // 1. Read the current document state + currentBalanceDoc = await Balance.findOne({ user }).lean(); + const currentCredits = currentBalanceDoc ? currentBalanceDoc.tokenCredits : 0; + + // 2. Calculate the desired new state + const potentialNewCredits = currentCredits + incrementValue; + const newCredits = Math.max(0, potentialNewCredits); // Ensure balance doesn't go below zero + + // 3. Prepare the update payload + const updatePayload = { + $set: { + tokenCredits: newCredits, + ...(setValues || {}), // Merge other values to set + }, + }; + + // 4. Attempt the conditional update or upsert + let updatedBalance = null; + if (currentBalanceDoc) { + // --- Document Exists: Perform Conditional Update --- + // Try to update only if the tokenCredits match the value we read (currentCredits) + updatedBalance = await Balance.findOneAndUpdate( + { + user: user, + tokenCredits: currentCredits, // Optimistic lock: condition based on the read value + }, + updatePayload, + { + new: true, // Return the modified document + // lean: true, // .lean() is applied after query execution in Mongoose >= 6 + }, + ).lean(); // Use lean() for plain JS object + + if (updatedBalance) { + // Success! The update was applied based on the expected current state. + return updatedBalance; + } + // If updatedBalance is null, it means tokenCredits changed between read and write (conflict). + lastError = new Error(`Concurrency conflict for user ${user} on attempt ${attempt}.`); + // Proceed to retry logic below. + } else { + // --- Document Does Not Exist: Perform Conditional Upsert --- + // Try to insert the document, but only if it still doesn't exist. + // Using tokenCredits: {$exists: false} helps prevent race conditions where + // another process creates the doc between our findOne and findOneAndUpdate. + try { + updatedBalance = await Balance.findOneAndUpdate( + { + user: user, + // Attempt to match only if the document doesn't exist OR was just created + // without tokenCredits (less likely but possible). A simple { user } filter + // might also work, relying on the retry for conflicts. + // Let's use a simpler filter and rely on retry for races. + // tokenCredits: { $exists: false } // This condition might be too strict if doc exists with 0 credits + }, + updatePayload, + { + upsert: true, // Create if doesn't exist + new: true, // Return the created/updated document + // setDefaultsOnInsert: true, // Ensure schema defaults are applied on insert + // lean: true, + }, + ).lean(); + + if (updatedBalance) { + // Upsert succeeded (likely created the document) + return updatedBalance; + } + // If null, potentially a rare race condition during upsert. Retry should handle it. + lastError = new Error( + `Upsert race condition suspected for user ${user} on attempt ${attempt}.`, + ); + } catch (error) { + if (error.code === 11000) { + // E11000 duplicate key error on index + // This means another process created the document *just* before our upsert. + // It's a concurrency conflict during creation. We should retry. + lastError = error; // Store the error + // Proceed to retry logic below. + } else { + // Different error, rethrow + throw error; + } + } + } // End if/else (document exists?) + } catch (error) { + // Catch errors from findOne or unexpected findOneAndUpdate errors + logger.error(`[updateBalance] Error during attempt ${attempt} for user ${user}:`, error); + lastError = error; // Store the error + // Consider stopping retries for non-transient errors, but for now, we retry. + } + + // If we reached here, it means the update failed (conflict or error), wait and retry + if (attempt < maxRetries) { + const jitter = Math.random() * delay * 0.5; // Add jitter to delay + await new Promise((resolve) => setTimeout(resolve, delay + jitter)); + delay = Math.min(delay * 2, 2000); // Exponential backoff with cap + } + } // End for loop (retries) + + // If loop finishes without success, throw the last encountered error or a generic one + logger.error( + `[updateBalance] Failed to update balance for user ${user} after ${maxRetries} attempts.`, + ); + throw ( + lastError || + new Error( + `Failed to update balance for user ${user} after maximum retries due to persistent conflicts.`, + ) + ); +}; + +/** Method to calculate and set the tokenValue for a transaction */ +function calculateTokenValue(txn) { + const { valueKey, tokenType, model, endpointTokenConfig, inputTokenCount } = txn; + const multiplier = Math.abs( + getMultiplier({ valueKey, tokenType, model, endpointTokenConfig, inputTokenCount }), + ); + txn.rate = multiplier; + txn.tokenValue = txn.rawAmount * multiplier; + if (txn.context && txn.tokenType === 'completion' && txn.context === 'incomplete') { + txn.tokenValue = Math.ceil(txn.tokenValue * cancelRate); + txn.rate *= cancelRate; + } +} + +/** + * New static method to create an auto-refill transaction that does NOT trigger a balance update. + * @param {object} txData - Transaction data. + * @param {string} txData.user - The user ID. + * @param {string} txData.tokenType - The type of token. + * @param {string} txData.context - The context of the transaction. + * @param {number} txData.rawAmount - The raw amount of tokens. + * @returns {Promise} - The created transaction. + */ +async function createAutoRefillTransaction(txData) { + if (txData.rawAmount != null && isNaN(txData.rawAmount)) { + return; + } + const transaction = new Transaction(txData); + transaction.endpointTokenConfig = txData.endpointTokenConfig; + transaction.inputTokenCount = txData.inputTokenCount; + calculateTokenValue(transaction); + await transaction.save(); + + const balanceResponse = await updateBalance({ + user: transaction.user, + incrementValue: txData.rawAmount, + setValues: { lastRefill: new Date() }, + }); + const result = { + rate: transaction.rate, + user: transaction.user.toString(), + balance: balanceResponse.tokenCredits, + }; + logger.debug('[Balance.check] Auto-refill performed', result); + result.transaction = transaction; + return result; +} + +/** + * Static method to create a transaction and update the balance + * @param {txData} _txData - Transaction data. + */ +async function createTransaction(_txData) { + const { balance, transactions, ...txData } = _txData; + if (txData.rawAmount != null && isNaN(txData.rawAmount)) { + return; + } + + if (transactions?.enabled === false) { + return; + } + + const transaction = new Transaction(txData); + transaction.endpointTokenConfig = txData.endpointTokenConfig; + transaction.inputTokenCount = txData.inputTokenCount; + calculateTokenValue(transaction); + + await transaction.save(); + if (!balance?.enabled) { + return; + } + + let incrementValue = transaction.tokenValue; + const balanceResponse = await updateBalance({ + user: transaction.user, + incrementValue, + }); + + return { + rate: transaction.rate, + user: transaction.user.toString(), + balance: balanceResponse.tokenCredits, + [transaction.tokenType]: incrementValue, + }; +} + +/** + * Static method to create a structured transaction and update the balance + * @param {txData} _txData - Transaction data. + */ +async function createStructuredTransaction(_txData) { + const { balance, transactions, ...txData } = _txData; + if (transactions?.enabled === false) { + return; + } + + const transaction = new Transaction(txData); + transaction.endpointTokenConfig = txData.endpointTokenConfig; + transaction.inputTokenCount = txData.inputTokenCount; + + calculateStructuredTokenValue(transaction); + + await transaction.save(); + + if (!balance?.enabled) { + return; + } + + let incrementValue = transaction.tokenValue; + + const balanceResponse = await updateBalance({ + user: transaction.user, + incrementValue, + }); + + return { + rate: transaction.rate, + user: transaction.user.toString(), + balance: balanceResponse.tokenCredits, + [transaction.tokenType]: incrementValue, + }; +} + +/** Method to calculate token value for structured tokens */ +function calculateStructuredTokenValue(txn) { + if (!txn.tokenType) { + txn.tokenValue = txn.rawAmount; + return; + } + + const { model, endpointTokenConfig, inputTokenCount } = txn; + + if (txn.tokenType === 'prompt') { + const inputMultiplier = getMultiplier({ + tokenType: 'prompt', + model, + endpointTokenConfig, + inputTokenCount, + }); + const writeMultiplier = + getCacheMultiplier({ cacheType: 'write', model, endpointTokenConfig }) ?? inputMultiplier; + const readMultiplier = + getCacheMultiplier({ cacheType: 'read', model, endpointTokenConfig }) ?? inputMultiplier; + + txn.rateDetail = { + input: inputMultiplier, + write: writeMultiplier, + read: readMultiplier, + }; + + const totalPromptTokens = + Math.abs(txn.inputTokens || 0) + + Math.abs(txn.writeTokens || 0) + + Math.abs(txn.readTokens || 0); + + if (totalPromptTokens > 0) { + txn.rate = + (Math.abs(inputMultiplier * (txn.inputTokens || 0)) + + Math.abs(writeMultiplier * (txn.writeTokens || 0)) + + Math.abs(readMultiplier * (txn.readTokens || 0))) / + totalPromptTokens; + } else { + txn.rate = Math.abs(inputMultiplier); // Default to input rate if no tokens + } + + txn.tokenValue = -( + Math.abs(txn.inputTokens || 0) * inputMultiplier + + Math.abs(txn.writeTokens || 0) * writeMultiplier + + Math.abs(txn.readTokens || 0) * readMultiplier + ); + + txn.rawAmount = -totalPromptTokens; + } else if (txn.tokenType === 'completion') { + const multiplier = getMultiplier({ + tokenType: txn.tokenType, + model, + endpointTokenConfig, + inputTokenCount, + }); + txn.rate = Math.abs(multiplier); + txn.tokenValue = -Math.abs(txn.rawAmount) * multiplier; + txn.rawAmount = -Math.abs(txn.rawAmount); + } + + if (txn.context && txn.tokenType === 'completion' && txn.context === 'incomplete') { + txn.tokenValue = Math.ceil(txn.tokenValue * cancelRate); + txn.rate *= cancelRate; + if (txn.rateDetail) { + txn.rateDetail = Object.fromEntries( + Object.entries(txn.rateDetail).map(([k, v]) => [k, v * cancelRate]), + ); + } + } +} + +/** + * Queries and retrieves transactions based on a given filter. + * @async + * @function getTransactions + * @param {Object} filter - MongoDB filter object to apply when querying transactions. + * @returns {Promise} A promise that resolves to an array of matched transactions. + * @throws {Error} Throws an error if querying the database fails. + */ +async function getTransactions(filter) { + try { + return await Transaction.find(filter).lean(); + } catch (error) { + logger.error('Error querying transactions:', error); + throw error; + } +} + +module.exports = { + getTransactions, + createTransaction, + createAutoRefillTransaction, + createStructuredTransaction, +}; diff --git a/packages/data-schemas/src/methods/transaction.spec.ts b/api/models/Transaction.spec.js similarity index 70% rename from packages/data-schemas/src/methods/transaction.spec.ts rename to api/models/Transaction.spec.js index ee7df36c57..4b478d4dc3 100644 --- a/packages/data-schemas/src/methods/transaction.spec.ts +++ b/api/models/Transaction.spec.js @@ -1,63 +1,14 @@ -import mongoose from 'mongoose'; -import { MongoMemoryServer } from 'mongodb-memory-server'; -import type { ITransaction } from '~/schema/transaction'; -import type { TxData } from './transaction'; -import type { IBalance } from '..'; -import { createTxMethods, tokenValues, premiumTokenValues } from './tx'; -import { matchModelName, findMatchingPattern } from './test-helpers'; -import { createSpendTokensMethods } from './spendTokens'; -import { createTransactionMethods } from './transaction'; -import { createModels } from '~/models'; - -jest.mock('~/config/winston', () => ({ - error: jest.fn(), - warn: jest.fn(), - info: jest.fn(), - debug: jest.fn(), -})); - -let mongoServer: InstanceType; -let Balance: mongoose.Model; -let Transaction: mongoose.Model; -let spendTokens: ReturnType['spendTokens']; -let spendStructuredTokens: ReturnType['spendStructuredTokens']; -let createTransaction: ReturnType['createTransaction']; -let createStructuredTransaction: ReturnType< - typeof createTransactionMethods ->['createStructuredTransaction']; -let getMultiplier: ReturnType['getMultiplier']; -let getCacheMultiplier: ReturnType['getCacheMultiplier']; +const mongoose = require('mongoose'); +const { MongoMemoryServer } = require('mongodb-memory-server'); +const { spendTokens, spendStructuredTokens } = require('./spendTokens'); +const { getMultiplier, getCacheMultiplier, premiumTokenValues, tokenValues } = require('./tx'); +const { createTransaction, createStructuredTransaction } = require('./Transaction'); +const { Balance, Transaction } = require('~/db/models'); +let mongoServer; beforeAll(async () => { mongoServer = await MongoMemoryServer.create(); const mongoUri = mongoServer.getUri(); - - // Register models - const models = createModels(mongoose); - Object.assign(mongoose.models, models); - - Balance = mongoose.models.Balance; - Transaction = mongoose.models.Transaction; - - // Create methods from factories (following the chain in methods/index.ts) - const txMethods = createTxMethods(mongoose, { matchModelName, findMatchingPattern }); - getMultiplier = txMethods.getMultiplier; - getCacheMultiplier = txMethods.getCacheMultiplier; - - const transactionMethods = createTransactionMethods(mongoose, { - getMultiplier: txMethods.getMultiplier, - getCacheMultiplier: txMethods.getCacheMultiplier, - }); - createTransaction = transactionMethods.createTransaction; - createStructuredTransaction = transactionMethods.createStructuredTransaction; - - const spendMethods = createSpendTokensMethods(mongoose, { - createTransaction: transactionMethods.createTransaction, - createStructuredTransaction: transactionMethods.createStructuredTransaction, - }); - spendTokens = spendMethods.spendTokens; - spendStructuredTokens = spendMethods.spendStructuredTokens; - await mongoose.connect(mongoUri); }); @@ -102,7 +53,7 @@ describe('Regular Token Spending Tests', () => { const expectedTotalCost = 100 * promptMultiplier + 50 * completionMultiplier; const expectedBalance = initialBalance - expectedTotalCost; - expect(updatedBalance?.tokenCredits).toBeCloseTo(expectedBalance, 0); + expect(updatedBalance.tokenCredits).toBeCloseTo(expectedBalance, 0); }); test('spendTokens should handle zero completion tokens', async () => { @@ -133,7 +84,7 @@ describe('Regular Token Spending Tests', () => { const updatedBalance = await Balance.findOne({ user: userId }); const promptMultiplier = getMultiplier({ model, tokenType: 'prompt' }); const expectedCost = 100 * promptMultiplier; - expect(updatedBalance?.tokenCredits).toBeCloseTo(initialBalance - expectedCost, 0); + expect(updatedBalance.tokenCredits).toBeCloseTo(initialBalance - expectedCost, 0); }); test('spendTokens should handle undefined token counts', async () => { @@ -186,7 +137,7 @@ describe('Regular Token Spending Tests', () => { const updatedBalance = await Balance.findOne({ user: userId }); const promptMultiplier = getMultiplier({ model, tokenType: 'prompt' }); const expectedCost = 100 * promptMultiplier; - expect(updatedBalance?.tokenCredits).toBeCloseTo(initialBalance - expectedCost, 0); + expect(updatedBalance.tokenCredits).toBeCloseTo(initialBalance - expectedCost, 0); }); test('spendTokens should not update balance when balance feature is disabled', async () => { @@ -215,7 +166,7 @@ describe('Regular Token Spending Tests', () => { // Assert: Balance should remain unchanged. const updatedBalance = await Balance.findOne({ user: userId }); - expect(updatedBalance?.tokenCredits).toBe(initialBalance); + expect(updatedBalance.tokenCredits).toBe(initialBalance); }); }); @@ -247,8 +198,8 @@ describe('Structured Token Spending Tests', () => { const promptMultiplier = getMultiplier({ model, tokenType: 'prompt' }); const completionMultiplier = getMultiplier({ model, tokenType: 'completion' }); - const writeMultiplier = getCacheMultiplier({ model, cacheType: 'write' }) ?? promptMultiplier; - const readMultiplier = getCacheMultiplier({ model, cacheType: 'read' }) ?? promptMultiplier; + const writeMultiplier = getCacheMultiplier({ model, cacheType: 'write' }); + const readMultiplier = getCacheMultiplier({ model, cacheType: 'read' }); // Act const result = await spendStructuredTokens(txData, tokenUsage); @@ -263,18 +214,16 @@ describe('Structured Token Spending Tests', () => { const expectedBalance = initialBalance - expectedTotalCost; // Assert - expect(result?.completion?.balance).toBeLessThan(initialBalance); + expect(result.completion.balance).toBeLessThan(initialBalance); const allowedDifference = 100; - expect(Math.abs((result?.completion?.balance ?? 0) - expectedBalance)).toBeLessThan( - allowedDifference, - ); - const balanceDecrease = initialBalance - (result?.completion?.balance ?? 0); + expect(Math.abs(result.completion.balance - expectedBalance)).toBeLessThan(allowedDifference); + const balanceDecrease = initialBalance - result.completion.balance; expect(balanceDecrease).toBeCloseTo(expectedTotalCost, 0); const expectedPromptTokenValue = -expectedPromptCost; const expectedCompletionTokenValue = -expectedCompletionCost; - expect(result?.prompt?.prompt).toBeCloseTo(expectedPromptTokenValue, 1); - expect(result?.completion?.completion).toBe(expectedCompletionTokenValue); + expect(result.prompt.prompt).toBeCloseTo(expectedPromptTokenValue, 1); + expect(result.completion.completion).toBe(expectedCompletionTokenValue); }); test('should handle zero completion tokens in structured spending', async () => { @@ -307,7 +256,7 @@ describe('Structured Token Spending Tests', () => { // Assert expect(result.prompt).toBeDefined(); expect(result.completion).toBeUndefined(); - expect(result?.prompt?.prompt).toBeLessThan(0); + expect(result.prompt.prompt).toBeLessThan(0); }); test('should handle only prompt tokens in structured spending', async () => { @@ -339,7 +288,7 @@ describe('Structured Token Spending Tests', () => { // Assert expect(result.prompt).toBeDefined(); expect(result.completion).toBeUndefined(); - expect(result?.prompt?.prompt).toBeLessThan(0); + expect(result.prompt.prompt).toBeLessThan(0); }); test('should handle undefined token counts in structured spending', async () => { @@ -398,7 +347,7 @@ describe('Structured Token Spending Tests', () => { // Assert: // (Assuming a multiplier for completion of 15 and a cancel rate of 1.15 as noted in the original test.) - expect(result?.completion?.completion).toBeCloseTo(-50 * 15 * 1.15, 0); + expect(result.completion.completion).toBeCloseTo(-50 * 15 * 1.15, 0); }); }); @@ -410,7 +359,7 @@ describe('NaN Handling Tests', () => { await Balance.create({ user: userId, tokenCredits: initialBalance }); const model = 'gpt-3.5-turbo'; - const txData: TxData = { + const txData = { user: userId, conversationId: 'test-conversation-id', model, @@ -427,7 +376,7 @@ describe('NaN Handling Tests', () => { // Assert: No transaction should be created and balance remains unchanged. expect(result).toBeUndefined(); const balance = await Balance.findOne({ user: userId }); - expect(balance?.tokenCredits).toBe(initialBalance); + expect(balance.tokenCredits).toBe(initialBalance); }); }); @@ -439,7 +388,7 @@ describe('Transactions Config Tests', () => { await Balance.create({ user: userId, tokenCredits: initialBalance }); const model = 'gpt-3.5-turbo'; - const txData: TxData = { + const txData = { user: userId, conversationId: 'test-conversation-id', model, @@ -458,7 +407,7 @@ describe('Transactions Config Tests', () => { const transactions = await Transaction.find({ user: userId }); expect(transactions).toHaveLength(0); const balance = await Balance.findOne({ user: userId }); - expect(balance?.tokenCredits).toBe(initialBalance); + expect(balance.tokenCredits).toBe(initialBalance); }); test('createTransaction should save when transactions.enabled is true', async () => { @@ -468,7 +417,7 @@ describe('Transactions Config Tests', () => { await Balance.create({ user: userId, tokenCredits: initialBalance }); const model = 'gpt-3.5-turbo'; - const txData: TxData = { + const txData = { user: userId, conversationId: 'test-conversation-id', model, @@ -485,7 +434,7 @@ describe('Transactions Config Tests', () => { // Assert: Transaction should be created expect(result).toBeDefined(); - expect(result?.balance).toBeLessThan(initialBalance); + expect(result.balance).toBeLessThan(initialBalance); const transactions = await Transaction.find({ user: userId }); expect(transactions).toHaveLength(1); expect(transactions[0].rawAmount).toBe(-100); @@ -498,7 +447,7 @@ describe('Transactions Config Tests', () => { await Balance.create({ user: userId, tokenCredits: initialBalance }); const model = 'gpt-3.5-turbo'; - const txData: TxData = { + const txData = { user: userId, conversationId: 'test-conversation-id', model, @@ -515,7 +464,7 @@ describe('Transactions Config Tests', () => { // Assert: Transaction should be created (backward compatibility) expect(result).toBeDefined(); - expect(result?.balance).toBeLessThan(initialBalance); + expect(result.balance).toBeLessThan(initialBalance); const transactions = await Transaction.find({ user: userId }); expect(transactions).toHaveLength(1); }); @@ -527,7 +476,7 @@ describe('Transactions Config Tests', () => { await Balance.create({ user: userId, tokenCredits: initialBalance }); const model = 'gpt-3.5-turbo'; - const txData: TxData = { + const txData = { user: userId, conversationId: 'test-conversation-id', model, @@ -548,7 +497,7 @@ describe('Transactions Config Tests', () => { expect(transactions).toHaveLength(1); expect(transactions[0].rawAmount).toBe(-100); const balance = await Balance.findOne({ user: userId }); - expect(balance?.tokenCredits).toBe(initialBalance); + expect(balance.tokenCredits).toBe(initialBalance); }); test('createStructuredTransaction should not save when transactions.enabled is false', async () => { @@ -558,7 +507,7 @@ describe('Transactions Config Tests', () => { await Balance.create({ user: userId, tokenCredits: initialBalance }); const model = 'claude-3-5-sonnet'; - const txData: TxData = { + const txData = { user: userId, conversationId: 'test-conversation-id', model, @@ -578,7 +527,7 @@ describe('Transactions Config Tests', () => { const transactions = await Transaction.find({ user: userId }); expect(transactions).toHaveLength(0); const balance = await Balance.findOne({ user: userId }); - expect(balance?.tokenCredits).toBe(initialBalance); + expect(balance.tokenCredits).toBe(initialBalance); }); test('createStructuredTransaction should save transaction but not update balance when balance is disabled but transactions enabled', async () => { @@ -588,7 +537,7 @@ describe('Transactions Config Tests', () => { await Balance.create({ user: userId, tokenCredits: initialBalance }); const model = 'claude-3-5-sonnet'; - const txData: TxData = { + const txData = { user: userId, conversationId: 'test-conversation-id', model, @@ -612,7 +561,7 @@ describe('Transactions Config Tests', () => { expect(transactions[0].writeTokens).toBe(-100); expect(transactions[0].readTokens).toBe(-5); const balance = await Balance.findOne({ user: userId }); - expect(balance?.tokenCredits).toBe(initialBalance); + expect(balance.tokenCredits).toBe(initialBalance); }); }); @@ -636,11 +585,11 @@ describe('calculateTokenValue Edge Cases', () => { }); const expectedRate = getMultiplier({ model, tokenType: 'prompt' }); - expect(result?.rate).toBe(expectedRate); + expect(result.rate).toBe(expectedRate); const tx = await Transaction.findOne({ user: userId }); - expect(tx?.tokenValue).toBe(-promptTokens * expectedRate); - expect(tx?.rate).toBe(expectedRate); + expect(tx.tokenValue).toBe(-promptTokens * expectedRate); + expect(tx.rate).toBe(expectedRate); }); test('should derive valueKey and apply correct rate for an unknown model with tokenType', async () => { @@ -659,9 +608,9 @@ describe('calculateTokenValue Edge Cases', () => { }); const tx = await Transaction.findOne({ user: userId }); - expect(tx?.rate).toBeDefined(); - expect(tx?.rate).toBeGreaterThan(0); - expect(tx?.tokenValue).toBe((tx?.rawAmount ?? 0) * (tx?.rate ?? 0)); + expect(tx.rate).toBeDefined(); + expect(tx.rate).toBeGreaterThan(0); + expect(tx.tokenValue).toBe(tx.rawAmount * tx.rate); }); test('should correctly apply model-derived multiplier without valueKey for completion', async () => { @@ -684,10 +633,10 @@ describe('calculateTokenValue Edge Cases', () => { const expectedRate = getMultiplier({ model, tokenType: 'completion' }); expect(expectedRate).toBe(tokenValues[model].completion); - expect(result?.rate).toBe(expectedRate); + expect(result.rate).toBe(expectedRate); const updatedBalance = await Balance.findOne({ user: userId }); - expect(updatedBalance?.tokenCredits).toBeCloseTo( + expect(updatedBalance.tokenCredits).toBeCloseTo( initialBalance - completionTokens * expectedRate, 0, ); @@ -721,7 +670,7 @@ describe('Premium Token Pricing Integration Tests', () => { promptTokens * standardPromptRate + completionTokens * standardCompletionRate; const updatedBalance = await Balance.findOne({ user: userId }); - expect(updatedBalance?.tokenCredits).toBeCloseTo(initialBalance - expectedCost, 0); + expect(updatedBalance.tokenCredits).toBeCloseTo(initialBalance - expectedCost, 0); }); test('spendTokens should apply premium pricing when prompt tokens exceed premium threshold', async () => { @@ -750,7 +699,7 @@ describe('Premium Token Pricing Integration Tests', () => { promptTokens * premiumPromptRate + completionTokens * premiumCompletionRate; const updatedBalance = await Balance.findOne({ user: userId }); - expect(updatedBalance?.tokenCredits).toBeCloseTo(initialBalance - expectedCost, 0); + expect(updatedBalance.tokenCredits).toBeCloseTo(initialBalance - expectedCost, 0); }); test('spendTokens should apply standard pricing at exactly the premium threshold', async () => { @@ -779,7 +728,7 @@ describe('Premium Token Pricing Integration Tests', () => { promptTokens * standardPromptRate + completionTokens * standardCompletionRate; const updatedBalance = await Balance.findOne({ user: userId }); - expect(updatedBalance?.tokenCredits).toBeCloseTo(initialBalance - expectedCost, 0); + expect(updatedBalance.tokenCredits).toBeCloseTo(initialBalance - expectedCost, 0); }); test('spendStructuredTokens should apply premium pricing when total input tokens exceed threshold', async () => { @@ -813,13 +762,8 @@ describe('Premium Token Pricing Integration Tests', () => { const premiumPromptRate = premiumTokenValues[model].prompt; const premiumCompletionRate = premiumTokenValues[model].completion; - const promptMultiplier = getMultiplier({ - model, - tokenType: 'prompt', - inputTokenCount: totalInput, - }); - const writeMultiplier = getCacheMultiplier({ model, cacheType: 'write' }) ?? promptMultiplier; - const readMultiplier = getCacheMultiplier({ model, cacheType: 'read' }) ?? promptMultiplier; + const writeMultiplier = getCacheMultiplier({ model, cacheType: 'write' }); + const readMultiplier = getCacheMultiplier({ model, cacheType: 'read' }); const expectedPromptCost = tokenUsage.promptTokens.input * premiumPromptRate + @@ -830,7 +774,7 @@ describe('Premium Token Pricing Integration Tests', () => { const updatedBalance = await Balance.findOne({ user: userId }); expect(totalInput).toBeGreaterThan(premiumTokenValues[model].threshold); - expect(updatedBalance?.tokenCredits).toBeCloseTo(initialBalance - expectedTotalCost, 0); + expect(updatedBalance.tokenCredits).toBeCloseTo(initialBalance - expectedTotalCost, 0); }); test('spendStructuredTokens should apply standard pricing when total input tokens are below threshold', async () => { @@ -864,13 +808,8 @@ describe('Premium Token Pricing Integration Tests', () => { const standardPromptRate = tokenValues[model].prompt; const standardCompletionRate = tokenValues[model].completion; - const promptMultiplier = getMultiplier({ - model, - tokenType: 'prompt', - inputTokenCount: totalInput, - }); - const writeMultiplier = getCacheMultiplier({ model, cacheType: 'write' }) ?? promptMultiplier; - const readMultiplier = getCacheMultiplier({ model, cacheType: 'read' }) ?? promptMultiplier; + const writeMultiplier = getCacheMultiplier({ model, cacheType: 'write' }); + const readMultiplier = getCacheMultiplier({ model, cacheType: 'read' }); const expectedPromptCost = tokenUsage.promptTokens.input * standardPromptRate + @@ -881,145 +820,7 @@ describe('Premium Token Pricing Integration Tests', () => { const updatedBalance = await Balance.findOne({ user: userId }); expect(totalInput).toBeLessThanOrEqual(premiumTokenValues[model].threshold); - expect(updatedBalance?.tokenCredits).toBeCloseTo(initialBalance - expectedTotalCost, 0); - }); - - test('spendTokens should apply standard pricing for gemini-3.1-pro-preview below threshold', async () => { - const userId = new mongoose.Types.ObjectId(); - const initialBalance = 100000000; - await Balance.create({ user: userId, tokenCredits: initialBalance }); - - const model = 'gemini-3.1-pro-preview'; - const promptTokens = 100000; - const completionTokens = 500; - - const txData = { - user: userId, - conversationId: 'test-gemini31-below', - model, - context: 'test', - endpointTokenConfig: null, - balance: { enabled: true }, - }; - - await spendTokens(txData, { promptTokens, completionTokens }); - - const standardPromptRate = tokenValues['gemini-3.1'].prompt; - const standardCompletionRate = tokenValues['gemini-3.1'].completion; - const expectedCost = - promptTokens * standardPromptRate + completionTokens * standardCompletionRate; - - const updatedBalance = await Balance.findOne({ user: userId }); - expect(updatedBalance?.tokenCredits).toBeCloseTo(initialBalance - expectedCost, 0); - }); - - test('spendTokens should apply premium pricing for gemini-3.1-pro-preview above threshold', async () => { - const userId = new mongoose.Types.ObjectId(); - const initialBalance = 100000000; - await Balance.create({ user: userId, tokenCredits: initialBalance }); - - const model = 'gemini-3.1-pro-preview'; - const promptTokens = 250000; - const completionTokens = 500; - - const txData = { - user: userId, - conversationId: 'test-gemini31-above', - model, - context: 'test', - endpointTokenConfig: null, - balance: { enabled: true }, - }; - - await spendTokens(txData, { promptTokens, completionTokens }); - - const premiumPromptRate = premiumTokenValues['gemini-3.1'].prompt; - const premiumCompletionRate = premiumTokenValues['gemini-3.1'].completion; - const expectedCost = - promptTokens * premiumPromptRate + completionTokens * premiumCompletionRate; - - const updatedBalance = await Balance.findOne({ user: userId }); - expect(updatedBalance?.tokenCredits).toBeCloseTo(initialBalance - expectedCost, 0); - }); - - test('spendTokens should apply standard pricing for gemini-3.1-pro-preview at exactly the threshold', async () => { - const userId = new mongoose.Types.ObjectId(); - const initialBalance = 100000000; - await Balance.create({ user: userId, tokenCredits: initialBalance }); - - const model = 'gemini-3.1-pro-preview'; - const promptTokens = premiumTokenValues['gemini-3.1'].threshold; - const completionTokens = 500; - - const txData = { - user: userId, - conversationId: 'test-gemini31-exact', - model, - context: 'test', - endpointTokenConfig: null, - balance: { enabled: true }, - }; - - await spendTokens(txData, { promptTokens, completionTokens }); - - const standardPromptRate = tokenValues['gemini-3.1'].prompt; - const standardCompletionRate = tokenValues['gemini-3.1'].completion; - const expectedCost = - promptTokens * standardPromptRate + completionTokens * standardCompletionRate; - - const updatedBalance = await Balance.findOne({ user: userId }); - expect(updatedBalance?.tokenCredits).toBeCloseTo(initialBalance - expectedCost, 0); - }); - - test('spendStructuredTokens should apply premium pricing for gemini-3.1 when total input exceeds threshold', async () => { - const userId = new mongoose.Types.ObjectId(); - const initialBalance = 100000000; - await Balance.create({ user: userId, tokenCredits: initialBalance }); - - const model = 'gemini-3.1-pro-preview'; - const txData = { - user: userId, - conversationId: 'test-gemini31-structured-premium', - model, - context: 'message', - endpointTokenConfig: null, - balance: { enabled: true }, - }; - - const tokenUsage = { - promptTokens: { - input: 200000, - write: 10000, - read: 5000, - }, - completionTokens: 1000, - }; - - const totalInput = - tokenUsage.promptTokens.input + tokenUsage.promptTokens.write + tokenUsage.promptTokens.read; - - await spendStructuredTokens(txData, tokenUsage); - - const premiumPromptRate = premiumTokenValues['gemini-3.1'].prompt; - const premiumCompletionRate = premiumTokenValues['gemini-3.1'].completion; - const promptMultiplier = getMultiplier({ - model, - tokenType: 'prompt', - inputTokenCount: totalInput, - }); - const writeMultiplier = getCacheMultiplier({ model, cacheType: 'write' }) ?? promptMultiplier; - const readMultiplier = getCacheMultiplier({ model, cacheType: 'read' }) ?? promptMultiplier; - - const expectedPromptCost = - tokenUsage.promptTokens.input * premiumPromptRate + - tokenUsage.promptTokens.write * writeMultiplier + - tokenUsage.promptTokens.read * readMultiplier; - const expectedCompletionCost = tokenUsage.completionTokens * premiumCompletionRate; - const expectedTotalCost = expectedPromptCost + expectedCompletionCost; - - const updatedBalance = await Balance.findOne({ user: userId }); - expect(totalInput).toBeGreaterThan(premiumTokenValues['gemini-3.1'].threshold); - expect(updatedBalance?.tokenCredits).toBeCloseTo(initialBalance - expectedTotalCost, 0); + expect(updatedBalance.tokenCredits).toBeCloseTo(initialBalance - expectedTotalCost, 0); }); test('non-premium models should not be affected by inputTokenCount regardless of prompt size', async () => { @@ -1048,6 +849,6 @@ describe('Premium Token Pricing Integration Tests', () => { promptTokens * standardPromptRate + completionTokens * standardCompletionRate; const updatedBalance = await Balance.findOne({ user: userId }); - expect(updatedBalance?.tokenCredits).toBeCloseTo(initialBalance - expectedCost, 0); + expect(updatedBalance.tokenCredits).toBeCloseTo(initialBalance - expectedCost, 0); }); }); diff --git a/api/models/balanceMethods.js b/api/models/balanceMethods.js new file mode 100644 index 0000000000..e614872eac --- /dev/null +++ b/api/models/balanceMethods.js @@ -0,0 +1,156 @@ +const { logger } = require('@librechat/data-schemas'); +const { ViolationTypes } = require('librechat-data-provider'); +const { createAutoRefillTransaction } = require('./Transaction'); +const { logViolation } = require('~/cache'); +const { getMultiplier } = require('./tx'); +const { Balance } = require('~/db/models'); + +function isInvalidDate(date) { + return isNaN(date); +} + +/** + * Simple check method that calculates token cost and returns balance info. + * The auto-refill logic has been moved to balanceMethods.js to prevent circular dependencies. + */ +const checkBalanceRecord = async function ({ + user, + model, + endpoint, + valueKey, + tokenType, + amount, + endpointTokenConfig, +}) { + const multiplier = getMultiplier({ valueKey, tokenType, model, endpoint, endpointTokenConfig }); + const tokenCost = amount * multiplier; + + // Retrieve the balance record + let record = await Balance.findOne({ user }).lean(); + if (!record) { + logger.debug('[Balance.check] No balance record found for user', { user }); + return { + canSpend: false, + balance: 0, + tokenCost, + }; + } + let balance = record.tokenCredits; + + logger.debug('[Balance.check] Initial state', { + user, + model, + endpoint, + valueKey, + tokenType, + amount, + balance, + multiplier, + endpointTokenConfig: !!endpointTokenConfig, + }); + + // Only perform auto-refill if spending would bring the balance to 0 or below + if (balance - tokenCost <= 0 && record.autoRefillEnabled && record.refillAmount > 0) { + const lastRefillDate = new Date(record.lastRefill); + const now = new Date(); + if ( + isInvalidDate(lastRefillDate) || + now >= + addIntervalToDate(lastRefillDate, record.refillIntervalValue, record.refillIntervalUnit) + ) { + try { + /** @type {{ rate: number, user: string, balance: number, transaction: import('@librechat/data-schemas').ITransaction}} */ + const result = await createAutoRefillTransaction({ + user: user, + tokenType: 'credits', + context: 'autoRefill', + rawAmount: record.refillAmount, + }); + balance = result.balance; + } catch (error) { + logger.error('[Balance.check] Failed to record transaction for auto-refill', error); + } + } + } + + logger.debug('[Balance.check] Token cost', { tokenCost }); + return { canSpend: balance >= tokenCost, balance, tokenCost }; +}; + +/** + * Adds a time interval to a given date. + * @param {Date} date - The starting date. + * @param {number} value - The numeric value of the interval. + * @param {'seconds'|'minutes'|'hours'|'days'|'weeks'|'months'} unit - The unit of time. + * @returns {Date} A new Date representing the starting date plus the interval. + */ +const addIntervalToDate = (date, value, unit) => { + const result = new Date(date); + switch (unit) { + case 'seconds': + result.setSeconds(result.getSeconds() + value); + break; + case 'minutes': + result.setMinutes(result.getMinutes() + value); + break; + case 'hours': + result.setHours(result.getHours() + value); + break; + case 'days': + result.setDate(result.getDate() + value); + break; + case 'weeks': + result.setDate(result.getDate() + value * 7); + break; + case 'months': + result.setMonth(result.getMonth() + value); + break; + default: + break; + } + return result; +}; + +/** + * Checks the balance for a user and determines if they can spend a certain amount. + * If the user cannot spend the amount, it logs a violation and denies the request. + * + * @async + * @function + * @param {Object} params - The function parameters. + * @param {ServerRequest} params.req - The Express request object. + * @param {Express.Response} params.res - The Express response object. + * @param {Object} params.txData - The transaction data. + * @param {string} params.txData.user - The user ID or identifier. + * @param {('prompt' | 'completion')} params.txData.tokenType - The type of token. + * @param {number} params.txData.amount - The amount of tokens. + * @param {string} params.txData.model - The model name or identifier. + * @param {string} [params.txData.endpointTokenConfig] - The token configuration for the endpoint. + * @returns {Promise} Throws error if the user cannot spend the amount. + * @throws {Error} Throws an error if there's an issue with the balance check. + */ +const checkBalance = async ({ req, res, txData }) => { + const { canSpend, balance, tokenCost } = await checkBalanceRecord(txData); + if (canSpend) { + return true; + } + + const type = ViolationTypes.TOKEN_BALANCE; + const errorMessage = { + type, + balance, + tokenCost, + promptTokens: txData.amount, + }; + + if (txData.generations && txData.generations.length > 0) { + errorMessage.generations = txData.generations; + } + + await logViolation(req, res, type, errorMessage, 0); + throw new Error(JSON.stringify(errorMessage)); +}; + +module.exports = { + checkBalance, +}; diff --git a/packages/data-schemas/src/methods/convoStructure.spec.ts b/api/models/convoStructure.spec.js similarity index 69% rename from packages/data-schemas/src/methods/convoStructure.spec.ts rename to api/models/convoStructure.spec.js index 77a9913233..440f21cb06 100644 --- a/packages/data-schemas/src/methods/convoStructure.spec.ts +++ b/api/models/convoStructure.spec.js @@ -1,35 +1,13 @@ -import mongoose from 'mongoose'; -import type { TMessage } from 'librechat-data-provider'; -import { buildTree } from 'librechat-data-provider'; -import { MongoMemoryServer } from 'mongodb-memory-server'; -import { createModels } from '~/models'; -import { createMessageMethods } from './message'; -import type { IMessage } from '..'; - -jest.mock('~/config/winston', () => ({ - error: jest.fn(), - warn: jest.fn(), - info: jest.fn(), - debug: jest.fn(), -})); - -let mongod: InstanceType; -let Message: mongoose.Model; -let getMessages: ReturnType['getMessages']; -let bulkSaveMessages: ReturnType['bulkSaveMessages']; +const mongoose = require('mongoose'); +const { buildTree } = require('librechat-data-provider'); +const { MongoMemoryServer } = require('mongodb-memory-server'); +const { getMessages, bulkSaveMessages } = require('./Message'); +const { Message } = require('~/db/models'); +let mongod; beforeAll(async () => { mongod = await MongoMemoryServer.create(); const uri = mongod.getUri(); - - const models = createModels(mongoose); - Object.assign(mongoose.models, models); - Message = mongoose.models.Message; - - const methods = createMessageMethods(mongoose); - getMessages = methods.getMessages; - bulkSaveMessages = methods.bulkSaveMessages; - await mongoose.connect(uri); }); @@ -83,13 +61,11 @@ describe('Conversation Structure Tests', () => { // Add common properties to all messages messages.forEach((msg) => { - Object.assign(msg, { - conversationId, - user: userId, - isCreatedByUser: false, - error: false, - unfinished: false, - }); + msg.conversationId = conversationId; + msg.user = userId; + msg.isCreatedByUser = false; + msg.error = false; + msg.unfinished = false; }); // Save messages with overrideTimestamp omitted (default is false) @@ -99,10 +75,10 @@ describe('Conversation Structure Tests', () => { const retrievedMessages = await getMessages({ conversationId, user: userId }); // Build tree - const tree = buildTree({ messages: retrievedMessages as TMessage[] }); + const tree = buildTree({ messages: retrievedMessages }); // Check if the tree is incorrect (folded/corrupted) - expect(tree!.length).toBeGreaterThan(1); // Should have multiple root messages, indicating corruption + expect(tree.length).toBeGreaterThan(1); // Should have multiple root messages, indicating corruption }); test('Fix: Conversation structure maintained with more than 16 messages', async () => { @@ -126,17 +102,17 @@ describe('Conversation Structure Tests', () => { const retrievedMessages = await getMessages({ conversationId, user: userId }); // Build tree - const tree = buildTree({ messages: retrievedMessages as TMessage[] }); + const tree = buildTree({ messages: retrievedMessages }); // Check if the tree is correct - expect(tree!.length).toBe(1); // Should have only one root message - let currentNode = tree![0]; + expect(tree.length).toBe(1); // Should have only one root message + let currentNode = tree[0]; for (let i = 1; i < 20; i++) { - expect(currentNode.children!.length).toBe(1); - currentNode = currentNode.children![0]; + expect(currentNode.children.length).toBe(1); + currentNode = currentNode.children[0]; expect(currentNode.text).toBe(`Message ${i}`); } - expect(currentNode.children!.length).toBe(0); // Last message should have no children + expect(currentNode.children.length).toBe(0); // Last message should have no children }); test('Simulate MongoDB ordering issue with more than 16 messages and close timestamps', async () => { @@ -155,13 +131,15 @@ describe('Conversation Structure Tests', () => { // Add common properties to all messages messages.forEach((msg) => { - Object.assign(msg, { isCreatedByUser: false, error: false, unfinished: false }); + msg.isCreatedByUser = false; + msg.error = false; + msg.unfinished = false; }); await bulkSaveMessages(messages, true); const retrievedMessages = await getMessages({ conversationId, user: userId }); - const tree = buildTree({ messages: retrievedMessages as TMessage[] }); - expect(tree!.length).toBeGreaterThan(1); + const tree = buildTree({ messages: retrievedMessages }); + expect(tree.length).toBeGreaterThan(1); }); test('Fix: Preserve order with more than 16 messages by maintaining original timestamps', async () => { @@ -180,7 +158,9 @@ describe('Conversation Structure Tests', () => { // Add common properties to all messages messages.forEach((msg) => { - Object.assign(msg, { isCreatedByUser: false, error: false, unfinished: false }); + msg.isCreatedByUser = false; + msg.error = false; + msg.unfinished = false; }); // Save messages with overriding timestamps (preserve original timestamps) @@ -190,17 +170,17 @@ describe('Conversation Structure Tests', () => { const retrievedMessages = await getMessages({ conversationId, user: userId }); // Build tree - const tree = buildTree({ messages: retrievedMessages as TMessage[] }); + const tree = buildTree({ messages: retrievedMessages }); // Check if the tree is correct - expect(tree!.length).toBe(1); // Should have only one root message - let currentNode = tree![0]; + expect(tree.length).toBe(1); // Should have only one root message + let currentNode = tree[0]; for (let i = 1; i < 20; i++) { - expect(currentNode.children!.length).toBe(1); - currentNode = currentNode.children![0]; + expect(currentNode.children.length).toBe(1); + currentNode = currentNode.children[0]; expect(currentNode.text).toBe(`Message ${i}`); } - expect(currentNode.children!.length).toBe(0); // Last message should have no children + expect(currentNode.children.length).toBe(0); // Last message should have no children }); test('Random order dates between parent and children messages', async () => { @@ -237,13 +217,11 @@ describe('Conversation Structure Tests', () => { // Add common properties to all messages messages.forEach((msg) => { - Object.assign(msg, { - conversationId, - user: userId, - isCreatedByUser: false, - error: false, - unfinished: false, - }); + msg.conversationId = conversationId; + msg.user = userId; + msg.isCreatedByUser = false; + msg.error = false; + msg.unfinished = false; }); // Save messages with overrideTimestamp set to true @@ -263,16 +241,16 @@ describe('Conversation Structure Tests', () => { ); // Build tree - const tree = buildTree({ messages: retrievedMessages as TMessage[] }); + const tree = buildTree({ messages: retrievedMessages }); // Debug log to see the tree structure console.log( 'Tree structure:', - tree!.map((root) => ({ + tree.map((root) => ({ messageId: root.messageId, - children: root.children!.map((child) => ({ + children: root.children.map((child) => ({ messageId: child.messageId, - children: child.children!.map((grandchild) => ({ + children: child.children.map((grandchild) => ({ messageId: grandchild.messageId, })), })), @@ -284,14 +262,14 @@ describe('Conversation Structure Tests', () => { // Check if messages are properly linked const parentMsg = retrievedMessages.find((msg) => msg.messageId === 'parent'); - expect(parentMsg!.parentMessageId).toBeNull(); // Parent should have null parentMessageId + expect(parentMsg.parentMessageId).toBeNull(); // Parent should have null parentMessageId const childMsg1 = retrievedMessages.find((msg) => msg.messageId === 'child1'); - expect(childMsg1!.parentMessageId).toBe('parent'); + expect(childMsg1.parentMessageId).toBe('parent'); // Then check tree structure - expect(tree!.length).toBe(1); // Should have only one root message - expect(tree![0].messageId).toBe('parent'); - expect(tree![0].children!.length).toBe(2); // Should have two children + expect(tree.length).toBe(1); // Should have only one root message + expect(tree[0].messageId).toBe('parent'); + expect(tree[0].children.length).toBe(2); // Should have two children }); }); diff --git a/api/models/index.js b/api/models/index.js index 2a1cb222f9..d0b10be079 100644 --- a/api/models/index.js +++ b/api/models/index.js @@ -1,22 +1,48 @@ const mongoose = require('mongoose'); const { createMethods } = require('@librechat/data-schemas'); -const { matchModelName, findMatchingPattern } = require('@librechat/api'); -const getLogStores = require('~/cache/getLogStores'); - -const methods = createMethods(mongoose, { - matchModelName, - findMatchingPattern, - getCache: getLogStores, -}); +const methods = createMethods(mongoose); +const { comparePassword } = require('./userMethods'); +const { + getMessage, + getMessages, + saveMessage, + recordMessage, + updateMessage, + deleteMessagesSince, + deleteMessages, +} = require('./Message'); +const { getConvoTitle, getConvo, saveConvo, deleteConvos } = require('./Conversation'); +const { getPreset, getPresets, savePreset, deletePresets } = require('./Preset'); +const { File } = require('~/db/models'); const seedDatabase = async () => { await methods.initializeRoles(); await methods.seedDefaultRoles(); await methods.ensureDefaultCategories(); - await methods.seedSystemGrants(); }; module.exports = { ...methods, seedDatabase, + comparePassword, + + getMessage, + getMessages, + saveMessage, + recordMessage, + updateMessage, + deleteMessagesSince, + deleteMessages, + + getConvoTitle, + getConvo, + saveConvo, + deleteConvos, + + getPreset, + getPresets, + savePreset, + deletePresets, + + Files: File, }; diff --git a/api/models/interface.js b/api/models/interface.js new file mode 100644 index 0000000000..a79a8e747f --- /dev/null +++ b/api/models/interface.js @@ -0,0 +1,24 @@ +const { logger } = require('@librechat/data-schemas'); +const { updateInterfacePermissions: updateInterfacePerms } = require('@librechat/api'); +const { getRoleByName, updateAccessPermissions } = require('./Role'); + +/** + * Update interface permissions based on app configuration. + * Must be done independently from loading the app config. + * @param {AppConfig} appConfig + */ +async function updateInterfacePermissions(appConfig) { + try { + await updateInterfacePerms({ + appConfig, + getRoleByName, + updateAccessPermissions, + }); + } catch (error) { + logger.error('Error updating interface permissions:', error); + } +} + +module.exports = { + updateInterfacePermissions, +}; diff --git a/api/models/inviteUser.js b/api/models/inviteUser.js new file mode 100644 index 0000000000..eda8394225 --- /dev/null +++ b/api/models/inviteUser.js @@ -0,0 +1,68 @@ +const mongoose = require('mongoose'); +const { logger, hashToken, getRandomValues } = require('@librechat/data-schemas'); +const { createToken, findToken } = require('~/models'); + +/** + * @module inviteUser + * @description This module provides functions to create and get user invites + */ + +/** + * @function createInvite + * @description This function creates a new user invite + * @param {string} email - The email of the user to invite + * @returns {Promise} A promise that resolves to the saved invite document + * @throws {Error} If there is an error creating the invite + */ +const createInvite = async (email) => { + try { + const token = await getRandomValues(32); + const hash = await hashToken(token); + const encodedToken = encodeURIComponent(token); + + const fakeUserId = new mongoose.Types.ObjectId(); + + await createToken({ + userId: fakeUserId, + email, + token: hash, + createdAt: Date.now(), + expiresIn: 604800, + }); + + return encodedToken; + } catch (error) { + logger.error('[createInvite] Error creating invite', error); + return { message: 'Error creating invite' }; + } +}; + +/** + * @function getInvite + * @description This function retrieves a user invite + * @param {string} encodedToken - The token of the invite to retrieve + * @param {string} email - The email of the user to validate + * @returns {Promise} A promise that resolves to the retrieved invite document + * @throws {Error} If there is an error retrieving the invite, if the invite does not exist, or if the email does not match + */ +const getInvite = async (encodedToken, email) => { + try { + const token = decodeURIComponent(encodedToken); + const hash = await hashToken(token); + const invite = await findToken({ token: hash, email }); + + if (!invite) { + throw new Error('Invite not found or email does not match'); + } + + return invite; + } catch (error) { + logger.error('[getInvite] Error getting invite:', error); + return { error: true, message: error.message }; + } +}; + +module.exports = { + createInvite, + getInvite, +}; diff --git a/api/models/loadAddedAgent.js b/api/models/loadAddedAgent.js new file mode 100644 index 0000000000..aa83375eae --- /dev/null +++ b/api/models/loadAddedAgent.js @@ -0,0 +1,218 @@ +const { logger } = require('@librechat/data-schemas'); +const { getCustomEndpointConfig } = require('@librechat/api'); +const { + Tools, + Constants, + isAgentsEndpoint, + isEphemeralAgentId, + appendAgentIdSuffix, + encodeEphemeralAgentId, +} = require('librechat-data-provider'); +const { getMCPServerTools } = require('~/server/services/Config'); + +const { mcp_all, mcp_delimiter } = Constants; + +/** + * Constant for added conversation agent ID + */ +const ADDED_AGENT_ID = 'added_agent'; + +/** + * Get an agent document based on the provided ID. + * @param {Object} searchParameter - The search parameters to find the agent. + * @param {string} searchParameter.id - The ID of the agent. + * @returns {Promise} + */ +let getAgent; + +/** + * Set the getAgent function (dependency injection to avoid circular imports) + * @param {Function} fn + */ +const setGetAgent = (fn) => { + getAgent = fn; +}; + +/** + * Load an agent from an added conversation (TConversation). + * Used for multi-convo parallel agent execution. + * + * @param {Object} params + * @param {import('express').Request} params.req + * @param {import('librechat-data-provider').TConversation} params.conversation - The added conversation + * @param {import('librechat-data-provider').Agent} [params.primaryAgent] - The primary agent (used to duplicate tools when both are ephemeral) + * @returns {Promise} The agent config as a plain object, or null if invalid. + */ +const loadAddedAgent = async ({ req, conversation, primaryAgent }) => { + if (!conversation) { + return null; + } + + // If there's an agent_id, load the existing agent + if (conversation.agent_id && !isEphemeralAgentId(conversation.agent_id)) { + if (!getAgent) { + throw new Error('getAgent not initialized - call setGetAgent first'); + } + const agent = await getAgent({ + id: conversation.agent_id, + }); + + if (!agent) { + logger.warn(`[loadAddedAgent] Agent ${conversation.agent_id} not found`); + return null; + } + + agent.version = agent.versions ? agent.versions.length : 0; + // Append suffix to distinguish from primary agent (matches ephemeral format) + // This is needed when both agents have the same ID or for consistent parallel content attribution + agent.id = appendAgentIdSuffix(agent.id, 1); + return agent; + } + + // Otherwise, create an ephemeral agent config from the conversation + const { model, endpoint, promptPrefix, spec, ...rest } = conversation; + + if (!endpoint || !model) { + logger.warn('[loadAddedAgent] Missing required endpoint or model for ephemeral agent'); + return null; + } + + // If both primary and added agents are ephemeral, duplicate tools from primary agent + const primaryIsEphemeral = primaryAgent && isEphemeralAgentId(primaryAgent.id); + if (primaryIsEphemeral && Array.isArray(primaryAgent.tools)) { + // Get endpoint config and model spec for display name fallbacks + const appConfig = req.config; + let endpointConfig = appConfig?.endpoints?.[endpoint]; + if (!isAgentsEndpoint(endpoint) && !endpointConfig) { + try { + endpointConfig = getCustomEndpointConfig({ endpoint, appConfig }); + } catch (err) { + logger.error('[loadAddedAgent] Error getting custom endpoint config', err); + } + } + + // Look up model spec for label fallback + const modelSpecs = appConfig?.modelSpecs?.list; + const modelSpec = spec != null && spec !== '' ? modelSpecs?.find((s) => s.name === spec) : null; + + // For ephemeral agents, use modelLabel if provided, then model spec's label, + // then modelDisplayLabel from endpoint config, otherwise empty string to show model name + const sender = rest.modelLabel ?? modelSpec?.label ?? endpointConfig?.modelDisplayLabel ?? ''; + + const ephemeralId = encodeEphemeralAgentId({ endpoint, model, sender, index: 1 }); + + return { + id: ephemeralId, + instructions: promptPrefix || '', + provider: endpoint, + model_parameters: {}, + model, + tools: [...primaryAgent.tools], + }; + } + + // Extract ephemeral agent options from conversation if present + const ephemeralAgent = rest.ephemeralAgent; + const mcpServers = new Set(ephemeralAgent?.mcp); + const userId = req.user?.id; + + // Check model spec for MCP servers + const modelSpecs = req.config?.modelSpecs?.list; + let modelSpec = null; + if (spec != null && spec !== '') { + modelSpec = modelSpecs?.find((s) => s.name === spec) || null; + } + if (modelSpec?.mcpServers) { + for (const mcpServer of modelSpec.mcpServers) { + mcpServers.add(mcpServer); + } + } + + /** @type {string[]} */ + const tools = []; + if (ephemeralAgent?.execute_code === true || modelSpec?.executeCode === true) { + tools.push(Tools.execute_code); + } + if (ephemeralAgent?.file_search === true || modelSpec?.fileSearch === true) { + tools.push(Tools.file_search); + } + if (ephemeralAgent?.web_search === true || modelSpec?.webSearch === true) { + tools.push(Tools.web_search); + } + + const addedServers = new Set(); + if (mcpServers.size > 0) { + for (const mcpServer of mcpServers) { + if (addedServers.has(mcpServer)) { + continue; + } + const serverTools = await getMCPServerTools(userId, mcpServer); + if (!serverTools) { + tools.push(`${mcp_all}${mcp_delimiter}${mcpServer}`); + addedServers.add(mcpServer); + continue; + } + tools.push(...Object.keys(serverTools)); + addedServers.add(mcpServer); + } + } + + // Build model_parameters from conversation fields + const model_parameters = {}; + const paramKeys = [ + 'temperature', + 'top_p', + 'topP', + 'topK', + 'presence_penalty', + 'frequency_penalty', + 'maxOutputTokens', + 'maxTokens', + 'max_tokens', + ]; + + for (const key of paramKeys) { + if (rest[key] != null) { + model_parameters[key] = rest[key]; + } + } + + // Get endpoint config for modelDisplayLabel fallback + const appConfig = req.config; + let endpointConfig = appConfig?.endpoints?.[endpoint]; + if (!isAgentsEndpoint(endpoint) && !endpointConfig) { + try { + endpointConfig = getCustomEndpointConfig({ endpoint, appConfig }); + } catch (err) { + logger.error('[loadAddedAgent] Error getting custom endpoint config', err); + } + } + + // For ephemeral agents, use modelLabel if provided, then model spec's label, + // then modelDisplayLabel from endpoint config, otherwise empty string to show model name + const sender = rest.modelLabel ?? modelSpec?.label ?? endpointConfig?.modelDisplayLabel ?? ''; + + /** Encoded ephemeral agent ID with endpoint, model, sender, and index=1 to distinguish from primary */ + const ephemeralId = encodeEphemeralAgentId({ endpoint, model, sender, index: 1 }); + + const result = { + id: ephemeralId, + instructions: promptPrefix || '', + provider: endpoint, + model_parameters, + model, + tools, + }; + + if (ephemeralAgent?.artifacts != null && ephemeralAgent.artifacts) { + result.artifacts = ephemeralAgent.artifacts; + } + + return result; +}; + +module.exports = { + ADDED_AGENT_ID, + loadAddedAgent, + setGetAgent, +}; diff --git a/api/models/spendTokens.js b/api/models/spendTokens.js new file mode 100644 index 0000000000..afe05969d8 --- /dev/null +++ b/api/models/spendTokens.js @@ -0,0 +1,140 @@ +const { logger } = require('@librechat/data-schemas'); +const { createTransaction, createStructuredTransaction } = require('./Transaction'); +/** + * Creates up to two transactions to record the spending of tokens. + * + * @function + * @async + * @param {txData} txData - Transaction data. + * @param {Object} tokenUsage - The number of tokens used. + * @param {Number} tokenUsage.promptTokens - The number of prompt tokens used. + * @param {Number} tokenUsage.completionTokens - The number of completion tokens used. + * @returns {Promise} - Returns nothing. + * @throws {Error} - Throws an error if there's an issue creating the transactions. + */ +const spendTokens = async (txData, tokenUsage) => { + const { promptTokens, completionTokens } = tokenUsage; + logger.debug( + `[spendTokens] conversationId: ${txData.conversationId}${ + txData?.context ? ` | Context: ${txData?.context}` : '' + } | Token usage: `, + { + promptTokens, + completionTokens, + }, + ); + let prompt, completion; + const normalizedPromptTokens = Math.max(promptTokens ?? 0, 0); + try { + if (promptTokens !== undefined) { + prompt = await createTransaction({ + ...txData, + tokenType: 'prompt', + rawAmount: promptTokens === 0 ? 0 : -normalizedPromptTokens, + inputTokenCount: normalizedPromptTokens, + }); + } + + if (completionTokens !== undefined) { + completion = await createTransaction({ + ...txData, + tokenType: 'completion', + rawAmount: completionTokens === 0 ? 0 : -Math.max(completionTokens, 0), + inputTokenCount: normalizedPromptTokens, + }); + } + + if (prompt || completion) { + logger.debug('[spendTokens] Transaction data record against balance:', { + user: txData.user, + prompt: prompt?.prompt, + promptRate: prompt?.rate, + completion: completion?.completion, + completionRate: completion?.rate, + balance: completion?.balance ?? prompt?.balance, + }); + } else { + logger.debug('[spendTokens] No transactions incurred against balance'); + } + } catch (err) { + logger.error('[spendTokens]', err); + } +}; + +/** + * Creates transactions to record the spending of structured tokens. + * + * @function + * @async + * @param {txData} txData - Transaction data. + * @param {Object} tokenUsage - The number of tokens used. + * @param {Object} tokenUsage.promptTokens - The number of prompt tokens used. + * @param {Number} tokenUsage.promptTokens.input - The number of input tokens. + * @param {Number} tokenUsage.promptTokens.write - The number of write tokens. + * @param {Number} tokenUsage.promptTokens.read - The number of read tokens. + * @param {Number} tokenUsage.completionTokens - The number of completion tokens used. + * @returns {Promise} - Returns nothing. + * @throws {Error} - Throws an error if there's an issue creating the transactions. + */ +const spendStructuredTokens = async (txData, tokenUsage) => { + const { promptTokens, completionTokens } = tokenUsage; + logger.debug( + `[spendStructuredTokens] conversationId: ${txData.conversationId}${ + txData?.context ? ` | Context: ${txData?.context}` : '' + } | Token usage: `, + { + promptTokens, + completionTokens, + }, + ); + let prompt, completion; + try { + if (promptTokens) { + const input = Math.max(promptTokens.input ?? 0, 0); + const write = Math.max(promptTokens.write ?? 0, 0); + const read = Math.max(promptTokens.read ?? 0, 0); + const totalInputTokens = input + write + read; + prompt = await createStructuredTransaction({ + ...txData, + tokenType: 'prompt', + inputTokens: -input, + writeTokens: -write, + readTokens: -read, + inputTokenCount: totalInputTokens, + }); + } + + if (completionTokens) { + const totalInputTokens = promptTokens + ? Math.max(promptTokens.input ?? 0, 0) + + Math.max(promptTokens.write ?? 0, 0) + + Math.max(promptTokens.read ?? 0, 0) + : undefined; + completion = await createTransaction({ + ...txData, + tokenType: 'completion', + rawAmount: -Math.max(completionTokens, 0), + inputTokenCount: totalInputTokens, + }); + } + + if (prompt || completion) { + logger.debug('[spendStructuredTokens] Transaction data record against balance:', { + user: txData.user, + prompt: prompt?.prompt, + promptRate: prompt?.rate, + completion: completion?.completion, + completionRate: completion?.rate, + balance: completion?.balance ?? prompt?.balance, + }); + } else { + logger.debug('[spendStructuredTokens] No transactions incurred against balance'); + } + } catch (err) { + logger.error('[spendStructuredTokens]', err); + } + + return { prompt, completion }; +}; + +module.exports = { spendTokens, spendStructuredTokens }; diff --git a/packages/data-schemas/src/methods/spendTokens.spec.ts b/api/models/spendTokens.spec.js similarity index 76% rename from packages/data-schemas/src/methods/spendTokens.spec.ts rename to api/models/spendTokens.spec.js index d505663d57..c076d29700 100644 --- a/packages/data-schemas/src/methods/spendTokens.spec.ts +++ b/api/models/spendTokens.spec.js @@ -1,60 +1,30 @@ -import mongoose from 'mongoose'; -import { MongoMemoryServer } from 'mongodb-memory-server'; -import { matchModelName, findMatchingPattern } from './test-helpers'; -import { createModels } from '~/models'; -import { createTxMethods, tokenValues, premiumTokenValues } from './tx'; -import { createTransactionMethods } from './transaction'; -import { createSpendTokensMethods } from './spendTokens'; -import type { ITransaction } from '~/schema/transaction'; -import type { IBalance } from '..'; +const mongoose = require('mongoose'); +const { MongoMemoryServer } = require('mongodb-memory-server'); +const { createTransaction, createAutoRefillTransaction } = require('./Transaction'); +const { tokenValues, premiumTokenValues, getCacheMultiplier } = require('./tx'); +const { spendTokens, spendStructuredTokens } = require('./spendTokens'); -jest.mock('~/config/winston', () => ({ - error: jest.fn(), - warn: jest.fn(), - info: jest.fn(), - debug: jest.fn(), +require('~/db/models'); + +jest.mock('~/config', () => ({ + logger: { + debug: jest.fn(), + error: jest.fn(), + }, })); -let mongoServer: InstanceType; -let spendTokens: ReturnType['spendTokens']; -let spendStructuredTokens: ReturnType['spendStructuredTokens']; -let createTransaction: ReturnType['createTransaction']; -let createAutoRefillTransaction: ReturnType< - typeof createTransactionMethods ->['createAutoRefillTransaction']; -let getCacheMultiplier: ReturnType['getCacheMultiplier']; - describe('spendTokens', () => { - let userId: mongoose.Types.ObjectId; - let Transaction: mongoose.Model; - let Balance: mongoose.Model; + let mongoServer; + let userId; + let Transaction; + let Balance; beforeAll(async () => { mongoServer = await MongoMemoryServer.create(); await mongoose.connect(mongoServer.getUri()); - const models = createModels(mongoose); - Object.assign(mongoose.models, models); - - Transaction = mongoose.models.Transaction; - Balance = mongoose.models.Balance; - - const txMethods = createTxMethods(mongoose, { matchModelName, findMatchingPattern }); - getCacheMultiplier = txMethods.getCacheMultiplier; - - const transactionMethods = createTransactionMethods(mongoose, { - getMultiplier: txMethods.getMultiplier, - getCacheMultiplier: txMethods.getCacheMultiplier, - }); - createTransaction = transactionMethods.createTransaction; - createAutoRefillTransaction = transactionMethods.createAutoRefillTransaction; - - const spendMethods = createSpendTokensMethods(mongoose, { - createTransaction: transactionMethods.createTransaction, - createStructuredTransaction: transactionMethods.createStructuredTransaction, - }); - spendTokens = spendMethods.spendTokens; - spendStructuredTokens = spendMethods.spendStructuredTokens; + Transaction = mongoose.model('Transaction'); + Balance = mongoose.model('Balance'); }); afterAll(async () => { @@ -109,7 +79,7 @@ describe('spendTokens', () => { // Verify balance was updated const balance = await Balance.findOne({ user: userId }); expect(balance).toBeDefined(); - expect(balance!.tokenCredits).toBeLessThan(10000); // Balance should be reduced + expect(balance.tokenCredits).toBeLessThan(10000); // Balance should be reduced }); it('should handle zero completion tokens', async () => { @@ -141,7 +111,7 @@ describe('spendTokens', () => { expect(transactions[0].tokenType).toBe('completion'); // In JavaScript -0 and 0 are different but functionally equivalent // Use Math.abs to handle both 0 and -0 - expect(Math.abs(transactions[0].rawAmount!)).toBe(0); + expect(Math.abs(transactions[0].rawAmount)).toBe(0); // Check prompt transaction expect(transactions[1].tokenType).toBe('prompt'); @@ -193,7 +163,7 @@ describe('spendTokens', () => { // Verify balance was not updated (should still be 10000) const balance = await Balance.findOne({ user: userId }); - expect(balance!.tokenCredits).toBe(10000); + expect(balance.tokenCredits).toBe(10000); }); it('should not allow balance to go below zero when spending tokens', async () => { @@ -226,7 +196,7 @@ describe('spendTokens', () => { // Verify balance was reduced to exactly 0, not negative const balance = await Balance.findOne({ user: userId }); expect(balance).toBeDefined(); - expect(balance!.tokenCredits).toBe(0); + expect(balance.tokenCredits).toBe(0); // Check that the transaction records show the adjusted values const transactionResults = await Promise.all( @@ -274,7 +244,7 @@ describe('spendTokens', () => { // Check balance after first transaction let balance = await Balance.findOne({ user: userId }); - expect(balance!.tokenCredits).toBe(0); + expect(balance.tokenCredits).toBe(0); // Second transaction - should keep balance at 0, not make it negative or increase it const txData2 = { @@ -294,7 +264,7 @@ describe('spendTokens', () => { // Check balance after second transaction - should still be 0 balance = await Balance.findOne({ user: userId }); - expect(balance!.tokenCredits).toBe(0); + expect(balance.tokenCredits).toBe(0); // Verify all transactions were created const transactions = await Transaction.find({ user: userId }); @@ -305,7 +275,7 @@ describe('spendTokens', () => { // Log the transaction details for debugging console.log('Transaction details:'); - transactionDetails.forEach((tx, i: number) => { + transactionDetails.forEach((tx, i) => { console.log(`Transaction ${i + 1}:`, { tokenType: tx.tokenType, rawAmount: tx.rawAmount, @@ -329,7 +299,7 @@ describe('spendTokens', () => { console.log('Direct Transaction.create result:', directResult); // The completion value should never be positive - expect(directResult!.completion).not.toBeGreaterThan(0); + expect(directResult.completion).not.toBeGreaterThan(0); }); it('should ensure tokenValue is always negative for spending tokens', async () => { @@ -401,7 +371,7 @@ describe('spendTokens', () => { // Check balance after first transaction let balance = await Balance.findOne({ user: userId }); - expect(balance!.tokenCredits).toBe(0); + expect(balance.tokenCredits).toBe(0); // Second transaction - should keep balance at 0, not make it negative or increase it const txData2 = { @@ -425,7 +395,7 @@ describe('spendTokens', () => { // Check balance after second transaction - should still be 0 balance = await Balance.findOne({ user: userId }); - expect(balance!.tokenCredits).toBe(0); + expect(balance.tokenCredits).toBe(0); // Verify all transactions were created const transactions = await Transaction.find({ user: userId }); @@ -436,7 +406,7 @@ describe('spendTokens', () => { // Log the transaction details for debugging console.log('Structured transaction details:'); - transactionDetails.forEach((tx, i: number) => { + transactionDetails.forEach((tx, i) => { console.log(`Transaction ${i + 1}:`, { tokenType: tx.tokenType, rawAmount: tx.rawAmount, @@ -483,7 +453,7 @@ describe('spendTokens', () => { // Verify balance was reduced to exactly 0, not negative const balance = await Balance.findOne({ user: userId }); expect(balance).toBeDefined(); - expect(balance!.tokenCredits).toBe(0); + expect(balance.tokenCredits).toBe(0); // The result should show the adjusted values expect(result).toEqual({ @@ -524,7 +494,7 @@ describe('spendTokens', () => { })); // Process all transactions concurrently to simulate race conditions - const promises: Promise[] = []; + const promises = []; let expectedTotalSpend = 0; for (let i = 0; i < collectedUsage.length; i++) { @@ -597,10 +567,10 @@ describe('spendTokens', () => { console.log('Initial balance:', initialBalance); console.log('Expected total spend:', expectedTotalSpend); console.log('Expected final balance:', expectedFinalBalance); - console.log('Actual final balance:', finalBalance!.tokenCredits); + console.log('Actual final balance:', finalBalance.tokenCredits); // Allow for small rounding differences - expect(finalBalance!.tokenCredits).toBeCloseTo(expectedFinalBalance, 0); + expect(finalBalance.tokenCredits).toBeCloseTo(expectedFinalBalance, 0); // Verify all transactions were created const transactions = await Transaction.find({ @@ -617,19 +587,19 @@ describe('spendTokens', () => { let totalTokenValue = 0; transactions.forEach((tx) => { console.log(`${tx.tokenType}: rawAmount=${tx.rawAmount}, tokenValue=${tx.tokenValue}`); - totalTokenValue += tx.tokenValue!; + totalTokenValue += tx.tokenValue; }); console.log('Total token value from transactions:', totalTokenValue); // The difference between expected and actual is significant // This is likely due to the multipliers being different in the test environment // Let's adjust our expectation based on the actual transactions - const actualSpend = initialBalance - finalBalance!.tokenCredits; + const actualSpend = initialBalance - finalBalance.tokenCredits; console.log('Actual spend:', actualSpend); // Instead of checking the exact balance, let's verify that: // 1. The balance was reduced (tokens were spent) - expect(finalBalance!.tokenCredits).toBeLessThan(initialBalance); + expect(finalBalance.tokenCredits).toBeLessThan(initialBalance); // 2. The total token value from transactions matches the actual spend expect(Math.abs(totalTokenValue)).toBeCloseTo(actualSpend, -3); // Allow for larger differences }); @@ -646,7 +616,7 @@ describe('spendTokens', () => { const numberOfRefills = 25; const refillAmount = 1000; - const promises: Promise[] = []; + const promises = []; for (let i = 0; i < numberOfRefills; i++) { promises.push( createAutoRefillTransaction({ @@ -672,10 +642,10 @@ describe('spendTokens', () => { console.log('Initial balance (Increase Test):', initialBalance); console.log(`Performed ${numberOfRefills} refills of ${refillAmount} each.`); console.log('Expected final balance (Increase Test):', expectedFinalBalance); - console.log('Actual final balance (Increase Test):', finalBalance!.tokenCredits); + console.log('Actual final balance (Increase Test):', finalBalance.tokenCredits); // Use toBeCloseTo for safety, though toBe should work for integer math - expect(finalBalance!.tokenCredits).toBeCloseTo(expectedFinalBalance, 0); + expect(finalBalance.tokenCredits).toBeCloseTo(expectedFinalBalance, 0); // Verify all transactions were created const transactions = await Transaction.find({ @@ -687,13 +657,12 @@ describe('spendTokens', () => { expect(transactions.length).toBe(numberOfRefills); // Optional: Verify the sum of increments from the results matches the balance change - const totalIncrementReported = results.reduce((sum: number, result) => { + const totalIncrementReported = results.reduce((sum, result) => { // Assuming createAutoRefillTransaction returns an object with the increment amount // Adjust this based on the actual return structure. // Let's assume it returns { balance: newBalance, transaction: { rawAmount: ... } } // Or perhaps we check the transaction.rawAmount directly - const r = result as Record>; - return sum + ((r?.transaction?.rawAmount as number) || 0); + return sum + (result?.transaction?.rawAmount || 0); }, 0); console.log('Total increment reported by results:', totalIncrementReported); expect(totalIncrementReported).toBe(expectedFinalBalance - initialBalance); @@ -704,7 +673,7 @@ describe('spendTokens', () => { // For refills, rawAmount is positive, and tokenValue might be calculated based on it // Let's assume tokenValue directly reflects the increment for simplicity here // If calculation is involved, adjust accordingly - totalTokenValueFromDb += tx.rawAmount!; // Or tx.tokenValue if that holds the increment + totalTokenValueFromDb += tx.rawAmount; // Or tx.tokenValue if that holds the increment }); console.log('Total rawAmount from DB transactions:', totalTokenValueFromDb); expect(totalTokenValueFromDb).toBeCloseTo(expectedFinalBalance - initialBalance, 0); @@ -764,7 +733,7 @@ describe('spendTokens', () => { // Verify balance was updated const balance = await Balance.findOne({ user: userId }); expect(balance).toBeDefined(); - expect(balance!.tokenCredits).toBeLessThan(10000); // Balance should be reduced + expect(balance.tokenCredits).toBeLessThan(10000); // Balance should be reduced }); describe('premium token pricing', () => { @@ -793,7 +762,7 @@ describe('spendTokens', () => { promptTokens * tokenValues[model].prompt + completionTokens * tokenValues[model].completion; const balance = await Balance.findOne({ user: userId }); - expect(balance?.tokenCredits).toBeCloseTo(initialBalance - expectedCost, 0); + expect(balance.tokenCredits).toBeCloseTo(initialBalance - expectedCost, 0); }); it('should charge premium rates for claude-opus-4-6 when prompt tokens exceed threshold', async () => { @@ -822,7 +791,7 @@ describe('spendTokens', () => { completionTokens * premiumTokenValues[model].completion; const balance = await Balance.findOne({ user: userId }); - expect(balance?.tokenCredits).toBeCloseTo(initialBalance - expectedCost, 0); + expect(balance.tokenCredits).toBeCloseTo(initialBalance - expectedCost, 0); }); it('should charge premium rates for both prompt and completion in structured tokens when above threshold', async () => { @@ -859,13 +828,12 @@ describe('spendTokens', () => { const expectedPromptCost = tokenUsage.promptTokens.input * premiumPromptRate + - tokenUsage.promptTokens.write * (writeRate ?? 0) + - tokenUsage.promptTokens.read * (readRate ?? 0); + tokenUsage.promptTokens.write * writeRate + + tokenUsage.promptTokens.read * readRate; const expectedCompletionCost = tokenUsage.completionTokens * premiumCompletionRate; - expect(result).not.toBeNull(); - expect(result!.prompt!.prompt).toBeCloseTo(-expectedPromptCost, 0); - expect(result!.completion!.completion).toBeCloseTo(-expectedCompletionCost, 0); + expect(result.prompt.prompt).toBeCloseTo(-expectedPromptCost, 0); + expect(result.completion.completion).toBeCloseTo(-expectedCompletionCost, 0); }); it('should charge standard rates for structured tokens when below threshold', async () => { @@ -902,143 +870,12 @@ describe('spendTokens', () => { const expectedPromptCost = tokenUsage.promptTokens.input * standardPromptRate + - tokenUsage.promptTokens.write * (writeRate ?? 0) + - tokenUsage.promptTokens.read * (readRate ?? 0); + tokenUsage.promptTokens.write * writeRate + + tokenUsage.promptTokens.read * readRate; const expectedCompletionCost = tokenUsage.completionTokens * standardCompletionRate; - expect(result).not.toBeNull(); - expect(result!.prompt!.prompt).toBeCloseTo(-expectedPromptCost, 0); - expect(result!.completion!.completion).toBeCloseTo(-expectedCompletionCost, 0); - }); - - it('should charge standard rates for gemini-3.1-pro-preview when prompt tokens are below threshold', async () => { - const initialBalance = 100000000; - await Balance.create({ - user: userId, - tokenCredits: initialBalance, - }); - - const model = 'gemini-3.1-pro-preview'; - const promptTokens = 100000; - const completionTokens = 500; - - const txData = { - user: userId, - conversationId: 'test-gemini31-standard-pricing', - model, - context: 'test', - balance: { enabled: true }, - }; - - await spendTokens(txData, { promptTokens, completionTokens }); - - const expectedCost = - promptTokens * tokenValues['gemini-3.1'].prompt + - completionTokens * tokenValues['gemini-3.1'].completion; - - const balance = await Balance.findOne({ user: userId }); - expect(balance!.tokenCredits).toBeCloseTo(initialBalance - expectedCost, 0); - }); - - it('should charge premium rates for gemini-3.1-pro-preview when prompt tokens exceed threshold', async () => { - const initialBalance = 100000000; - await Balance.create({ - user: userId, - tokenCredits: initialBalance, - }); - - const model = 'gemini-3.1-pro-preview'; - const promptTokens = 250000; - const completionTokens = 500; - - const txData = { - user: userId, - conversationId: 'test-gemini31-premium-pricing', - model, - context: 'test', - balance: { enabled: true }, - }; - - await spendTokens(txData, { promptTokens, completionTokens }); - - const expectedCost = - promptTokens * premiumTokenValues['gemini-3.1'].prompt + - completionTokens * premiumTokenValues['gemini-3.1'].completion; - - const balance = await Balance.findOne({ user: userId }); - expect(balance!.tokenCredits).toBeCloseTo(initialBalance - expectedCost, 0); - }); - - it('should charge premium rates for gemini-3.1-pro-preview-customtools when prompt tokens exceed threshold', async () => { - const initialBalance = 100000000; - await Balance.create({ - user: userId, - tokenCredits: initialBalance, - }); - - const model = 'gemini-3.1-pro-preview-customtools'; - const promptTokens = 250000; - const completionTokens = 500; - - const txData = { - user: userId, - conversationId: 'test-gemini31-customtools-premium', - model, - context: 'test', - balance: { enabled: true }, - }; - - await spendTokens(txData, { promptTokens, completionTokens }); - - const expectedCost = - promptTokens * premiumTokenValues['gemini-3.1'].prompt + - completionTokens * premiumTokenValues['gemini-3.1'].completion; - - const balance = await Balance.findOne({ user: userId }); - expect(balance!.tokenCredits).toBeCloseTo(initialBalance - expectedCost, 0); - }); - - it('should charge premium rates for structured gemini-3.1 tokens when total input exceeds threshold', async () => { - const initialBalance = 100000000; - await Balance.create({ - user: userId, - tokenCredits: initialBalance, - }); - - const model = 'gemini-3.1-pro-preview'; - const txData = { - user: userId, - conversationId: 'test-gemini31-structured-premium', - model, - context: 'test', - balance: { enabled: true }, - }; - - const tokenUsage = { - promptTokens: { - input: 200000, - write: 10000, - read: 5000, - }, - completionTokens: 1000, - }; - - const result = await spendStructuredTokens(txData, tokenUsage); - - const premiumPromptRate = premiumTokenValues['gemini-3.1'].prompt; - const premiumCompletionRate = premiumTokenValues['gemini-3.1'].completion; - const writeRate = getCacheMultiplier({ model, cacheType: 'write' }); - const readRate = getCacheMultiplier({ model, cacheType: 'read' }); - - const expectedPromptCost = - tokenUsage.promptTokens.input * premiumPromptRate + - tokenUsage.promptTokens.write * writeRate! + - tokenUsage.promptTokens.read * readRate!; - const expectedCompletionCost = tokenUsage.completionTokens * premiumCompletionRate; - - expect(result).not.toBeNull(); - expect(result!.prompt!.prompt).toBeCloseTo(-expectedPromptCost, 0); - expect(result!.completion!.completion).toBeCloseTo(-expectedCompletionCost, 0); + expect(result.prompt.prompt).toBeCloseTo(-expectedPromptCost, 0); + expect(result.completion.completion).toBeCloseTo(-expectedCompletionCost, 0); }); it('should not apply premium pricing to non-premium models regardless of prompt size', async () => { @@ -1066,7 +903,7 @@ describe('spendTokens', () => { promptTokens * tokenValues[model].prompt + completionTokens * tokenValues[model].completion; const balance = await Balance.findOne({ user: userId }); - expect(balance?.tokenCredits).toBeCloseTo(initialBalance - expectedCost, 0); + expect(balance.tokenCredits).toBeCloseTo(initialBalance - expectedCost, 0); }); }); @@ -1092,11 +929,11 @@ describe('spendTokens', () => { const completionTx = transactions.find((t) => t.tokenType === 'completion'); const promptTx = transactions.find((t) => t.tokenType === 'prompt'); - expect(Math.abs(promptTx?.rawAmount ?? 0)).toBe(0); - expect(completionTx?.rawAmount).toBe(-100); + expect(Math.abs(promptTx.rawAmount)).toBe(0); + expect(completionTx.rawAmount).toBe(-100); const standardCompletionRate = tokenValues['claude-opus-4-6'].completion; - expect(completionTx?.rate).toBe(standardCompletionRate); + expect(completionTx.rate).toBe(standardCompletionRate); }); it('should use normalized inputTokenCount for premium threshold check on completion', async () => { @@ -1126,8 +963,8 @@ describe('spendTokens', () => { const premiumPromptRate = premiumTokenValues[model].prompt; const premiumCompletionRate = premiumTokenValues[model].completion; - expect(promptTx?.rate).toBe(premiumPromptRate); - expect(completionTx?.rate).toBe(premiumCompletionRate); + expect(promptTx.rate).toBe(premiumPromptRate); + expect(completionTx.rate).toBe(premiumCompletionRate); }); it('should keep inputTokenCount as zero when promptTokens is zero', async () => { @@ -1150,10 +987,10 @@ describe('spendTokens', () => { const completionTx = transactions.find((t) => t.tokenType === 'completion'); const promptTx = transactions.find((t) => t.tokenType === 'prompt'); - expect(Math.abs(promptTx?.rawAmount ?? 0)).toBe(0); + expect(Math.abs(promptTx.rawAmount)).toBe(0); const standardCompletionRate = tokenValues['claude-opus-4-6'].completion; - expect(completionTx?.rate).toBe(standardCompletionRate); + expect(completionTx.rate).toBe(standardCompletionRate); }); it('should not trigger premium pricing with negative promptTokens on premium model', async () => { @@ -1178,7 +1015,7 @@ describe('spendTokens', () => { const completionTx = transactions.find((t) => t.tokenType === 'completion'); const standardCompletionRate = tokenValues[model].completion; - expect(completionTx?.rate).toBe(standardCompletionRate); + expect(completionTx.rate).toBe(standardCompletionRate); }); it('should normalize negative structured token values to zero in spendStructuredTokens', async () => { @@ -1212,14 +1049,14 @@ describe('spendTokens', () => { const completionTx = transactions.find((t) => t.tokenType === 'completion'); const promptTx = transactions.find((t) => t.tokenType === 'prompt'); - expect(Math.abs(promptTx?.inputTokens ?? 0)).toBe(0); - expect(promptTx?.writeTokens).toBe(-50); - expect(Math.abs(promptTx?.readTokens ?? 0)).toBe(0); + expect(Math.abs(promptTx.inputTokens)).toBe(0); + expect(promptTx.writeTokens).toBe(-50); + expect(Math.abs(promptTx.readTokens)).toBe(0); - expect(Math.abs(completionTx?.rawAmount ?? 0)).toBe(0); + expect(Math.abs(completionTx.rawAmount)).toBe(0); const standardRate = tokenValues[model].completion; - expect(completionTx?.rate).toBe(standardRate); + expect(completionTx.rate).toBe(standardRate); }); }); }); diff --git a/packages/data-schemas/src/methods/tx.ts b/api/models/tx.js similarity index 59% rename from packages/data-schemas/src/methods/tx.ts rename to api/models/tx.js index a048874457..9a6305ec5c 100644 --- a/packages/data-schemas/src/methods/tx.ts +++ b/api/models/tx.js @@ -1,46 +1,57 @@ +const { matchModelName, findMatchingPattern } = require('@librechat/api'); +const defaultRate = 6; + /** * Token Pricing Configuration * - * Pattern Matching - * ================ - * `findMatchingPattern` uses `modelName.includes(key)` and selects the **longest** - * matching key. If a key's length equals the model name's length (exact match), it - * returns immediately — no further keys are checked. + * IMPORTANT: Key Ordering for Pattern Matching + * ============================================ + * The `findMatchingPattern` function iterates through object keys in REVERSE order + * (last-defined keys are checked first) and uses `modelName.includes(key)` for matching. * - * For keys of different lengths, definition order does not affect the result — the - * longest match always wins. For **same-length ties**, the function iterates in - * reverse, so the last-defined key wins. Key ordering therefore matters for: - * 1. **Performance**: list older/legacy models first, newer models last — newer - * models are more commonly used and will match earlier in the reverse scan. - * 2. **Same-length tie-breaking**: when two keys of equal length both match, - * the last-defined key wins. + * This means: + * 1. BASE PATTERNS must be defined FIRST (e.g., "kimi", "moonshot") + * 2. SPECIFIC PATTERNS must be defined AFTER their base patterns (e.g., "kimi-k2", "kimi-k2.5") + * + * Example ordering for Kimi models: + * kimi: { prompt: 0.6, completion: 2.5 }, // Base pattern - checked last + * 'kimi-k2': { prompt: 0.6, completion: 2.5 }, // More specific - checked before "kimi" + * 'kimi-k2.5': { prompt: 0.6, completion: 3.0 }, // Most specific - checked first + * + * Why this matters: + * - Model name "kimi-k2.5" contains both "kimi" and "kimi-k2" as substrings + * - If "kimi" were checked first, it would incorrectly match and return wrong pricing + * - By defining specific patterns AFTER base patterns, they're checked first in reverse iteration + * + * This applies to BOTH `tokenValues` and `cacheTokenValues` objects. + * + * When adding new model families: + * 1. Define the base/generic pattern first + * 2. Define increasingly specific patterns after + * 3. Ensure no pattern is a substring of another that should match differently */ -export interface TxDeps { - /** From @librechat/api — matches a model name to a canonical key. */ - matchModelName: (model: string, endpoint?: string) => string | undefined; - /** From @librechat/api — finds the longest key in `values` whose key is a substring of `model`. */ - findMatchingPattern: ( - model: string, - values: Record>, - ) => string | undefined; -} - -export const defaultRate = 6; - -/** AWS Bedrock pricing (source: https://aws.amazon.com/bedrock/pricing/) */ -const bedrockValues: Record = { +/** + * AWS Bedrock pricing + * source: https://aws.amazon.com/bedrock/pricing/ + */ +const bedrockValues = { + // Basic llama2 patterns (base defaults to smallest variant) llama2: { prompt: 0.75, completion: 1.0 }, 'llama-2': { prompt: 0.75, completion: 1.0 }, 'llama2-13b': { prompt: 0.75, completion: 1.0 }, 'llama2:70b': { prompt: 1.95, completion: 2.56 }, 'llama2-70b': { prompt: 1.95, completion: 2.56 }, + + // Basic llama3 patterns (base defaults to smallest variant) llama3: { prompt: 0.3, completion: 0.6 }, 'llama-3': { prompt: 0.3, completion: 0.6 }, 'llama3-8b': { prompt: 0.3, completion: 0.6 }, 'llama3:8b': { prompt: 0.3, completion: 0.6 }, 'llama3-70b': { prompt: 2.65, completion: 3.5 }, 'llama3:70b': { prompt: 2.65, completion: 3.5 }, + + // llama3-x-Nb pattern (base defaults to smallest variant) 'llama3-1': { prompt: 0.22, completion: 0.22 }, 'llama3-1-8b': { prompt: 0.22, completion: 0.22 }, 'llama3-1-70b': { prompt: 0.72, completion: 0.72 }, @@ -52,6 +63,8 @@ const bedrockValues: Record = { 'llama3-2-90b': { prompt: 0.72, completion: 0.72 }, 'llama3-3': { prompt: 2.65, completion: 3.5 }, 'llama3-3-70b': { prompt: 2.65, completion: 3.5 }, + + // llama3.x:Nb pattern (base defaults to smallest variant) 'llama3.1': { prompt: 0.22, completion: 0.22 }, 'llama3.1:8b': { prompt: 0.22, completion: 0.22 }, 'llama3.1:70b': { prompt: 0.72, completion: 0.72 }, @@ -63,6 +76,8 @@ const bedrockValues: Record = { 'llama3.2:90b': { prompt: 0.72, completion: 0.72 }, 'llama3.3': { prompt: 2.65, completion: 3.5 }, 'llama3.3:70b': { prompt: 2.65, completion: 3.5 }, + + // llama-3.x-Nb pattern (base defaults to smallest variant) 'llama-3.1': { prompt: 0.22, completion: 0.22 }, 'llama-3.1-8b': { prompt: 0.22, completion: 0.22 }, 'llama-3.1-70b': { prompt: 0.72, completion: 0.72 }, @@ -81,17 +96,21 @@ const bedrockValues: Record = { 'mistral-large-2407': { prompt: 3.0, completion: 9.0 }, 'command-text': { prompt: 1.5, completion: 2.0 }, 'command-light': { prompt: 0.3, completion: 0.6 }, + // AI21 models 'j2-mid': { prompt: 12.5, completion: 12.5 }, 'j2-ultra': { prompt: 18.8, completion: 18.8 }, 'jamba-instruct': { prompt: 0.5, completion: 0.7 }, + // Amazon Titan models 'titan-text-lite': { prompt: 0.15, completion: 0.2 }, 'titan-text-express': { prompt: 0.2, completion: 0.6 }, 'titan-text-premier': { prompt: 0.5, completion: 1.5 }, + // Amazon Nova models 'nova-micro': { prompt: 0.035, completion: 0.14 }, 'nova-lite': { prompt: 0.06, completion: 0.24 }, 'nova-pro': { prompt: 0.8, completion: 3.2 }, 'nova-premier': { prompt: 2.5, completion: 12.5 }, 'deepseek.r1': { prompt: 1.35, completion: 5.4 }, + // Moonshot/Kimi models on Bedrock 'moonshot.kimi': { prompt: 0.6, completion: 2.5 }, 'moonshot.kimi-k2': { prompt: 0.6, completion: 2.5 }, 'moonshot.kimi-k2.5': { prompt: 0.6, completion: 3.0 }, @@ -101,19 +120,23 @@ const bedrockValues: Record = { /** * Mapping of model token sizes to their respective multipliers for prompt and completion. * The rates are 1 USD per 1M tokens. + * @type {Object.} */ -export const tokenValues: Record = Object.assign( +const tokenValues = Object.assign( { + // Legacy token size mappings (generic patterns - check LAST) '8k': { prompt: 30, completion: 60 }, '32k': { prompt: 60, completion: 120 }, '4k': { prompt: 1.5, completion: 2 }, '16k': { prompt: 3, completion: 4 }, + // Generic fallback patterns (check LAST) 'claude-': { prompt: 0.8, completion: 2.4 }, deepseek: { prompt: 0.28, completion: 0.42 }, command: { prompt: 0.38, completion: 0.38 }, - gemma: { prompt: 0.02, completion: 0.04 }, + gemma: { prompt: 0.02, completion: 0.04 }, // Base pattern (using gemma-3n-e4b pricing) gemini: { prompt: 0.5, completion: 1.5 }, 'gpt-oss': { prompt: 0.05, completion: 0.2 }, + // Specific model variants (check FIRST - more specific patterns at end) 'gpt-3.5-turbo-1106': { prompt: 1, completion: 2 }, 'gpt-3.5-turbo-0125': { prompt: 0.5, completion: 1.5 }, 'gpt-4-1106': { prompt: 10, completion: 30 }, @@ -127,14 +150,9 @@ export const tokenValues: Record 'gpt-5': { prompt: 1.25, completion: 10 }, 'gpt-5.1': { prompt: 1.25, completion: 10 }, 'gpt-5.2': { prompt: 1.75, completion: 14 }, - 'gpt-5.3': { prompt: 1.75, completion: 14 }, - 'gpt-5.4': { prompt: 2.5, completion: 15 }, - // TODO: gpt-5.4-pro pricing not yet officially published — verify before release - 'gpt-5.4-pro': { prompt: 5, completion: 30 }, 'gpt-5-nano': { prompt: 0.05, completion: 0.4 }, 'gpt-5-mini': { prompt: 0.25, completion: 2 }, 'gpt-5-pro': { prompt: 15, completion: 120 }, - 'gpt-5.2-pro': { prompt: 21, completion: 168 }, o1: { prompt: 15, completion: 60 }, 'o1-mini': { prompt: 1.1, completion: 4.4 }, 'o1-preview': { prompt: 15, completion: 60 }, @@ -166,26 +184,24 @@ export const tokenValues: Record 'deepseek-reasoner': { prompt: 0.28, completion: 0.42 }, 'deepseek-r1': { prompt: 0.4, completion: 2.0 }, 'deepseek-v3': { prompt: 0.2, completion: 0.8 }, - 'gemma-2': { prompt: 0.01, completion: 0.03 }, - 'gemma-3': { prompt: 0.02, completion: 0.04 }, + 'gemma-2': { prompt: 0.01, completion: 0.03 }, // Base pattern (using gemma-2-9b pricing) + 'gemma-3': { prompt: 0.02, completion: 0.04 }, // Base pattern (using gemma-3n-e4b pricing) 'gemma-3-27b': { prompt: 0.09, completion: 0.16 }, 'gemini-1.5': { prompt: 2.5, completion: 10 }, 'gemini-1.5-flash': { prompt: 0.15, completion: 0.6 }, 'gemini-1.5-flash-8b': { prompt: 0.075, completion: 0.3 }, - 'gemini-2.0': { prompt: 0.1, completion: 0.4 }, + 'gemini-2.0': { prompt: 0.1, completion: 0.4 }, // Base pattern (using 2.0-flash pricing) 'gemini-2.0-flash': { prompt: 0.1, completion: 0.4 }, 'gemini-2.0-flash-lite': { prompt: 0.075, completion: 0.3 }, - 'gemini-2.5': { prompt: 0.3, completion: 2.5 }, + 'gemini-2.5': { prompt: 0.3, completion: 2.5 }, // Base pattern (using 2.5-flash pricing) 'gemini-2.5-flash': { prompt: 0.3, completion: 2.5 }, 'gemini-2.5-flash-lite': { prompt: 0.1, completion: 0.4 }, 'gemini-2.5-pro': { prompt: 1.25, completion: 10 }, 'gemini-2.5-flash-image': { prompt: 0.15, completion: 30 }, 'gemini-3': { prompt: 2, completion: 12 }, 'gemini-3-pro-image': { prompt: 2, completion: 120 }, - 'gemini-3.1': { prompt: 2, completion: 12 }, - 'gemini-3.1-flash-lite': { prompt: 0.25, completion: 1.5 }, 'gemini-pro-vision': { prompt: 0.5, completion: 1.5 }, - grok: { prompt: 2.0, completion: 10.0 }, + grok: { prompt: 2.0, completion: 10.0 }, // Base pattern defaults to grok-2 'grok-beta': { prompt: 5.0, completion: 15.0 }, 'grok-vision-beta': { prompt: 5.0, completion: 15.0 }, 'grok-2': { prompt: 2.0, completion: 10.0 }, @@ -200,7 +216,7 @@ export const tokenValues: Record 'grok-3-mini-fast': { prompt: 0.6, completion: 4 }, 'grok-4': { prompt: 3.0, completion: 15.0 }, 'grok-4-fast': { prompt: 0.2, completion: 0.5 }, - 'grok-4-1-fast': { prompt: 0.2, completion: 0.5 }, + 'grok-4-1-fast': { prompt: 0.2, completion: 0.5 }, // covers reasoning & non-reasoning variants 'grok-code-fast': { prompt: 0.2, completion: 1.5 }, codestral: { prompt: 0.3, completion: 0.9 }, 'ministral-3b': { prompt: 0.04, completion: 0.04 }, @@ -210,9 +226,10 @@ export const tokenValues: Record 'pixtral-large': { prompt: 2.0, completion: 6.0 }, 'mistral-large': { prompt: 2.0, completion: 6.0 }, 'mixtral-8x22b': { prompt: 0.65, completion: 0.65 }, - kimi: { prompt: 0.6, completion: 2.5 }, - moonshot: { prompt: 2.0, completion: 5.0 }, - 'kimi-latest': { prompt: 0.2, completion: 2.0 }, + // Moonshot/Kimi models (base patterns first, specific patterns last for correct matching) + kimi: { prompt: 0.6, completion: 2.5 }, // Base pattern + moonshot: { prompt: 2.0, completion: 5.0 }, // Base pattern (using 128k pricing) + 'kimi-latest': { prompt: 0.2, completion: 2.0 }, // Uses 8k/32k/128k pricing dynamically 'kimi-k2': { prompt: 0.6, completion: 2.5 }, 'kimi-k2.5': { prompt: 0.6, completion: 3.0 }, 'kimi-k2-turbo': { prompt: 1.15, completion: 8.0 }, @@ -234,10 +251,12 @@ export const tokenValues: Record 'moonshot-v1-128k': { prompt: 2.0, completion: 5.0 }, 'moonshot-v1-128k-vision': { prompt: 2.0, completion: 5.0 }, 'moonshot-v1-128k-vision-preview': { prompt: 2.0, completion: 5.0 }, + // GPT-OSS models (specific sizes) 'gpt-oss:20b': { prompt: 0.05, completion: 0.2 }, 'gpt-oss-20b': { prompt: 0.05, completion: 0.2 }, 'gpt-oss:120b': { prompt: 0.15, completion: 0.6 }, 'gpt-oss-120b': { prompt: 0.15, completion: 0.6 }, + // GLM models (Zhipu AI) - general to specific glm4: { prompt: 0.1, completion: 0.1 }, 'glm-4': { prompt: 0.1, completion: 0.1 }, 'glm-4-32b': { prompt: 0.1, completion: 0.1 }, @@ -245,22 +264,26 @@ export const tokenValues: Record 'glm-4.5-air': { prompt: 0.14, completion: 0.86 }, 'glm-4.5v': { prompt: 0.6, completion: 1.8 }, 'glm-4.6': { prompt: 0.5, completion: 1.75 }, - qwen: { prompt: 0.08, completion: 0.33 }, - 'qwen2.5': { prompt: 0.08, completion: 0.33 }, + // Qwen models + qwen: { prompt: 0.08, completion: 0.33 }, // Qwen base pattern (using qwen2.5-72b pricing) + 'qwen2.5': { prompt: 0.08, completion: 0.33 }, // Qwen 2.5 base pattern 'qwen-turbo': { prompt: 0.05, completion: 0.2 }, 'qwen-plus': { prompt: 0.4, completion: 1.2 }, 'qwen-max': { prompt: 1.6, completion: 6.4 }, 'qwq-32b': { prompt: 0.15, completion: 0.4 }, - qwen3: { prompt: 0.035, completion: 0.138 }, + // Qwen3 models + qwen3: { prompt: 0.035, completion: 0.138 }, // Qwen3 base pattern (using qwen3-4b pricing) 'qwen3-8b': { prompt: 0.035, completion: 0.138 }, 'qwen3-14b': { prompt: 0.05, completion: 0.22 }, 'qwen3-30b-a3b': { prompt: 0.06, completion: 0.22 }, 'qwen3-32b': { prompt: 0.05, completion: 0.2 }, 'qwen3-235b-a22b': { prompt: 0.08, completion: 0.55 }, + // Qwen3 VL (Vision-Language) models 'qwen3-vl-8b-thinking': { prompt: 0.18, completion: 2.1 }, 'qwen3-vl-8b-instruct': { prompt: 0.18, completion: 0.69 }, 'qwen3-vl-30b-a3b': { prompt: 0.29, completion: 1.0 }, 'qwen3-vl-235b-a22b': { prompt: 0.3, completion: 1.2 }, + // Qwen3 specialized models 'qwen3-max': { prompt: 1.2, completion: 6 }, 'qwen3-coder': { prompt: 0.22, completion: 0.95 }, 'qwen3-coder-30b-a3b': { prompt: 0.06, completion: 0.25 }, @@ -273,9 +296,11 @@ export const tokenValues: Record /** * Mapping of model token sizes to their respective multipliers for cached input, read and write. + * See Anthropic's documentation on this: https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching#pricing * The rates are 1 USD per 1M tokens. + * @type {Object.} */ -export const cacheTokenValues: Record = { +const cacheTokenValues = { 'claude-3.7-sonnet': { write: 3.75, read: 0.3 }, 'claude-3-7-sonnet': { write: 3.75, read: 0.3 }, 'claude-3.5-sonnet': { write: 3.75, read: 0.3 }, @@ -289,27 +314,11 @@ export const cacheTokenValues: Record = 'claude-opus-4': { write: 18.75, read: 1.5 }, 'claude-opus-4-5': { write: 6.25, read: 0.5 }, 'claude-opus-4-6': { write: 6.25, read: 0.5 }, - 'gpt-4o': { write: 2.5, read: 1.25 }, - 'gpt-4o-mini': { write: 0.15, read: 0.075 }, - 'gpt-4.1': { write: 2, read: 0.5 }, - 'gpt-4.1-mini': { write: 0.4, read: 0.1 }, - 'gpt-4.1-nano': { write: 0.1, read: 0.025 }, - 'gpt-5': { write: 1.25, read: 0.125 }, - 'gpt-5.1': { write: 1.25, read: 0.125 }, - 'gpt-5.2': { write: 1.75, read: 0.175 }, - 'gpt-5.3': { write: 1.75, read: 0.175 }, - 'gpt-5.4': { write: 2.5, read: 0.25 }, - 'gpt-5-mini': { write: 0.25, read: 0.025 }, - 'gpt-5-nano': { write: 0.05, read: 0.005 }, - o1: { write: 15, read: 7.5 }, - 'o1-mini': { write: 1.1, read: 0.55 }, - 'o1-preview': { write: 15, read: 7.5 }, - o3: { write: 2, read: 0.5 }, - 'o3-mini': { write: 1.1, read: 0.275 }, - 'o4-mini': { write: 1.1, read: 0.275 }, + // DeepSeek models - cache hit: $0.028/1M, cache miss: $0.28/1M deepseek: { write: 0.28, read: 0.028 }, 'deepseek-chat': { write: 0.28, read: 0.028 }, 'deepseek-reasoner': { write: 0.28, read: 0.028 }, + // Moonshot/Kimi models - cache hit: $0.15/1M (k2) or $0.10/1M (k2.5), cache miss: $0.60/1M kimi: { write: 0.6, read: 0.15 }, 'kimi-k2': { write: 0.6, read: 0.15 }, 'kimi-k2.5': { write: 0.6, read: 0.1 }, @@ -321,177 +330,174 @@ export const cacheTokenValues: Record = 'kimi-k2-0711-preview': { write: 0.6, read: 0.15 }, 'kimi-k2-thinking': { write: 0.6, read: 0.15 }, 'kimi-k2-thinking-turbo': { write: 1.15, read: 0.15 }, - // Gemini 3.1 Pro - cache write: $2.00/1M, cache read: $0.20/1M - 'gemini-3.1': { write: 2, read: 0.2 }, - // Gemini 3.1 Flash-Lite - cache write: $0.25/1M, cache read: $0.025/1M - 'gemini-3.1-flash-lite': { write: 0.25, read: 0.025 }, }; /** * Premium (tiered) pricing for models whose rates change based on prompt size. + * Each entry specifies the token threshold and the rates that apply above it. + * @type {Object.} */ -export const premiumTokenValues: Record< - string, - { threshold: number; prompt: number; completion: number } -> = { +const premiumTokenValues = { 'claude-opus-4-6': { threshold: 200000, prompt: 10, completion: 37.5 }, 'claude-sonnet-4-6': { threshold: 200000, prompt: 6, completion: 22.5 }, - 'gemini-3.1': { threshold: 200000, prompt: 4, completion: 18 }, }; -export function createTxMethods(_mongoose: typeof import('mongoose'), txDeps: TxDeps) { - const { matchModelName, findMatchingPattern } = txDeps; - - /** - * Retrieves the key associated with a given model name. - */ - function getValueKey(model: string, endpoint?: string): string | undefined { - if (!model || typeof model !== 'string') { - return undefined; - } - - if (!endpoint || (typeof endpoint === 'string' && !tokenValues[endpoint])) { - const matchedKey = findMatchingPattern(model, tokenValues); - if (matchedKey) { - return matchedKey; - } - } - - const modelName = matchModelName(model, endpoint); - if (!modelName) { - return undefined; - } - - if (modelName.includes('gpt-3.5-turbo-16k')) { - return '16k'; - } else if (modelName.includes('gpt-3.5')) { - return '4k'; - } else if (modelName.includes('gpt-4-vision')) { - return 'gpt-4-1106'; - } else if (modelName.includes('gpt-4-0125')) { - return 'gpt-4-1106'; - } else if (modelName.includes('gpt-4-turbo')) { - return 'gpt-4-1106'; - } else if (modelName.includes('gpt-4-32k')) { - return '32k'; - } else if (modelName.includes('gpt-4')) { - return '8k'; - } - +/** + * Retrieves the key associated with a given model name. + * + * @param {string} model - The model name to match. + * @param {string} endpoint - The endpoint name to match. + * @returns {string|undefined} The key corresponding to the model name, or undefined if no match is found. + */ +const getValueKey = (model, endpoint) => { + if (!model || typeof model !== 'string') { return undefined; } - /** - * Checks if premium (tiered) pricing applies and returns the premium rate. - */ - function getPremiumRate( - valueKey: string, - tokenType: string, - inputTokenCount?: number | null, - ): number | null { - if (inputTokenCount == null) { - return null; + // Use findMatchingPattern directly against tokenValues for efficient lookup + if (!endpoint || (typeof endpoint === 'string' && !tokenValues[endpoint])) { + const matchedKey = findMatchingPattern(model, tokenValues); + if (matchedKey) { + return matchedKey; } - const premiumEntry = premiumTokenValues[valueKey]; - if (!premiumEntry || inputTokenCount <= premiumEntry.threshold) { - return null; - } - return premiumEntry[tokenType as 'prompt' | 'completion'] ?? null; } - /** - * Retrieves the multiplier for a given value key and token type. - */ - function getMultiplier({ - model, - valueKey, - endpoint, - tokenType, - inputTokenCount, - endpointTokenConfig, - }: { - model?: string; - valueKey?: string; - endpoint?: string; - tokenType?: 'prompt' | 'completion'; - inputTokenCount?: number; - endpointTokenConfig?: Record>; - }): number { - if (endpointTokenConfig && model) { - return endpointTokenConfig?.[model]?.[tokenType as string] ?? defaultRate; - } + // Fallback: use matchModelName for edge cases and legacy handling + const modelName = matchModelName(model, endpoint); + if (!modelName) { + return undefined; + } - if (valueKey && tokenType) { - const premiumRate = getPremiumRate(valueKey, tokenType, inputTokenCount); - if (premiumRate != null) { - return premiumRate; - } - return tokenValues[valueKey]?.[tokenType] ?? defaultRate; - } + // Legacy token size mappings and aliases for older models + if (modelName.includes('gpt-3.5-turbo-16k')) { + return '16k'; + } else if (modelName.includes('gpt-3.5')) { + return '4k'; + } else if (modelName.includes('gpt-4-vision')) { + return 'gpt-4-1106'; // Alias for gpt-4-vision + } else if (modelName.includes('gpt-4-0125')) { + return 'gpt-4-1106'; // Alias for gpt-4-0125 + } else if (modelName.includes('gpt-4-turbo')) { + return 'gpt-4-1106'; // Alias for gpt-4-turbo + } else if (modelName.includes('gpt-4-32k')) { + return '32k'; + } else if (modelName.includes('gpt-4')) { + return '8k'; + } - if (!tokenType || !model) { - return 1; - } + return undefined; +}; - valueKey = getValueKey(model, endpoint); - if (!valueKey) { - return defaultRate; - } +/** + * Retrieves the multiplier for a given value key and token type. If no value key is provided, + * it attempts to derive it from the model name. + * + * @param {Object} params - The parameters for the function. + * @param {string} [params.valueKey] - The key corresponding to the model name. + * @param {'prompt' | 'completion'} [params.tokenType] - The type of token (e.g., 'prompt' or 'completion'). + * @param {string} [params.model] - The model name to derive the value key from if not provided. + * @param {string} [params.endpoint] - The endpoint name to derive the value key from if not provided. + * @param {EndpointTokenConfig} [params.endpointTokenConfig] - The token configuration for the endpoint. + * @param {number} [params.inputTokenCount] - Total input token count for tiered pricing. + * @returns {number} The multiplier for the given parameters, or a default value if not found. + */ +const getMultiplier = ({ + model, + valueKey, + endpoint, + tokenType, + inputTokenCount, + endpointTokenConfig, +}) => { + if (endpointTokenConfig) { + return endpointTokenConfig?.[model]?.[tokenType] ?? defaultRate; + } + if (valueKey && tokenType) { const premiumRate = getPremiumRate(valueKey, tokenType, inputTokenCount); if (premiumRate != null) { return premiumRate; } - return tokenValues[valueKey]?.[tokenType] ?? defaultRate; } - /** - * Retrieves the cache multiplier for a given value key and token type. - */ - function getCacheMultiplier({ - valueKey, - cacheType, - model, - endpoint, - endpointTokenConfig, - }: { - valueKey?: string; - cacheType?: 'write' | 'read'; - model?: string; - endpoint?: string; - endpointTokenConfig?: Record>; - }): number | null { - if (endpointTokenConfig && model) { - return endpointTokenConfig?.[model]?.[cacheType as string] ?? null; - } + if (!tokenType || !model) { + return 1; + } - if (valueKey && cacheType) { - return cacheTokenValues[valueKey]?.[cacheType] ?? null; - } + valueKey = getValueKey(model, endpoint); + if (!valueKey) { + return defaultRate; + } - if (!cacheType || !model) { - return null; - } + const premiumRate = getPremiumRate(valueKey, tokenType, inputTokenCount); + if (premiumRate != null) { + return premiumRate; + } - valueKey = getValueKey(model, endpoint); - if (!valueKey) { - return null; - } + return tokenValues[valueKey]?.[tokenType] ?? defaultRate; +}; +/** + * Checks if premium (tiered) pricing applies and returns the premium rate. + * Each model defines its own threshold in `premiumTokenValues`. + * @param {string} valueKey + * @param {string} tokenType + * @param {number} [inputTokenCount] + * @returns {number|null} + */ +const getPremiumRate = (valueKey, tokenType, inputTokenCount) => { + if (inputTokenCount == null) { + return null; + } + const premiumEntry = premiumTokenValues[valueKey]; + if (!premiumEntry || inputTokenCount <= premiumEntry.threshold) { + return null; + } + return premiumEntry[tokenType] ?? null; +}; + +/** + * Retrieves the cache multiplier for a given value key and token type. If no value key is provided, + * it attempts to derive it from the model name. + * + * @param {Object} params - The parameters for the function. + * @param {string} [params.valueKey] - The key corresponding to the model name. + * @param {'write' | 'read'} [params.cacheType] - The type of token (e.g., 'write' or 'read'). + * @param {string} [params.model] - The model name to derive the value key from if not provided. + * @param {string} [params.endpoint] - The endpoint name to derive the value key from if not provided. + * @param {EndpointTokenConfig} [params.endpointTokenConfig] - The token configuration for the endpoint. + * @returns {number | null} The multiplier for the given parameters, or `null` if not found. + */ +const getCacheMultiplier = ({ valueKey, cacheType, model, endpoint, endpointTokenConfig }) => { + if (endpointTokenConfig) { + return endpointTokenConfig?.[model]?.[cacheType] ?? null; + } + + if (valueKey && cacheType) { return cacheTokenValues[valueKey]?.[cacheType] ?? null; } - return { - tokenValues, - premiumTokenValues, - getValueKey, - getMultiplier, - getPremiumRate, - getCacheMultiplier, - defaultRate, - cacheTokenValues, - }; -} + if (!cacheType || !model) { + return null; + } -export type TxMethods = ReturnType; + valueKey = getValueKey(model, endpoint); + if (!valueKey) { + return null; + } + + // If we got this far, and values[cacheType] is undefined somehow, return a rough average of default multipliers + return cacheTokenValues[valueKey]?.[cacheType] ?? null; +}; + +module.exports = { + tokenValues, + premiumTokenValues, + getValueKey, + getMultiplier, + getPremiumRate, + getCacheMultiplier, + defaultRate, + cacheTokenValues, +}; diff --git a/packages/data-schemas/src/methods/tx.spec.ts b/api/models/tx.spec.js similarity index 85% rename from packages/data-schemas/src/methods/tx.spec.ts rename to api/models/tx.spec.js index d1e12e5a55..df1bec8619 100644 --- a/packages/data-schemas/src/methods/tx.spec.ts +++ b/api/models/tx.spec.js @@ -1,18 +1,16 @@ /** Note: No hard-coded values should be used in this file. */ -import { matchModelName, findMatchingPattern } from './test-helpers'; -import { EModelEndpoint } from 'librechat-data-provider'; -import { - createTxMethods, - tokenValues, - cacheTokenValues, - premiumTokenValues, +const { maxTokensMap } = require('@librechat/api'); +const { EModelEndpoint } = require('librechat-data-provider'); +const { defaultRate, -} from './tx'; - -const { getValueKey, getMultiplier, getPremiumRate, getCacheMultiplier } = createTxMethods( - {} as typeof import('mongoose'), - { matchModelName, findMatchingPattern }, -); + tokenValues, + getValueKey, + getMultiplier, + getPremiumRate, + cacheTokenValues, + getCacheMultiplier, + premiumTokenValues, +} = require('./tx'); describe('getValueKey', () => { it('should return "16k" for model name containing "gpt-3.5-turbo-16k"', () => { @@ -54,24 +52,6 @@ describe('getValueKey', () => { expect(getValueKey('openai/gpt-5.2')).toBe('gpt-5.2'); }); - it('should return "gpt-5.3" for model name containing "gpt-5.3"', () => { - expect(getValueKey('gpt-5.3')).toBe('gpt-5.3'); - expect(getValueKey('gpt-5.3-chat-latest')).toBe('gpt-5.3'); - expect(getValueKey('gpt-5.3-codex')).toBe('gpt-5.3'); - expect(getValueKey('openai/gpt-5.3')).toBe('gpt-5.3'); - }); - - it('should return "gpt-5.4" for model name containing "gpt-5.4"', () => { - expect(getValueKey('gpt-5.4')).toBe('gpt-5.4'); - expect(getValueKey('gpt-5.4-thinking')).toBe('gpt-5.4'); - expect(getValueKey('openai/gpt-5.4')).toBe('gpt-5.4'); - }); - - it('should return "gpt-5.4-pro" for model name containing "gpt-5.4-pro"', () => { - expect(getValueKey('gpt-5.4-pro')).toBe('gpt-5.4-pro'); - expect(getValueKey('openai/gpt-5.4-pro')).toBe('gpt-5.4-pro'); - }); - it('should return "gpt-3.5-turbo-1106" for model name containing "gpt-3.5-turbo-1106"', () => { expect(getValueKey('gpt-3.5-turbo-1106-some-other-info')).toBe('gpt-3.5-turbo-1106'); expect(getValueKey('openai/gpt-3.5-turbo-1106')).toBe('gpt-3.5-turbo-1106'); @@ -158,12 +138,6 @@ describe('getValueKey', () => { expect(getValueKey('gpt-5-pro-preview')).toBe('gpt-5-pro'); }); - it('should return "gpt-5.2-pro" for model name containing "gpt-5.2-pro"', () => { - expect(getValueKey('gpt-5.2-pro')).toBe('gpt-5.2-pro'); - expect(getValueKey('gpt-5.2-pro-2025-03-01')).toBe('gpt-5.2-pro'); - expect(getValueKey('openai/gpt-5.2-pro')).toBe('gpt-5.2-pro'); - }); - it('should return "gpt-4o" for model type of "gpt-4o"', () => { expect(getValueKey('gpt-4o-2024-08-06')).toBe('gpt-4o'); expect(getValueKey('gpt-4o-2024-08-06-0718')).toBe('gpt-4o'); @@ -265,7 +239,6 @@ describe('getMultiplier', () => { }); it('should return defaultRate if tokenType is provided but not found in tokenValues', () => { - // @ts-expect-error: intentionally passing invalid tokenType to test error handling expect(getMultiplier({ valueKey: '8k', tokenType: 'unknownType' })).toBe(defaultRate); }); @@ -363,18 +336,6 @@ describe('getMultiplier', () => { ); }); - it('should return the correct multiplier for gpt-5.2-pro', () => { - expect(getMultiplier({ model: 'gpt-5.2-pro', tokenType: 'prompt' })).toBe( - tokenValues['gpt-5.2-pro'].prompt, - ); - expect(getMultiplier({ model: 'gpt-5.2-pro', tokenType: 'completion' })).toBe( - tokenValues['gpt-5.2-pro'].completion, - ); - expect(getMultiplier({ model: 'openai/gpt-5.2-pro', tokenType: 'prompt' })).toBe( - tokenValues['gpt-5.2-pro'].prompt, - ); - }); - it('should return the correct multiplier for gpt-5.1', () => { expect(getMultiplier({ model: 'gpt-5.1', tokenType: 'prompt' })).toBe( tokenValues['gpt-5.1'].prompt, @@ -399,48 +360,6 @@ describe('getMultiplier', () => { ); }); - it('should return the correct multiplier for gpt-5.3', () => { - expect(getMultiplier({ model: 'gpt-5.3', tokenType: 'prompt' })).toBe( - tokenValues['gpt-5.3'].prompt, - ); - expect(getMultiplier({ model: 'gpt-5.3', tokenType: 'completion' })).toBe( - tokenValues['gpt-5.3'].completion, - ); - expect(getMultiplier({ model: 'gpt-5.3-codex', tokenType: 'prompt' })).toBe( - tokenValues['gpt-5.3'].prompt, - ); - expect(getMultiplier({ model: 'openai/gpt-5.3', tokenType: 'completion' })).toBe( - tokenValues['gpt-5.3'].completion, - ); - }); - - it('should return the correct multiplier for gpt-5.4', () => { - expect(getMultiplier({ model: 'gpt-5.4', tokenType: 'prompt' })).toBe( - tokenValues['gpt-5.4'].prompt, - ); - expect(getMultiplier({ model: 'gpt-5.4', tokenType: 'completion' })).toBe( - tokenValues['gpt-5.4'].completion, - ); - expect(getMultiplier({ model: 'gpt-5.4-thinking', tokenType: 'prompt' })).toBe( - tokenValues['gpt-5.4'].prompt, - ); - expect(getMultiplier({ model: 'openai/gpt-5.4', tokenType: 'completion' })).toBe( - tokenValues['gpt-5.4'].completion, - ); - }); - - it('should return the correct multiplier for gpt-5.4-pro', () => { - expect(getMultiplier({ model: 'gpt-5.4-pro', tokenType: 'prompt' })).toBe( - tokenValues['gpt-5.4-pro'].prompt, - ); - expect(getMultiplier({ model: 'gpt-5.4-pro', tokenType: 'completion' })).toBe( - tokenValues['gpt-5.4-pro'].completion, - ); - expect(getMultiplier({ model: 'openai/gpt-5.4-pro', tokenType: 'prompt' })).toBe( - tokenValues['gpt-5.4-pro'].prompt, - ); - }); - it('should return the correct multiplier for gpt-4o', () => { const valueKey = getValueKey('gpt-4o-2024-08-06'); expect(getMultiplier({ valueKey, tokenType: 'prompt' })).toBe(tokenValues['gpt-4o'].prompt); @@ -609,7 +528,7 @@ describe('AWS Bedrock Model Tests', () => { const results = awsModels.map((model) => { const valueKey = getValueKey(model, EModelEndpoint.bedrock); const multiplier = getMultiplier({ valueKey, tokenType: 'prompt' }); - return tokenValues[valueKey!].prompt && multiplier === tokenValues[valueKey!].prompt; + return tokenValues[valueKey].prompt && multiplier === tokenValues[valueKey].prompt; }); expect(results.every(Boolean)).toBe(true); }); @@ -618,7 +537,7 @@ describe('AWS Bedrock Model Tests', () => { const results = awsModels.map((model) => { const valueKey = getValueKey(model, EModelEndpoint.bedrock); const multiplier = getMultiplier({ valueKey, tokenType: 'completion' }); - return tokenValues[valueKey!].completion && multiplier === tokenValues[valueKey!].completion; + return tokenValues[valueKey].completion && multiplier === tokenValues[valueKey].completion; }); expect(results.every(Boolean)).toBe(true); }); @@ -874,7 +793,7 @@ describe('Deepseek Model Tests', () => { const results = deepseekModels.map((model) => { const valueKey = getValueKey(model); const multiplier = getMultiplier({ valueKey, tokenType: 'prompt' }); - return tokenValues[valueKey!].prompt && multiplier === tokenValues[valueKey!].prompt; + return tokenValues[valueKey].prompt && multiplier === tokenValues[valueKey].prompt; }); expect(results.every(Boolean)).toBe(true); }); @@ -883,7 +802,7 @@ describe('Deepseek Model Tests', () => { const results = deepseekModels.map((model) => { const valueKey = getValueKey(model); const multiplier = getMultiplier({ valueKey, tokenType: 'completion' }); - return tokenValues[valueKey!].completion && multiplier === tokenValues[valueKey!].completion; + return tokenValues[valueKey].completion && multiplier === tokenValues[valueKey].completion; }); expect(results.every(Boolean)).toBe(true); }); @@ -893,7 +812,7 @@ describe('Deepseek Model Tests', () => { const valueKey = getValueKey(model); expect(valueKey).toBe(model); const multiplier = getMultiplier({ valueKey, tokenType: 'prompt' }); - const result = tokenValues[valueKey!].prompt && multiplier === tokenValues[valueKey!].prompt; + const result = tokenValues[valueKey].prompt && multiplier === tokenValues[valueKey].prompt; expect(result).toBe(true); }); @@ -1358,7 +1277,6 @@ describe('getCacheMultiplier', () => { it('should return null if cacheType is provided but not found in cacheTokenValues', () => { expect( - // @ts-expect-error: intentionally passing invalid cacheType to test error handling getCacheMultiplier({ valueKey: 'claude-3-5-sonnet', cacheType: 'unknownType' }), ).toBeNull(); }); @@ -1408,73 +1326,6 @@ describe('getCacheMultiplier', () => { ).toBeNull(); }); - it('should return correct cache multipliers for OpenAI models', () => { - const openaiCacheModels = [ - 'gpt-4o', - 'gpt-4o-mini', - 'gpt-4.1', - 'gpt-4.1-mini', - 'gpt-4.1-nano', - 'gpt-5', - 'gpt-5.1', - 'gpt-5.2', - 'gpt-5.3', - 'gpt-5.4', - 'gpt-5-mini', - 'gpt-5-nano', - 'o1', - 'o1-mini', - 'o1-preview', - 'o3', - 'o3-mini', - 'o4-mini', - ]; - - for (const model of openaiCacheModels) { - expect(getCacheMultiplier({ model, cacheType: 'write' })).toBe(cacheTokenValues[model].write); - expect(getCacheMultiplier({ model, cacheType: 'read' })).toBe(cacheTokenValues[model].read); - } - }); - - it('should return correct cache multipliers for OpenAI dated variants', () => { - expect(getCacheMultiplier({ model: 'gpt-4o-2024-08-06', cacheType: 'read' })).toBe( - cacheTokenValues['gpt-4o'].read, - ); - expect(getCacheMultiplier({ model: 'gpt-4.1-2026-01-01', cacheType: 'read' })).toBe( - cacheTokenValues['gpt-4.1'].read, - ); - expect(getCacheMultiplier({ model: 'gpt-5.3-codex', cacheType: 'read' })).toBe( - cacheTokenValues['gpt-5.3'].read, - ); - expect(getCacheMultiplier({ model: 'openai/gpt-5.3', cacheType: 'write' })).toBe( - cacheTokenValues['gpt-5.3'].write, - ); - }); - - it('should return null for pro models that do not support caching', () => { - expect(getCacheMultiplier({ model: 'gpt-5-pro', cacheType: 'read' })).toBeNull(); - expect(getCacheMultiplier({ model: 'gpt-5-pro', cacheType: 'write' })).toBeNull(); - expect(getCacheMultiplier({ model: 'gpt-5.2-pro', cacheType: 'read' })).toBeNull(); - expect(getCacheMultiplier({ model: 'gpt-5.2-pro', cacheType: 'write' })).toBeNull(); - expect(getCacheMultiplier({ model: 'gpt-5.4-pro', cacheType: 'read' })).toBeNull(); - expect(getCacheMultiplier({ model: 'gpt-5.4-pro', cacheType: 'write' })).toBeNull(); - }); - - it('should have consistent 10% cache read pricing for gpt-5.x models', () => { - const gpt5CacheModels = [ - 'gpt-5', - 'gpt-5.1', - 'gpt-5.2', - 'gpt-5.3', - 'gpt-5.4', - 'gpt-5-mini', - 'gpt-5-nano', - ]; - for (const model of gpt5CacheModels) { - expect(cacheTokenValues[model].read).toBeCloseTo(cacheTokenValues[model].write * 0.1, 10); - } - }); - it('should handle models with "bedrock/" prefix', () => { expect( getCacheMultiplier({ @@ -1494,9 +1345,6 @@ describe('getCacheMultiplier', () => { describe('Google Model Tests', () => { const googleModels = [ 'gemini-3', - 'gemini-3.1-pro-preview', - 'gemini-3.1-pro-preview-customtools', - 'gemini-3.1-flash-lite-preview', 'gemini-2.5-pro', 'gemini-2.5-flash', 'gemini-2.5-flash-lite', @@ -1533,17 +1381,14 @@ describe('Google Model Tests', () => { }); results.forEach(({ valueKey, promptRate, completionRate }) => { - expect(promptRate).toBe(tokenValues[valueKey!].prompt); - expect(completionRate).toBe(tokenValues[valueKey!].completion); + expect(promptRate).toBe(tokenValues[valueKey].prompt); + expect(completionRate).toBe(tokenValues[valueKey].completion); }); }); it('should map to the correct model keys', () => { const expected = { 'gemini-3': 'gemini-3', - 'gemini-3.1-pro-preview': 'gemini-3.1', - 'gemini-3.1-pro-preview-customtools': 'gemini-3.1', - 'gemini-3.1-flash-lite-preview': 'gemini-3.1-flash-lite', 'gemini-2.5-pro': 'gemini-2.5-pro', 'gemini-2.5-flash': 'gemini-2.5-flash', 'gemini-2.5-flash-lite': 'gemini-2.5-flash-lite', @@ -1587,190 +1432,6 @@ describe('Google Model Tests', () => { ).toBe(tokenValues[expected].completion); }); }); - - it('should return correct prompt and completion rates for Gemini 3.1', () => { - expect( - getMultiplier({ - model: 'gemini-3.1-pro-preview', - tokenType: 'prompt', - endpoint: EModelEndpoint.google, - }), - ).toBe(tokenValues['gemini-3.1'].prompt); - expect( - getMultiplier({ - model: 'gemini-3.1-pro-preview', - tokenType: 'completion', - endpoint: EModelEndpoint.google, - }), - ).toBe(tokenValues['gemini-3.1'].completion); - expect( - getMultiplier({ - model: 'gemini-3.1-pro-preview-customtools', - tokenType: 'prompt', - endpoint: EModelEndpoint.google, - }), - ).toBe(tokenValues['gemini-3.1'].prompt); - expect( - getMultiplier({ - model: 'gemini-3.1-pro-preview-customtools', - tokenType: 'completion', - endpoint: EModelEndpoint.google, - }), - ).toBe(tokenValues['gemini-3.1'].completion); - }); - - it('should return correct cache rates for Gemini 3.1', () => { - ['gemini-3.1-pro-preview', 'gemini-3.1-pro-preview-customtools'].forEach((model) => { - expect(getCacheMultiplier({ model, cacheType: 'write' })).toBe( - cacheTokenValues['gemini-3.1'].write, - ); - expect(getCacheMultiplier({ model, cacheType: 'read' })).toBe( - cacheTokenValues['gemini-3.1'].read, - ); - }); - }); - - it('should return correct rates for Gemini 3.1 Flash-Lite', () => { - const model = 'gemini-3.1-flash-lite-preview'; - expect(getMultiplier({ model, tokenType: 'prompt', endpoint: EModelEndpoint.google })).toBe( - tokenValues['gemini-3.1-flash-lite'].prompt, - ); - expect(getMultiplier({ model, tokenType: 'completion', endpoint: EModelEndpoint.google })).toBe( - tokenValues['gemini-3.1-flash-lite'].completion, - ); - expect(getCacheMultiplier({ model, cacheType: 'write' })).toBe( - cacheTokenValues['gemini-3.1-flash-lite'].write, - ); - expect(getCacheMultiplier({ model, cacheType: 'read' })).toBe( - cacheTokenValues['gemini-3.1-flash-lite'].read, - ); - }); -}); - -describe('Gemini 3.1 Premium Token Pricing', () => { - const premiumKey = 'gemini-3.1'; - const premiumEntry = premiumTokenValues[premiumKey]; - const { threshold } = premiumEntry; - const belowThreshold = threshold - 1; - const aboveThreshold = threshold + 1; - const wellAboveThreshold = threshold * 2; - - it('should have premium pricing defined for gemini-3.1', () => { - expect(premiumEntry).toBeDefined(); - expect(premiumEntry.threshold).toBeDefined(); - expect(premiumEntry.prompt).toBeDefined(); - expect(premiumEntry.completion).toBeDefined(); - expect(premiumEntry.prompt).toBeGreaterThan(tokenValues[premiumKey].prompt); - expect(premiumEntry.completion).toBeGreaterThan(tokenValues[premiumKey].completion); - }); - - it('should return null from getPremiumRate when inputTokenCount is below or at threshold', () => { - expect(getPremiumRate(premiumKey, 'prompt', belowThreshold)).toBeNull(); - expect(getPremiumRate(premiumKey, 'completion', belowThreshold)).toBeNull(); - expect(getPremiumRate(premiumKey, 'prompt', threshold)).toBeNull(); - }); - - it('should return premium rate from getPremiumRate when inputTokenCount exceeds threshold', () => { - expect(getPremiumRate(premiumKey, 'prompt', aboveThreshold)).toBe(premiumEntry.prompt); - expect(getPremiumRate(premiumKey, 'completion', aboveThreshold)).toBe(premiumEntry.completion); - expect(getPremiumRate(premiumKey, 'prompt', wellAboveThreshold)).toBe(premiumEntry.prompt); - }); - - it('should return null from getPremiumRate when inputTokenCount is undefined or null', () => { - expect(getPremiumRate(premiumKey, 'prompt', undefined)).toBeNull(); - expect(getPremiumRate(premiumKey, 'prompt', null)).toBeNull(); - }); - - it('should return standard rate from getMultiplier when inputTokenCount is below threshold', () => { - expect( - getMultiplier({ - model: 'gemini-3.1-pro-preview', - tokenType: 'prompt', - inputTokenCount: belowThreshold, - }), - ).toBe(tokenValues[premiumKey].prompt); - expect( - getMultiplier({ - model: 'gemini-3.1-pro-preview', - tokenType: 'completion', - inputTokenCount: belowThreshold, - }), - ).toBe(tokenValues[premiumKey].completion); - }); - - it('should return premium rate from getMultiplier when inputTokenCount exceeds threshold', () => { - expect( - getMultiplier({ - model: 'gemini-3.1-pro-preview', - tokenType: 'prompt', - inputTokenCount: aboveThreshold, - }), - ).toBe(premiumEntry.prompt); - expect( - getMultiplier({ - model: 'gemini-3.1-pro-preview', - tokenType: 'completion', - inputTokenCount: aboveThreshold, - }), - ).toBe(premiumEntry.completion); - }); - - it('should return standard rate from getMultiplier when inputTokenCount is exactly at threshold', () => { - expect( - getMultiplier({ - model: 'gemini-3.1-pro-preview', - tokenType: 'prompt', - inputTokenCount: threshold, - }), - ).toBe(tokenValues[premiumKey].prompt); - }); - - it('should apply premium pricing to customtools variant above threshold', () => { - expect( - getMultiplier({ - model: 'gemini-3.1-pro-preview-customtools', - tokenType: 'prompt', - inputTokenCount: aboveThreshold, - }), - ).toBe(premiumEntry.prompt); - expect( - getMultiplier({ - model: 'gemini-3.1-pro-preview-customtools', - tokenType: 'completion', - inputTokenCount: aboveThreshold, - }), - ).toBe(premiumEntry.completion); - }); - - it('should use standard rate when inputTokenCount is not provided', () => { - expect(getMultiplier({ model: 'gemini-3.1-pro-preview', tokenType: 'prompt' })).toBe( - tokenValues[premiumKey].prompt, - ); - expect(getMultiplier({ model: 'gemini-3.1-pro-preview', tokenType: 'completion' })).toBe( - tokenValues[premiumKey].completion, - ); - }); - - it('should apply premium pricing through getMultiplier with valueKey path', () => { - const valueKey = getValueKey('gemini-3.1-pro-preview'); - expect(valueKey).toBe(premiumKey); - expect(getMultiplier({ valueKey, tokenType: 'prompt', inputTokenCount: aboveThreshold })).toBe( - premiumEntry.prompt, - ); - expect( - getMultiplier({ valueKey, tokenType: 'completion', inputTokenCount: aboveThreshold }), - ).toBe(premiumEntry.completion); - }); - - it('should apply standard pricing through getMultiplier with valueKey path when below threshold', () => { - const valueKey = getValueKey('gemini-3.1-pro-preview'); - expect(getMultiplier({ valueKey, tokenType: 'prompt', inputTokenCount: belowThreshold })).toBe( - tokenValues[premiumKey].prompt, - ); - expect( - getMultiplier({ valueKey, tokenType: 'completion', inputTokenCount: belowThreshold }), - ).toBe(tokenValues[premiumKey].completion); - }); }); describe('Grok Model Tests - Pricing', () => { @@ -2314,7 +1975,7 @@ describe('Premium Token Pricing', () => { it('should return null from getPremiumRate when inputTokenCount is undefined or null', () => { expect(getPremiumRate(premiumModel, 'prompt', undefined)).toBeNull(); - expect(getPremiumRate(premiumModel, 'prompt', undefined)).toBeNull(); + expect(getPremiumRate(premiumModel, 'prompt', null)).toBeNull(); }); it('should return null from getPremiumRate for models without premium pricing', () => { @@ -2416,5 +2077,118 @@ describe('Premium Token Pricing', () => { }); }); -// Cross-package sync validation tests (tokens.ts ↔ tx.ts) moved to -// packages/api tests since they require maxTokensMap from @librechat/api. +describe('tokens.ts and tx.js sync validation', () => { + it('should resolve all models in maxTokensMap to pricing via getValueKey', () => { + const tokensKeys = Object.keys(maxTokensMap[EModelEndpoint.openAI]); + const txKeys = Object.keys(tokenValues); + + const unresolved = []; + + tokensKeys.forEach((key) => { + // Skip legacy token size mappings (e.g., '4k', '8k', '16k', '32k') + if (/^\d+k$/.test(key)) return; + + // Skip generic pattern keys (end with '-' or ':') + if (key.endsWith('-') || key.endsWith(':')) return; + + // Try to resolve via getValueKey + const resolvedKey = getValueKey(key); + + // If it resolves and the resolved key has pricing, success + if (resolvedKey && txKeys.includes(resolvedKey)) return; + + // If it resolves to a legacy key (4k, 8k, etc), also OK + if (resolvedKey && /^\d+k$/.test(resolvedKey)) return; + + // If we get here, this model can't get pricing - flag it + unresolved.push({ + key, + resolvedKey: resolvedKey || 'undefined', + context: maxTokensMap[EModelEndpoint.openAI][key], + }); + }); + + if (unresolved.length > 0) { + console.log('\nModels that cannot resolve to pricing via getValueKey:'); + unresolved.forEach(({ key, resolvedKey, context }) => { + console.log(` - '${key}' → '${resolvedKey}' (context: ${context})`); + }); + } + + expect(unresolved).toEqual([]); + }); + + it('should not have redundant dated variants with same pricing and context as base model', () => { + const txKeys = Object.keys(tokenValues); + const redundant = []; + + txKeys.forEach((key) => { + // Check if this is a dated variant (ends with -YYYY-MM-DD) + if (key.match(/.*-\d{4}-\d{2}-\d{2}$/)) { + const baseKey = key.replace(/-\d{4}-\d{2}-\d{2}$/, ''); + + if (txKeys.includes(baseKey)) { + const variantPricing = tokenValues[key]; + const basePricing = tokenValues[baseKey]; + const variantContext = maxTokensMap[EModelEndpoint.openAI][key]; + const baseContext = maxTokensMap[EModelEndpoint.openAI][baseKey]; + + const samePricing = + variantPricing.prompt === basePricing.prompt && + variantPricing.completion === basePricing.completion; + const sameContext = variantContext === baseContext; + + if (samePricing && sameContext) { + redundant.push({ + key, + baseKey, + pricing: `${variantPricing.prompt}/${variantPricing.completion}`, + context: variantContext, + }); + } + } + } + }); + + if (redundant.length > 0) { + console.log('\nRedundant dated variants found (same pricing and context as base):'); + redundant.forEach(({ key, baseKey, pricing, context }) => { + console.log(` - '${key}' → '${baseKey}' (pricing: ${pricing}, context: ${context})`); + console.log(` Can be removed - pattern matching will handle it`); + }); + } + + expect(redundant).toEqual([]); + }); + + it('should have context windows in tokens.ts for all models with pricing in tx.js (openAI catch-all)', () => { + const txKeys = Object.keys(tokenValues); + const missingContext = []; + + txKeys.forEach((key) => { + // Skip legacy token size mappings (4k, 8k, 16k, 32k) + if (/^\d+k$/.test(key)) return; + + // Check if this model has a context window defined + const context = maxTokensMap[EModelEndpoint.openAI][key]; + + if (!context) { + const pricing = tokenValues[key]; + missingContext.push({ + key, + pricing: `${pricing.prompt}/${pricing.completion}`, + }); + } + }); + + if (missingContext.length > 0) { + console.log('\nModels with pricing but missing context in tokens.ts:'); + missingContext.forEach(({ key, pricing }) => { + console.log(` - '${key}' (pricing: ${pricing})`); + console.log(` Add to tokens.ts openAIModels/bedrockModels/etc.`); + }); + } + + expect(missingContext).toEqual([]); + }); +}); diff --git a/api/models/userMethods.js b/api/models/userMethods.js new file mode 100644 index 0000000000..b57b24e641 --- /dev/null +++ b/api/models/userMethods.js @@ -0,0 +1,31 @@ +const bcrypt = require('bcryptjs'); + +/** + * Compares the provided password with the user's password. + * + * @param {IUser} user - The user to compare the password for. + * @param {string} candidatePassword - The password to test against the user's password. + * @returns {Promise} A promise that resolves to a boolean indicating if the password matches. + */ +const comparePassword = async (user, candidatePassword) => { + if (!user) { + throw new Error('No user provided'); + } + + if (!user.password) { + throw new Error('No password, likely an email first registered via Social/OIDC login'); + } + + return new Promise((resolve, reject) => { + bcrypt.compare(candidatePassword, user.password, (err, isMatch) => { + if (err) { + reject(err); + } + resolve(isMatch); + }); + }); +}; + +module.exports = { + comparePassword, +}; diff --git a/api/package.json b/api/package.json index 86b0f22c0b..1c40ddb337 100644 --- a/api/package.json +++ b/api/package.json @@ -1,6 +1,6 @@ { "name": "@librechat/backend", - "version": "v0.8.4", + "version": "v0.8.3-rc1", "description": "", "scripts": { "start": "echo 'please run this from the root directory'", @@ -35,7 +35,7 @@ "homepage": "https://librechat.ai", "dependencies": { "@anthropic-ai/vertex-sdk": "^0.14.3", - "@aws-sdk/client-bedrock-runtime": "^3.1013.0", + "@aws-sdk/client-bedrock-runtime": "^3.980.0", "@aws-sdk/client-s3": "^3.980.0", "@aws-sdk/s3-request-presigner": "^3.758.0", "@azure/identity": "^4.7.0", @@ -44,15 +44,14 @@ "@google/genai": "^1.19.0", "@keyv/redis": "^4.3.3", "@langchain/core": "^0.3.80", - "@librechat/agents": "^3.1.63", + "@librechat/agents": "^3.1.50", "@librechat/api": "*", "@librechat/data-schemas": "*", "@microsoft/microsoft-graph-client": "^3.0.7", - "@modelcontextprotocol/sdk": "^1.27.1", + "@modelcontextprotocol/sdk": "^1.26.0", "@node-saml/passport-saml": "^5.1.0", "@smithy/node-http-handler": "^4.4.5", - "ai-tokenizer": "^1.0.6", - "axios": "1.13.6", + "axios": "^1.13.5", "bcryptjs": "^2.4.3", "compression": "^1.8.1", "connect-redis": "^8.1.0", @@ -64,13 +63,13 @@ "eventsource": "^3.0.2", "express": "^5.2.1", "express-mongo-sanitize": "^2.2.0", - "express-rate-limit": "^8.3.0", + "express-rate-limit": "^8.2.1", "express-session": "^1.18.2", "express-static-gzip": "^2.2.0", - "file-type": "^21.3.2", + "file-type": "^18.7.0", "firebase": "^11.0.2", "form-data": "^4.0.4", - "handlebars": "^4.7.9", + "handlebars": "^4.7.7", "https-proxy-agent": "^7.0.6", "ioredis": "^5.3.2", "js-yaml": "^4.1.1", @@ -81,17 +80,16 @@ "klona": "^2.0.6", "librechat-data-provider": "*", "lodash": "^4.17.23", - "mammoth": "^1.11.0", "mathjs": "^15.1.0", "meilisearch": "^0.38.0", "memorystore": "^1.6.7", "mime": "^3.0.0", "module-alias": "^2.2.3", "mongoose": "^8.12.1", - "multer": "^2.1.1", + "multer": "^2.0.2", "nanoid": "^3.3.7", "node-fetch": "^2.7.0", - "nodemailer": "^8.0.4", + "nodemailer": "^7.0.11", "ollama": "^0.5.0", "openai": "5.8.2", "openid-client": "^6.5.0", @@ -104,16 +102,14 @@ "passport-jwt": "^4.0.1", "passport-ldapauth": "^3.0.1", "passport-local": "^1.0.0", - "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.24.1", + "undici": "^7.18.2", "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/api/server/cleanup.js b/api/server/cleanup.js index c27814292d..c482a2267e 100644 --- a/api/server/cleanup.js +++ b/api/server/cleanup.js @@ -35,6 +35,7 @@ const graphPropsToClean = [ 'tools', 'signal', 'config', + 'agentContexts', 'messages', 'contentData', 'stepKeyIds', @@ -123,6 +124,9 @@ function disposeClient(client) { if (client.maxContextTokens) { client.maxContextTokens = null; } + if (client.contextStrategy) { + client.contextStrategy = null; + } if (client.currentDateString) { client.currentDateString = null; } @@ -273,16 +277,7 @@ function disposeClient(client) { if (client.run) { if (client.run.Graph) { - if (typeof client.run.Graph.clearHeavyState === 'function') { - client.run.Graph.clearHeavyState(); - } else { - client.run.Graph.resetValues(); - } - - if (client.run.Graph.agentContexts) { - client.run.Graph.agentContexts.clear(); - client.run.Graph.agentContexts = null; - } + client.run.Graph.resetValues(); graphPropsToClean.forEach((prop) => { if (client.run.Graph[prop] !== undefined) { diff --git a/api/server/controllers/AuthController.js b/api/server/controllers/AuthController.js index eb44feffa4..588391b535 100644 --- a/api/server/controllers/AuthController.js +++ b/api/server/controllers/AuthController.js @@ -18,7 +18,7 @@ const { findUser, } = require('~/models'); const { getGraphApiToken } = require('~/server/services/GraphTokenService'); -const { getOpenIdConfig, getOpenIdEmail } = require('~/strategies'); +const { getOpenIdConfig } = require('~/strategies'); const registrationController = async (req, res) => { try { @@ -87,7 +87,7 @@ const refreshController = async (req, res) => { const claims = tokenset.claims(); const { user, error, migration } = await findOpenIDUser({ findUser, - email: getOpenIdEmail(claims), + email: claims.email, openidId: claims.sub, idOnTheSource: claims.oid, strategyName: 'refreshController', @@ -119,8 +119,14 @@ const refreshController = async (req, res) => { const token = setOpenIDAuthTokens(tokenset, req, res, user._id.toString(), refreshToken); - const { password: _pw, __v: _v, totpSecret: _ts, backupCodes: _bc, ...safeUser } = user; - return res.status(200).send({ token, user: safeUser }); + user.federatedTokens = { + access_token: tokenset.access_token, + id_token: tokenset.id_token, + refresh_token: refreshToken, + expires_at: claims.exp, + }; + + return res.status(200).send({ token, user }); } catch (error) { logger.error('[refreshController] OpenID token refresh error', error); return res.status(403).send('Invalid OpenID refresh token'); @@ -190,6 +196,15 @@ const graphTokenController = async (req, res) => { }); } + // Extract access token from Authorization header + const authHeader = req.headers.authorization; + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return res.status(401).json({ + message: 'Valid authorization token required', + }); + } + + // Get scopes from query parameters const scopes = req.query.scopes; if (!scopes) { return res.status(400).json({ @@ -197,13 +212,7 @@ const graphTokenController = async (req, res) => { }); } - const accessToken = req.user.federatedTokens?.access_token; - if (!accessToken) { - return res.status(401).json({ - message: 'No federated access token available for token exchange', - }); - } - + const accessToken = authHeader.substring(7); // Remove 'Bearer ' prefix const tokenResponse = await getGraphApiToken(req.user, accessToken, scopes); res.json(tokenResponse); diff --git a/api/server/controllers/AuthController.spec.js b/api/server/controllers/AuthController.spec.js deleted file mode 100644 index 964947def9..0000000000 --- a/api/server/controllers/AuthController.spec.js +++ /dev/null @@ -1,318 +0,0 @@ -jest.mock('@librechat/data-schemas', () => ({ - logger: { error: jest.fn(), debug: jest.fn(), warn: jest.fn(), info: jest.fn() }, -})); -jest.mock('~/server/services/GraphTokenService', () => ({ - getGraphApiToken: jest.fn(), -})); -jest.mock('~/server/services/AuthService', () => ({ - requestPasswordReset: jest.fn(), - setOpenIDAuthTokens: jest.fn(), - resetPassword: jest.fn(), - setAuthTokens: jest.fn(), - registerUser: jest.fn(), -})); -jest.mock('~/strategies', () => ({ getOpenIdConfig: jest.fn(), getOpenIdEmail: jest.fn() })); -jest.mock('openid-client', () => ({ refreshTokenGrant: jest.fn() })); -jest.mock('~/models', () => ({ - deleteAllUserSessions: jest.fn(), - getUserById: jest.fn(), - findSession: jest.fn(), - updateUser: jest.fn(), - findUser: jest.fn(), -})); -jest.mock('@librechat/api', () => ({ - isEnabled: jest.fn(), - findOpenIDUser: jest.fn(), -})); - -const openIdClient = require('openid-client'); -const { isEnabled, findOpenIDUser } = require('@librechat/api'); -const { graphTokenController, refreshController } = require('./AuthController'); -const { getGraphApiToken } = require('~/server/services/GraphTokenService'); -const { setOpenIDAuthTokens } = require('~/server/services/AuthService'); -const { getOpenIdConfig, getOpenIdEmail } = require('~/strategies'); -const { updateUser } = require('~/models'); - -describe('graphTokenController', () => { - let req, res; - - beforeEach(() => { - jest.clearAllMocks(); - isEnabled.mockReturnValue(true); - - req = { - user: { - openidId: 'oid-123', - provider: 'openid', - federatedTokens: { - access_token: 'federated-access-token', - id_token: 'federated-id-token', - }, - }, - headers: { authorization: 'Bearer app-jwt-which-is-id-token' }, - query: { scopes: 'https://graph.microsoft.com/.default' }, - }; - - res = { - status: jest.fn().mockReturnThis(), - json: jest.fn(), - }; - - getGraphApiToken.mockResolvedValue({ - access_token: 'graph-access-token', - token_type: 'Bearer', - expires_in: 3600, - }); - }); - - it('should pass federatedTokens.access_token as OBO assertion, not the auth header bearer token', async () => { - await graphTokenController(req, res); - - expect(getGraphApiToken).toHaveBeenCalledWith( - req.user, - 'federated-access-token', - 'https://graph.microsoft.com/.default', - ); - expect(getGraphApiToken).not.toHaveBeenCalledWith( - expect.anything(), - 'app-jwt-which-is-id-token', - expect.anything(), - ); - }); - - it('should return the graph token response on success', async () => { - await graphTokenController(req, res); - - expect(res.json).toHaveBeenCalledWith({ - access_token: 'graph-access-token', - token_type: 'Bearer', - expires_in: 3600, - }); - }); - - it('should return 403 when user is not authenticated via Entra ID', async () => { - req.user.provider = 'google'; - req.user.openidId = undefined; - - await graphTokenController(req, res); - - expect(res.status).toHaveBeenCalledWith(403); - expect(getGraphApiToken).not.toHaveBeenCalled(); - }); - - it('should return 403 when OPENID_REUSE_TOKENS is not enabled', async () => { - isEnabled.mockReturnValue(false); - - await graphTokenController(req, res); - - expect(res.status).toHaveBeenCalledWith(403); - expect(getGraphApiToken).not.toHaveBeenCalled(); - }); - - it('should return 400 when scopes query param is missing', async () => { - req.query.scopes = undefined; - - await graphTokenController(req, res); - - expect(res.status).toHaveBeenCalledWith(400); - expect(getGraphApiToken).not.toHaveBeenCalled(); - }); - - it('should return 401 when federatedTokens.access_token is missing', async () => { - req.user.federatedTokens = {}; - - await graphTokenController(req, res); - - expect(res.status).toHaveBeenCalledWith(401); - expect(getGraphApiToken).not.toHaveBeenCalled(); - }); - - it('should return 401 when federatedTokens is absent entirely', async () => { - req.user.federatedTokens = undefined; - - await graphTokenController(req, res); - - expect(res.status).toHaveBeenCalledWith(401); - expect(getGraphApiToken).not.toHaveBeenCalled(); - }); - - it('should return 500 when getGraphApiToken throws', async () => { - getGraphApiToken.mockRejectedValue(new Error('OBO exchange failed')); - - await graphTokenController(req, res); - - expect(res.status).toHaveBeenCalledWith(500); - expect(res.json).toHaveBeenCalledWith({ - message: 'Failed to obtain Microsoft Graph token', - }); - }); -}); - -describe('refreshController – OpenID path', () => { - const mockTokenset = { - claims: jest.fn(), - access_token: 'new-access', - id_token: 'new-id', - refresh_token: 'new-refresh', - }; - - const baseClaims = { - sub: 'oidc-sub-123', - oid: 'oid-456', - email: 'user@example.com', - exp: 9999999999, - }; - - const defaultUser = { - _id: 'user-db-id', - email: baseClaims.email, - openidId: baseClaims.sub, - password: '$2b$10$hashedpassword', - __v: 0, - totpSecret: 'encrypted-totp-secret', - backupCodes: ['hashed-code-1', 'hashed-code-2'], - }; - - let req, res; - - beforeEach(() => { - jest.clearAllMocks(); - - isEnabled.mockReturnValue(true); - getOpenIdConfig.mockReturnValue({ some: 'config' }); - openIdClient.refreshTokenGrant.mockResolvedValue(mockTokenset); - mockTokenset.claims.mockReturnValue(baseClaims); - getOpenIdEmail.mockReturnValue(baseClaims.email); - setOpenIDAuthTokens.mockReturnValue('new-app-token'); - findOpenIDUser.mockResolvedValue({ user: { ...defaultUser }, error: null, migration: false }); - updateUser.mockResolvedValue({}); - - req = { - headers: { cookie: 'token_provider=openid; refreshToken=stored-refresh' }, - session: {}, - }; - - res = { - status: jest.fn().mockReturnThis(), - send: jest.fn().mockReturnThis(), - redirect: jest.fn(), - }; - }); - - it('should call getOpenIdEmail with token claims and use result for findOpenIDUser', async () => { - await refreshController(req, res); - - expect(getOpenIdEmail).toHaveBeenCalledWith(baseClaims); - expect(findOpenIDUser).toHaveBeenCalledWith( - expect.objectContaining({ email: baseClaims.email }), - ); - expect(res.status).toHaveBeenCalledWith(200); - }); - - it('should use OPENID_EMAIL_CLAIM-resolved value when claim is present in token', async () => { - const claimsWithUpn = { ...baseClaims, upn: 'user@corp.example.com' }; - mockTokenset.claims.mockReturnValue(claimsWithUpn); - getOpenIdEmail.mockReturnValue('user@corp.example.com'); - - const user = { - _id: 'user-db-id', - email: 'user@corp.example.com', - openidId: baseClaims.sub, - }; - findOpenIDUser.mockResolvedValue({ user, error: null, migration: false }); - - await refreshController(req, res); - - expect(getOpenIdEmail).toHaveBeenCalledWith(claimsWithUpn); - expect(findOpenIDUser).toHaveBeenCalledWith( - expect.objectContaining({ email: 'user@corp.example.com' }), - ); - expect(res.status).toHaveBeenCalledWith(200); - }); - - it('should fall back to claims.email when configured claim is absent from token claims', async () => { - getOpenIdEmail.mockReturnValue(baseClaims.email); - - await refreshController(req, res); - - expect(findOpenIDUser).toHaveBeenCalledWith( - expect.objectContaining({ email: baseClaims.email }), - ); - }); - - it('should not expose sensitive fields or federatedTokens in refresh response', async () => { - await refreshController(req, res); - - const sentPayload = res.send.mock.calls[0][0]; - expect(sentPayload).toEqual({ - token: 'new-app-token', - user: expect.objectContaining({ - _id: 'user-db-id', - email: baseClaims.email, - openidId: baseClaims.sub, - }), - }); - expect(sentPayload.user).not.toHaveProperty('federatedTokens'); - expect(sentPayload.user).not.toHaveProperty('password'); - expect(sentPayload.user).not.toHaveProperty('totpSecret'); - expect(sentPayload.user).not.toHaveProperty('backupCodes'); - expect(sentPayload.user).not.toHaveProperty('__v'); - }); - - it('should update openidId when migration is triggered on refresh', async () => { - const user = { _id: 'user-db-id', email: baseClaims.email, openidId: null }; - findOpenIDUser.mockResolvedValue({ user, error: null, migration: true }); - - await refreshController(req, res); - - expect(updateUser).toHaveBeenCalledWith( - 'user-db-id', - expect.objectContaining({ provider: 'openid', openidId: baseClaims.sub }), - ); - expect(res.status).toHaveBeenCalledWith(200); - }); - - it('should return 401 and redirect to /login when findOpenIDUser returns no user', async () => { - findOpenIDUser.mockResolvedValue({ user: null, error: null, migration: false }); - - await refreshController(req, res); - - expect(res.status).toHaveBeenCalledWith(401); - expect(res.redirect).toHaveBeenCalledWith('/login'); - }); - - it('should return 401 and redirect when findOpenIDUser returns an error', async () => { - findOpenIDUser.mockResolvedValue({ user: null, error: 'AUTH_FAILED', migration: false }); - - await refreshController(req, res); - - expect(res.status).toHaveBeenCalledWith(401); - expect(res.redirect).toHaveBeenCalledWith('/login'); - }); - - it('should skip OpenID path when token_provider is not openid', async () => { - req.headers.cookie = 'token_provider=local; refreshToken=some-token'; - - await refreshController(req, res); - - expect(openIdClient.refreshTokenGrant).not.toHaveBeenCalled(); - }); - - it('should skip OpenID path when OPENID_REUSE_TOKENS is disabled', async () => { - isEnabled.mockReturnValue(false); - - await refreshController(req, res); - - expect(openIdClient.refreshTokenGrant).not.toHaveBeenCalled(); - }); - - it('should return 200 with token not provided when refresh token is absent', async () => { - req.headers.cookie = 'token_provider=openid'; - req.session = {}; - - await refreshController(req, res); - - expect(res.status).toHaveBeenCalledWith(200); - expect(res.send).toHaveBeenCalledWith('Refresh token not provided'); - }); -}); diff --git a/api/server/controllers/Balance.js b/api/server/controllers/Balance.js index fd9b32e74c..c892a73b0c 100644 --- a/api/server/controllers/Balance.js +++ b/api/server/controllers/Balance.js @@ -1,22 +1,24 @@ -const { findBalanceByUser } = require('~/models'); +const { Balance } = require('~/db/models'); async function balanceController(req, res) { - const balanceData = await findBalanceByUser(req.user.id); + const balanceData = await Balance.findOne( + { user: req.user.id }, + '-_id tokenCredits autoRefillEnabled refillIntervalValue refillIntervalUnit lastRefill refillAmount', + ).lean(); if (!balanceData) { return res.status(404).json({ error: 'Balance not found' }); } - const { _id: _, ...result } = balanceData; - - if (!result.autoRefillEnabled) { - delete result.refillIntervalValue; - delete result.refillIntervalUnit; - delete result.lastRefill; - delete result.refillAmount; + // If auto-refill is not enabled, remove auto-refill related fields from the response + if (!balanceData.autoRefillEnabled) { + delete balanceData.refillIntervalValue; + delete balanceData.refillIntervalUnit; + delete balanceData.lastRefill; + delete balanceData.refillAmount; } - res.status(200).json(result); + res.status(200).json(balanceData); } module.exports = balanceController; diff --git a/api/server/controllers/ModelController.js b/api/server/controllers/ModelController.js index 4738d45111..805d9eef27 100644 --- a/api/server/controllers/ModelController.js +++ b/api/server/controllers/ModelController.js @@ -1,12 +1,40 @@ const { logger } = require('@librechat/data-schemas'); +const { CacheKeys } = require('librechat-data-provider'); const { loadDefaultModels, loadConfigModels } = require('~/server/services/Config'); +const { getLogStores } = require('~/cache'); -const getModelsConfig = (req) => loadModels(req); +/** + * @param {ServerRequest} req + * @returns {Promise} The models config. + */ +const getModelsConfig = async (req) => { + const cache = getLogStores(CacheKeys.CONFIG_STORE); + let modelsConfig = await cache.get(CacheKeys.MODELS_CONFIG); + if (!modelsConfig) { + modelsConfig = await loadModels(req); + } + return modelsConfig; +}; + +/** + * Loads the models from the config. + * @param {ServerRequest} req - The Express request object. + * @returns {Promise} The models config. + */ async function loadModels(req) { + const cache = getLogStores(CacheKeys.CONFIG_STORE); + const cachedModelsConfig = await cache.get(CacheKeys.MODELS_CONFIG); + if (cachedModelsConfig) { + return cachedModelsConfig; + } const defaultModelsConfig = await loadDefaultModels(req); const customModelsConfig = await loadConfigModels(req); - return { ...defaultModelsConfig, ...customModelsConfig }; + + const modelConfig = { ...defaultModelsConfig, ...customModelsConfig }; + + await cache.set(CacheKeys.MODELS_CONFIG, modelConfig); + return modelConfig; } async function modelController(req, res) { diff --git a/api/server/controllers/PermissionsController.js b/api/server/controllers/PermissionsController.js index 1f200fce83..51993d083c 100644 --- a/api/server/controllers/PermissionsController.js +++ b/api/server/controllers/PermissionsController.js @@ -9,17 +9,22 @@ const { enrichRemoteAgentPrincipals, backfillRemoteAgentPermissions } = require( const { bulkUpdateResourcePermissions, ensureGroupPrincipalExists, - getResourcePermissionsMap, - findAccessibleResources, getEffectivePermissions, ensurePrincipalExists, getAvailableRoles, + findAccessibleResources, + getResourcePermissionsMap, } = require('~/server/services/PermissionService'); +const { + searchPrincipals: searchLocalPrincipals, + sortPrincipalsByRelevance, + calculateRelevanceScore, +} = require('~/models'); const { entraIdPrincipalFeatureEnabled, searchEntraIdPrincipals, } = require('~/server/services/GraphApiService'); -const db = require('~/models'); +const { AclEntry, AccessRole } = require('~/db/models'); /** * Generic controller for resource permission endpoints @@ -150,18 +155,6 @@ 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) { - db.removeAgentFromUserFavorites(resourceId, revokedUserIds).catch((err) => { - logger.error('[removeRevokedAgentFromFavorites] Error cleaning up favorites', err); - }); - } - /** @type {TUpdateResourcePermissionsResponse} */ const response = { message: 'Permissions updated successfully', @@ -192,7 +185,8 @@ const getResourcePermissions = async (req, res) => { const { resourceType, resourceId } = req.params; validateResourceType(resourceType); - const results = await db.aggregateAclEntries([ + // Use aggregation pipeline for efficient single-query data retrieval + const results = await AclEntry.aggregate([ // Match ACL entries for this resource { $match: { @@ -288,12 +282,7 @@ const getResourcePermissions = async (req, res) => { } if (resourceType === ResourceType.REMOTE_AGENT) { - const enricherDeps = { - aggregateAclEntries: db.aggregateAclEntries, - bulkWriteAclEntries: db.bulkWriteAclEntries, - findRoleByIdentifier: db.findRoleByIdentifier, - logger, - }; + const enricherDeps = { AclEntry, AccessRole, logger }; const enrichResult = await enrichRemoteAgentPrincipals(enricherDeps, resourceId, principals); principals = enrichResult.principals; backfillRemoteAgentPermissions(enricherDeps, resourceId, enrichResult.entriesToBackfill); @@ -410,7 +399,7 @@ const searchPrincipals = async (req, res) => { typeFilters = validTypes.length > 0 ? validTypes : null; } - const localResults = await db.searchPrincipals(query.trim(), searchLimit, typeFilters); + const localResults = await searchLocalPrincipals(query.trim(), searchLimit, typeFilters); let allPrincipals = [...localResults]; const useEntraId = entraIdPrincipalFeatureEnabled(req.user); @@ -466,11 +455,10 @@ const searchPrincipals = async (req, res) => { } const scoredResults = allPrincipals.map((item) => ({ ...item, - _searchScore: db.calculateRelevanceScore(item, query.trim()), + _searchScore: calculateRelevanceScore(item, query.trim()), })); - const finalResults = db - .sortPrincipalsByRelevance(scoredResults) + const finalResults = sortPrincipalsByRelevance(scoredResults) .slice(0, searchLimit) .map((result) => { const { _searchScore, ...resultWithoutScore } = result; diff --git a/api/server/controllers/PluginController.js b/api/server/controllers/PluginController.js index c5d5c5b888..279ffb15fd 100644 --- a/api/server/controllers/PluginController.js +++ b/api/server/controllers/PluginController.js @@ -1,37 +1,61 @@ const { logger } = require('@librechat/data-schemas'); +const { CacheKeys } = require('librechat-data-provider'); const { getToolkitKey, checkPluginAuth, filterUniquePlugins } = require('@librechat/api'); const { getCachedTools, setCachedTools } = require('~/server/services/Config'); const { availableTools, toolkits } = require('~/app/clients/tools'); const { getAppConfig } = require('~/server/services/Config'); +const { getLogStores } = require('~/cache'); const getAvailablePluginsController = async (req, res) => { try { - const appConfig = await getAppConfig({ role: req.user?.role, tenantId: req.user?.tenantId }); - const { filteredTools = [], includedTools = [] } = appConfig; - - const uniquePlugins = filterUniquePlugins(availableTools); - const includeSet = new Set(includedTools); - const filterSet = new Set(filteredTools); - - /** includedTools takes precedence — filteredTools ignored when both are set. */ - const plugins = []; - for (const plugin of uniquePlugins) { - if (includeSet.size > 0) { - if (!includeSet.has(plugin.pluginKey)) { - continue; - } - } else if (filterSet.has(plugin.pluginKey)) { - continue; - } - plugins.push(checkPluginAuth(plugin) ? { ...plugin, authenticated: true } : plugin); + const cache = getLogStores(CacheKeys.TOOL_CACHE); + const cachedPlugins = await cache.get(CacheKeys.PLUGINS); + if (cachedPlugins) { + res.status(200).json(cachedPlugins); + return; } + const appConfig = await getAppConfig({ role: req.user?.role }); + /** @type {{ filteredTools: string[], includedTools: string[] }} */ + const { filteredTools = [], includedTools = [] } = appConfig; + /** @type {import('@librechat/api').LCManifestTool[]} */ + const pluginManifest = availableTools; + + const uniquePlugins = filterUniquePlugins(pluginManifest); + let authenticatedPlugins = []; + for (const plugin of uniquePlugins) { + authenticatedPlugins.push( + checkPluginAuth(plugin) ? { ...plugin, authenticated: true } : plugin, + ); + } + + let plugins = authenticatedPlugins; + + if (includedTools.length > 0) { + plugins = plugins.filter((plugin) => includedTools.includes(plugin.pluginKey)); + } else { + plugins = plugins.filter((plugin) => !filteredTools.includes(plugin.pluginKey)); + } + + await cache.set(CacheKeys.PLUGINS, plugins); res.status(200).json(plugins); } catch (error) { res.status(500).json({ message: error.message }); } }; +/** + * Retrieves and returns a list of available tools, either from a cache or by reading a plugin manifest file. + * + * This function first attempts to retrieve the list of tools from a cache. If the tools are not found in the cache, + * it reads a plugin manifest file, filters for unique plugins, and determines if each plugin is authenticated. + * Only plugins that are marked as available in the application's local state are included in the final list. + * The resulting list of tools is then cached and sent to the client. + * + * @param {object} req - The request object, containing information about the HTTP request. + * @param {object} res - The response object, used to send back the desired HTTP response. + * @returns {Promise} A promise that resolves when the function has completed. + */ const getAvailableTools = async (req, res) => { try { const userId = req.user?.id; @@ -39,10 +63,18 @@ const getAvailableTools = async (req, res) => { logger.warn('[getAvailableTools] User ID not found in request'); return res.status(401).json({ message: 'Unauthorized' }); } + const cache = getLogStores(CacheKeys.TOOL_CACHE); + const cachedToolsArray = await cache.get(CacheKeys.TOOLS); - const appConfig = - req.config ?? (await getAppConfig({ role: req.user?.role, tenantId: req.user?.tenantId })); + const appConfig = req.config ?? (await getAppConfig({ role: req.user?.role })); + // Return early if we have cached tools + if (cachedToolsArray != null) { + res.status(200).json(cachedToolsArray); + return; + } + + /** @type {Record | null} Get tool definitions to filter which tools are actually available */ let toolDefinitions = await getCachedTools(); if (toolDefinitions == null && appConfig?.availableTools != null) { @@ -51,17 +83,26 @@ const getAvailableTools = async (req, res) => { toolDefinitions = appConfig.availableTools; } - const uniquePlugins = filterUniquePlugins(availableTools); - const toolDefKeysList = toolDefinitions ? Object.keys(toolDefinitions) : null; - const toolDefKeys = toolDefKeysList ? new Set(toolDefKeysList) : null; + /** @type {import('@librechat/api').LCManifestTool[]} */ + let pluginManifest = availableTools; + /** @type {TPlugin[]} Deduplicate and authenticate plugins */ + const uniquePlugins = filterUniquePlugins(pluginManifest); + const authenticatedPlugins = uniquePlugins.map((plugin) => { + if (checkPluginAuth(plugin)) { + return { ...plugin, authenticated: true }; + } else { + return plugin; + } + }); + + /** Filter plugins based on availability */ const toolsOutput = []; - for (const plugin of uniquePlugins) { - const isToolDefined = toolDefKeys?.has(plugin.pluginKey) === true; + for (const plugin of authenticatedPlugins) { + const isToolDefined = toolDefinitions?.[plugin.pluginKey] !== undefined; const isToolkit = plugin.toolkit === true && - toolDefKeysList != null && - toolDefKeysList.some( + Object.keys(toolDefinitions ?? {}).some( (key) => getToolkitKey({ toolkits, toolName: key }) === plugin.pluginKey, ); @@ -69,10 +110,13 @@ const getAvailableTools = async (req, res) => { continue; } - toolsOutput.push(checkPluginAuth(plugin) ? { ...plugin, authenticated: true } : plugin); + toolsOutput.push(plugin); } - res.status(200).json(toolsOutput); + const finalTools = filterUniquePlugins(toolsOutput); + await cache.set(CacheKeys.TOOLS, finalTools); + + res.status(200).json(finalTools); } catch (error) { logger.error('[getAvailableTools]', error); res.status(500).json({ message: error.message }); diff --git a/api/server/controllers/PluginController.spec.js b/api/server/controllers/PluginController.spec.js index 9288680567..06a51a3bd6 100644 --- a/api/server/controllers/PluginController.spec.js +++ b/api/server/controllers/PluginController.spec.js @@ -1,4 +1,6 @@ +const { CacheKeys } = require('librechat-data-provider'); const { getCachedTools, getAppConfig } = require('~/server/services/Config'); +const { getLogStores } = require('~/cache'); jest.mock('@librechat/data-schemas', () => ({ logger: { @@ -17,15 +19,22 @@ jest.mock('~/server/services/Config', () => ({ setCachedTools: jest.fn(), })); +// loadAndFormatTools mock removed - no longer used in PluginController +// getMCPManager mock removed - no longer used in PluginController + jest.mock('~/app/clients/tools', () => ({ availableTools: [], toolkits: [], })); +jest.mock('~/cache', () => ({ + getLogStores: jest.fn(), +})); + const { getAvailableTools, getAvailablePluginsController } = require('./PluginController'); describe('PluginController', () => { - let mockReq, mockRes; + let mockReq, mockRes, mockCache; beforeEach(() => { jest.clearAllMocks(); @@ -37,12 +46,17 @@ describe('PluginController', () => { }, }; mockRes = { status: jest.fn().mockReturnThis(), json: jest.fn() }; + mockCache = { get: jest.fn(), set: jest.fn() }; + getLogStores.mockReturnValue(mockCache); + // Clear availableTools and toolkits arrays before each test require('~/app/clients/tools').availableTools.length = 0; require('~/app/clients/tools').toolkits.length = 0; + // Reset getCachedTools mock to ensure clean state getCachedTools.mockReset(); + // Reset getAppConfig mock to ensure clean state with default values getAppConfig.mockReset(); getAppConfig.mockResolvedValue({ filteredTools: [], @@ -50,8 +64,31 @@ describe('PluginController', () => { }); }); + describe('cache namespace', () => { + it('getAvailablePluginsController should use TOOL_CACHE namespace', async () => { + mockCache.get.mockResolvedValue([]); + await getAvailablePluginsController(mockReq, mockRes); + expect(getLogStores).toHaveBeenCalledWith(CacheKeys.TOOL_CACHE); + }); + + it('getAvailableTools should use TOOL_CACHE namespace', async () => { + mockCache.get.mockResolvedValue([]); + await getAvailableTools(mockReq, mockRes); + expect(getLogStores).toHaveBeenCalledWith(CacheKeys.TOOL_CACHE); + }); + + it('should NOT use CONFIG_STORE namespace for tool/plugin operations', async () => { + mockCache.get.mockResolvedValue([]); + await getAvailablePluginsController(mockReq, mockRes); + await getAvailableTools(mockReq, mockRes); + const allCalls = getLogStores.mock.calls.flat(); + expect(allCalls).not.toContain(CacheKeys.CONFIG_STORE); + }); + }); + describe('getAvailablePluginsController', () => { it('should use filterUniquePlugins to remove duplicate plugins', async () => { + // Add plugins with duplicates to availableTools const mockPlugins = [ { name: 'Plugin1', pluginKey: 'key1', description: 'First' }, { name: 'Plugin1', pluginKey: 'key1', description: 'First duplicate' }, @@ -60,6 +97,9 @@ describe('PluginController', () => { require('~/app/clients/tools').availableTools.push(...mockPlugins); + mockCache.get.mockResolvedValue(null); + + // Configure getAppConfig to return the expected config getAppConfig.mockResolvedValueOnce({ filteredTools: [], includedTools: [], @@ -69,16 +109,21 @@ describe('PluginController', () => { expect(mockRes.status).toHaveBeenCalledWith(200); const responseData = mockRes.json.mock.calls[0][0]; + // The real filterUniquePlugins should have removed the duplicate expect(responseData).toHaveLength(2); expect(responseData[0].pluginKey).toBe('key1'); expect(responseData[1].pluginKey).toBe('key2'); }); it('should use checkPluginAuth to verify plugin authentication', async () => { + // checkPluginAuth returns false for plugins without authConfig + // so authenticated property won't be added const mockPlugin = { name: 'Plugin1', pluginKey: 'key1', description: 'First' }; require('~/app/clients/tools').availableTools.push(mockPlugin); + mockCache.get.mockResolvedValue(null); + // Configure getAppConfig to return the expected config getAppConfig.mockResolvedValueOnce({ filteredTools: [], includedTools: [], @@ -87,9 +132,23 @@ describe('PluginController', () => { await getAvailablePluginsController(mockReq, mockRes); const responseData = mockRes.json.mock.calls[0][0]; + // The real checkPluginAuth returns false for plugins without authConfig, so authenticated property is not added expect(responseData[0].authenticated).toBeUndefined(); }); + it('should return cached plugins when available', async () => { + const cachedPlugins = [ + { name: 'CachedPlugin', pluginKey: 'cached', description: 'Cached plugin' }, + ]; + + mockCache.get.mockResolvedValue(cachedPlugins); + + await getAvailablePluginsController(mockReq, mockRes); + + // When cache is hit, we return immediately without processing + expect(mockRes.json).toHaveBeenCalledWith(cachedPlugins); + }); + it('should filter plugins based on includedTools', async () => { const mockPlugins = [ { name: 'Plugin1', pluginKey: 'key1', description: 'First' }, @@ -97,7 +156,9 @@ describe('PluginController', () => { ]; require('~/app/clients/tools').availableTools.push(...mockPlugins); + mockCache.get.mockResolvedValue(null); + // Configure getAppConfig to return config with includedTools getAppConfig.mockResolvedValueOnce({ filteredTools: [], includedTools: ['key1'], @@ -109,47 +170,6 @@ describe('PluginController', () => { expect(responseData).toHaveLength(1); expect(responseData[0].pluginKey).toBe('key1'); }); - - it('should exclude plugins in filteredTools', async () => { - const mockPlugins = [ - { name: 'Plugin1', pluginKey: 'key1', description: 'First' }, - { name: 'Plugin2', pluginKey: 'key2', description: 'Second' }, - ]; - - require('~/app/clients/tools').availableTools.push(...mockPlugins); - - getAppConfig.mockResolvedValueOnce({ - filteredTools: ['key2'], - includedTools: [], - }); - - await getAvailablePluginsController(mockReq, mockRes); - - const responseData = mockRes.json.mock.calls[0][0]; - expect(responseData).toHaveLength(1); - expect(responseData[0].pluginKey).toBe('key1'); - }); - - it('should ignore filteredTools when includedTools is set', async () => { - const mockPlugins = [ - { name: 'Plugin1', pluginKey: 'key1', description: 'First' }, - { name: 'Plugin2', pluginKey: 'key2', description: 'Second' }, - { name: 'Plugin3', pluginKey: 'key3', description: 'Third' }, - ]; - - require('~/app/clients/tools').availableTools.push(...mockPlugins); - - getAppConfig.mockResolvedValueOnce({ - includedTools: ['key1', 'key2'], - filteredTools: ['key2'], - }); - - await getAvailablePluginsController(mockReq, mockRes); - - const responseData = mockRes.json.mock.calls[0][0]; - expect(responseData).toHaveLength(2); - expect(responseData.map((p) => p.pluginKey)).toEqual(['key1', 'key2']); - }); }); describe('getAvailableTools', () => { @@ -165,11 +185,12 @@ describe('PluginController', () => { }, }; - require('~/app/clients/tools').availableTools.push( + const mockCachedPlugins = [ { name: 'user-tool', pluginKey: 'user-tool', description: 'Duplicate user tool' }, { name: 'ManifestTool', pluginKey: 'manifest-tool', description: 'Manifest tool' }, - ); + ]; + mockCache.get.mockResolvedValue(mockCachedPlugins); getCachedTools.mockResolvedValueOnce(mockUserTools); mockReq.config = { mcpConfig: null, @@ -181,19 +202,24 @@ describe('PluginController', () => { expect(mockRes.status).toHaveBeenCalledWith(200); const responseData = mockRes.json.mock.calls[0][0]; expect(Array.isArray(responseData)).toBe(true); + // The real filterUniquePlugins should have deduplicated tools with same pluginKey const userToolCount = responseData.filter((tool) => tool.pluginKey === 'user-tool').length; expect(userToolCount).toBe(1); }); it('should use checkPluginAuth to verify authentication status', async () => { + // Add a plugin to availableTools that will be checked const mockPlugin = { name: 'Tool1', pluginKey: 'tool1', description: 'Tool 1', + // No authConfig means checkPluginAuth returns false }; require('~/app/clients/tools').availableTools.push(mockPlugin); + mockCache.get.mockResolvedValue(null); + // getCachedTools returns the tool definitions getCachedTools.mockResolvedValueOnce({ tool1: { type: 'function', @@ -216,6 +242,7 @@ describe('PluginController', () => { expect(Array.isArray(responseData)).toBe(true); const tool = responseData.find((t) => t.pluginKey === 'tool1'); expect(tool).toBeDefined(); + // The real checkPluginAuth returns false for plugins without authConfig, so authenticated property is not added expect(tool.authenticated).toBeUndefined(); }); @@ -229,12 +256,15 @@ describe('PluginController', () => { require('~/app/clients/tools').availableTools.push(mockToolkit); + // Mock toolkits to have a mapping require('~/app/clients/tools').toolkits.push({ name: 'Toolkit1', pluginKey: 'toolkit1', tools: ['toolkit1_function'], }); + mockCache.get.mockResolvedValue(null); + // getCachedTools returns the tool definitions getCachedTools.mockResolvedValueOnce({ toolkit1_function: { type: 'function', @@ -262,7 +292,7 @@ describe('PluginController', () => { describe('helper function integration', () => { it('should handle error cases gracefully', async () => { - getCachedTools.mockRejectedValue(new Error('Cache error')); + mockCache.get.mockRejectedValue(new Error('Cache error')); await getAvailableTools(mockReq, mockRes); @@ -272,7 +302,17 @@ describe('PluginController', () => { }); describe('edge cases with undefined/null values', () => { - it('should handle null cachedTools', async () => { + it('should handle undefined cache gracefully', async () => { + getLogStores.mockReturnValue(undefined); + + await getAvailableTools(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(500); + }); + + it('should handle null cachedTools and cachedUserTools', async () => { + mockCache.get.mockResolvedValue(null); + // getCachedTools returns empty object instead of null getCachedTools.mockResolvedValueOnce({}); mockReq.config = { mcpConfig: null, @@ -281,40 +321,51 @@ describe('PluginController', () => { await getAvailableTools(mockReq, mockRes); + // Should handle null values gracefully expect(mockRes.status).toHaveBeenCalledWith(200); expect(mockRes.json).toHaveBeenCalledWith([]); }); it('should handle when getCachedTools returns undefined', async () => { + mockCache.get.mockResolvedValue(null); mockReq.config = { mcpConfig: null, paths: { structuredTools: '/mock/path' }, }; + // Mock getCachedTools to return undefined getCachedTools.mockReset(); getCachedTools.mockResolvedValueOnce(undefined); await getAvailableTools(mockReq, mockRes); + // Should handle undefined values gracefully expect(mockRes.status).toHaveBeenCalledWith(200); expect(mockRes.json).toHaveBeenCalledWith([]); }); it('should handle empty toolDefinitions object', async () => { + mockCache.get.mockResolvedValue(null); + // Reset getCachedTools to ensure clean state getCachedTools.mockReset(); getCachedTools.mockResolvedValue({}); - mockReq.config = {}; + mockReq.config = {}; // No mcpConfig at all + // Ensure no plugins are available require('~/app/clients/tools').availableTools.length = 0; await getAvailableTools(mockReq, mockRes); + // With empty tool definitions, no tools should be in the final output expect(mockRes.json).toHaveBeenCalledWith([]); }); it('should handle undefined filteredTools and includedTools', async () => { mockReq.config = {}; + mockCache.get.mockResolvedValue(null); + // Configure getAppConfig to return config with undefined properties + // The controller will use default values [] for filteredTools and includedTools getAppConfig.mockResolvedValueOnce({}); await getAvailablePluginsController(mockReq, mockRes); @@ -331,8 +382,13 @@ describe('PluginController', () => { toolkit: true, }; + // No need to mock app.locals anymore as it's not used + + // Add the toolkit to availableTools require('~/app/clients/tools').availableTools.push(mockToolkit); + mockCache.get.mockResolvedValue(null); + // getCachedTools returns empty object to avoid null reference error getCachedTools.mockResolvedValueOnce({}); mockReq.config = { mcpConfig: null, @@ -341,32 +397,43 @@ describe('PluginController', () => { await getAvailableTools(mockReq, mockRes); + // Should handle null toolDefinitions gracefully expect(mockRes.status).toHaveBeenCalledWith(200); }); - it('should handle undefined toolDefinitions when checking isToolDefined', async () => { + it('should handle undefined toolDefinitions when checking isToolDefined (traversaal_search bug)', async () => { + // This test reproduces the bug where toolDefinitions is undefined + // and accessing toolDefinitions[plugin.pluginKey] causes a TypeError const mockPlugin = { name: 'Traversaal Search', pluginKey: 'traversaal_search', description: 'Search plugin', }; + // Add the plugin to availableTools require('~/app/clients/tools').availableTools.push(mockPlugin); + mockCache.get.mockResolvedValue(null); + mockReq.config = { mcpConfig: null, paths: { structuredTools: '/mock/path' }, }; + // CRITICAL: getCachedTools returns undefined + // This is what causes the bug when trying to access toolDefinitions[plugin.pluginKey] getCachedTools.mockResolvedValueOnce(undefined); + // This should not throw an error with the optional chaining fix await getAvailableTools(mockReq, mockRes); + // Should handle undefined toolDefinitions gracefully and return empty array expect(mockRes.status).toHaveBeenCalledWith(200); expect(mockRes.json).toHaveBeenCalledWith([]); }); it('should re-initialize tools from appConfig when cache returns null', async () => { + // Setup: Initial state with tools in appConfig const mockAppTools = { tool1: { type: 'function', @@ -386,12 +453,15 @@ describe('PluginController', () => { }, }; + // Add matching plugins to availableTools require('~/app/clients/tools').availableTools.push( { name: 'Tool 1', pluginKey: 'tool1', description: 'Tool 1' }, { name: 'Tool 2', pluginKey: 'tool2', description: 'Tool 2' }, ); - getCachedTools.mockResolvedValueOnce(null); + // Simulate cache cleared state (returns null) + mockCache.get.mockResolvedValue(null); + getCachedTools.mockResolvedValueOnce(null); // Global tools (cache cleared) mockReq.config = { filteredTools: [], @@ -399,12 +469,15 @@ describe('PluginController', () => { availableTools: mockAppTools, }; + // Mock setCachedTools to verify it's called to re-initialize const { setCachedTools } = require('~/server/services/Config'); await getAvailableTools(mockReq, mockRes); + // Should have re-initialized the cache with tools from appConfig expect(setCachedTools).toHaveBeenCalledWith(mockAppTools); + // Should still return tools successfully expect(mockRes.status).toHaveBeenCalledWith(200); const responseData = mockRes.json.mock.calls[0][0]; expect(responseData).toHaveLength(2); @@ -413,22 +486,29 @@ describe('PluginController', () => { }); it('should handle cache clear without appConfig.availableTools gracefully', async () => { + // Setup: appConfig without availableTools getAppConfig.mockResolvedValue({ filteredTools: [], includedTools: [], + // No availableTools property }); + // Clear availableTools array require('~/app/clients/tools').availableTools.length = 0; - getCachedTools.mockResolvedValueOnce(null); + // Cache returns null (cleared state) + mockCache.get.mockResolvedValue(null); + getCachedTools.mockResolvedValueOnce(null); // Global tools (cache cleared) mockReq.config = { filteredTools: [], includedTools: [], + // No availableTools }; await getAvailableTools(mockReq, mockRes); + // Should handle gracefully without crashing expect(mockRes.status).toHaveBeenCalledWith(200); expect(mockRes.json).toHaveBeenCalledWith([]); }); diff --git a/api/server/controllers/TwoFactorController.js b/api/server/controllers/TwoFactorController.js index 18a0ee3f5a..fde5965261 100644 --- a/api/server/controllers/TwoFactorController.js +++ b/api/server/controllers/TwoFactorController.js @@ -1,6 +1,5 @@ const { encryptV3, logger } = require('@librechat/data-schemas'); const { - verifyOTPOrBackupCode, generateBackupCodes, generateTOTPSecret, verifyBackupCode, @@ -14,42 +13,24 @@ const safeAppTitle = (process.env.APP_TITLE || 'LibreChat').replace(/\s+/g, ''); /** * Enable 2FA for the user by generating a new TOTP secret and backup codes. * The secret is encrypted and stored, and 2FA is marked as disabled until confirmed. - * If 2FA is already enabled, requires OTP or backup code verification to re-enroll. */ const enable2FA = async (req, res) => { try { const userId = req.user.id; - const existingUser = await getUserById( - userId, - '+totpSecret +backupCodes _id twoFactorEnabled email', - ); - - if (existingUser && existingUser.twoFactorEnabled) { - const { token, backupCode } = req.body; - const result = await verifyOTPOrBackupCode({ - user: existingUser, - token, - backupCode, - persistBackupUse: false, - }); - - if (!result.verified) { - const msg = result.message ?? 'TOTP token or backup code is required to re-enroll 2FA'; - return res.status(result.status ?? 400).json({ message: msg }); - } - } - const secret = generateTOTPSecret(); const { plainCodes, codeObjects } = await generateBackupCodes(); + + // Encrypt the secret with v3 encryption before saving. const encryptedSecret = encryptV3(secret); + // Update the user record: store the secret & backup codes and set twoFactorEnabled to false. const user = await updateUser(userId, { - pendingTotpSecret: encryptedSecret, - pendingBackupCodes: codeObjects, + totpSecret: encryptedSecret, + backupCodes: codeObjects, + twoFactorEnabled: false, }); - const email = user.email || (existingUser && existingUser.email) || ''; - const otpauthUrl = `otpauth://totp/${safeAppTitle}:${email}?secret=${secret}&issuer=${safeAppTitle}`; + const otpauthUrl = `otpauth://totp/${safeAppTitle}:${user.email}?secret=${secret}&issuer=${safeAppTitle}`; return res.status(200).json({ otpauthUrl, backupCodes: plainCodes }); } catch (err) { @@ -65,14 +46,13 @@ const verify2FA = async (req, res) => { try { const userId = req.user.id; const { token, backupCode } = req.body; - const user = await getUserById(userId, '+totpSecret +pendingTotpSecret +backupCodes _id'); - const secretSource = user?.pendingTotpSecret ?? user?.totpSecret; + const user = await getUserById(userId, '_id totpSecret backupCodes'); - if (!user || !secretSource) { + if (!user || !user.totpSecret) { return res.status(400).json({ message: '2FA not initiated' }); } - const secret = await getTOTPSecret(secretSource); + const secret = await getTOTPSecret(user.totpSecret); let isVerified = false; if (token) { @@ -98,28 +78,15 @@ const confirm2FA = async (req, res) => { try { const userId = req.user.id; const { token } = req.body; - const user = await getUserById( - userId, - '+totpSecret +pendingTotpSecret +pendingBackupCodes _id', - ); - const secretSource = user?.pendingTotpSecret ?? user?.totpSecret; + const user = await getUserById(userId, '_id totpSecret'); - if (!user || !secretSource) { + if (!user || !user.totpSecret) { return res.status(400).json({ message: '2FA not initiated' }); } - const secret = await getTOTPSecret(secretSource); + const secret = await getTOTPSecret(user.totpSecret); if (await verifyTOTP(secret, token)) { - const update = { - totpSecret: user.pendingTotpSecret ?? user.totpSecret, - twoFactorEnabled: true, - pendingTotpSecret: null, - pendingBackupCodes: [], - }; - if (user.pendingBackupCodes?.length) { - update.backupCodes = user.pendingBackupCodes; - } - await updateUser(userId, update); + await updateUser(userId, { twoFactorEnabled: true }); return res.status(200).json(); } return res.status(400).json({ message: 'Invalid token.' }); @@ -137,27 +104,31 @@ const disable2FA = async (req, res) => { try { const userId = req.user.id; const { token, backupCode } = req.body; - const user = await getUserById(userId, '+totpSecret +backupCodes _id twoFactorEnabled'); + const user = await getUserById(userId, '_id totpSecret backupCodes'); if (!user || !user.totpSecret) { return res.status(400).json({ message: '2FA is not setup for this user' }); } if (user.twoFactorEnabled) { - const result = await verifyOTPOrBackupCode({ user, token, backupCode }); + const secret = await getTOTPSecret(user.totpSecret); + let isVerified = false; - if (!result.verified) { - const msg = result.message ?? 'Either token or backup code is required to disable 2FA'; - return res.status(result.status ?? 400).json({ message: msg }); + if (token) { + isVerified = await verifyTOTP(secret, token); + } else if (backupCode) { + isVerified = await verifyBackupCode({ user, backupCode }); + } else { + return res + .status(400) + .json({ message: 'Either token or backup code is required to disable 2FA' }); + } + + if (!isVerified) { + return res.status(401).json({ message: 'Invalid token or backup code' }); } } - await updateUser(userId, { - totpSecret: null, - backupCodes: [], - twoFactorEnabled: false, - pendingTotpSecret: null, - pendingBackupCodes: [], - }); + await updateUser(userId, { totpSecret: null, backupCodes: [], twoFactorEnabled: false }); return res.status(200).json(); } catch (err) { logger.error('[disable2FA]', err); @@ -167,28 +138,10 @@ const disable2FA = async (req, res) => { /** * Regenerate backup codes for the user. - * Requires OTP or backup code verification if 2FA is already enabled. */ const regenerateBackupCodes = async (req, res) => { try { const userId = req.user.id; - const user = await getUserById(userId, '+totpSecret +backupCodes _id twoFactorEnabled'); - - if (!user) { - return res.status(404).json({ message: 'User not found' }); - } - - if (user.twoFactorEnabled) { - const { token, backupCode } = req.body; - const result = await verifyOTPOrBackupCode({ user, token, backupCode }); - - if (!result.verified) { - const msg = - result.message ?? 'TOTP token or backup code is required to regenerate backup codes'; - return res.status(result.status ?? 400).json({ message: msg }); - } - } - const { plainCodes, codeObjects } = await generateBackupCodes(); await updateUser(userId, { backupCodes: codeObjects }); return res.status(200).json({ diff --git a/api/server/controllers/UserController.js b/api/server/controllers/UserController.js index 16b68968d9..7a9dd8125e 100644 --- a/api/server/controllers/UserController.js +++ b/api/server/controllers/UserController.js @@ -1,32 +1,52 @@ -const mongoose = require('mongoose'); const { logger, webSearchKeys } = require('@librechat/data-schemas'); +const { Tools, CacheKeys, Constants, FileSources } = require('librechat-data-provider'); const { - getNewS3URL, - needsRefresh, MCPOAuthHandler, MCPTokenStorage, normalizeHttpError, extractWebSearchEnvVars, } = require('@librechat/api'); const { - Tools, - CacheKeys, - Constants, - FileSources, - ResourceType, -} = require('librechat-data-provider'); + deleteAllUserSessions, + deleteAllSharedLinks, + updateUserPlugins, + deleteUserById, + deleteMessages, + deletePresets, + deleteUserKey, + deleteConvos, + deleteFiles, + updateUser, + findToken, + getFiles, +} = require('~/models'); +const { + ConversationTag, + AgentApiKey, + Transaction, + MemoryEntry, + Assistant, + AclEntry, + Balance, + Action, + Group, + Token, + User, +} = require('~/db/models'); const { updateUserPluginAuth, deleteUserPluginAuth } = require('~/server/services/PluginService'); -const { verifyOTPOrBackupCode } = require('~/server/services/twoFactorService'); const { verifyEmail, resendVerificationEmail } = require('~/server/services/AuthService'); const { getMCPManager, getFlowStateManager, getMCPServersRegistry } = require('~/config'); const { invalidateCachedTools } = require('~/server/services/Config/getCachedTools'); +const { needsRefresh, getNewS3URL } = require('~/server/services/Files/S3/crud'); const { processDeleteRequest } = require('~/server/services/Files/process'); const { getAppConfig } = require('~/server/services/Config'); +const { deleteToolCalls } = require('~/models/ToolCall'); +const { deleteUserPrompts } = require('~/models/Prompt'); +const { deleteUserAgents } = require('~/models/Agent'); const { getLogStores } = require('~/cache'); -const db = require('~/models'); const getUserController = async (req, res) => { - const appConfig = await getAppConfig({ role: req.user?.role, tenantId: req.user?.tenantId }); + const appConfig = await getAppConfig({ role: req.user?.role }); /** @type {IUser} */ const userData = req.user.toObject != null ? req.user.toObject() : { ...req.user }; /** @@ -44,7 +64,7 @@ const getUserController = async (req, res) => { const originalAvatar = userData.avatar; try { userData.avatar = await getNewS3URL(userData.avatar); - await db.updateUser(userData.id, { avatar: userData.avatar }); + await updateUser(userData.id, { avatar: userData.avatar }); } catch (error) { userData.avatar = originalAvatar; logger.error('Error getting new S3 URL for avatar:', error); @@ -55,7 +75,7 @@ const getUserController = async (req, res) => { const getTermsStatusController = async (req, res) => { try { - const user = await db.getUserById(req.user.id, 'termsAccepted'); + const user = await User.findById(req.user.id); if (!user) { return res.status(404).json({ message: 'User not found' }); } @@ -68,7 +88,7 @@ const getTermsStatusController = async (req, res) => { const acceptTermsController = async (req, res) => { try { - const user = await db.updateUser(req.user.id, { termsAccepted: true }); + const user = await User.findByIdAndUpdate(req.user.id, { termsAccepted: true }, { new: true }); if (!user) { return res.status(404).json({ message: 'User not found' }); } @@ -81,7 +101,7 @@ const acceptTermsController = async (req, res) => { const deleteUserFiles = async (req) => { try { - const userFiles = await db.getFiles({ user: req.user.id }); + const userFiles = await getFiles({ user: req.user.id }); await processDeleteRequest({ req, files: userFiles, @@ -91,86 +111,13 @@ 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; - const AclEntry = mongoose.models.AclEntry; - if (!MCPServer) { - return; - } - - const userObjectId = new mongoose.Types.ObjectId(userId); - const soleOwnedIds = await db.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, tenantId: req.user?.tenantId }); + const appConfig = await getAppConfig({ role: req.user?.role }); const { user } = req; const { pluginKey, action, auth, isEntityTool } = req.body; try { if (!isEntityTool) { - await db.updateUserPlugins(user._id, user.plugins, pluginKey, action); + await updateUserPlugins(user._id, user.plugins, pluginKey, action); } if (auth == null) { @@ -294,50 +241,37 @@ const deleteUserController = async (req, res) => { const { user } = req; try { - const existingUser = await db.getUserById( - user.id, - '+totpSecret +backupCodes _id twoFactorEnabled', - ); - if (existingUser && existingUser.twoFactorEnabled) { - const { token, backupCode } = req.body; - const result = await verifyOTPOrBackupCode({ user: existingUser, token, backupCode }); - - if (!result.verified) { - const msg = - result.message ?? - 'TOTP token or backup code is required to delete account with 2FA enabled'; - return res.status(result.status ?? 400).json({ message: msg }); - } - } - - await db.deleteMessages({ user: user.id }); - await db.deleteAllUserSessions({ userId: user.id }); - await db.deleteTransactions({ user: user.id }); - await db.deleteUserKey({ userId: user.id, all: true }); - await db.deleteBalances({ user: user._id }); - await db.deletePresets(user.id); + await deleteMessages({ user: user.id }); // delete user messages + await deleteAllUserSessions({ userId: user.id }); // delete user sessions + await Transaction.deleteMany({ user: user.id }); // delete user transactions + await deleteUserKey({ userId: user.id, all: true }); // delete user keys + await Balance.deleteMany({ user: user._id }); // delete user balances + await deletePresets(user.id); // delete user presets try { - await db.deleteConvos(user.id); + await deleteConvos(user.id); // delete user convos } catch (error) { logger.error('[deleteUserController] Error deleting user convos, likely no convos', error); } - await deleteUserPluginAuth(user.id, null, true); - await db.deleteUserById(user.id); - await db.deleteAllSharedLinks(user.id); - await deleteUserFiles(req); - await db.deleteFiles(null, user.id); - await db.deleteToolCalls(user.id); - await db.deleteUserAgents(user.id); - await db.deleteAllAgentApiKeys(user._id); - await db.deleteAssistants({ user: user.id }); - await db.deleteConversationTags({ user: user.id }); - await db.deleteAllUserMemories(user.id); - await db.deleteUserPrompts(user.id); - await deleteUserMcpServers(user.id); - await db.deleteActions({ user: user.id }); - await db.deleteTokens({ userId: user.id }); - await db.removeUserFromAllGroups(user.id); - await db.deleteAclEntries({ principalId: user._id }); + await deleteUserPluginAuth(user.id, null, true); // delete user plugin auth + await deleteUserById(user.id); // delete user + await deleteAllSharedLinks(user.id); // delete user shared links + await deleteUserFiles(req); // delete user files + await deleteFiles(null, user.id); // delete database files in case of orphaned files from previous steps + await deleteToolCalls(user.id); // delete user tool calls + await deleteUserAgents(user.id); // delete user agents + await AgentApiKey.deleteMany({ user: user._id }); // delete user agent API keys + 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 Action.deleteMany({ user: user.id }); // delete user actions + await Token.deleteMany({ userId: user.id }); // delete user OAuth tokens + await Group.updateMany( + // remove user from all groups + { memberIds: user.id }, + { $pull: { memberIds: user.id } }, + ); + await AclEntry.deleteMany({ principalId: user._id }); // delete user ACL entries logger.info(`User deleted account. Email: ${user.email} ID: ${user.id}`); res.status(200).send({ message: 'User deleted' }); } catch (err) { @@ -397,7 +331,7 @@ const maybeUninstallOAuthMCP = async (userId, pluginKey, appConfig) => { const clientTokenData = await MCPTokenStorage.getClientInfoAndMetadata({ userId, serverName, - findToken: db.findToken, + findToken, }); if (clientTokenData == null) { return; @@ -408,7 +342,7 @@ const maybeUninstallOAuthMCP = async (userId, pluginKey, appConfig) => { const tokens = await MCPTokenStorage.getTokens({ userId, serverName, - findToken: db.findToken, + findToken, }); // 3. revoke OAuth tokens at the provider @@ -418,7 +352,6 @@ const maybeUninstallOAuthMCP = async (userId, pluginKey, appConfig) => { serverConfig.oauth?.revocation_endpoint_auth_methods_supported ?? clientMetadata.revocation_endpoint_auth_methods_supported; const oauthHeaders = serverConfig.oauth_headers ?? {}; - const allowedDomains = getMCPServersRegistry().getAllowedDomains(); if (tokens?.access_token) { try { @@ -434,7 +367,6 @@ const maybeUninstallOAuthMCP = async (userId, pluginKey, appConfig) => { revocationEndpointAuthMethodsSupported, }, oauthHeaders, - allowedDomains, ); } catch (error) { logger.error(`Error revoking OAuth access token for ${serverName}:`, error); @@ -455,7 +387,6 @@ const maybeUninstallOAuthMCP = async (userId, pluginKey, appConfig) => { revocationEndpointAuthMethodsSupported, }, oauthHeaders, - allowedDomains, ); } catch (error) { logger.error(`Error revoking OAuth refresh token for ${serverName}:`, error); @@ -467,7 +398,7 @@ const maybeUninstallOAuthMCP = async (userId, pluginKey, appConfig) => { userId, serverName, deleteToken: async (filter) => { - await db.deleteTokens(filter); + await Token.deleteOne(filter); }, }); @@ -487,5 +418,4 @@ module.exports = { verifyEmailController, updateUserPluginsController, resendVerificationController, - deleteUserMcpServers, }; diff --git a/api/server/controllers/UserController.spec.js b/api/server/controllers/UserController.spec.js deleted file mode 100644 index 4a96072062..0000000000 --- a/api/server/controllers/UserController.spec.js +++ /dev/null @@ -1,225 +0,0 @@ -const mongoose = require('mongoose'); -const { MongoMemoryServer } = require('mongodb-memory-server'); - -jest.mock('@librechat/data-schemas', () => { - const actual = jest.requireActual('@librechat/data-schemas'); - return { - ...actual, - logger: { - debug: jest.fn(), - error: jest.fn(), - warn: jest.fn(), - info: jest.fn(), - }, - }; -}); - -jest.mock('~/models', () => { - const _mongoose = require('mongoose'); - return { - deleteAllUserSessions: jest.fn().mockResolvedValue(undefined), - deleteAllSharedLinks: jest.fn().mockResolvedValue(undefined), - deleteAllAgentApiKeys: jest.fn().mockResolvedValue(undefined), - deleteConversationTags: jest.fn().mockResolvedValue(undefined), - deleteAllUserMemories: jest.fn().mockResolvedValue(undefined), - deleteTransactions: jest.fn().mockResolvedValue(undefined), - deleteAclEntries: jest.fn().mockResolvedValue(undefined), - updateUserPlugins: jest.fn(), - deleteAssistants: jest.fn().mockResolvedValue(undefined), - deleteUserById: jest.fn().mockResolvedValue(undefined), - deleteUserPrompts: jest.fn().mockResolvedValue(undefined), - deleteMessages: jest.fn().mockResolvedValue(undefined), - deleteBalances: jest.fn().mockResolvedValue(undefined), - deleteActions: jest.fn().mockResolvedValue(undefined), - deletePresets: jest.fn().mockResolvedValue(undefined), - deleteUserKey: jest.fn().mockResolvedValue(undefined), - deleteToolCalls: jest.fn().mockResolvedValue(undefined), - deleteUserAgents: jest.fn().mockResolvedValue(undefined), - deleteTokens: jest.fn().mockResolvedValue(undefined), - deleteConvos: jest.fn().mockResolvedValue(undefined), - deleteFiles: jest.fn().mockResolvedValue(undefined), - updateUser: jest.fn(), - getUserById: jest.fn().mockResolvedValue(null), - findToken: jest.fn(), - getFiles: jest.fn().mockResolvedValue([]), - removeUserFromAllGroups: jest.fn().mockImplementation(async (userId) => { - const Group = _mongoose.models.Group; - await Group.updateMany({ memberIds: userId }, { $pullAll: { memberIds: [userId] } }); - }), - }; -}); - -jest.mock('~/server/services/PluginService', () => ({ - updateUserPluginAuth: jest.fn(), - deleteUserPluginAuth: jest.fn().mockResolvedValue(undefined), -})); - -jest.mock('~/server/services/AuthService', () => ({ - verifyEmail: jest.fn(), - resendVerificationEmail: jest.fn(), -})); - -jest.mock('sharp', () => - jest.fn(() => ({ - metadata: jest.fn().mockResolvedValue({}), - toFormat: jest.fn().mockReturnThis(), - toBuffer: jest.fn().mockResolvedValue(Buffer.alloc(0)), - })), -); - -jest.mock('@librechat/api', () => ({ - ...jest.requireActual('@librechat/api'), - needsRefresh: jest.fn(), - getNewS3URL: jest.fn(), -})); - -jest.mock('~/server/services/Files/process', () => ({ - processDeleteRequest: jest.fn().mockResolvedValue(undefined), -})); - -jest.mock('~/server/services/Config', () => ({ - getAppConfig: jest.fn().mockResolvedValue({}), - getMCPManager: jest.fn(), - getFlowStateManager: jest.fn(), - getMCPServersRegistry: jest.fn(), -})); - -jest.mock('~/cache', () => ({ - getLogStores: jest.fn(), -})); - -let mongoServer; - -beforeAll(async () => { - mongoServer = await MongoMemoryServer.create(); - await mongoose.connect(mongoServer.getUri()); -}); - -afterAll(async () => { - await mongoose.disconnect(); - await mongoServer.stop(); -}); - -afterEach(async () => { - const collections = mongoose.connection.collections; - for (const key in collections) { - await collections[key].deleteMany({}); - } -}); - -const { deleteUserController } = require('./UserController'); -const { Group } = require('~/db/models'); -const { deleteConvos } = require('~/models'); - -describe('deleteUserController', () => { - const mockRes = { - status: jest.fn().mockReturnThis(), - send: jest.fn().mockReturnThis(), - json: jest.fn().mockReturnThis(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('should return 200 on successful deletion', async () => { - const userId = new mongoose.Types.ObjectId(); - const req = { user: { id: userId.toString(), _id: userId, email: 'test@test.com' } }; - - await deleteUserController(req, mockRes); - - expect(mockRes.status).toHaveBeenCalledWith(200); - expect(mockRes.send).toHaveBeenCalledWith({ message: 'User deleted' }); - }); - - it('should remove the user from all groups via $pullAll', async () => { - const userId = new mongoose.Types.ObjectId(); - const userIdStr = userId.toString(); - const otherUser = new mongoose.Types.ObjectId().toString(); - - await Group.create([ - { name: 'Group A', memberIds: [userIdStr, otherUser], source: 'local' }, - { name: 'Group B', memberIds: [userIdStr], source: 'local' }, - { name: 'Group C', memberIds: [otherUser], source: 'local' }, - ]); - - const req = { user: { id: userIdStr, _id: userId, email: 'del@test.com' } }; - await deleteUserController(req, mockRes); - - const groups = await Group.find({}).sort({ name: 1 }).lean(); - expect(groups[0].memberIds).toEqual([otherUser]); - expect(groups[1].memberIds).toEqual([]); - expect(groups[2].memberIds).toEqual([otherUser]); - }); - - it('should handle user that exists in no groups', async () => { - const userId = new mongoose.Types.ObjectId(); - await Group.create({ name: 'Empty', memberIds: ['someone-else'], source: 'local' }); - - const req = { user: { id: userId.toString(), _id: userId, email: 'no-groups@test.com' } }; - await deleteUserController(req, mockRes); - - expect(mockRes.status).toHaveBeenCalledWith(200); - const group = await Group.findOne({ name: 'Empty' }).lean(); - expect(group.memberIds).toEqual(['someone-else']); - }); - - it('should remove duplicate memberIds if the user appears more than once', async () => { - const userId = new mongoose.Types.ObjectId(); - const userIdStr = userId.toString(); - - await Group.create({ - name: 'Dupes', - memberIds: [userIdStr, 'other', userIdStr], - source: 'local', - }); - - const req = { user: { id: userIdStr, _id: userId, email: 'dupe@test.com' } }; - await deleteUserController(req, mockRes); - - const group = await Group.findOne({ name: 'Dupes' }).lean(); - expect(group.memberIds).toEqual(['other']); - }); - - it('should still succeed when deleteConvos throws', async () => { - const userId = new mongoose.Types.ObjectId(); - deleteConvos.mockRejectedValueOnce(new Error('no convos')); - - const req = { user: { id: userId.toString(), _id: userId, email: 'convos@test.com' } }; - await deleteUserController(req, mockRes); - - expect(mockRes.status).toHaveBeenCalledWith(200); - expect(mockRes.send).toHaveBeenCalledWith({ message: 'User deleted' }); - }); - - it('should return 500 when a critical operation fails', async () => { - const userId = new mongoose.Types.ObjectId(); - const { deleteMessages } = require('~/models'); - deleteMessages.mockRejectedValueOnce(new Error('db down')); - - const req = { user: { id: userId.toString(), _id: userId, email: 'fail@test.com' } }; - await deleteUserController(req, mockRes); - - expect(mockRes.status).toHaveBeenCalledWith(500); - expect(mockRes.json).toHaveBeenCalledWith({ message: 'Something went wrong.' }); - }); - - it('should use string user.id (not ObjectId user._id) for memberIds removal', async () => { - const userId = new mongoose.Types.ObjectId(); - const userIdStr = userId.toString(); - const otherUser = 'other-user-id'; - - await Group.create({ - name: 'StringCheck', - memberIds: [userIdStr, otherUser], - source: 'local', - }); - - const req = { user: { id: userIdStr, _id: userId, email: 'stringcheck@test.com' } }; - await deleteUserController(req, mockRes); - - const group = await Group.findOne({ name: 'StringCheck' }).lean(); - expect(group.memberIds).toEqual([otherUser]); - expect(group.memberIds).not.toContain(userIdStr); - }); -}); diff --git a/api/server/controllers/__tests__/PermissionsController.spec.js b/api/server/controllers/__tests__/PermissionsController.spec.js deleted file mode 100644 index a8d9518455..0000000000 --- a/api/server/controllers/__tests__/PermissionsController.spec.js +++ /dev/null @@ -1,242 +0,0 @@ -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(), -})); - -const mockRemoveAgentFromUserFavorites = jest.fn(); - -jest.mock('~/models', () => ({ - searchPrincipals: jest.fn(), - sortPrincipalsByRelevance: jest.fn(), - calculateRelevanceScore: jest.fn(), - removeAgentFromUserFavorites: (...args) => mockRemoveAgentFromUserFavorites(...args), -})); - -jest.mock('~/server/services/GraphApiService', () => ({ - entraIdPrincipalFeatureEnabled: jest.fn(() => false), - searchEntraIdPrincipals: jest.fn(), -})); - -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: [], - }); - - mockRemoveAgentFromUserFavorites.mockResolvedValue(undefined); - }); - - 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(mockRemoveAgentFromUserFavorites).toHaveBeenCalledWith(agentObjectId, [revokedUserId]); - }); - - 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(mockRemoveAgentFromUserFavorites).toHaveBeenCalledWith(agentObjectId, [revokedUserId]); - }); - - 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(mockRemoveAgentFromUserFavorites).toHaveBeenCalledWith(agentObjectId, [validId]); - }); - - 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(mockRemoveAgentFromUserFavorites).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(mockRemoveAgentFromUserFavorites).not.toHaveBeenCalled(); - }); - - it('handles agent not found gracefully', async () => { - mockRemoveAgentFromUserFavorites.mockResolvedValue(undefined); - - 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(mockRemoveAgentFromUserFavorites).toHaveBeenCalled(); - expect(res.status).toHaveBeenCalledWith(200); - }); - - it('logs error when removeAgentFromUserFavorites fails without blocking response', async () => { - mockRemoveAgentFromUserFavorites.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/api/server/controllers/__tests__/TwoFactorController.spec.js b/api/server/controllers/__tests__/TwoFactorController.spec.js deleted file mode 100644 index 62531d94a1..0000000000 --- a/api/server/controllers/__tests__/TwoFactorController.spec.js +++ /dev/null @@ -1,264 +0,0 @@ -const mockGetUserById = jest.fn(); -const mockUpdateUser = jest.fn(); -const mockVerifyOTPOrBackupCode = jest.fn(); -const mockGenerateTOTPSecret = jest.fn(); -const mockGenerateBackupCodes = jest.fn(); -const mockEncryptV3 = jest.fn(); - -jest.mock('@librechat/data-schemas', () => ({ - encryptV3: (...args) => mockEncryptV3(...args), - logger: { error: jest.fn() }, -})); - -jest.mock('~/server/services/twoFactorService', () => ({ - verifyOTPOrBackupCode: (...args) => mockVerifyOTPOrBackupCode(...args), - generateBackupCodes: (...args) => mockGenerateBackupCodes(...args), - generateTOTPSecret: (...args) => mockGenerateTOTPSecret(...args), - verifyBackupCode: jest.fn(), - getTOTPSecret: jest.fn(), - verifyTOTP: jest.fn(), -})); - -jest.mock('~/models', () => ({ - getUserById: (...args) => mockGetUserById(...args), - updateUser: (...args) => mockUpdateUser(...args), -})); - -const { enable2FA, regenerateBackupCodes } = require('~/server/controllers/TwoFactorController'); - -function createRes() { - const res = {}; - res.status = jest.fn().mockReturnValue(res); - res.json = jest.fn().mockReturnValue(res); - return res; -} - -const PLAIN_CODES = ['code1', 'code2', 'code3']; -const CODE_OBJECTS = [ - { codeHash: 'h1', used: false, usedAt: null }, - { codeHash: 'h2', used: false, usedAt: null }, - { codeHash: 'h3', used: false, usedAt: null }, -]; - -beforeEach(() => { - jest.clearAllMocks(); - mockGenerateTOTPSecret.mockReturnValue('NEWSECRET'); - mockGenerateBackupCodes.mockResolvedValue({ plainCodes: PLAIN_CODES, codeObjects: CODE_OBJECTS }); - mockEncryptV3.mockReturnValue('encrypted-secret'); -}); - -describe('enable2FA', () => { - it('allows first-time setup without token — writes to pending fields', async () => { - const req = { user: { id: 'user1' }, body: {} }; - const res = createRes(); - mockGetUserById.mockResolvedValue({ _id: 'user1', twoFactorEnabled: false, email: 'a@b.com' }); - mockUpdateUser.mockResolvedValue({ email: 'a@b.com' }); - - await enable2FA(req, res); - - expect(res.status).toHaveBeenCalledWith(200); - expect(res.json).toHaveBeenCalledWith( - expect.objectContaining({ otpauthUrl: expect.any(String), backupCodes: PLAIN_CODES }), - ); - expect(mockVerifyOTPOrBackupCode).not.toHaveBeenCalled(); - const updateCall = mockUpdateUser.mock.calls[0][1]; - expect(updateCall).toHaveProperty('pendingTotpSecret', 'encrypted-secret'); - expect(updateCall).toHaveProperty('pendingBackupCodes', CODE_OBJECTS); - expect(updateCall).not.toHaveProperty('twoFactorEnabled'); - expect(updateCall).not.toHaveProperty('totpSecret'); - expect(updateCall).not.toHaveProperty('backupCodes'); - }); - - it('re-enrollment writes to pending fields, leaving live 2FA intact', async () => { - const req = { user: { id: 'user1' }, body: { token: '123456' } }; - const res = createRes(); - const existingUser = { - _id: 'user1', - twoFactorEnabled: true, - totpSecret: 'enc-secret', - email: 'a@b.com', - }; - mockGetUserById.mockResolvedValue(existingUser); - mockVerifyOTPOrBackupCode.mockResolvedValue({ verified: true }); - mockUpdateUser.mockResolvedValue({ email: 'a@b.com' }); - - await enable2FA(req, res); - - expect(mockVerifyOTPOrBackupCode).toHaveBeenCalledWith({ - user: existingUser, - token: '123456', - backupCode: undefined, - persistBackupUse: false, - }); - expect(res.status).toHaveBeenCalledWith(200); - const updateCall = mockUpdateUser.mock.calls[0][1]; - expect(updateCall).toHaveProperty('pendingTotpSecret', 'encrypted-secret'); - expect(updateCall).toHaveProperty('pendingBackupCodes', CODE_OBJECTS); - expect(updateCall).not.toHaveProperty('twoFactorEnabled'); - expect(updateCall).not.toHaveProperty('totpSecret'); - }); - - it('allows re-enrollment with valid backup code (persistBackupUse: false)', async () => { - const req = { user: { id: 'user1' }, body: { backupCode: 'backup123' } }; - const res = createRes(); - const existingUser = { - _id: 'user1', - twoFactorEnabled: true, - totpSecret: 'enc-secret', - email: 'a@b.com', - }; - mockGetUserById.mockResolvedValue(existingUser); - mockVerifyOTPOrBackupCode.mockResolvedValue({ verified: true }); - mockUpdateUser.mockResolvedValue({ email: 'a@b.com' }); - - await enable2FA(req, res); - - expect(mockVerifyOTPOrBackupCode).toHaveBeenCalledWith( - expect.objectContaining({ persistBackupUse: false }), - ); - expect(res.status).toHaveBeenCalledWith(200); - }); - - it('returns error when no token provided and 2FA is enabled', async () => { - const req = { user: { id: 'user1' }, body: {} }; - const res = createRes(); - mockGetUserById.mockResolvedValue({ - _id: 'user1', - twoFactorEnabled: true, - totpSecret: 'enc-secret', - }); - mockVerifyOTPOrBackupCode.mockResolvedValue({ verified: false, status: 400 }); - - await enable2FA(req, res); - - expect(res.status).toHaveBeenCalledWith(400); - expect(mockUpdateUser).not.toHaveBeenCalled(); - }); - - it('returns 401 when invalid token provided and 2FA is enabled', async () => { - const req = { user: { id: 'user1' }, body: { token: 'wrong' } }; - const res = createRes(); - mockGetUserById.mockResolvedValue({ - _id: 'user1', - twoFactorEnabled: true, - totpSecret: 'enc-secret', - }); - mockVerifyOTPOrBackupCode.mockResolvedValue({ - verified: false, - status: 401, - message: 'Invalid token or backup code', - }); - - await enable2FA(req, res); - - expect(res.status).toHaveBeenCalledWith(401); - expect(res.json).toHaveBeenCalledWith({ message: 'Invalid token or backup code' }); - expect(mockUpdateUser).not.toHaveBeenCalled(); - }); -}); - -describe('regenerateBackupCodes', () => { - it('returns 404 when user not found', async () => { - const req = { user: { id: 'user1' }, body: {} }; - const res = createRes(); - mockGetUserById.mockResolvedValue(null); - - await regenerateBackupCodes(req, res); - - expect(res.status).toHaveBeenCalledWith(404); - expect(res.json).toHaveBeenCalledWith({ message: 'User not found' }); - }); - - it('requires OTP when 2FA is enabled', async () => { - const req = { user: { id: 'user1' }, body: { token: '123456' } }; - const res = createRes(); - mockGetUserById.mockResolvedValue({ - _id: 'user1', - twoFactorEnabled: true, - totpSecret: 'enc-secret', - }); - mockVerifyOTPOrBackupCode.mockResolvedValue({ verified: true }); - mockUpdateUser.mockResolvedValue({}); - - await regenerateBackupCodes(req, res); - - expect(mockVerifyOTPOrBackupCode).toHaveBeenCalled(); - expect(res.status).toHaveBeenCalledWith(200); - expect(res.json).toHaveBeenCalledWith({ - backupCodes: PLAIN_CODES, - backupCodesHash: CODE_OBJECTS, - }); - }); - - it('returns error when no token provided and 2FA is enabled', async () => { - const req = { user: { id: 'user1' }, body: {} }; - const res = createRes(); - mockGetUserById.mockResolvedValue({ - _id: 'user1', - twoFactorEnabled: true, - totpSecret: 'enc-secret', - }); - mockVerifyOTPOrBackupCode.mockResolvedValue({ verified: false, status: 400 }); - - await regenerateBackupCodes(req, res); - - expect(res.status).toHaveBeenCalledWith(400); - }); - - it('returns 401 when invalid token provided and 2FA is enabled', async () => { - const req = { user: { id: 'user1' }, body: { token: 'wrong' } }; - const res = createRes(); - mockGetUserById.mockResolvedValue({ - _id: 'user1', - twoFactorEnabled: true, - totpSecret: 'enc-secret', - }); - mockVerifyOTPOrBackupCode.mockResolvedValue({ - verified: false, - status: 401, - message: 'Invalid token or backup code', - }); - - await regenerateBackupCodes(req, res); - - expect(res.status).toHaveBeenCalledWith(401); - expect(res.json).toHaveBeenCalledWith({ message: 'Invalid token or backup code' }); - }); - - it('includes backupCodesHash in response', async () => { - const req = { user: { id: 'user1' }, body: { token: '123456' } }; - const res = createRes(); - mockGetUserById.mockResolvedValue({ - _id: 'user1', - twoFactorEnabled: true, - totpSecret: 'enc-secret', - }); - mockVerifyOTPOrBackupCode.mockResolvedValue({ verified: true }); - mockUpdateUser.mockResolvedValue({}); - - await regenerateBackupCodes(req, res); - - const responseBody = res.json.mock.calls[0][0]; - expect(responseBody).toHaveProperty('backupCodesHash', CODE_OBJECTS); - expect(responseBody).toHaveProperty('backupCodes', PLAIN_CODES); - }); - - it('allows regeneration without token when 2FA is not enabled', async () => { - const req = { user: { id: 'user1' }, body: {} }; - const res = createRes(); - mockGetUserById.mockResolvedValue({ - _id: 'user1', - twoFactorEnabled: false, - }); - mockUpdateUser.mockResolvedValue({}); - - await regenerateBackupCodes(req, res); - - expect(mockVerifyOTPOrBackupCode).not.toHaveBeenCalled(); - expect(res.status).toHaveBeenCalledWith(200); - expect(res.json).toHaveBeenCalledWith({ - backupCodes: PLAIN_CODES, - backupCodesHash: CODE_OBJECTS, - }); - }); -}); diff --git a/api/server/controllers/__tests__/deleteUser.spec.js b/api/server/controllers/__tests__/deleteUser.spec.js deleted file mode 100644 index a382a6cdc7..0000000000 --- a/api/server/controllers/__tests__/deleteUser.spec.js +++ /dev/null @@ -1,287 +0,0 @@ -const mockGetUserById = jest.fn(); -const mockDeleteMessages = jest.fn(); -const mockDeleteAllUserSessions = jest.fn(); -const mockDeleteUserById = jest.fn(); -const mockDeleteAllSharedLinks = jest.fn(); -const mockDeletePresets = jest.fn(); -const mockDeleteUserKey = jest.fn(); -const mockDeleteConvos = jest.fn(); -const mockDeleteFiles = jest.fn(); -const mockGetFiles = jest.fn(); -const mockUpdateUserPlugins = jest.fn(); -const mockUpdateUser = jest.fn(); -const mockFindToken = jest.fn(); -const mockVerifyOTPOrBackupCode = jest.fn(); -const mockDeleteUserPluginAuth = jest.fn(); -const mockProcessDeleteRequest = jest.fn(); -const mockDeleteToolCalls = jest.fn(); -const mockDeleteUserAgents = jest.fn(); -const mockDeleteUserPrompts = jest.fn(); - -jest.mock('@librechat/data-schemas', () => ({ - logger: { error: jest.fn(), info: jest.fn() }, - webSearchKeys: [], -})); - -jest.mock('librechat-data-provider', () => ({ - Tools: {}, - CacheKeys: {}, - Constants: { mcp_delimiter: '::', mcp_prefix: 'mcp_' }, - FileSources: {}, -})); - -jest.mock('@librechat/api', () => ({ - MCPOAuthHandler: {}, - MCPTokenStorage: {}, - normalizeHttpError: jest.fn(), - extractWebSearchEnvVars: jest.fn(), - needsRefresh: jest.fn(), - getNewS3URL: jest.fn(), -})); - -jest.mock('~/models', () => ({ - deleteAllUserSessions: (...args) => mockDeleteAllUserSessions(...args), - deleteAllSharedLinks: (...args) => mockDeleteAllSharedLinks(...args), - updateUserPlugins: (...args) => mockUpdateUserPlugins(...args), - deleteUserById: (...args) => mockDeleteUserById(...args), - deleteMessages: (...args) => mockDeleteMessages(...args), - deletePresets: (...args) => mockDeletePresets(...args), - deleteUserKey: (...args) => mockDeleteUserKey(...args), - getUserById: (...args) => mockGetUserById(...args), - deleteConvos: (...args) => mockDeleteConvos(...args), - deleteFiles: (...args) => mockDeleteFiles(...args), - updateUser: (...args) => mockUpdateUser(...args), - findToken: (...args) => mockFindToken(...args), - getFiles: (...args) => mockGetFiles(...args), - deleteToolCalls: (...args) => mockDeleteToolCalls(...args), - deleteUserAgents: (...args) => mockDeleteUserAgents(...args), - deleteUserPrompts: (...args) => mockDeleteUserPrompts(...args), - deleteTransactions: jest.fn(), - deleteBalances: jest.fn(), - deleteAllAgentApiKeys: jest.fn(), - deleteAssistants: jest.fn(), - deleteConversationTags: jest.fn(), - deleteAllUserMemories: jest.fn(), - deleteActions: jest.fn(), - deleteTokens: jest.fn(), - removeUserFromAllGroups: jest.fn(), - deleteAclEntries: jest.fn(), - getSoleOwnedResourceIds: jest.fn().mockResolvedValue([]), -})); - -jest.mock('~/server/services/PluginService', () => ({ - updateUserPluginAuth: jest.fn(), - deleteUserPluginAuth: (...args) => mockDeleteUserPluginAuth(...args), -})); - -jest.mock('~/server/services/twoFactorService', () => ({ - verifyOTPOrBackupCode: (...args) => mockVerifyOTPOrBackupCode(...args), -})); - -jest.mock('~/server/services/AuthService', () => ({ - verifyEmail: jest.fn(), - resendVerificationEmail: jest.fn(), -})); - -jest.mock('~/config', () => ({ - getMCPManager: jest.fn(), - getFlowStateManager: jest.fn(), - getMCPServersRegistry: jest.fn(), -})); - -jest.mock('~/server/services/Config/getCachedTools', () => ({ - invalidateCachedTools: jest.fn(), -})); - -jest.mock('~/server/services/Files/process', () => ({ - processDeleteRequest: (...args) => mockProcessDeleteRequest(...args), -})); - -jest.mock('~/server/services/Config', () => ({ - getAppConfig: jest.fn(), -})); - -jest.mock('~/cache', () => ({ - getLogStores: jest.fn(), -})); - -const { deleteUserController } = require('~/server/controllers/UserController'); - -function createRes() { - const res = {}; - res.status = jest.fn().mockReturnValue(res); - res.json = jest.fn().mockReturnValue(res); - res.send = jest.fn().mockReturnValue(res); - return res; -} - -function stubDeletionMocks() { - mockDeleteMessages.mockResolvedValue(); - mockDeleteAllUserSessions.mockResolvedValue(); - mockDeleteUserKey.mockResolvedValue(); - mockDeletePresets.mockResolvedValue(); - mockDeleteConvos.mockResolvedValue(); - mockDeleteUserPluginAuth.mockResolvedValue(); - mockDeleteUserById.mockResolvedValue(); - mockDeleteAllSharedLinks.mockResolvedValue(); - mockGetFiles.mockResolvedValue([]); - mockProcessDeleteRequest.mockResolvedValue(); - mockDeleteFiles.mockResolvedValue(); - mockDeleteToolCalls.mockResolvedValue(); - mockDeleteUserAgents.mockResolvedValue(); - mockDeleteUserPrompts.mockResolvedValue(); -} - -beforeEach(() => { - jest.clearAllMocks(); - stubDeletionMocks(); -}); - -describe('deleteUserController - 2FA enforcement', () => { - it('proceeds with deletion when 2FA is not enabled', async () => { - const req = { user: { id: 'user1', _id: 'user1', email: 'a@b.com' }, body: {} }; - const res = createRes(); - mockGetUserById.mockResolvedValue({ _id: 'user1', twoFactorEnabled: false }); - - await deleteUserController(req, res); - - expect(res.status).toHaveBeenCalledWith(200); - expect(res.send).toHaveBeenCalledWith({ message: 'User deleted' }); - expect(mockDeleteMessages).toHaveBeenCalled(); - expect(mockVerifyOTPOrBackupCode).not.toHaveBeenCalled(); - }); - - it('proceeds with deletion when user has no 2FA record', async () => { - const req = { user: { id: 'user1', _id: 'user1', email: 'a@b.com' }, body: {} }; - const res = createRes(); - mockGetUserById.mockResolvedValue(null); - - await deleteUserController(req, res); - - expect(res.status).toHaveBeenCalledWith(200); - expect(res.send).toHaveBeenCalledWith({ message: 'User deleted' }); - }); - - it('returns error when 2FA is enabled and verification fails with 400', async () => { - const req = { user: { id: 'user1', _id: 'user1' }, body: {} }; - const res = createRes(); - mockGetUserById.mockResolvedValue({ - _id: 'user1', - twoFactorEnabled: true, - totpSecret: 'enc-secret', - }); - mockVerifyOTPOrBackupCode.mockResolvedValue({ verified: false, status: 400 }); - - await deleteUserController(req, res); - - expect(res.status).toHaveBeenCalledWith(400); - expect(mockDeleteMessages).not.toHaveBeenCalled(); - }); - - it('returns 401 when 2FA is enabled and invalid TOTP token provided', async () => { - const existingUser = { - _id: 'user1', - twoFactorEnabled: true, - totpSecret: 'enc-secret', - }; - const req = { user: { id: 'user1', _id: 'user1' }, body: { token: 'wrong' } }; - const res = createRes(); - mockGetUserById.mockResolvedValue(existingUser); - mockVerifyOTPOrBackupCode.mockResolvedValue({ - verified: false, - status: 401, - message: 'Invalid token or backup code', - }); - - await deleteUserController(req, res); - - expect(mockVerifyOTPOrBackupCode).toHaveBeenCalledWith({ - user: existingUser, - token: 'wrong', - backupCode: undefined, - }); - expect(res.status).toHaveBeenCalledWith(401); - expect(res.json).toHaveBeenCalledWith({ message: 'Invalid token or backup code' }); - expect(mockDeleteMessages).not.toHaveBeenCalled(); - }); - - it('returns 401 when 2FA is enabled and invalid backup code provided', async () => { - const existingUser = { - _id: 'user1', - twoFactorEnabled: true, - totpSecret: 'enc-secret', - backupCodes: [], - }; - const req = { user: { id: 'user1', _id: 'user1' }, body: { backupCode: 'bad-code' } }; - const res = createRes(); - mockGetUserById.mockResolvedValue(existingUser); - mockVerifyOTPOrBackupCode.mockResolvedValue({ - verified: false, - status: 401, - message: 'Invalid token or backup code', - }); - - await deleteUserController(req, res); - - expect(mockVerifyOTPOrBackupCode).toHaveBeenCalledWith({ - user: existingUser, - token: undefined, - backupCode: 'bad-code', - }); - expect(res.status).toHaveBeenCalledWith(401); - expect(mockDeleteMessages).not.toHaveBeenCalled(); - }); - - it('deletes account when valid TOTP token provided with 2FA enabled', async () => { - const existingUser = { - _id: 'user1', - twoFactorEnabled: true, - totpSecret: 'enc-secret', - }; - const req = { - user: { id: 'user1', _id: 'user1', email: 'a@b.com' }, - body: { token: '123456' }, - }; - const res = createRes(); - mockGetUserById.mockResolvedValue(existingUser); - mockVerifyOTPOrBackupCode.mockResolvedValue({ verified: true }); - - await deleteUserController(req, res); - - expect(mockVerifyOTPOrBackupCode).toHaveBeenCalledWith({ - user: existingUser, - token: '123456', - backupCode: undefined, - }); - expect(res.status).toHaveBeenCalledWith(200); - expect(res.send).toHaveBeenCalledWith({ message: 'User deleted' }); - expect(mockDeleteMessages).toHaveBeenCalled(); - }); - - it('deletes account when valid backup code provided with 2FA enabled', async () => { - const existingUser = { - _id: 'user1', - twoFactorEnabled: true, - totpSecret: 'enc-secret', - backupCodes: [{ codeHash: 'h1', used: false }], - }; - const req = { - user: { id: 'user1', _id: 'user1', email: 'a@b.com' }, - body: { backupCode: 'valid-code' }, - }; - const res = createRes(); - mockGetUserById.mockResolvedValue(existingUser); - mockVerifyOTPOrBackupCode.mockResolvedValue({ verified: true }); - - await deleteUserController(req, res); - - expect(mockVerifyOTPOrBackupCode).toHaveBeenCalledWith({ - user: existingUser, - token: undefined, - backupCode: 'valid-code', - }); - expect(res.status).toHaveBeenCalledWith(200); - expect(res.send).toHaveBeenCalledWith({ message: 'User deleted' }); - expect(mockDeleteMessages).toHaveBeenCalled(); - }); -}); diff --git a/api/server/controllers/__tests__/deleteUserMcpServers.spec.js b/api/server/controllers/__tests__/deleteUserMcpServers.spec.js deleted file mode 100644 index fcb3211f24..0000000000 --- a/api/server/controllers/__tests__/deleteUserMcpServers.spec.js +++ /dev/null @@ -1,319 +0,0 @@ -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 deleted file mode 100644 index b08e502800..0000000000 --- a/api/server/controllers/__tests__/deleteUserResourceCoverage.spec.js +++ /dev/null @@ -1,53 +0,0 @@ -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/controllers/agents/__tests__/openai.spec.js b/api/server/controllers/agents/__tests__/openai.spec.js index c959be6cf4..8592c79a2d 100644 --- a/api/server/controllers/agents/__tests__/openai.spec.js +++ b/api/server/controllers/agents/__tests__/openai.spec.js @@ -3,7 +3,6 @@ * Tests that recordCollectedUsage is called correctly for token spending */ -const mockProcessStream = jest.fn().mockResolvedValue(undefined); const mockSpendTokens = jest.fn().mockResolvedValue({}); const mockSpendStructuredTokens = jest.fn().mockResolvedValue({}); const mockRecordCollectedUsage = jest @@ -36,7 +35,7 @@ jest.mock('@librechat/agents', () => ({ jest.mock('@librechat/api', () => ({ writeSSE: jest.fn(), createRun: jest.fn().mockResolvedValue({ - processStream: mockProcessStream, + processStream: jest.fn().mockResolvedValue(undefined), }), createChunk: jest.fn().mockReturnValue({}), buildToolSet: jest.fn().mockReturnValue(new Set()), @@ -69,7 +68,6 @@ jest.mock('@librechat/api', () => ({ toolCalls: new Map(), usage: { promptTokens: 100, completionTokens: 50, reasoningTokens: 0 }, }), - resolveRecursionLimit: jest.fn().mockReturnValue(50), createToolExecuteHandler: jest.fn().mockReturnValue({ handle: jest.fn() }), isChatCompletionValidationFailure: jest.fn().mockReturnValue(false), })); @@ -79,25 +77,33 @@ jest.mock('~/server/services/ToolService', () => ({ loadToolsForExecution: jest.fn().mockResolvedValue([]), })); -const mockGetMultiplier = jest.fn().mockReturnValue(1); -const mockGetCacheMultiplier = jest.fn().mockReturnValue(null); +jest.mock('~/models/spendTokens', () => ({ + spendTokens: mockSpendTokens, + spendStructuredTokens: mockSpendStructuredTokens, +})); jest.mock('~/server/controllers/agents/callbacks', () => ({ createToolEndCallback: jest.fn().mockReturnValue(jest.fn()), - buildSummarizationHandlers: jest.fn().mockReturnValue({}), - markSummarizationUsage: jest.fn().mockImplementation((usage) => usage), - agentLogHandlerObj: { handle: jest.fn() }, })); jest.mock('~/server/services/PermissionService', () => ({ findAccessibleResources: jest.fn().mockResolvedValue([]), })); -const mockUpdateBalance = jest.fn().mockResolvedValue({}); -const mockBulkInsertTransactions = jest.fn().mockResolvedValue(undefined); +jest.mock('~/models/Conversation', () => ({ + getConvoFiles: jest.fn().mockResolvedValue([]), +})); + +jest.mock('~/models/Agent', () => ({ + getAgent: jest.fn().mockResolvedValue({ + id: 'agent-123', + provider: 'openAI', + model_parameters: { model: 'gpt-4' }, + }), + getAgents: jest.fn().mockResolvedValue([]), +})); jest.mock('~/models', () => ({ - getAgent: jest.fn().mockResolvedValue({ id: 'agent-123', name: 'Test Agent' }), getFiles: jest.fn(), getUserKey: jest.fn(), getMessages: jest.fn(), @@ -106,14 +112,6 @@ jest.mock('~/models', () => ({ getUserCodeFiles: jest.fn(), getToolFilesByIds: jest.fn(), getCodeGeneratedFiles: jest.fn(), - updateBalance: mockUpdateBalance, - bulkInsertTransactions: mockBulkInsertTransactions, - spendTokens: mockSpendTokens, - spendStructuredTokens: mockSpendStructuredTokens, - getMultiplier: mockGetMultiplier, - getCacheMultiplier: mockGetCacheMultiplier, - getConvoFiles: jest.fn().mockResolvedValue([]), - getConvo: jest.fn().mockResolvedValue(null), })); describe('OpenAIChatCompletionController', () => { @@ -151,92 +149,13 @@ describe('OpenAIChatCompletionController', () => { }; }); - describe('conversation ownership validation', () => { - it('should skip ownership check when conversation_id is not provided', async () => { - const { getConvo } = require('~/models'); - await OpenAIChatCompletionController(req, res); - expect(getConvo).not.toHaveBeenCalled(); - }); - - it('should return 400 when conversation_id is not a string', async () => { - const { validateRequest } = require('@librechat/api'); - validateRequest.mockReturnValueOnce({ - request: { model: 'agent-123', messages: [], stream: false, conversation_id: { $gt: '' } }, - }); - - await OpenAIChatCompletionController(req, res); - expect(res.status).toHaveBeenCalledWith(400); - }); - - it('should return 404 when conversation is not owned by user', async () => { - const { validateRequest } = require('@librechat/api'); - const { getConvo } = require('~/models'); - validateRequest.mockReturnValueOnce({ - request: { - model: 'agent-123', - messages: [], - stream: false, - conversation_id: 'convo-abc', - }, - }); - getConvo.mockResolvedValueOnce(null); - - await OpenAIChatCompletionController(req, res); - expect(getConvo).toHaveBeenCalledWith('user-123', 'convo-abc'); - expect(res.status).toHaveBeenCalledWith(404); - }); - - it('should proceed when conversation is owned by user', async () => { - const { validateRequest } = require('@librechat/api'); - const { getConvo } = require('~/models'); - validateRequest.mockReturnValueOnce({ - request: { - model: 'agent-123', - messages: [], - stream: false, - conversation_id: 'convo-abc', - }, - }); - getConvo.mockResolvedValueOnce({ conversationId: 'convo-abc', user: 'user-123' }); - - await OpenAIChatCompletionController(req, res); - expect(getConvo).toHaveBeenCalledWith('user-123', 'convo-abc'); - expect(res.status).not.toHaveBeenCalledWith(404); - }); - - it('should return 500 when getConvo throws a DB error', async () => { - const { validateRequest } = require('@librechat/api'); - const { getConvo } = require('~/models'); - validateRequest.mockReturnValueOnce({ - request: { - model: 'agent-123', - messages: [], - stream: false, - conversation_id: 'convo-abc', - }, - }); - getConvo.mockRejectedValueOnce(new Error('DB connection failed')); - - await OpenAIChatCompletionController(req, res); - expect(res.status).toHaveBeenCalledWith(500); - }); - }); - describe('token usage recording', () => { it('should call recordCollectedUsage after successful non-streaming completion', async () => { await OpenAIChatCompletionController(req, res); expect(mockRecordCollectedUsage).toHaveBeenCalledTimes(1); expect(mockRecordCollectedUsage).toHaveBeenCalledWith( - { - spendTokens: mockSpendTokens, - spendStructuredTokens: mockSpendStructuredTokens, - pricing: { getMultiplier: mockGetMultiplier, getCacheMultiplier: mockGetCacheMultiplier }, - bulkWriteOps: { - insertMany: mockBulkInsertTransactions, - updateBalance: mockUpdateBalance, - }, - }, + { spendTokens: mockSpendTokens, spendStructuredTokens: mockSpendStructuredTokens }, expect.objectContaining({ user: 'user-123', conversationId: expect.any(String), @@ -263,18 +182,12 @@ describe('OpenAIChatCompletionController', () => { ); }); - it('should pass spendTokens, spendStructuredTokens, pricing, and bulkWriteOps as dependencies', async () => { + it('should pass spendTokens and spendStructuredTokens as dependencies', async () => { await OpenAIChatCompletionController(req, res); const [deps] = mockRecordCollectedUsage.mock.calls[0]; expect(deps).toHaveProperty('spendTokens', mockSpendTokens); expect(deps).toHaveProperty('spendStructuredTokens', mockSpendStructuredTokens); - expect(deps).toHaveProperty('pricing'); - expect(deps.pricing).toHaveProperty('getMultiplier', mockGetMultiplier); - expect(deps.pricing).toHaveProperty('getCacheMultiplier', mockGetCacheMultiplier); - expect(deps).toHaveProperty('bulkWriteOps'); - expect(deps.bulkWriteOps).toHaveProperty('insertMany', mockBulkInsertTransactions); - expect(deps.bulkWriteOps).toHaveProperty('updateBalance', mockUpdateBalance); }); it('should include model from primaryConfig in recordCollectedUsage params', async () => { @@ -288,36 +201,4 @@ describe('OpenAIChatCompletionController', () => { ); }); }); - - describe('recursionLimit resolution', () => { - it('should pass resolveRecursionLimit result to processStream config', async () => { - const { resolveRecursionLimit } = require('@librechat/api'); - resolveRecursionLimit.mockReturnValueOnce(75); - - await OpenAIChatCompletionController(req, res); - - expect(mockProcessStream).toHaveBeenCalledWith( - expect.anything(), - expect.objectContaining({ recursionLimit: 75 }), - expect.anything(), - ); - }); - - it('should call resolveRecursionLimit with agentsEConfig and agent', async () => { - const { resolveRecursionLimit } = require('@librechat/api'); - const { getAgent } = require('~/models'); - const mockAgent = { id: 'agent-123', name: 'Test', recursion_limit: 200 }; - getAgent.mockResolvedValueOnce(mockAgent); - - req.config = { - endpoints: { - agents: { recursionLimit: 100, maxRecursionLimit: 150, allowedProviders: [] }, - }, - }; - - await OpenAIChatCompletionController(req, res); - - expect(resolveRecursionLimit).toHaveBeenCalledWith(req.config.endpoints.agents, mockAgent); - }); - }); }); diff --git a/api/server/controllers/agents/__tests__/responses.unit.spec.js b/api/server/controllers/agents/__tests__/responses.unit.spec.js index 26f5f5d30b..e16ca394b2 100644 --- a/api/server/controllers/agents/__tests__/responses.unit.spec.js +++ b/api/server/controllers/agents/__tests__/responses.unit.spec.js @@ -101,33 +101,37 @@ jest.mock('~/server/services/ToolService', () => ({ loadToolsForExecution: jest.fn().mockResolvedValue([]), })); -const mockGetMultiplier = jest.fn().mockReturnValue(1); -const mockGetCacheMultiplier = jest.fn().mockReturnValue(null); +jest.mock('~/models/spendTokens', () => ({ + spendTokens: mockSpendTokens, + spendStructuredTokens: mockSpendStructuredTokens, +})); -jest.mock('~/server/controllers/agents/callbacks', () => { - const noop = { handle: jest.fn() }; - return { - createToolEndCallback: jest.fn().mockReturnValue(jest.fn()), - createResponsesToolEndCallback: jest.fn().mockReturnValue(jest.fn()), - markSummarizationUsage: jest.fn().mockImplementation((usage) => usage), - agentLogHandlerObj: noop, - buildSummarizationHandlers: jest.fn().mockReturnValue({ - on_summarize_start: noop, - on_summarize_delta: noop, - on_summarize_complete: noop, - }), - }; -}); +jest.mock('~/server/controllers/agents/callbacks', () => ({ + createToolEndCallback: jest.fn().mockReturnValue(jest.fn()), + createResponsesToolEndCallback: jest.fn().mockReturnValue(jest.fn()), +})); jest.mock('~/server/services/PermissionService', () => ({ findAccessibleResources: jest.fn().mockResolvedValue([]), })); -const mockUpdateBalance = jest.fn().mockResolvedValue({}); -const mockBulkInsertTransactions = jest.fn().mockResolvedValue(undefined); +jest.mock('~/models/Conversation', () => ({ + getConvoFiles: jest.fn().mockResolvedValue([]), + saveConvo: jest.fn().mockResolvedValue({}), + getConvo: jest.fn().mockResolvedValue(null), +})); + +jest.mock('~/models/Agent', () => ({ + getAgent: jest.fn().mockResolvedValue({ + id: 'agent-123', + name: 'Test Agent', + provider: 'anthropic', + model_parameters: { model: 'claude-3' }, + }), + getAgents: jest.fn().mockResolvedValue([]), +})); jest.mock('~/models', () => ({ - getAgent: jest.fn().mockResolvedValue({ id: 'agent-123', name: 'Test Agent' }), getFiles: jest.fn(), getUserKey: jest.fn(), getMessages: jest.fn().mockResolvedValue([]), @@ -137,15 +141,6 @@ jest.mock('~/models', () => ({ getUserCodeFiles: jest.fn(), getToolFilesByIds: jest.fn(), getCodeGeneratedFiles: jest.fn(), - updateBalance: mockUpdateBalance, - bulkInsertTransactions: mockBulkInsertTransactions, - spendTokens: mockSpendTokens, - spendStructuredTokens: mockSpendStructuredTokens, - getMultiplier: mockGetMultiplier, - getCacheMultiplier: mockGetCacheMultiplier, - getConvoFiles: jest.fn().mockResolvedValue([]), - saveConvo: jest.fn().mockResolvedValue({}), - getConvo: jest.fn().mockResolvedValue(null), })); describe('createResponse controller', () => { @@ -183,117 +178,13 @@ describe('createResponse controller', () => { }; }); - describe('conversation ownership validation', () => { - it('should skip ownership check when previous_response_id is not provided', async () => { - const { getConvo } = require('~/models'); - await createResponse(req, res); - expect(getConvo).not.toHaveBeenCalled(); - }); - - it('should return 400 when previous_response_id is not a string', async () => { - const { validateResponseRequest, sendResponsesErrorResponse } = require('@librechat/api'); - validateResponseRequest.mockReturnValueOnce({ - request: { - model: 'agent-123', - input: 'Hello', - stream: false, - previous_response_id: { $gt: '' }, - }, - }); - - await createResponse(req, res); - expect(sendResponsesErrorResponse).toHaveBeenCalledWith( - res, - 400, - 'previous_response_id must be a string', - 'invalid_request', - ); - }); - - it('should return 404 when conversation is not owned by user', async () => { - const { validateResponseRequest, sendResponsesErrorResponse } = require('@librechat/api'); - const { getConvo } = require('~/models'); - validateResponseRequest.mockReturnValueOnce({ - request: { - model: 'agent-123', - input: 'Hello', - stream: false, - previous_response_id: 'resp_abc', - }, - }); - getConvo.mockResolvedValueOnce(null); - - await createResponse(req, res); - expect(getConvo).toHaveBeenCalledWith('user-123', 'resp_abc'); - expect(sendResponsesErrorResponse).toHaveBeenCalledWith( - res, - 404, - 'Conversation not found', - 'not_found', - ); - }); - - it('should proceed when conversation is owned by user', async () => { - const { validateResponseRequest, sendResponsesErrorResponse } = require('@librechat/api'); - const { getConvo } = require('~/models'); - validateResponseRequest.mockReturnValueOnce({ - request: { - model: 'agent-123', - input: 'Hello', - stream: false, - previous_response_id: 'resp_abc', - }, - }); - getConvo.mockResolvedValueOnce({ conversationId: 'resp_abc', user: 'user-123' }); - - await createResponse(req, res); - expect(getConvo).toHaveBeenCalledWith('user-123', 'resp_abc'); - expect(sendResponsesErrorResponse).not.toHaveBeenCalledWith( - res, - 404, - expect.any(String), - expect.any(String), - ); - }); - - it('should return 500 when getConvo throws a DB error', async () => { - const { validateResponseRequest, sendResponsesErrorResponse } = require('@librechat/api'); - const { getConvo } = require('~/models'); - validateResponseRequest.mockReturnValueOnce({ - request: { - model: 'agent-123', - input: 'Hello', - stream: false, - previous_response_id: 'resp_abc', - }, - }); - getConvo.mockRejectedValueOnce(new Error('DB connection failed')); - - await createResponse(req, res); - expect(sendResponsesErrorResponse).toHaveBeenCalledWith( - res, - 500, - expect.any(String), - expect.any(String), - ); - }); - }); - describe('token usage recording - non-streaming', () => { it('should call recordCollectedUsage after successful non-streaming completion', async () => { await createResponse(req, res); expect(mockRecordCollectedUsage).toHaveBeenCalledTimes(1); expect(mockRecordCollectedUsage).toHaveBeenCalledWith( - { - spendTokens: mockSpendTokens, - spendStructuredTokens: mockSpendStructuredTokens, - pricing: { getMultiplier: mockGetMultiplier, getCacheMultiplier: mockGetCacheMultiplier }, - bulkWriteOps: { - insertMany: mockBulkInsertTransactions, - updateBalance: mockUpdateBalance, - }, - }, + { spendTokens: mockSpendTokens, spendStructuredTokens: mockSpendStructuredTokens }, expect.objectContaining({ user: 'user-123', conversationId: expect.any(String), @@ -318,18 +209,12 @@ describe('createResponse controller', () => { ); }); - it('should pass spendTokens, spendStructuredTokens, pricing, and bulkWriteOps as dependencies', async () => { + it('should pass spendTokens and spendStructuredTokens as dependencies', async () => { await createResponse(req, res); const [deps] = mockRecordCollectedUsage.mock.calls[0]; expect(deps).toHaveProperty('spendTokens', mockSpendTokens); expect(deps).toHaveProperty('spendStructuredTokens', mockSpendStructuredTokens); - expect(deps).toHaveProperty('pricing'); - expect(deps.pricing).toHaveProperty('getMultiplier', mockGetMultiplier); - expect(deps.pricing).toHaveProperty('getCacheMultiplier', mockGetCacheMultiplier); - expect(deps).toHaveProperty('bulkWriteOps'); - expect(deps.bulkWriteOps).toHaveProperty('insertMany', mockBulkInsertTransactions); - expect(deps.bulkWriteOps).toHaveProperty('updateBalance', mockUpdateBalance); }); it('should include model from primaryConfig in recordCollectedUsage params', async () => { @@ -359,15 +244,7 @@ describe('createResponse controller', () => { expect(mockRecordCollectedUsage).toHaveBeenCalledTimes(1); expect(mockRecordCollectedUsage).toHaveBeenCalledWith( - { - spendTokens: mockSpendTokens, - spendStructuredTokens: mockSpendStructuredTokens, - pricing: { getMultiplier: mockGetMultiplier, getCacheMultiplier: mockGetCacheMultiplier }, - bulkWriteOps: { - insertMany: mockBulkInsertTransactions, - updateBalance: mockUpdateBalance, - }, - }, + { spendTokens: mockSpendTokens, spendStructuredTokens: mockSpendStructuredTokens }, expect.objectContaining({ user: 'user-123', context: 'message', @@ -380,7 +257,28 @@ describe('createResponse controller', () => { it('should collect usage from on_chat_model_end events', async () => { const api = require('@librechat/api'); + let capturedOnChatModelEnd; + api.createAggregatorEventHandlers.mockImplementation(() => { + return { + on_message_delta: { handle: jest.fn() }, + on_reasoning_delta: { handle: jest.fn() }, + on_run_step: { handle: jest.fn() }, + on_run_step_delta: { handle: jest.fn() }, + on_chat_model_end: { + handle: jest.fn((event, data) => { + if (capturedOnChatModelEnd) { + capturedOnChatModelEnd(event, data); + } + }), + }, + }; + }); + api.createRun.mockImplementation(async ({ customHandlers }) => { + capturedOnChatModelEnd = (event, data) => { + customHandlers.on_chat_model_end.handle(event, data); + }; + return { processStream: jest.fn().mockImplementation(async () => { customHandlers.on_chat_model_end.handle('on_chat_model_end', { @@ -397,6 +295,7 @@ describe('createResponse controller', () => { }); await createResponse(req, res); + expect(mockRecordCollectedUsage).toHaveBeenCalledWith( expect.any(Object), expect.objectContaining({ diff --git a/api/server/controllers/agents/__tests__/v1.duplicate-actions.spec.js b/api/server/controllers/agents/__tests__/v1.duplicate-actions.spec.js deleted file mode 100644 index cc298bd03a..0000000000 --- a/api/server/controllers/agents/__tests__/v1.duplicate-actions.spec.js +++ /dev/null @@ -1,159 +0,0 @@ -jest.mock('~/server/services/PermissionService', () => ({ - findPubliclyAccessibleResources: jest.fn(), - findAccessibleResources: jest.fn(), - hasPublicPermission: jest.fn(), - grantPermission: jest.fn().mockResolvedValue({}), -})); - -jest.mock('~/server/services/Config', () => ({ - getCachedTools: jest.fn(), - getMCPServerTools: jest.fn(), -})); - -const mongoose = require('mongoose'); -const { actionDelimiter } = require('librechat-data-provider'); -const { agentSchema, actionSchema } = require('@librechat/data-schemas'); -const { MongoMemoryServer } = require('mongodb-memory-server'); -const { duplicateAgent } = require('../v1'); - -let mongoServer; - -beforeAll(async () => { - mongoServer = await MongoMemoryServer.create(); - const mongoUri = mongoServer.getUri(); - if (!mongoose.models.Agent) { - mongoose.model('Agent', agentSchema); - } - if (!mongoose.models.Action) { - mongoose.model('Action', actionSchema); - } - await mongoose.connect(mongoUri); -}, 20000); - -afterAll(async () => { - await mongoose.disconnect(); - await mongoServer.stop(); -}); - -beforeEach(async () => { - await mongoose.models.Agent.deleteMany({}); - await mongoose.models.Action.deleteMany({}); -}); - -describe('duplicateAgentHandler — action domain extraction', () => { - it('builds duplicated action entries using metadata.domain, not action_id', async () => { - const userId = new mongoose.Types.ObjectId(); - const originalAgentId = `agent_original`; - - const agent = await mongoose.models.Agent.create({ - id: originalAgentId, - name: 'Test Agent', - author: userId.toString(), - provider: 'openai', - model: 'gpt-4', - tools: [], - actions: [`api.example.com${actionDelimiter}act_original`], - versions: [{ name: 'Test Agent', createdAt: new Date(), updatedAt: new Date() }], - }); - - await mongoose.models.Action.create({ - user: userId, - action_id: 'act_original', - agent_id: originalAgentId, - metadata: { domain: 'api.example.com' }, - }); - - const req = { - params: { id: agent.id }, - user: { id: userId.toString() }, - }; - const res = { - status: jest.fn().mockReturnThis(), - json: jest.fn(), - }; - - await duplicateAgent(req, res); - - expect(res.status).toHaveBeenCalledWith(201); - - const { agent: newAgent, actions: newActions } = res.json.mock.calls[0][0]; - - expect(newAgent.id).not.toBe(originalAgentId); - expect(String(newAgent.author)).toBe(userId.toString()); - expect(newActions).toHaveLength(1); - expect(newActions[0].metadata.domain).toBe('api.example.com'); - expect(newActions[0].agent_id).toBe(newAgent.id); - - for (const actionEntry of newAgent.actions) { - const [domain, actionId] = actionEntry.split(actionDelimiter); - expect(domain).toBe('api.example.com'); - expect(actionId).toBeTruthy(); - expect(actionId).not.toBe('act_original'); - } - - const allActions = await mongoose.models.Action.find({}).lean(); - expect(allActions).toHaveLength(2); - - const originalAction = allActions.find((a) => a.action_id === 'act_original'); - expect(originalAction.agent_id).toBe(originalAgentId); - - const duplicatedAction = allActions.find((a) => a.action_id !== 'act_original'); - expect(duplicatedAction.agent_id).toBe(newAgent.id); - expect(duplicatedAction.metadata.domain).toBe('api.example.com'); - }); - - it('strips sensitive metadata fields from duplicated actions', async () => { - const userId = new mongoose.Types.ObjectId(); - const originalAgentId = 'agent_sensitive'; - - await mongoose.models.Agent.create({ - id: originalAgentId, - name: 'Sensitive Agent', - author: userId.toString(), - provider: 'openai', - model: 'gpt-4', - tools: [], - actions: [`secure.api.com${actionDelimiter}act_secret`], - versions: [{ name: 'Sensitive Agent', createdAt: new Date(), updatedAt: new Date() }], - }); - - await mongoose.models.Action.create({ - user: userId, - action_id: 'act_secret', - agent_id: originalAgentId, - metadata: { - domain: 'secure.api.com', - api_key: 'sk-secret-key-12345', - oauth_client_id: 'client_id_xyz', - oauth_client_secret: 'client_secret_xyz', - }, - }); - - const req = { - params: { id: originalAgentId }, - user: { id: userId.toString() }, - }; - const res = { - status: jest.fn().mockReturnThis(), - json: jest.fn(), - }; - - await duplicateAgent(req, res); - - expect(res.status).toHaveBeenCalledWith(201); - - const duplicatedAction = await mongoose.models.Action.findOne({ - agent_id: { $ne: originalAgentId }, - }).lean(); - - expect(duplicatedAction.metadata.domain).toBe('secure.api.com'); - expect(duplicatedAction.metadata.api_key).toBeUndefined(); - expect(duplicatedAction.metadata.oauth_client_id).toBeUndefined(); - expect(duplicatedAction.metadata.oauth_client_secret).toBeUndefined(); - - const originalAction = await mongoose.models.Action.findOne({ - action_id: 'act_secret', - }).lean(); - expect(originalAction.metadata.api_key).toBe('sk-secret-key-12345'); - }); -}); diff --git a/api/server/controllers/agents/__tests__/v1.spec.js b/api/server/controllers/agents/__tests__/v1.spec.js index 39cf994fef..b7e7b67a22 100644 --- a/api/server/controllers/agents/__tests__/v1.spec.js +++ b/api/server/controllers/agents/__tests__/v1.spec.js @@ -1,8 +1,10 @@ const { duplicateAgent } = require('../v1'); -const { getAgent, createAgent, getActions } = require('~/models'); +const { getAgent, createAgent } = require('~/models/Agent'); +const { getActions } = require('~/models/Action'); const { nanoid } = require('nanoid'); -jest.mock('~/models'); +jest.mock('~/models/Agent'); +jest.mock('~/models/Action'); jest.mock('nanoid'); describe('duplicateAgent', () => { diff --git a/api/server/controllers/agents/callbacks.js b/api/server/controllers/agents/callbacks.js index 40fdf74212..0bb935795d 100644 --- a/api/server/controllers/agents/callbacks.js +++ b/api/server/controllers/agents/callbacks.js @@ -1,13 +1,7 @@ const { nanoid } = require('nanoid'); const { logger } = require('@librechat/data-schemas'); +const { Constants, EnvVar, GraphEvents, ToolEndHandler } = require('@librechat/agents'); const { Tools, StepTypes, FileContext, ErrorTypes } = require('librechat-data-provider'); -const { - EnvVar, - Constants, - GraphEvents, - GraphNodeKeys, - ToolEndHandler, -} = require('@librechat/agents'); const { sendEvent, GenerationJobManager, @@ -77,9 +71,7 @@ class ModelEndHandler { usage.model = modelName; } - const taggedUsage = markSummarizationUsage(usage, metadata); - - this.collectedUsage.push(taggedUsage); + this.collectedUsage.push(usage); } catch (error) { logger.error('Error handling model end event:', error); return this.finalize(errorMessage); @@ -141,7 +133,6 @@ function getDefaultHandlers({ collectedUsage, streamId = null, toolExecuteOptions = null, - summarizationOptions = null, }) { if (!res || !aggregateContent) { throw new Error( @@ -254,37 +245,6 @@ function getDefaultHandlers({ handlers[GraphEvents.ON_TOOL_EXECUTE] = createToolExecuteHandler(toolExecuteOptions); } - if (summarizationOptions?.enabled !== false) { - handlers[GraphEvents.ON_SUMMARIZE_START] = { - handle: async (_event, data) => { - await emitEvent(res, streamId, { - event: GraphEvents.ON_SUMMARIZE_START, - data, - }); - }, - }; - handlers[GraphEvents.ON_SUMMARIZE_DELTA] = { - handle: async (_event, data) => { - aggregateContent({ event: GraphEvents.ON_SUMMARIZE_DELTA, data }); - await emitEvent(res, streamId, { - event: GraphEvents.ON_SUMMARIZE_DELTA, - data, - }); - }, - }; - handlers[GraphEvents.ON_SUMMARIZE_COMPLETE] = { - handle: async (_event, data) => { - aggregateContent({ event: GraphEvents.ON_SUMMARIZE_COMPLETE, data }); - await emitEvent(res, streamId, { - event: GraphEvents.ON_SUMMARIZE_COMPLETE, - data, - }); - }, - }; - } - - handlers[GraphEvents.ON_AGENT_LOG] = { handle: agentLogHandler }; - return handlers; } @@ -708,62 +668,8 @@ function createResponsesToolEndCallback({ req, res, tracker, artifactPromises }) }; } -const ALLOWED_LOG_LEVELS = new Set(['debug', 'info', 'warn', 'error']); - -function agentLogHandler(_event, data) { - if (!data) { - return; - } - const logFn = ALLOWED_LOG_LEVELS.has(data.level) ? logger[data.level] : logger.debug; - const meta = typeof data.data === 'object' && data.data != null ? data.data : {}; - logFn(`[agents:${data.scope ?? 'unknown'}] ${data.message ?? ''}`, { - ...meta, - runId: data.runId, - agentId: data.agentId, - }); -} - -function markSummarizationUsage(usage, metadata) { - const node = metadata?.langgraph_node; - if (typeof node === 'string' && node.startsWith(GraphNodeKeys.SUMMARIZE)) { - return { ...usage, usage_type: 'summarization' }; - } - return usage; -} - -const agentLogHandlerObj = { handle: agentLogHandler }; - -/** - * Builds the three summarization SSE event handlers. - * In streaming mode, each event is forwarded to the client via `res.write`. - * In non-streaming mode, the handlers are no-ops. - * @param {{ isStreaming: boolean, res: import('express').Response }} opts - */ -function buildSummarizationHandlers({ isStreaming, res }) { - if (!isStreaming) { - const noop = { handle: () => {} }; - return { on_summarize_start: noop, on_summarize_delta: noop, on_summarize_complete: noop }; - } - const writeEvent = (name) => ({ - handle: async (_event, data) => { - if (!res.writableEnded) { - res.write(`event: ${name}\ndata: ${JSON.stringify(data)}\n\n`); - } - }, - }); - return { - on_summarize_start: writeEvent('on_summarize_start'), - on_summarize_delta: writeEvent('on_summarize_delta'), - on_summarize_complete: writeEvent('on_summarize_complete'), - }; -} - module.exports = { - agentLogHandler, - agentLogHandlerObj, getDefaultHandlers, createToolEndCallback, - markSummarizationUsage, - buildSummarizationHandlers, createResponsesToolEndCallback, }; diff --git a/api/server/controllers/agents/client.js b/api/server/controllers/agents/client.js index 3c1f91bd60..49240a6b3b 100644 --- a/api/server/controllers/agents/client.js +++ b/api/server/controllers/agents/client.js @@ -3,31 +3,26 @@ const { logger } = require('@librechat/data-schemas'); const { getBufferString, HumanMessage } = require('@langchain/core/messages'); const { createRun, - isEnabled, + Tokenizer, checkAccess, buildToolSet, - logToolError, sanitizeTitle, + logToolError, payloadParser, resolveHeaders, createSafeUser, initializeAgent, getBalanceConfig, - omitTitleOptions, getProviderConfig, + omitTitleOptions, memoryInstructions, - createTokenCounter, applyContextToAgent, - recordCollectedUsage, + createTokenCounter, GenerationJobManager, getTransactionsConfig, - resolveRecursionLimit, createMemoryProcessor, - loadAgent: loadAgentFn, createMultiAgentMapper, filterMalformedContentParts, - countFormattedMessageTokens, - hydrateMissingIndexTokenCounts, } = require('@librechat/api'); const { Callback, @@ -48,17 +43,16 @@ const { isEphemeralAgentId, removeNullishValues, } = require('librechat-data-provider'); -const { filterFilesByAgentAccess } = require('~/server/services/Files/permissions'); +const { spendTokens, spendStructuredTokens } = require('~/models/spendTokens'); const { encodeAndFormat } = require('~/server/services/Files/images/encode'); const { createContextHandlers } = require('~/app/clients/prompts'); -const { resolveConfigServers } = require('~/server/services/MCP'); -const { getMCPServerTools } = require('~/server/services/Config'); +const { getConvoFiles } = require('~/models/Conversation'); const BaseClient = require('~/app/clients/BaseClient'); +const { getRoleByName } = require('~/models/Role'); +const { loadAgent } = require('~/models/Agent'); const { getMCPManager } = require('~/config'); const db = require('~/models'); -const loadAgent = (params) => loadAgentFn(params, { getAgent: db.getAgent, getMCPServerTools }); - class AgentClient extends BaseClient { constructor(options = {}) { super(null, options); @@ -66,6 +60,9 @@ class AgentClient extends BaseClient { * @type {string} */ this.clientName = EModelEndpoint.agents; + /** @type {'discard' | 'summarize'} */ + this.contextStrategy = 'discard'; + /** @deprecated @type {true} - Is a Chat Completion Request */ this.isChatCompletion = true; @@ -217,6 +214,7 @@ class AgentClient extends BaseClient { })) : []), ]; + if (this.options.attachments) { const attachments = await this.options.attachments; const latestMessage = orderedMessages[orderedMessages.length - 1]; @@ -243,11 +241,6 @@ class AgentClient extends BaseClient { ); } - /** @type {Record} */ - const canonicalTokenCountMap = {}; - /** @type {Record} */ - const tokenCountMap = {}; - let promptTokenTotal = 0; const formattedMessages = orderedMessages.map((message, i) => { const formattedMessage = formatMessage({ message, @@ -267,14 +260,12 @@ class AgentClient extends BaseClient { } } - const dbTokenCount = orderedMessages[i].tokenCount; - const needsTokenCount = !dbTokenCount || message.fileContext; + const needsTokenCount = + (this.contextStrategy && !orderedMessages[i].tokenCount) || message.fileContext; + /* If tokens were never counted, or, is a Vision request and the message has files, count again */ if (needsTokenCount || (this.isVisionModel && (message.image_urls || message.files))) { - orderedMessages[i].tokenCount = countFormattedMessageTokens( - formattedMessage, - this.getEncoding(), - ); + orderedMessages[i].tokenCount = this.getTokenCountForMessage(formattedMessage); } /* If message has files, calculate image token cost */ @@ -288,37 +279,17 @@ class AgentClient extends BaseClient { if (file.metadata?.fileIdentifier) { continue; } + // orderedMessages[i].tokenCount += this.calculateImageTokenCost({ + // width: file.width, + // height: file.height, + // detail: this.options.imageDetail ?? ImageDetail.auto, + // }); } } - const tokenCount = Number(orderedMessages[i].tokenCount); - const normalizedTokenCount = Number.isFinite(tokenCount) && tokenCount > 0 ? tokenCount : 0; - canonicalTokenCountMap[i] = normalizedTokenCount; - promptTokenTotal += normalizedTokenCount; - - if (message.messageId) { - tokenCountMap[message.messageId] = normalizedTokenCount; - } - - if (isEnabled(process.env.AGENT_DEBUG_LOGGING)) { - const role = message.isCreatedByUser ? 'user' : 'assistant'; - const hasSummary = - Array.isArray(message.content) && message.content.some((p) => p && p.type === 'summary'); - const suffix = hasSummary ? '[S]' : ''; - const id = (message.messageId ?? message.id ?? '').slice(-8); - const recalced = needsTokenCount ? orderedMessages[i].tokenCount : null; - logger.debug( - `[AgentClient] msg[${i}] ${role}${suffix} id=…${id} db=${dbTokenCount} needsRecount=${needsTokenCount} recalced=${recalced} tokens=${normalizedTokenCount}`, - ); - } - return formattedMessage; }); - payload = formattedMessages; - messages = orderedMessages; - promptTokens = promptTokenTotal; - /** * Build shared run context - applies to ALL agents in the run. * This includes: file context (latest message), augmented prompt (RAG), memory context. @@ -348,20 +319,23 @@ class AgentClient extends BaseClient { const sharedRunContext = sharedRunContextParts.join('\n\n'); - /** Preserve canonical pre-format token counts for all history entering graph formatting */ - this.indexTokenCountMap = canonicalTokenCountMap; + /** @type {Record | undefined} */ + let tokenCountMap; - /** Extract contextMeta from the parent response (second-to-last in ordered chain; - * last is the current user message). Seeds the pruner's calibration EMA for this run. */ - const parentResponse = - orderedMessages.length >= 2 ? orderedMessages[orderedMessages.length - 2] : undefined; - if (parentResponse?.contextMeta && !parentResponse.isCreatedByUser) { - this.contextMeta = parentResponse.contextMeta; + if (this.contextStrategy) { + ({ payload, promptTokens, tokenCountMap, messages } = await this.handleContextStrategy({ + orderedMessages, + formattedMessages, + })); + } + + for (let i = 0; i < messages.length; i++) { + this.indexTokenCountMap[i] = messages[i].tokenCount; } const result = { - prompt: payload, tokenCountMap, + prompt: payload, promptTokens, messages, }; @@ -379,9 +353,6 @@ class AgentClient extends BaseClient { */ const ephemeralAgent = this.options.req.body.ephemeralAgent; const mcpManager = getMCPManager(); - - const configServers = await resolveConfigServers(this.options.req); - await Promise.all( allAgents.map(({ agent, agentId }) => applyContextToAgent({ @@ -389,7 +360,6 @@ class AgentClient extends BaseClient { agentId, logger, mcpManager, - configServers, sharedRunContext, ephemeralAgent: agentId === this.options.agent.id ? ephemeralAgent : undefined, }), @@ -439,7 +409,7 @@ class AgentClient extends BaseClient { user, permissionType: PermissionTypes.MEMORIES, permissions: [Permissions.USE], - getRoleByName: db.getRoleByName, + getRoleByName, }); if (!hasAccess) { @@ -499,14 +469,13 @@ class AgentClient extends BaseClient { }, }, { + getConvoFiles, getFiles: db.getFiles, getUserKey: db.getUserKey, - getConvoFiles: db.getConvoFiles, updateFilesUsage: db.updateFilesUsage, getUserKeyValues: db.getUserKeyValues, getToolFilesByIds: db.getToolFilesByIds, getCodeGeneratedFiles: db.getCodeGeneratedFiles, - filterFilesByAgentAccess, }, ); @@ -655,29 +624,82 @@ class AgentClient extends BaseClient { context = 'message', collectedUsage = this.collectedUsage, }) { - const result = await recordCollectedUsage( - { - spendTokens: db.spendTokens, - spendStructuredTokens: db.spendStructuredTokens, - pricing: { getMultiplier: db.getMultiplier, getCacheMultiplier: db.getCacheMultiplier }, - bulkWriteOps: { insertMany: db.bulkInsertTransactions, updateBalance: db.updateBalance }, - }, - { - user: this.user ?? this.options.req.user?.id, - conversationId: this.conversationId, - collectedUsage, - model: model ?? this.model ?? this.options.agent.model_parameters.model, + if (!collectedUsage || !collectedUsage.length) { + return; + } + // Use first entry's input_tokens as the base input (represents initial user message context) + // Support both OpenAI format (input_token_details) and Anthropic format (cache_*_input_tokens) + const firstUsage = collectedUsage[0]; + const input_tokens = + (firstUsage?.input_tokens || 0) + + (Number(firstUsage?.input_token_details?.cache_creation) || + Number(firstUsage?.cache_creation_input_tokens) || + 0) + + (Number(firstUsage?.input_token_details?.cache_read) || + Number(firstUsage?.cache_read_input_tokens) || + 0); + + // Sum output_tokens directly from all entries - works for both sequential and parallel execution + // This avoids the incremental calculation that produced negative values for parallel agents + let total_output_tokens = 0; + + for (const usage of collectedUsage) { + if (!usage) { + continue; + } + + // Support both OpenAI format (input_token_details) and Anthropic format (cache_*_input_tokens) + const cache_creation = + Number(usage.input_token_details?.cache_creation) || + Number(usage.cache_creation_input_tokens) || + 0; + const cache_read = + Number(usage.input_token_details?.cache_read) || Number(usage.cache_read_input_tokens) || 0; + + // Accumulate output tokens for the usage summary + total_output_tokens += Number(usage.output_tokens) || 0; + + const txMetadata = { context, - messageId: this.responseMessageId, balance, transactions, + conversationId: this.conversationId, + user: this.user ?? this.options.req.user?.id, endpointTokenConfig: this.options.endpointTokenConfig, - }, - ); + model: usage.model ?? model ?? this.model ?? this.options.agent.model_parameters.model, + }; - if (result) { - this.usage = result; + if (cache_creation > 0 || cache_read > 0) { + spendStructuredTokens(txMetadata, { + promptTokens: { + input: usage.input_tokens, + write: cache_creation, + read: cache_read, + }, + completionTokens: usage.output_tokens, + }).catch((err) => { + logger.error( + '[api/server/controllers/agents/client.js #recordCollectedUsage] Error spending structured tokens', + err, + ); + }); + continue; + } + spendTokens(txMetadata, { + promptTokens: usage.input_tokens, + completionTokens: usage.output_tokens, + }).catch((err) => { + logger.error( + '[api/server/controllers/agents/client.js #recordCollectedUsage] Error spending tokens', + err, + ); + }); } + + this.usage = { + input_tokens, + output_tokens: total_output_tokens, + }; } /** @@ -693,7 +715,39 @@ class AgentClient extends BaseClient { * @returns {number} */ getTokenCountForResponse({ content }) { - return countFormattedMessageTokens({ role: 'assistant', content }, this.getEncoding()); + return this.getTokenCountForMessage({ + role: 'assistant', + content, + }); + } + + /** + * Calculates the correct token count for the current user message based on the token count map and API usage. + * Edge case: If the calculation results in a negative value, it returns the original estimate. + * If revisiting a conversation with a chat history entirely composed of token estimates, + * the cumulative token count going forward should become more accurate as the conversation progresses. + * @param {Object} params - The parameters for the calculation. + * @param {Record} params.tokenCountMap - A map of message IDs to their token counts. + * @param {string} params.currentMessageId - The ID of the current message to calculate. + * @param {OpenAIUsageMetadata} params.usage - The usage object returned by the API. + * @returns {number} The correct token count for the current user message. + */ + calculateCurrentTokenCount({ tokenCountMap, currentMessageId, usage }) { + const originalEstimate = tokenCountMap[currentMessageId] || 0; + + if (!usage || typeof usage[this.inputTokensKey] !== 'number') { + return originalEstimate; + } + + tokenCountMap[currentMessageId] = 0; + const totalTokensFromMap = Object.values(tokenCountMap).reduce((sum, count) => { + const numCount = Number(count); + return sum + (isNaN(numCount) ? 0 : numCount); + }, 0); + const totalInputTokens = usage[this.inputTokensKey] ?? 0; + + const currentMessageTokens = totalInputTokens - totalTokensFromMap; + return currentMessageTokens > 0 ? currentMessageTokens : originalEstimate; } /** @@ -734,41 +788,18 @@ class AgentClient extends BaseClient { }, user: createSafeUser(this.options.req.user), }, - recursionLimit: resolveRecursionLimit(agentsEConfig, this.options.agent), + recursionLimit: agentsEConfig?.recursionLimit ?? 50, signal: abortController.signal, streamMode: 'values', version: 'v2', }; const toolSet = buildToolSet(this.options.agent); - const tokenCounter = createTokenCounter(this.getEncoding()); - let { - messages: initialMessages, - indexTokenCountMap, - summary: initialSummary, - boundaryTokenAdjustment, - } = formatAgentMessages(payload, this.indexTokenCountMap, toolSet); - if (boundaryTokenAdjustment) { - logger.debug( - `[AgentClient] Boundary token adjustment: ${boundaryTokenAdjustment.original} → ${boundaryTokenAdjustment.adjusted} (${boundaryTokenAdjustment.remainingChars}/${boundaryTokenAdjustment.totalChars} chars)`, - ); - } - if (indexTokenCountMap && isEnabled(process.env.AGENT_DEBUG_LOGGING)) { - const entries = Object.entries(indexTokenCountMap); - const perMsg = entries.map(([idx, count]) => { - const msg = initialMessages[Number(idx)]; - const type = msg ? msg._getType() : '?'; - return `${idx}:${type}=${count}`; - }); - logger.debug( - `[AgentClient] Token map after format: [${perMsg.join(', ')}] (payload=${payload.length}, formatted=${initialMessages.length})`, - ); - } - indexTokenCountMap = hydrateMissingIndexTokenCounts({ - messages: initialMessages, - indexTokenCountMap, - tokenCounter, - }); + let { messages: initialMessages, indexTokenCountMap } = formatAgentMessages( + payload, + this.indexTokenCountMap, + toolSet, + ); /** * @param {BaseMessage[]} messages @@ -782,6 +813,17 @@ class AgentClient extends BaseClient { agents.push(...this.agentConfigs.values()); } + if (agents[0].recursion_limit && typeof agents[0].recursion_limit === 'number') { + config.recursionLimit = agents[0].recursion_limit; + } + + if ( + agentsEConfig?.maxRecursionLimit && + config.recursionLimit > agentsEConfig?.maxRecursionLimit + ) { + config.recursionLimit = agentsEConfig?.maxRecursionLimit; + } + // TODO: needs to be added as part of AgentContext initialization // const noSystemModelRegex = [/\b(o1-preview|o1-mini|amazon\.titan-text)\b/gi]; // const noSystemMessages = noSystemModelRegex.some((regex) => @@ -811,32 +853,16 @@ class AgentClient extends BaseClient { memoryPromise = this.runMemory(messages); - /** Seed calibration state from previous run if encoding matches */ - const currentEncoding = this.getEncoding(); - const prevMeta = this.contextMeta; - const encodingMatch = prevMeta?.encoding === currentEncoding; - const calibrationRatio = - encodingMatch && prevMeta?.calibrationRatio > 0 ? prevMeta.calibrationRatio : undefined; - - if (prevMeta) { - logger.debug( - `[AgentClient] contextMeta from parent: ratio=${prevMeta.calibrationRatio}, encoding=${prevMeta.encoding}, current=${currentEncoding}, seeded=${calibrationRatio ?? 'none'}`, - ); - } - run = await createRun({ agents, messages, indexTokenCountMap, - initialSummary, - calibrationRatio, runId: this.responseMessageId, signal: abortController.signal, customHandlers: this.options.eventHandlers, requestBody: config.configurable.requestBody, user: createSafeUser(this.options.req?.user), - summarizationConfig: appConfig?.summarization, - tokenCounter, + tokenCounter: createTokenCounter(this.getEncoding()), }); if (!run) { @@ -865,11 +891,9 @@ class AgentClient extends BaseClient { config.signal = null; }; - const hideSequentialOutputs = config.configurable.hide_sequential_outputs; await runAgents(initialMessages); - /** @deprecated Agent Chain */ - if (hideSequentialOutputs) { + if (config.configurable.hide_sequential_outputs) { this.contentParts = this.contentParts.filter((part, index) => { // Include parts that are either: // 1. At or after the finalContentStart index @@ -898,18 +922,6 @@ class AgentClient extends BaseClient { }); } } finally { - /** Capture calibration state from the run for persistence on the response message. - * Runs in finally so values are captured even on abort. */ - const ratio = this.run?.getCalibrationRatio() ?? 0; - if (ratio > 0 && ratio !== 1) { - this.contextMeta = { - calibrationRatio: Math.round(ratio * 1000) / 1000, - encoding: this.getEncoding(), - }; - } else { - this.contextMeta = undefined; - } - try { const attachments = await this.awaitMemoryWithTimeout(memoryPromise); if (attachments && attachments.length > 0) { @@ -1095,7 +1107,6 @@ class AgentClient extends BaseClient { titlePrompt: endpointConfig?.titlePrompt, titlePromptTemplate: endpointConfig?.titlePromptTemplate, chainOptions: { - runName: 'TitleRun', signal: abortController.signal, callbacks: [ { @@ -1136,7 +1147,6 @@ class AgentClient extends BaseClient { model: clientOptions.model, balance: balanceConfig, transactions: transactionsConfig, - messageId: this.responseMessageId, }).catch((err) => { logger.error( '[api/server/controllers/agents/client.js #titleConvo] Error recording collected usage', @@ -1170,12 +1180,11 @@ class AgentClient extends BaseClient { context = 'message', }) { try { - await db.spendTokens( + await spendTokens( { model, context, balance, - messageId: this.responseMessageId, conversationId: this.conversationId, user: this.user ?? this.options.req.user?.id, endpointTokenConfig: this.options.endpointTokenConfig, @@ -1189,12 +1198,11 @@ class AgentClient extends BaseClient { 'reasoning_tokens' in usage && typeof usage.reasoning_tokens === 'number' ) { - await db.spendTokens( + await spendTokens( { model, balance, context: 'reasoning', - messageId: this.responseMessageId, conversationId: this.conversationId, user: this.user ?? this.options.req.user?.id, endpointTokenConfig: this.options.endpointTokenConfig, @@ -1210,13 +1218,19 @@ class AgentClient extends BaseClient { } } - /** Anthropic Claude models use a distinct BPE tokenizer; all others default to o200k_base. */ getEncoding() { - if (this.model && this.model.toLowerCase().includes('claude')) { - return 'claude'; - } return 'o200k_base'; } + + /** + * Returns the token count of a given text. It also checks and resets the tokenizers if necessary. + * @param {string} text - The text to get the token count for. + * @returns {number} The token count of the given text. + */ + getTokenCount(text) { + const encoding = this.getEncoding(); + return Tokenizer.getTokenCount(text, encoding); + } } module.exports = AgentClient; diff --git a/api/server/controllers/agents/client.test.js b/api/server/controllers/agents/client.test.js index 1595f652f7..9dd3567047 100644 --- a/api/server/controllers/agents/client.test.js +++ b/api/server/controllers/agents/client.test.js @@ -15,19 +15,13 @@ jest.mock('@librechat/api', () => ({ checkAccess: jest.fn(), initializeAgent: jest.fn(), createMemoryProcessor: jest.fn(), +})); + +jest.mock('~/models/Agent', () => ({ loadAgent: jest.fn(), })); -jest.mock('~/server/services/Config', () => ({ - getMCPServerTools: jest.fn(), -})); - -jest.mock('~/server/services/MCP', () => ({ - resolveConfigServers: jest.fn().mockResolvedValue({}), -})); - -jest.mock('~/models', () => ({ - getAgent: jest.fn(), +jest.mock('~/models/Role', () => ({ getRoleByName: jest.fn(), })); @@ -269,7 +263,6 @@ describe('AgentClient - titleConvo', () => { transactions: { enabled: true, }, - messageId: 'response-123', }); }); @@ -1319,7 +1312,7 @@ describe('AgentClient - titleConvo', () => { }); // Verify formatInstructionsForContext was called with correct server names - expect(mockFormatInstructions).toHaveBeenCalledWith(['server1', 'server2'], {}); + expect(mockFormatInstructions).toHaveBeenCalledWith(['server1', 'server2']); // Verify the instructions do NOT contain [object Promise] expect(client.options.agent.instructions).not.toContain('[object Promise]'); @@ -1359,10 +1352,10 @@ describe('AgentClient - titleConvo', () => { }); // Verify formatInstructionsForContext was called with ephemeral server names - expect(mockFormatInstructions).toHaveBeenCalledWith( - ['ephemeral-server1', 'ephemeral-server2'], - {}, - ); + expect(mockFormatInstructions).toHaveBeenCalledWith([ + 'ephemeral-server1', + 'ephemeral-server2', + ]); // Verify no [object Promise] in instructions expect(client.options.agent.instructions).not.toContain('[object Promise]'); @@ -1822,7 +1815,7 @@ describe('AgentClient - titleConvo', () => { /** Traversal stops at msg-2 (has summary), so we get msg-4 -> msg-3 -> msg-2 */ expect(result).toHaveLength(3); - expect(result[0].content).toEqual([{ type: 'text', text: 'Summary of conversation' }]); + expect(result[0].text).toBe('Summary of conversation'); expect(result[0].role).toBe('system'); expect(result[0].mapped).toBe(true); expect(result[1].mapped).toBe(true); @@ -2144,7 +2137,7 @@ describe('AgentClient - titleConvo', () => { }; mockCheckAccess = require('@librechat/api').checkAccess; - mockLoadAgent = require('@librechat/api').loadAgent; + mockLoadAgent = require('~/models/Agent').loadAgent; mockInitializeAgent = require('@librechat/api').initializeAgent; mockCreateMemoryProcessor = require('@librechat/api').createMemoryProcessor; }); @@ -2201,7 +2194,6 @@ describe('AgentClient - titleConvo', () => { expect.objectContaining({ agent_id: differentAgentId, }), - expect.any(Object), ); expect(mockInitializeAgent).toHaveBeenCalledWith( expect.objectContaining({ diff --git a/api/server/controllers/agents/errors.js b/api/server/controllers/agents/errors.js index b16ce75591..54b296a5d2 100644 --- a/api/server/controllers/agents/errors.js +++ b/api/server/controllers/agents/errors.js @@ -3,8 +3,8 @@ const { logger } = require('@librechat/data-schemas'); const { CacheKeys, ViolationTypes } = require('librechat-data-provider'); const { sendResponse } = require('~/server/middleware/error'); const { recordUsage } = require('~/server/services/Threads'); +const { getConvo } = require('~/models/Conversation'); const getLogStores = require('~/cache/getLogStores'); -const { getConvo } = require('~/models'); /** * @typedef {Object} ErrorHandlerContext diff --git a/api/server/controllers/agents/filterAuthorizedTools.spec.js b/api/server/controllers/agents/filterAuthorizedTools.spec.js deleted file mode 100644 index e6b41aef16..0000000000 --- a/api/server/controllers/agents/filterAuthorizedTools.spec.js +++ /dev/null @@ -1,692 +0,0 @@ -const mongoose = require('mongoose'); -const { v4: uuidv4 } = require('uuid'); -const { Constants } = require('librechat-data-provider'); -const { agentSchema } = require('@librechat/data-schemas'); -const { MongoMemoryServer } = require('mongodb-memory-server'); - -const d = Constants.mcp_delimiter; - -const mockGetAllServerConfigs = jest.fn(); - -jest.mock('~/server/services/Config', () => ({ - getCachedTools: jest.fn().mockResolvedValue({ - web_search: true, - execute_code: true, - file_search: true, - }), -})); - -jest.mock('~/config', () => ({ - getMCPServersRegistry: jest.fn(() => ({ - getAllServerConfigs: mockGetAllServerConfigs, - })), -})); - -jest.mock('~/server/services/MCP', () => ({ - resolveConfigServers: jest.fn().mockResolvedValue({}), -})); - -jest.mock('~/server/services/Files/strategies', () => ({ - getStrategyFunctions: jest.fn(), -})); - -jest.mock('~/server/services/Files/images/avatar', () => ({ - resizeAvatar: jest.fn(), -})); - -jest.mock('~/server/services/Files/process', () => ({ - filterFile: jest.fn(), -})); - -jest.mock('~/server/services/PermissionService', () => ({ - findAccessibleResources: jest.fn().mockResolvedValue([]), - findPubliclyAccessibleResources: jest.fn().mockResolvedValue([]), - grantPermission: jest.fn(), - hasPublicPermission: jest.fn().mockResolvedValue(false), - checkPermission: jest.fn().mockResolvedValue(true), -})); - -jest.mock('~/models', () => { - const mongoose = require('mongoose'); - const { createModels, createMethods } = require('@librechat/data-schemas'); - createModels(mongoose); - const methods = createMethods(mongoose); - return { - ...methods, - getCategoriesWithCounts: jest.fn(), - deleteFileByFilter: jest.fn(), - }; -}); - -jest.mock('~/cache', () => ({ - getLogStores: jest.fn(() => ({ - get: jest.fn(), - set: jest.fn(), - delete: jest.fn(), - })), -})); - -const { - filterAuthorizedTools, - createAgent: createAgentHandler, - updateAgent: updateAgentHandler, - duplicateAgent: duplicateAgentHandler, - revertAgentVersion: revertAgentVersionHandler, -} = require('./v1'); - -const { getMCPServersRegistry } = require('~/config'); - -let Agent; - -describe('MCP Tool Authorization', () => { - let mongoServer; - let mockReq; - let mockRes; - - beforeAll(async () => { - mongoServer = await MongoMemoryServer.create(); - const mongoUri = mongoServer.getUri(); - await mongoose.connect(mongoUri); - Agent = mongoose.models.Agent || mongoose.model('Agent', agentSchema); - }, 20000); - - afterAll(async () => { - await mongoose.disconnect(); - await mongoServer.stop(); - }); - - beforeEach(async () => { - await Agent.deleteMany({}); - jest.clearAllMocks(); - - getMCPServersRegistry.mockImplementation(() => ({ - getAllServerConfigs: mockGetAllServerConfigs, - })); - mockGetAllServerConfigs.mockResolvedValue({ - authorizedServer: { type: 'sse', url: 'https://authorized.example.com' }, - anotherServer: { type: 'sse', url: 'https://another.example.com' }, - }); - - mockReq = { - user: { - id: new mongoose.Types.ObjectId().toString(), - role: 'USER', - }, - body: {}, - params: {}, - query: {}, - app: { locals: { fileStrategy: 'local' } }, - }; - - mockRes = { - status: jest.fn().mockReturnThis(), - json: jest.fn().mockReturnThis(), - }; - }); - - describe('filterAuthorizedTools', () => { - const availableTools = { web_search: true, custom_tool: true }; - const userId = 'test-user-123'; - - test('should keep authorized MCP tools and strip unauthorized ones', async () => { - const result = await filterAuthorizedTools({ - tools: [`toolA${d}authorizedServer`, `toolB${d}forbiddenServer`, 'web_search'], - userId, - availableTools, - }); - - expect(result).toContain(`toolA${d}authorizedServer`); - expect(result).toContain('web_search'); - expect(result).not.toContain(`toolB${d}forbiddenServer`); - }); - - test('should keep system tools without querying MCP registry', async () => { - const result = await filterAuthorizedTools({ - tools: ['execute_code', 'file_search', 'web_search'], - userId, - availableTools: {}, - }); - - expect(result).toEqual(['execute_code', 'file_search', 'web_search']); - expect(mockGetAllServerConfigs).not.toHaveBeenCalled(); - }); - - test('should not query MCP registry when no MCP tools are present', async () => { - const result = await filterAuthorizedTools({ - tools: ['web_search', 'custom_tool'], - userId, - availableTools, - }); - - expect(result).toEqual(['web_search', 'custom_tool']); - expect(mockGetAllServerConfigs).not.toHaveBeenCalled(); - }); - - test('should filter all MCP tools when registry is uninitialized', async () => { - getMCPServersRegistry.mockImplementation(() => { - throw new Error('MCPServersRegistry has not been initialized.'); - }); - - const result = await filterAuthorizedTools({ - tools: [`toolA${d}someServer`, 'web_search'], - userId, - availableTools, - }); - - expect(result).toEqual(['web_search']); - expect(result).not.toContain(`toolA${d}someServer`); - }); - - test('should handle mixed authorized and unauthorized MCP tools', async () => { - const result = await filterAuthorizedTools({ - tools: [ - 'web_search', - `search${d}authorizedServer`, - `attack${d}victimServer`, - 'execute_code', - `list${d}anotherServer`, - `steal${d}nonexistent`, - ], - userId, - availableTools, - }); - - expect(result).toEqual([ - 'web_search', - `search${d}authorizedServer`, - 'execute_code', - `list${d}anotherServer`, - ]); - }); - - test('should handle empty tools array', async () => { - const result = await filterAuthorizedTools({ - tools: [], - userId, - availableTools, - }); - - expect(result).toEqual([]); - expect(mockGetAllServerConfigs).not.toHaveBeenCalled(); - }); - - test('should handle null/undefined tool entries gracefully', async () => { - const result = await filterAuthorizedTools({ - tools: [null, undefined, '', 'web_search'], - userId, - availableTools, - }); - - expect(result).toEqual(['web_search']); - }); - - test('should call getAllServerConfigs with the correct userId', async () => { - await filterAuthorizedTools({ - tools: [`tool${d}authorizedServer`], - userId: 'specific-user-id', - availableTools, - }); - - expect(mockGetAllServerConfigs).toHaveBeenCalledWith('specific-user-id', undefined); - }); - - test('should pass configServers to getAllServerConfigs and allow config-override servers', async () => { - const configServers = { - 'config-override-server': { type: 'sse', url: 'https://override.example.com' }, - }; - mockGetAllServerConfigs.mockResolvedValue({ - 'config-override-server': configServers['config-override-server'], - }); - - const result = await filterAuthorizedTools({ - tools: [`tool${d}config-override-server`, `tool${d}unauthorizedServer`], - userId, - availableTools, - configServers, - }); - - expect(mockGetAllServerConfigs).toHaveBeenCalledWith(userId, configServers); - expect(result).toContain(`tool${d}config-override-server`); - expect(result).not.toContain(`tool${d}unauthorizedServer`); - }); - - test('should only call getAllServerConfigs once even with multiple MCP tools', async () => { - await filterAuthorizedTools({ - tools: [`tool1${d}authorizedServer`, `tool2${d}anotherServer`, `tool3${d}unknownServer`], - userId, - availableTools, - }); - - expect(mockGetAllServerConfigs).toHaveBeenCalledTimes(1); - }); - - test('should preserve existing MCP tools when registry is unavailable', async () => { - getMCPServersRegistry.mockImplementation(() => { - throw new Error('MCPServersRegistry has not been initialized.'); - }); - - const existingTools = [`toolA${d}serverA`, `toolB${d}serverB`]; - - const result = await filterAuthorizedTools({ - tools: [...existingTools, `newTool${d}unknownServer`, 'web_search'], - userId, - availableTools, - existingTools, - }); - - expect(result).toContain(`toolA${d}serverA`); - expect(result).toContain(`toolB${d}serverB`); - expect(result).toContain('web_search'); - expect(result).not.toContain(`newTool${d}unknownServer`); - }); - - test('should still reject all MCP tools when registry is unavailable and no existingTools', async () => { - getMCPServersRegistry.mockImplementation(() => { - throw new Error('MCPServersRegistry has not been initialized.'); - }); - - const result = await filterAuthorizedTools({ - tools: [`toolA${d}serverA`, 'web_search'], - userId, - availableTools, - }); - - expect(result).toEqual(['web_search']); - }); - - test('should not preserve malformed existing tools when registry is unavailable', async () => { - getMCPServersRegistry.mockImplementation(() => { - throw new Error('MCPServersRegistry has not been initialized.'); - }); - - const malformedTool = `a${d}b${d}c`; - const result = await filterAuthorizedTools({ - tools: [malformedTool, `legit${d}serverA`, 'web_search'], - userId, - availableTools, - existingTools: [malformedTool, `legit${d}serverA`], - }); - - expect(result).toContain(`legit${d}serverA`); - expect(result).toContain('web_search'); - expect(result).not.toContain(malformedTool); - }); - - test('should reject malformed MCP tool keys with multiple delimiters', async () => { - const result = await filterAuthorizedTools({ - tools: [ - `attack${d}victimServer${d}authorizedServer`, - `legit${d}authorizedServer`, - `a${d}b${d}c${d}d`, - 'web_search', - ], - userId, - availableTools, - }); - - expect(result).toEqual([`legit${d}authorizedServer`, 'web_search']); - expect(result).not.toContainEqual(expect.stringContaining('victimServer')); - expect(result).not.toContainEqual(expect.stringContaining(`a${d}b`)); - }); - }); - - describe('createAgentHandler - MCP tool authorization', () => { - test('should strip unauthorized MCP tools on create', async () => { - mockReq.body = { - provider: 'openai', - model: 'gpt-4', - name: 'MCP Test Agent', - tools: ['web_search', `validTool${d}authorizedServer`, `attack${d}forbiddenServer`], - }; - - await createAgentHandler(mockReq, mockRes); - - expect(mockRes.status).toHaveBeenCalledWith(201); - const agent = mockRes.json.mock.calls[0][0]; - expect(agent.tools).toContain('web_search'); - expect(agent.tools).toContain(`validTool${d}authorizedServer`); - expect(agent.tools).not.toContain(`attack${d}forbiddenServer`); - }); - - test('should not 500 when MCP registry is uninitialized', async () => { - getMCPServersRegistry.mockImplementation(() => { - throw new Error('MCPServersRegistry has not been initialized.'); - }); - - mockReq.body = { - provider: 'openai', - model: 'gpt-4', - name: 'MCP Uninitialized Test', - tools: [`tool${d}someServer`, 'web_search'], - }; - - await createAgentHandler(mockReq, mockRes); - - expect(mockRes.status).toHaveBeenCalledWith(201); - const agent = mockRes.json.mock.calls[0][0]; - expect(agent.tools).toEqual(['web_search']); - }); - - test('should store mcpServerNames only for authorized servers', async () => { - mockReq.body = { - provider: 'openai', - model: 'gpt-4', - name: 'MCP Names Test', - tools: [`toolA${d}authorizedServer`, `toolB${d}forbiddenServer`], - }; - - await createAgentHandler(mockReq, mockRes); - - expect(mockRes.status).toHaveBeenCalledWith(201); - const agent = mockRes.json.mock.calls[0][0]; - const agentInDb = await Agent.findOne({ id: agent.id }); - expect(agentInDb.mcpServerNames).toContain('authorizedServer'); - expect(agentInDb.mcpServerNames).not.toContain('forbiddenServer'); - }); - }); - - describe('updateAgentHandler - MCP tool authorization', () => { - let existingAgentId; - let existingAgentAuthorId; - - beforeEach(async () => { - existingAgentAuthorId = new mongoose.Types.ObjectId(); - const agent = await Agent.create({ - id: `agent_${uuidv4()}`, - name: 'Original Agent', - provider: 'openai', - model: 'gpt-4', - author: existingAgentAuthorId, - tools: ['web_search', `existingTool${d}authorizedServer`], - mcpServerNames: ['authorizedServer'], - versions: [ - { - name: 'Original Agent', - provider: 'openai', - model: 'gpt-4', - tools: ['web_search', `existingTool${d}authorizedServer`], - createdAt: new Date(), - updatedAt: new Date(), - }, - ], - }); - existingAgentId = agent.id; - }); - - test('should preserve existing MCP tools even if editor lacks access', async () => { - mockGetAllServerConfigs.mockResolvedValue({}); - - mockReq.user.id = existingAgentAuthorId.toString(); - mockReq.params.id = existingAgentId; - mockReq.body = { - tools: ['web_search', `existingTool${d}authorizedServer`], - }; - - await updateAgentHandler(mockReq, mockRes); - - expect(mockRes.json).toHaveBeenCalled(); - const updatedAgent = mockRes.json.mock.calls[0][0]; - expect(updatedAgent.tools).toContain(`existingTool${d}authorizedServer`); - expect(updatedAgent.tools).toContain('web_search'); - }); - - test('should reject newly added unauthorized MCP tools', async () => { - mockReq.user.id = existingAgentAuthorId.toString(); - mockReq.params.id = existingAgentId; - mockReq.body = { - tools: ['web_search', `existingTool${d}authorizedServer`, `attack${d}forbiddenServer`], - }; - - await updateAgentHandler(mockReq, mockRes); - - expect(mockRes.json).toHaveBeenCalled(); - const updatedAgent = mockRes.json.mock.calls[0][0]; - expect(updatedAgent.tools).toContain('web_search'); - expect(updatedAgent.tools).toContain(`existingTool${d}authorizedServer`); - expect(updatedAgent.tools).not.toContain(`attack${d}forbiddenServer`); - }); - - test('should allow adding authorized MCP tools', async () => { - mockReq.user.id = existingAgentAuthorId.toString(); - mockReq.params.id = existingAgentId; - mockReq.body = { - tools: ['web_search', `existingTool${d}authorizedServer`, `newTool${d}anotherServer`], - }; - - await updateAgentHandler(mockReq, mockRes); - - expect(mockRes.json).toHaveBeenCalled(); - const updatedAgent = mockRes.json.mock.calls[0][0]; - expect(updatedAgent.tools).toContain(`newTool${d}anotherServer`); - }); - - test('should not query MCP registry when no new MCP tools added', async () => { - mockReq.user.id = existingAgentAuthorId.toString(); - mockReq.params.id = existingAgentId; - mockReq.body = { - tools: ['web_search', `existingTool${d}authorizedServer`], - }; - - await updateAgentHandler(mockReq, mockRes); - - expect(mockGetAllServerConfigs).not.toHaveBeenCalled(); - }); - - test('should preserve existing MCP tools when registry unavailable and user edits agent', async () => { - getMCPServersRegistry.mockImplementation(() => { - throw new Error('MCPServersRegistry has not been initialized.'); - }); - - mockReq.user.id = existingAgentAuthorId.toString(); - mockReq.params.id = existingAgentId; - mockReq.body = { - name: 'Renamed After Restart', - tools: ['web_search', `existingTool${d}authorizedServer`], - }; - - await updateAgentHandler(mockReq, mockRes); - - expect(mockRes.json).toHaveBeenCalled(); - const updatedAgent = mockRes.json.mock.calls[0][0]; - expect(updatedAgent.tools).toContain(`existingTool${d}authorizedServer`); - expect(updatedAgent.tools).toContain('web_search'); - expect(updatedAgent.name).toBe('Renamed After Restart'); - }); - - test('should preserve existing MCP tools when server not in configs (disconnected)', async () => { - mockGetAllServerConfigs.mockResolvedValue({}); - - mockReq.user.id = existingAgentAuthorId.toString(); - mockReq.params.id = existingAgentId; - mockReq.body = { - name: 'Edited While Disconnected', - tools: ['web_search', `existingTool${d}authorizedServer`], - }; - - await updateAgentHandler(mockReq, mockRes); - - expect(mockRes.json).toHaveBeenCalled(); - const updatedAgent = mockRes.json.mock.calls[0][0]; - expect(updatedAgent.tools).toContain(`existingTool${d}authorizedServer`); - expect(updatedAgent.name).toBe('Edited While Disconnected'); - }); - }); - - describe('duplicateAgentHandler - MCP tool authorization', () => { - let sourceAgentId; - let sourceAgentAuthorId; - - beforeEach(async () => { - sourceAgentAuthorId = new mongoose.Types.ObjectId(); - const agent = await Agent.create({ - id: `agent_${uuidv4()}`, - name: 'Source Agent', - provider: 'openai', - model: 'gpt-4', - author: sourceAgentAuthorId, - tools: ['web_search', `tool${d}authorizedServer`, `tool${d}forbiddenServer`], - mcpServerNames: ['authorizedServer', 'forbiddenServer'], - versions: [ - { - name: 'Source Agent', - provider: 'openai', - model: 'gpt-4', - tools: ['web_search', `tool${d}authorizedServer`, `tool${d}forbiddenServer`], - createdAt: new Date(), - updatedAt: new Date(), - }, - ], - }); - sourceAgentId = agent.id; - }); - - test('should strip unauthorized MCP tools from duplicated agent', async () => { - mockGetAllServerConfigs.mockResolvedValue({ - authorizedServer: { type: 'sse' }, - }); - - mockReq.user.id = sourceAgentAuthorId.toString(); - mockReq.params.id = sourceAgentId; - - await duplicateAgentHandler(mockReq, mockRes); - - expect(mockRes.status).toHaveBeenCalledWith(201); - const { agent: newAgent } = mockRes.json.mock.calls[0][0]; - expect(newAgent.id).not.toBe(sourceAgentId); - expect(newAgent.tools).toContain('web_search'); - expect(newAgent.tools).toContain(`tool${d}authorizedServer`); - expect(newAgent.tools).not.toContain(`tool${d}forbiddenServer`); - - const agentInDb = await Agent.findOne({ id: newAgent.id }); - expect(agentInDb.mcpServerNames).toContain('authorizedServer'); - expect(agentInDb.mcpServerNames).not.toContain('forbiddenServer'); - }); - - test('should preserve source agent MCP tools when registry is unavailable', async () => { - getMCPServersRegistry.mockImplementation(() => { - throw new Error('MCPServersRegistry has not been initialized.'); - }); - - mockReq.user.id = sourceAgentAuthorId.toString(); - mockReq.params.id = sourceAgentId; - - await duplicateAgentHandler(mockReq, mockRes); - - expect(mockRes.status).toHaveBeenCalledWith(201); - const { agent: newAgent } = mockRes.json.mock.calls[0][0]; - expect(newAgent.tools).toContain('web_search'); - expect(newAgent.tools).toContain(`tool${d}authorizedServer`); - expect(newAgent.tools).toContain(`tool${d}forbiddenServer`); - }); - }); - - describe('revertAgentVersionHandler - MCP tool authorization', () => { - let existingAgentId; - let existingAgentAuthorId; - - beforeEach(async () => { - existingAgentAuthorId = new mongoose.Types.ObjectId(); - const agent = await Agent.create({ - id: `agent_${uuidv4()}`, - name: 'Reverted Agent V2', - provider: 'openai', - model: 'gpt-4', - author: existingAgentAuthorId, - tools: ['web_search'], - versions: [ - { - name: 'Reverted Agent V1', - provider: 'openai', - model: 'gpt-4', - tools: ['web_search', `oldTool${d}revokedServer`], - createdAt: new Date(Date.now() - 10000), - updatedAt: new Date(Date.now() - 10000), - }, - { - name: 'Reverted Agent V2', - provider: 'openai', - model: 'gpt-4', - tools: ['web_search'], - createdAt: new Date(), - updatedAt: new Date(), - }, - ], - }); - existingAgentId = agent.id; - }); - - test('should strip unauthorized MCP tools after reverting to a previous version', async () => { - mockGetAllServerConfigs.mockResolvedValue({ - authorizedServer: { type: 'sse' }, - }); - - mockReq.user.id = existingAgentAuthorId.toString(); - mockReq.params.id = existingAgentId; - mockReq.body = { version_index: 0 }; - - await revertAgentVersionHandler(mockReq, mockRes); - - expect(mockRes.json).toHaveBeenCalled(); - const result = mockRes.json.mock.calls[0][0]; - expect(result.tools).toContain('web_search'); - expect(result.tools).not.toContain(`oldTool${d}revokedServer`); - - const agentInDb = await Agent.findOne({ id: existingAgentId }); - expect(agentInDb.tools).toContain('web_search'); - expect(agentInDb.tools).not.toContain(`oldTool${d}revokedServer`); - }); - - test('should keep authorized MCP tools after revert', async () => { - await Agent.updateOne( - { id: existingAgentId }, - { $set: { 'versions.0.tools': ['web_search', `tool${d}authorizedServer`] } }, - ); - - mockReq.user.id = existingAgentAuthorId.toString(); - mockReq.params.id = existingAgentId; - mockReq.body = { version_index: 0 }; - - await revertAgentVersionHandler(mockReq, mockRes); - - expect(mockRes.json).toHaveBeenCalled(); - const result = mockRes.json.mock.calls[0][0]; - expect(result.tools).toContain('web_search'); - expect(result.tools).toContain(`tool${d}authorizedServer`); - }); - - test('should preserve version MCP tools when registry is unavailable on revert', async () => { - await Agent.updateOne( - { id: existingAgentId }, - { - $set: { - 'versions.0.tools': [ - 'web_search', - `validTool${d}authorizedServer`, - `otherTool${d}anotherServer`, - ], - }, - }, - ); - - getMCPServersRegistry.mockImplementation(() => { - throw new Error('MCPServersRegistry has not been initialized.'); - }); - - mockReq.user.id = existingAgentAuthorId.toString(); - mockReq.params.id = existingAgentId; - mockReq.body = { version_index: 0 }; - - await revertAgentVersionHandler(mockReq, mockRes); - - expect(mockRes.json).toHaveBeenCalled(); - const result = mockRes.json.mock.calls[0][0]; - expect(result.tools).toContain('web_search'); - expect(result.tools).toContain(`validTool${d}authorizedServer`); - expect(result.tools).toContain(`otherTool${d}anotherServer`); - - const agentInDb = await Agent.findOne({ id: existingAgentId }); - expect(agentInDb.tools).toContain(`validTool${d}authorizedServer`); - expect(agentInDb.tools).toContain(`otherTool${d}anotherServer`); - }); - }); -}); diff --git a/api/server/controllers/agents/openai.js b/api/server/controllers/agents/openai.js index 9fa3af82c3..b334580eb1 100644 --- a/api/server/controllers/agents/openai.js +++ b/api/server/controllers/agents/openai.js @@ -15,21 +15,18 @@ const { createErrorResponse, recordCollectedUsage, getTransactionsConfig, - resolveRecursionLimit, createToolExecuteHandler, buildNonStreamingResponse, createOpenAIStreamTracker, createOpenAIContentAggregator, isChatCompletionValidationFailure, } = require('@librechat/api'); -const { - buildSummarizationHandlers, - markSummarizationUsage, - createToolEndCallback, - agentLogHandlerObj, -} = require('~/server/controllers/agents/callbacks'); const { loadAgentTools, loadToolsForExecution } = require('~/server/services/ToolService'); +const { createToolEndCallback } = require('~/server/controllers/agents/callbacks'); const { findAccessibleResources } = require('~/server/services/PermissionService'); +const { spendTokens, spendStructuredTokens } = require('~/models/spendTokens'); +const { getConvoFiles } = require('~/models/Conversation'); +const { getAgent, getAgents } = require('~/models/Agent'); const db = require('~/models'); /** @@ -132,6 +129,7 @@ const OpenAIChatCompletionController = async (req, res) => { const appConfig = req.config; const requestStartTime = Date.now(); + // Validate request const validation = validateRequest(req.body); if (isChatCompletionValidationFailure(validation)) { return sendErrorResponse(res, 400, validation.error); @@ -141,7 +139,7 @@ const OpenAIChatCompletionController = async (req, res) => { const agentId = request.model; // Look up the agent - const agent = await db.getAgent({ id: agentId }); + const agent = await getAgent({ id: agentId }); if (!agent) { return sendErrorResponse( res, @@ -152,18 +150,20 @@ const OpenAIChatCompletionController = async (req, res) => { ); } - const responseId = `chatcmpl-${nanoid()}`; + // Generate IDs + const requestId = `chatcmpl-${nanoid()}`; + const conversationId = request.conversation_id ?? nanoid(); + const parentMessageId = request.parent_message_id ?? null; const created = Math.floor(Date.now() / 1000); - /** @type {import('@librechat/api').OpenAIResponseContext} — key must be `requestId` to match the type used by createChunk/buildNonStreamingResponse */ const context = { created, - requestId: responseId, + requestId, model: agentId, }; logger.debug( - `[OpenAI API] Response ${responseId} started for agent ${agentId}, stream: ${request.stream}`, + `[OpenAI API] Request ${requestId} started for agent ${agentId}, stream: ${request.stream}`, ); // Set up abort controller @@ -178,25 +178,10 @@ const OpenAIChatCompletionController = async (req, res) => { }); try { - if (request.conversation_id != null) { - if (typeof request.conversation_id !== 'string') { - return sendErrorResponse( - res, - 400, - 'conversation_id must be a string', - 'invalid_request_error', - ); - } - if (!(await db.getConvo(req.user?.id, request.conversation_id))) { - return sendErrorResponse(res, 404, 'Conversation not found', 'invalid_request_error'); - } - } - - const conversationId = request.conversation_id ?? nanoid(); - const parentMessageId = request.parent_message_id ?? null; - - const agentsEConfig = appConfig?.endpoints?.[EModelEndpoint.agents]; - const allowedProviders = new Set(agentsEConfig?.allowedProviders); + // Build allowed providers set + const allowedProviders = new Set( + appConfig?.endpoints?.[EModelEndpoint.agents]?.allowedProviders, + ); // Create tool loader const loadTools = createToolLoader(abortController.signal); @@ -221,7 +206,7 @@ const OpenAIChatCompletionController = async (req, res) => { isInitialAgent: true, }, { - getConvoFiles: db.getConvoFiles, + getConvoFiles, getFiles: db.getFiles, getUserKey: db.getUserKey, getMessages: db.getMessages, @@ -280,22 +265,19 @@ const OpenAIChatCompletionController = async (req, res) => { toolRegistry: primaryConfig.toolRegistry, userMCPAuthMap: primaryConfig.userMCPAuthMap, tool_resources: primaryConfig.tool_resources, - actionsEnabled: primaryConfig.actionsEnabled, }); }, toolEndCallback, }; - const summarizationConfig = appConfig?.summarization; - const openaiMessages = convertMessages(request.messages); const toolSet = buildToolSet(primaryConfig); - const { - messages: formattedMessages, - indexTokenCountMap, - summary: initialSummary, - } = formatAgentMessages(openaiMessages, {}, toolSet); + const { messages: formattedMessages, indexTokenCountMap } = formatAgentMessages( + openaiMessages, + {}, + toolSet, + ); /** * Create a simple handler that processes data @@ -438,30 +420,24 @@ const OpenAIChatCompletionController = async (req, res) => { }), // Usage tracking - on_chat_model_end: { - handle: (_event, data, metadata) => { - const usage = data?.output?.usage_metadata; - if (usage) { - const taggedUsage = markSummarizationUsage(usage, metadata); - collectedUsage.push(taggedUsage); - const target = isStreaming ? tracker : aggregator; - target.usage.promptTokens += taggedUsage.input_tokens ?? 0; - target.usage.completionTokens += taggedUsage.output_tokens ?? 0; - } - }, - }, + on_chat_model_end: createHandler((data) => { + const usage = data?.output?.usage_metadata; + if (usage) { + collectedUsage.push(usage); + const target = isStreaming ? tracker : aggregator; + target.usage.promptTokens += usage.input_tokens ?? 0; + target.usage.completionTokens += usage.output_tokens ?? 0; + } + }), on_run_step_completed: createHandler(), // Use proper ToolEndHandler for processing artifacts (images, file citations, code output) on_tool_end: new ToolEndHandler(toolEndCallback, logger), on_chain_stream: createHandler(), on_chain_end: createHandler(), on_agent_update: createHandler(), - on_agent_log: agentLogHandlerObj, on_custom_event: createHandler(), + // Event-driven tool execution handler on_tool_execute: createToolExecuteHandler(toolExecuteOptions), - ...(summarizationConfig?.enabled !== false - ? buildSummarizationHandlers({ isStreaming, res }) - : {}), }; // Create and run the agent @@ -474,13 +450,11 @@ const OpenAIChatCompletionController = async (req, res) => { agents: [primaryConfig], messages: formattedMessages, indexTokenCountMap, - initialSummary, - runId: responseId, - summarizationConfig, + runId: requestId, signal: abortController.signal, customHandlers: handlers, requestBody: { - messageId: responseId, + messageId: requestId, conversationId, }, user: { id: userId }, @@ -490,19 +464,15 @@ const OpenAIChatCompletionController = async (req, res) => { throw new Error('Failed to create agent run'); } + // Process the stream const config = { runName: 'AgentRun', configurable: { thread_id: conversationId, user_id: userId, user: createSafeUser(req.user), - requestBody: { - messageId: responseId, - conversationId, - }, ...(userMCPAuthMap != null && { userMCPAuthMap }), }, - recursionLimit: resolveRecursionLimit(agentsEConfig, agent), signal: abortController.signal, streamMode: 'values', version: 'v2', @@ -520,18 +490,12 @@ const OpenAIChatCompletionController = async (req, res) => { const balanceConfig = getBalanceConfig(appConfig); const transactionsConfig = getTransactionsConfig(appConfig); recordCollectedUsage( - { - spendTokens: db.spendTokens, - spendStructuredTokens: db.spendStructuredTokens, - pricing: { getMultiplier: db.getMultiplier, getCacheMultiplier: db.getCacheMultiplier }, - bulkWriteOps: { insertMany: db.bulkInsertTransactions, updateBalance: db.updateBalance }, - }, + { spendTokens, spendStructuredTokens }, { user: userId, conversationId, collectedUsage, context: 'message', - messageId: responseId, balance: balanceConfig, transactions: transactionsConfig, model: primaryConfig.model || agent.model_parameters?.model, @@ -545,7 +509,7 @@ const OpenAIChatCompletionController = async (req, res) => { if (isStreaming) { sendFinalChunk(handlerConfig); res.end(); - logger.debug(`[OpenAI API] Response ${responseId} completed in ${duration}ms (streaming)`); + logger.debug(`[OpenAI API] Request ${requestId} completed in ${duration}ms (streaming)`); // Wait for artifact processing after response ends (non-blocking) if (artifactPromises.length > 0) { @@ -584,9 +548,7 @@ const OpenAIChatCompletionController = async (req, res) => { usage, ); res.json(response); - logger.debug( - `[OpenAI API] Response ${responseId} completed in ${duration}ms (non-streaming)`, - ); + logger.debug(`[OpenAI API] Request ${requestId} completed in ${duration}ms (non-streaming)`); } } catch (error) { const errorMessage = error instanceof Error ? error.message : 'An error occurred'; @@ -637,7 +599,7 @@ const ListModelsController = async (req, res) => { // Get the accessible agents let agents = []; if (accessibleAgentIds.length > 0) { - agents = await db.getAgents({ _id: { $in: accessibleAgentIds } }); + agents = await getAgents({ _id: { $in: accessibleAgentIds } }); } const models = agents.map((agent) => ({ @@ -680,7 +642,7 @@ const GetModelController = async (req, res) => { return sendErrorResponse(res, 401, 'Authentication required', 'auth_error'); } - const agent = await db.getAgent({ id: model }); + const agent = await getAgent({ id: model }); if (!agent) { return sendErrorResponse( diff --git a/api/server/controllers/agents/recordCollectedUsage.spec.js b/api/server/controllers/agents/recordCollectedUsage.spec.js index 009c5b262c..6904f2ed39 100644 --- a/api/server/controllers/agents/recordCollectedUsage.spec.js +++ b/api/server/controllers/agents/recordCollectedUsage.spec.js @@ -2,29 +2,21 @@ * Tests for AgentClient.recordCollectedUsage * * This is a critical function that handles token spending for agent LLM calls. - * The client now delegates to the TS recordCollectedUsage from @librechat/api, - * passing pricing and bulkWriteOps deps. + * It must correctly handle: + * - Sequential execution (single agent with tool calls) + * - Parallel execution (multiple agents with independent inputs) + * - Cache token handling (OpenAI and Anthropic formats) */ const { EModelEndpoint } = require('librechat-data-provider'); +// Mock dependencies before requiring the module const mockSpendTokens = jest.fn().mockResolvedValue(); const mockSpendStructuredTokens = jest.fn().mockResolvedValue(); -const mockGetMultiplier = jest.fn().mockReturnValue(1); -const mockGetCacheMultiplier = jest.fn().mockReturnValue(null); -const mockUpdateBalance = jest.fn().mockResolvedValue({}); -const mockBulkInsertTransactions = jest.fn().mockResolvedValue(undefined); -const mockRecordCollectedUsage = jest - .fn() - .mockResolvedValue({ input_tokens: 100, output_tokens: 50 }); -jest.mock('~/models', () => ({ +jest.mock('~/models/spendTokens', () => ({ spendTokens: (...args) => mockSpendTokens(...args), spendStructuredTokens: (...args) => mockSpendStructuredTokens(...args), - getMultiplier: mockGetMultiplier, - getCacheMultiplier: mockGetCacheMultiplier, - updateBalance: mockUpdateBalance, - bulkInsertTransactions: mockBulkInsertTransactions, })); jest.mock('~/config', () => ({ @@ -47,14 +39,6 @@ jest.mock('@librechat/agents', () => ({ }), })); -jest.mock('@librechat/api', () => { - const actual = jest.requireActual('@librechat/api'); - return { - ...actual, - recordCollectedUsage: (...args) => mockRecordCollectedUsage(...args), - }; -}); - const AgentClient = require('./client'); describe('AgentClient - recordCollectedUsage', () => { @@ -90,7 +74,30 @@ describe('AgentClient - recordCollectedUsage', () => { }); describe('basic functionality', () => { - it('should delegate to recordCollectedUsage with full deps', async () => { + it('should return early if collectedUsage is empty', async () => { + await client.recordCollectedUsage({ + collectedUsage: [], + balance: { enabled: true }, + transactions: { enabled: true }, + }); + + expect(mockSpendTokens).not.toHaveBeenCalled(); + expect(mockSpendStructuredTokens).not.toHaveBeenCalled(); + expect(client.usage).toBeUndefined(); + }); + + it('should return early if collectedUsage is null', async () => { + await client.recordCollectedUsage({ + collectedUsage: null, + balance: { enabled: true }, + transactions: { enabled: true }, + }); + + expect(mockSpendTokens).not.toHaveBeenCalled(); + expect(client.usage).toBeUndefined(); + }); + + it('should handle single usage entry correctly', async () => { const collectedUsage = [{ input_tokens: 100, output_tokens: 50, model: 'gpt-4' }]; await client.recordCollectedUsage({ @@ -99,57 +106,25 @@ describe('AgentClient - recordCollectedUsage', () => { transactions: { enabled: true }, }); - expect(mockRecordCollectedUsage).toHaveBeenCalledTimes(1); - const [deps, params] = mockRecordCollectedUsage.mock.calls[0]; - - expect(deps).toHaveProperty('spendTokens'); - expect(deps).toHaveProperty('spendStructuredTokens'); - expect(deps).toHaveProperty('pricing'); - expect(deps.pricing).toHaveProperty('getMultiplier'); - expect(deps.pricing).toHaveProperty('getCacheMultiplier'); - expect(deps).toHaveProperty('bulkWriteOps'); - expect(deps.bulkWriteOps).toHaveProperty('insertMany'); - expect(deps.bulkWriteOps).toHaveProperty('updateBalance'); - - expect(params).toEqual( + expect(mockSpendTokens).toHaveBeenCalledTimes(1); + expect(mockSpendTokens).toHaveBeenCalledWith( expect.objectContaining({ - user: 'user-123', conversationId: 'convo-123', - collectedUsage, - context: 'message', - balance: { enabled: true }, - transactions: { enabled: true }, + user: 'user-123', + model: 'gpt-4', }), + { promptTokens: 100, completionTokens: 50 }, ); + expect(client.usage.input_tokens).toBe(100); + expect(client.usage.output_tokens).toBe(50); }); - it('should not set this.usage if collectedUsage is empty (returns undefined)', async () => { - mockRecordCollectedUsage.mockResolvedValue(undefined); - - await client.recordCollectedUsage({ - collectedUsage: [], - balance: { enabled: true }, - transactions: { enabled: true }, - }); - - expect(client.usage).toBeUndefined(); - }); - - it('should not set this.usage if collectedUsage is null (returns undefined)', async () => { - mockRecordCollectedUsage.mockResolvedValue(undefined); - - await client.recordCollectedUsage({ - collectedUsage: null, - balance: { enabled: true }, - transactions: { enabled: true }, - }); - - expect(client.usage).toBeUndefined(); - }); - - it('should set this.usage from recordCollectedUsage result', async () => { - mockRecordCollectedUsage.mockResolvedValue({ input_tokens: 200, output_tokens: 75 }); - const collectedUsage = [{ input_tokens: 200, output_tokens: 75, model: 'gpt-4' }]; + it('should skip null entries in collectedUsage', async () => { + const collectedUsage = [ + { input_tokens: 100, output_tokens: 50, model: 'gpt-4' }, + null, + { input_tokens: 200, output_tokens: 60, model: 'gpt-4' }, + ]; await client.recordCollectedUsage({ collectedUsage, @@ -157,62 +132,82 @@ describe('AgentClient - recordCollectedUsage', () => { transactions: { enabled: true }, }); - expect(client.usage).toEqual({ input_tokens: 200, output_tokens: 75 }); + expect(mockSpendTokens).toHaveBeenCalledTimes(2); }); }); describe('sequential execution (single agent with tool calls)', () => { - it('should pass all usage entries to recordCollectedUsage', async () => { + it('should calculate tokens correctly for sequential tool calls', async () => { + // Sequential flow: output of call N becomes part of input for call N+1 + // Call 1: input=100, output=50 + // Call 2: input=150 (100+50), output=30 + // Call 3: input=180 (150+30), output=20 const collectedUsage = [ { input_tokens: 100, output_tokens: 50, model: 'gpt-4' }, { input_tokens: 150, output_tokens: 30, model: 'gpt-4' }, { input_tokens: 180, output_tokens: 20, model: 'gpt-4' }, ]; - mockRecordCollectedUsage.mockResolvedValue({ input_tokens: 100, output_tokens: 100 }); - await client.recordCollectedUsage({ collectedUsage, balance: { enabled: true }, transactions: { enabled: true }, }); - expect(mockRecordCollectedUsage).toHaveBeenCalledTimes(1); - const [, params] = mockRecordCollectedUsage.mock.calls[0]; - expect(params.collectedUsage).toHaveLength(3); + expect(mockSpendTokens).toHaveBeenCalledTimes(3); + // Total output should be sum of all output_tokens: 50 + 30 + 20 = 100 expect(client.usage.output_tokens).toBe(100); - expect(client.usage.input_tokens).toBe(100); + expect(client.usage.input_tokens).toBe(100); // First entry's input }); }); describe('parallel execution (multiple agents)', () => { - it('should pass parallel agent usage to recordCollectedUsage', async () => { + it('should handle parallel agents with independent input tokens', async () => { + // Parallel agents have INDEPENDENT input tokens (not cumulative) + // Agent A: input=100, output=50 + // Agent B: input=80, output=40 (different context, not 100+50) const collectedUsage = [ { input_tokens: 100, output_tokens: 50, model: 'gpt-4' }, { input_tokens: 80, output_tokens: 40, model: 'gpt-4' }, ]; - mockRecordCollectedUsage.mockResolvedValue({ input_tokens: 100, output_tokens: 90 }); - await client.recordCollectedUsage({ collectedUsage, balance: { enabled: true }, transactions: { enabled: true }, }); - expect(mockRecordCollectedUsage).toHaveBeenCalledTimes(1); - expect(client.usage.output_tokens).toBe(90); + expect(mockSpendTokens).toHaveBeenCalledTimes(2); + // Expected total output: 50 + 40 = 90 + // output_tokens must be positive and should reflect total output expect(client.usage.output_tokens).toBeGreaterThan(0); }); - /** Bug regression: parallel agents where second agent has LOWER input tokens produced negative output via incremental calculation. */ - it('should NOT produce negative output_tokens', async () => { + it('should NOT produce negative output_tokens for parallel execution', async () => { + // Critical bug scenario: parallel agents where second agent has LOWER input tokens const collectedUsage = [ { input_tokens: 200, output_tokens: 100, model: 'gpt-4' }, { input_tokens: 50, output_tokens: 30, model: 'gpt-4' }, ]; - mockRecordCollectedUsage.mockResolvedValue({ input_tokens: 200, output_tokens: 130 }); + await client.recordCollectedUsage({ + collectedUsage, + balance: { enabled: true }, + transactions: { enabled: true }, + }); + + // output_tokens MUST be positive for proper token tracking + expect(client.usage.output_tokens).toBeGreaterThan(0); + // Correct value should be 100 + 30 = 130 + }); + + it('should calculate correct total output for parallel agents', async () => { + // Three parallel agents with independent contexts + const collectedUsage = [ + { input_tokens: 100, output_tokens: 50, model: 'gpt-4' }, + { input_tokens: 120, output_tokens: 60, model: 'gpt-4-turbo' }, + { input_tokens: 80, output_tokens: 40, model: 'claude-3' }, + ]; await client.recordCollectedUsage({ collectedUsage, @@ -220,44 +215,111 @@ describe('AgentClient - recordCollectedUsage', () => { transactions: { enabled: true }, }); + expect(mockSpendTokens).toHaveBeenCalledTimes(3); + // Total output should be 50 + 60 + 40 = 150 + expect(client.usage.output_tokens).toBe(150); + }); + + it('should handle worst-case parallel scenario without negative tokens', async () => { + // Extreme case: first agent has very high input, subsequent have low + const collectedUsage = [ + { input_tokens: 1000, output_tokens: 500, model: 'gpt-4' }, + { input_tokens: 100, output_tokens: 50, model: 'gpt-4' }, + { input_tokens: 50, output_tokens: 25, model: 'gpt-4' }, + ]; + + await client.recordCollectedUsage({ + collectedUsage, + balance: { enabled: true }, + transactions: { enabled: true }, + }); + + // Must be positive, should be 500 + 50 + 25 = 575 expect(client.usage.output_tokens).toBeGreaterThan(0); - expect(client.usage.output_tokens).toBe(130); + expect(client.usage.output_tokens).toBe(575); }); }); describe('real-world scenarios', () => { - it('should correctly handle sequential tool calls with growing context', async () => { - const collectedUsage = [ - { input_tokens: 31596, output_tokens: 151, model: 'claude-opus-4-5-20251101' }, - { input_tokens: 35368, output_tokens: 150, model: 'claude-opus-4-5-20251101' }, - { input_tokens: 58362, output_tokens: 295, model: 'claude-opus-4-5-20251101' }, - { input_tokens: 112604, output_tokens: 193, model: 'claude-opus-4-5-20251101' }, - { input_tokens: 257440, output_tokens: 2217, model: 'claude-opus-4-5-20251101' }, - ]; - - mockRecordCollectedUsage.mockResolvedValue({ input_tokens: 31596, output_tokens: 3006 }); - - await client.recordCollectedUsage({ - collectedUsage, - balance: { enabled: true }, - transactions: { enabled: true }, - }); - - expect(client.usage.input_tokens).toBe(31596); - expect(client.usage.output_tokens).toBe(3006); - }); - - it('should correctly handle cache tokens', async () => { + it('should correctly sum output tokens for sequential tool calls with growing context', async () => { + // Real production data: Claude Opus with multiple tool calls + // Context grows as tool results are added, but output_tokens should only count model generations const collectedUsage = [ { - input_tokens: 788, - output_tokens: 163, - input_token_details: { cache_read: 0, cache_creation: 30808 }, + input_tokens: 31596, + output_tokens: 151, + total_tokens: 31747, + input_token_details: { cache_read: 0, cache_creation: 0 }, + model: 'claude-opus-4-5-20251101', + }, + { + input_tokens: 35368, + output_tokens: 150, + total_tokens: 35518, + input_token_details: { cache_read: 0, cache_creation: 0 }, + model: 'claude-opus-4-5-20251101', + }, + { + input_tokens: 58362, + output_tokens: 295, + total_tokens: 58657, + input_token_details: { cache_read: 0, cache_creation: 0 }, + model: 'claude-opus-4-5-20251101', + }, + { + input_tokens: 112604, + output_tokens: 193, + total_tokens: 112797, + input_token_details: { cache_read: 0, cache_creation: 0 }, + model: 'claude-opus-4-5-20251101', + }, + { + input_tokens: 257440, + output_tokens: 2217, + total_tokens: 259657, + input_token_details: { cache_read: 0, cache_creation: 0 }, model: 'claude-opus-4-5-20251101', }, ]; - mockRecordCollectedUsage.mockResolvedValue({ input_tokens: 31596, output_tokens: 163 }); + await client.recordCollectedUsage({ + collectedUsage, + balance: { enabled: true }, + transactions: { enabled: true }, + }); + + // input_tokens should be first entry's input (initial context) + expect(client.usage.input_tokens).toBe(31596); + + // output_tokens should be sum of all model outputs: 151 + 150 + 295 + 193 + 2217 = 3006 + // NOT the inflated value from incremental calculation (338,559) + expect(client.usage.output_tokens).toBe(3006); + + // Verify spendTokens was called for each entry with correct values + expect(mockSpendTokens).toHaveBeenCalledTimes(5); + expect(mockSpendTokens).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ model: 'claude-opus-4-5-20251101' }), + { promptTokens: 31596, completionTokens: 151 }, + ); + expect(mockSpendTokens).toHaveBeenNthCalledWith( + 5, + expect.objectContaining({ model: 'claude-opus-4-5-20251101' }), + { promptTokens: 257440, completionTokens: 2217 }, + ); + }); + + it('should handle single followup message correctly', async () => { + // Real production data: followup to the above conversation + const collectedUsage = [ + { + input_tokens: 263406, + output_tokens: 257, + total_tokens: 263663, + input_token_details: { cache_read: 0, cache_creation: 0 }, + model: 'claude-opus-4-5-20251101', + }, + ]; await client.recordCollectedUsage({ collectedUsage, @@ -265,14 +327,300 @@ describe('AgentClient - recordCollectedUsage', () => { transactions: { enabled: true }, }); + expect(client.usage.input_tokens).toBe(263406); + expect(client.usage.output_tokens).toBe(257); + + expect(mockSpendTokens).toHaveBeenCalledTimes(1); + expect(mockSpendTokens).toHaveBeenCalledWith( + expect.objectContaining({ model: 'claude-opus-4-5-20251101' }), + { promptTokens: 263406, completionTokens: 257 }, + ); + }); + + it('should ensure output_tokens > 0 check passes for BaseClient.sendMessage', async () => { + // This verifies the fix for the duplicate token spending bug + // BaseClient.sendMessage checks: if (usage != null && Number(usage[this.outputTokensKey]) > 0) + const collectedUsage = [ + { + input_tokens: 31596, + output_tokens: 151, + model: 'claude-opus-4-5-20251101', + }, + { + input_tokens: 35368, + output_tokens: 150, + model: 'claude-opus-4-5-20251101', + }, + ]; + + await client.recordCollectedUsage({ + collectedUsage, + balance: { enabled: true }, + transactions: { enabled: true }, + }); + + const usage = client.getStreamUsage(); + + // The check that was failing before the fix + expect(usage).not.toBeNull(); + expect(Number(usage.output_tokens)).toBeGreaterThan(0); + + // Verify correct value + expect(usage.output_tokens).toBe(301); // 151 + 150 + }); + + it('should correctly handle cache tokens with multiple tool calls', async () => { + // Real production data: Claude Opus with cache tokens (prompt caching) + // First entry has cache_creation, subsequent entries have cache_read + const collectedUsage = [ + { + input_tokens: 788, + output_tokens: 163, + total_tokens: 951, + input_token_details: { cache_read: 0, cache_creation: 30808 }, + model: 'claude-opus-4-5-20251101', + }, + { + input_tokens: 3802, + output_tokens: 149, + total_tokens: 3951, + input_token_details: { cache_read: 30808, cache_creation: 768 }, + model: 'claude-opus-4-5-20251101', + }, + { + input_tokens: 26808, + output_tokens: 225, + total_tokens: 27033, + input_token_details: { cache_read: 31576, cache_creation: 0 }, + model: 'claude-opus-4-5-20251101', + }, + { + input_tokens: 80912, + output_tokens: 204, + total_tokens: 81116, + input_token_details: { cache_read: 31576, cache_creation: 0 }, + model: 'claude-opus-4-5-20251101', + }, + { + input_tokens: 136454, + output_tokens: 206, + total_tokens: 136660, + input_token_details: { cache_read: 31576, cache_creation: 0 }, + model: 'claude-opus-4-5-20251101', + }, + { + input_tokens: 146316, + output_tokens: 224, + total_tokens: 146540, + input_token_details: { cache_read: 31576, cache_creation: 0 }, + model: 'claude-opus-4-5-20251101', + }, + { + input_tokens: 150402, + output_tokens: 1248, + total_tokens: 151650, + input_token_details: { cache_read: 31576, cache_creation: 0 }, + model: 'claude-opus-4-5-20251101', + }, + { + input_tokens: 156268, + output_tokens: 139, + total_tokens: 156407, + input_token_details: { cache_read: 31576, cache_creation: 0 }, + model: 'claude-opus-4-5-20251101', + }, + { + input_tokens: 167126, + output_tokens: 2961, + total_tokens: 170087, + input_token_details: { cache_read: 31576, cache_creation: 0 }, + model: 'claude-opus-4-5-20251101', + }, + ]; + + await client.recordCollectedUsage({ + collectedUsage, + balance: { enabled: true }, + transactions: { enabled: true }, + }); + + // input_tokens = first entry's input + cache_creation + cache_read + // = 788 + 30808 + 0 = 31596 expect(client.usage.input_tokens).toBe(31596); - expect(client.usage.output_tokens).toBe(163); + + // output_tokens = sum of all output_tokens + // = 163 + 149 + 225 + 204 + 206 + 224 + 1248 + 139 + 2961 = 5519 + expect(client.usage.output_tokens).toBe(5519); + + // First 2 entries have cache tokens, should use spendStructuredTokens + // Remaining 7 entries have cache_read but no cache_creation, still structured + expect(mockSpendStructuredTokens).toHaveBeenCalledTimes(9); + expect(mockSpendTokens).toHaveBeenCalledTimes(0); + + // Verify first entry uses structured tokens with cache_creation + expect(mockSpendStructuredTokens).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ model: 'claude-opus-4-5-20251101' }), + { + promptTokens: { input: 788, write: 30808, read: 0 }, + completionTokens: 163, + }, + ); + + // Verify second entry uses structured tokens with both cache_creation and cache_read + expect(mockSpendStructuredTokens).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ model: 'claude-opus-4-5-20251101' }), + { + promptTokens: { input: 3802, write: 768, read: 30808 }, + completionTokens: 149, + }, + ); + }); + }); + + describe('cache token handling', () => { + it('should handle OpenAI format cache tokens (input_token_details)', async () => { + const collectedUsage = [ + { + input_tokens: 100, + output_tokens: 50, + model: 'gpt-4', + input_token_details: { + cache_creation: 20, + cache_read: 10, + }, + }, + ]; + + await client.recordCollectedUsage({ + collectedUsage, + balance: { enabled: true }, + transactions: { enabled: true }, + }); + + expect(mockSpendStructuredTokens).toHaveBeenCalledTimes(1); + expect(mockSpendStructuredTokens).toHaveBeenCalledWith( + expect.objectContaining({ model: 'gpt-4' }), + { + promptTokens: { + input: 100, + write: 20, + read: 10, + }, + completionTokens: 50, + }, + ); + }); + + it('should handle Anthropic format cache tokens (cache_*_input_tokens)', async () => { + const collectedUsage = [ + { + input_tokens: 100, + output_tokens: 50, + model: 'claude-3', + cache_creation_input_tokens: 25, + cache_read_input_tokens: 15, + }, + ]; + + await client.recordCollectedUsage({ + collectedUsage, + balance: { enabled: true }, + transactions: { enabled: true }, + }); + + expect(mockSpendStructuredTokens).toHaveBeenCalledTimes(1); + expect(mockSpendStructuredTokens).toHaveBeenCalledWith( + expect.objectContaining({ model: 'claude-3' }), + { + promptTokens: { + input: 100, + write: 25, + read: 15, + }, + completionTokens: 50, + }, + ); + }); + + it('should use spendTokens for entries without cache tokens', async () => { + const collectedUsage = [{ input_tokens: 100, output_tokens: 50, model: 'gpt-4' }]; + + await client.recordCollectedUsage({ + collectedUsage, + balance: { enabled: true }, + transactions: { enabled: true }, + }); + + expect(mockSpendTokens).toHaveBeenCalledTimes(1); + expect(mockSpendStructuredTokens).not.toHaveBeenCalled(); + }); + + it('should handle mixed cache and non-cache entries', async () => { + const collectedUsage = [ + { input_tokens: 100, output_tokens: 50, model: 'gpt-4' }, + { + input_tokens: 150, + output_tokens: 30, + model: 'gpt-4', + input_token_details: { cache_creation: 10, cache_read: 5 }, + }, + { input_tokens: 200, output_tokens: 20, model: 'gpt-4' }, + ]; + + await client.recordCollectedUsage({ + collectedUsage, + balance: { enabled: true }, + transactions: { enabled: true }, + }); + + expect(mockSpendTokens).toHaveBeenCalledTimes(2); + expect(mockSpendStructuredTokens).toHaveBeenCalledTimes(1); + }); + + it('should include cache tokens in total input calculation', async () => { + const collectedUsage = [ + { + input_tokens: 100, + output_tokens: 50, + model: 'gpt-4', + input_token_details: { + cache_creation: 20, + cache_read: 10, + }, + }, + ]; + + await client.recordCollectedUsage({ + collectedUsage, + balance: { enabled: true }, + transactions: { enabled: true }, + }); + + // Total input should include cache tokens: 100 + 20 + 10 = 130 + expect(client.usage.input_tokens).toBe(130); }); }); describe('model fallback', () => { - it('should use param model when available', async () => { - mockRecordCollectedUsage.mockResolvedValue({ input_tokens: 100, output_tokens: 50 }); + it('should use usage.model when available', async () => { + const collectedUsage = [{ input_tokens: 100, output_tokens: 50, model: 'gpt-4-turbo' }]; + + await client.recordCollectedUsage({ + model: 'fallback-model', + collectedUsage, + balance: { enabled: true }, + transactions: { enabled: true }, + }); + + expect(mockSpendTokens).toHaveBeenCalledWith( + expect.objectContaining({ model: 'gpt-4-turbo' }), + expect.any(Object), + ); + }); + + it('should fallback to param model when usage.model is missing', async () => { const collectedUsage = [{ input_tokens: 100, output_tokens: 50 }]; await client.recordCollectedUsage({ @@ -282,13 +630,14 @@ describe('AgentClient - recordCollectedUsage', () => { transactions: { enabled: true }, }); - const [, params] = mockRecordCollectedUsage.mock.calls[0]; - expect(params.model).toBe('param-model'); + expect(mockSpendTokens).toHaveBeenCalledWith( + expect.objectContaining({ model: 'param-model' }), + expect.any(Object), + ); }); it('should fallback to client.model when param model is missing', async () => { client.model = 'client-model'; - mockRecordCollectedUsage.mockResolvedValue({ input_tokens: 100, output_tokens: 50 }); const collectedUsage = [{ input_tokens: 100, output_tokens: 50 }]; await client.recordCollectedUsage({ @@ -297,12 +646,13 @@ describe('AgentClient - recordCollectedUsage', () => { transactions: { enabled: true }, }); - const [, params] = mockRecordCollectedUsage.mock.calls[0]; - expect(params.model).toBe('client-model'); + expect(mockSpendTokens).toHaveBeenCalledWith( + expect.objectContaining({ model: 'client-model' }), + expect.any(Object), + ); }); it('should fallback to agent model_parameters.model as last resort', async () => { - mockRecordCollectedUsage.mockResolvedValue({ input_tokens: 100, output_tokens: 50 }); const collectedUsage = [{ input_tokens: 100, output_tokens: 50 }]; await client.recordCollectedUsage({ @@ -311,14 +661,15 @@ describe('AgentClient - recordCollectedUsage', () => { transactions: { enabled: true }, }); - const [, params] = mockRecordCollectedUsage.mock.calls[0]; - expect(params.model).toBe('gpt-4'); + expect(mockSpendTokens).toHaveBeenCalledWith( + expect.objectContaining({ model: 'gpt-4' }), + expect.any(Object), + ); }); }); describe('getStreamUsage integration', () => { it('should return the usage object set by recordCollectedUsage', async () => { - mockRecordCollectedUsage.mockResolvedValue({ input_tokens: 100, output_tokens: 50 }); const collectedUsage = [{ input_tokens: 100, output_tokens: 50, model: 'gpt-4' }]; await client.recordCollectedUsage({ @@ -328,7 +679,10 @@ describe('AgentClient - recordCollectedUsage', () => { }); const usage = client.getStreamUsage(); - expect(usage).toEqual({ input_tokens: 100, output_tokens: 50 }); + expect(usage).toEqual({ + input_tokens: 100, + output_tokens: 50, + }); }); it('should return undefined before recordCollectedUsage is called', () => { @@ -336,9 +690,9 @@ describe('AgentClient - recordCollectedUsage', () => { expect(usage).toBeUndefined(); }); - /** Verifies usage passes the check in BaseClient.sendMessage: if (usage != null && Number(usage[this.outputTokensKey]) > 0) */ it('should have output_tokens > 0 for BaseClient.sendMessage check', async () => { - mockRecordCollectedUsage.mockResolvedValue({ input_tokens: 200, output_tokens: 130 }); + // This test verifies the usage will pass the check in BaseClient.sendMessage: + // if (usage != null && Number(usage[this.outputTokensKey]) > 0) const collectedUsage = [ { input_tokens: 200, output_tokens: 100, model: 'gpt-4' }, { input_tokens: 50, output_tokens: 30, model: 'gpt-4' }, diff --git a/api/server/controllers/agents/request.js b/api/server/controllers/agents/request.js index 6f7e1b88c1..79387b6e89 100644 --- a/api/server/controllers/agents/request.js +++ b/api/server/controllers/agents/request.js @@ -3,9 +3,9 @@ const { Constants, ViolationTypes } = require('librechat-data-provider'); const { sendEvent, getViolationInfo, - buildMessageFiles, GenerationJobManager, decrementPendingRequest, + sanitizeFileForTransmit, sanitizeMessageForTransmit, checkAndIncrementPendingRequest, } = require('@librechat/api'); @@ -131,15 +131,9 @@ const ResumableAgentController = async (req, res, next, initializeClient, addTit partialMessage.agent_id = req.body.agent_id; } - await saveMessage( - { - userId: req?.user?.id, - isTemporary: req?.body?.isTemporary, - interfaceConfig: req?.config?.interfaceConfig, - }, - partialMessage, - { context: 'api/server/controllers/agents/request.js - partial response on disconnect' }, - ); + await saveMessage(req, partialMessage, { + context: 'api/server/controllers/agents/request.js - partial response on disconnect', + }); logger.debug( `[ResumableAgentController] Saved partial response for ${streamId}, content parts: ${aggregatedContent.length}`, @@ -258,10 +252,13 @@ const ResumableAgentController = async (req, res, next, initializeClient, addTit conversation.title = conversation && !conversation.title ? null : conversation?.title || 'New Chat'; - if (req.body.files && Array.isArray(client.options.attachments)) { - const files = buildMessageFiles(req.body.files, client.options.attachments); - if (files.length > 0) { - userMessage.files = files; + if (req.body.files && client.options?.attachments) { + userMessage.files = []; + const messageFiles = new Set(req.body.files.map((file) => file.file_id)); + for (const attachment of client.options.attachments) { + if (messageFiles.has(attachment.file_id)) { + userMessage.files.push(sanitizeFileForTransmit(attachment)); + } } delete userMessage.image_urls; } @@ -277,14 +274,8 @@ const ResumableAgentController = async (req, res, next, initializeClient, addTit // Save user message BEFORE sending final event to avoid race condition // where client refetch happens before database is updated - const reqCtx = { - userId: req?.user?.id, - isTemporary: req?.body?.isTemporary, - interfaceConfig: req?.config?.interfaceConfig, - }; - if (!client.skipSaveUserMessage && userMessage) { - await saveMessage(reqCtx, userMessage, { + await saveMessage(req, userMessage, { context: 'api/server/controllers/agents/request.js - resumable user message', }); } @@ -294,7 +285,7 @@ const ResumableAgentController = async (req, res, next, initializeClient, addTit // before the response is saved to the database, causing orphaned parentMessageIds. if (client.savedMessageIds && !client.savedMessageIds.has(messageId)) { await saveMessage( - reqCtx, + req, { ...response, user: userId, unfinished: wasAbortedBeforeComplete }, { context: 'api/server/controllers/agents/request.js - resumable response end' }, ); @@ -648,10 +639,14 @@ const _LegacyAgentController = async (req, res, next, initializeClient, addTitle conversation.title = conversation && !conversation.title ? null : conversation?.title || 'New Chat'; - if (req.body.files && Array.isArray(client.options.attachments)) { - const files = buildMessageFiles(req.body.files, client.options.attachments); - if (files.length > 0) { - userMessage.files = files; + // Process files if needed (sanitize to remove large text fields before transmission) + if (req.body.files && client.options?.attachments) { + userMessage.files = []; + const messageFiles = new Set(req.body.files.map((file) => file.file_id)); + for (const attachment of client.options.attachments) { + if (messageFiles.has(attachment.file_id)) { + userMessage.files.push(sanitizeFileForTransmit(attachment)); + } } delete userMessage.image_urls; } @@ -673,11 +668,7 @@ const _LegacyAgentController = async (req, res, next, initializeClient, addTitle // Save the message if needed if (client.savedMessageIds && !client.savedMessageIds.has(messageId)) { await saveMessage( - { - userId: req?.user?.id, - isTemporary: req?.body?.isTemporary, - interfaceConfig: req?.config?.interfaceConfig, - }, + req, { ...finalResponse, user: userId }, { context: 'api/server/controllers/agents/request.js - response end' }, ); @@ -706,15 +697,9 @@ const _LegacyAgentController = async (req, res, next, initializeClient, addTitle // Save user message if needed if (!client.skipSaveUserMessage) { - await saveMessage( - { - userId: req?.user?.id, - isTemporary: req?.body?.isTemporary, - interfaceConfig: req?.config?.interfaceConfig, - }, - userMessage, - { context: "api/server/controllers/agents/request.js - don't skip saving user message" }, - ); + await saveMessage(req, userMessage, { + context: "api/server/controllers/agents/request.js - don't skip saving user message", + }); } // Add title if needed - extract minimal data diff --git a/api/server/controllers/agents/responses.js b/api/server/controllers/agents/responses.js index 7abddf5e2f..afdb96be9f 100644 --- a/api/server/controllers/agents/responses.js +++ b/api/server/controllers/agents/responses.js @@ -32,13 +32,13 @@ const { } = require('@librechat/api'); const { createResponsesToolEndCallback, - buildSummarizationHandlers, - markSummarizationUsage, createToolEndCallback, - agentLogHandlerObj, } = require('~/server/controllers/agents/callbacks'); const { loadAgentTools, loadToolsForExecution } = require('~/server/services/ToolService'); const { findAccessibleResources } = require('~/server/services/PermissionService'); +const { getConvoFiles, saveConvo, getConvo } = require('~/models/Conversation'); +const { spendTokens, spendStructuredTokens } = require('~/models/spendTokens'); +const { getAgent, getAgents } = require('~/models/Agent'); const db = require('~/models'); /** @type {import('@librechat/api').AppConfig | null} */ @@ -213,12 +213,8 @@ async function saveResponseOutput(req, conversationId, responseId, response, age * @returns {Promise} */ async function saveConversation(req, conversationId, agentId, agent) { - await db.saveConvo( - { - userId: req?.user?.id, - isTemporary: req?.body?.isTemporary, - interfaceConfig: req?.config?.interfaceConfig, - }, + await saveConvo( + req, { conversationId, endpoint: EModelEndpoint.agents, @@ -280,10 +276,9 @@ const createResponse = async (req, res) => { const request = validation.request; const agentId = request.model; const isStreaming = request.stream === true; - const summarizationConfig = req.config?.summarization; // Look up the agent - const agent = await db.getAgent({ id: agentId }); + const agent = await getAgent({ id: agentId }); if (!agent) { return sendResponsesErrorResponse( res, @@ -296,6 +291,10 @@ const createResponse = async (req, res) => { // Generate IDs const responseId = generateResponseId(); + const conversationId = request.previous_response_id ?? uuidv4(); + const parentMessageId = null; + + // Create response context const context = createResponseContext(request, responseId); logger.debug( @@ -314,23 +313,6 @@ const createResponse = async (req, res) => { }); try { - if (request.previous_response_id != null) { - if (typeof request.previous_response_id !== 'string') { - return sendResponsesErrorResponse( - res, - 400, - 'previous_response_id must be a string', - 'invalid_request', - ); - } - if (!(await db.getConvo(req.user?.id, request.previous_response_id))) { - return sendResponsesErrorResponse(res, 404, 'Conversation not found', 'not_found'); - } - } - - const conversationId = request.previous_response_id ?? uuidv4(); - const parentMessageId = null; - // Build allowed providers set const allowedProviders = new Set( appConfig?.endpoints?.[EModelEndpoint.agents]?.allowedProviders, @@ -359,7 +341,7 @@ const createResponse = async (req, res) => { isInitialAgent: true, }, { - getConvoFiles: db.getConvoFiles, + getConvoFiles, getFiles: db.getFiles, getUserKey: db.getUserKey, getMessages: db.getMessages, @@ -391,11 +373,11 @@ const createResponse = async (req, res) => { const allMessages = [...previousMessages, ...inputMessages]; const toolSet = buildToolSet(primaryConfig); - const { - messages: formattedMessages, - indexTokenCountMap, - summary: initialSummary, - } = formatAgentMessages(allMessages, {}, toolSet); + const { messages: formattedMessages, indexTokenCountMap } = formatAgentMessages( + allMessages, + {}, + toolSet, + ); // Create tracker for streaming or aggregator for non-streaming const tracker = actuallyStreaming ? createResponseTracker() : null; @@ -446,7 +428,6 @@ const createResponse = async (req, res) => { toolRegistry: primaryConfig.toolRegistry, userMCPAuthMap: primaryConfig.userMCPAuthMap, tool_resources: primaryConfig.tool_resources, - actionsEnabled: primaryConfig.actionsEnabled, }); }, toolEndCallback, @@ -459,12 +440,11 @@ const createResponse = async (req, res) => { on_run_step: responsesHandlers.on_run_step, on_run_step_delta: responsesHandlers.on_run_step_delta, on_chat_model_end: { - handle: (event, data, metadata) => { + handle: (event, data) => { responsesHandlers.on_chat_model_end.handle(event, data); const usage = data?.output?.usage_metadata; if (usage) { - const taggedUsage = markSummarizationUsage(usage, metadata); - collectedUsage.push(taggedUsage); + collectedUsage.push(usage); } }, }, @@ -475,10 +455,6 @@ const createResponse = async (req, res) => { on_agent_update: { handle: () => {} }, on_custom_event: { handle: () => {} }, on_tool_execute: createToolExecuteHandler(toolExecuteOptions), - on_agent_log: agentLogHandlerObj, - ...(summarizationConfig?.enabled !== false - ? buildSummarizationHandlers({ isStreaming: actuallyStreaming, res }) - : {}), }; // Create and run the agent @@ -489,9 +465,7 @@ const createResponse = async (req, res) => { agents: [primaryConfig], messages: formattedMessages, indexTokenCountMap, - initialSummary, runId: responseId, - summarizationConfig, signal: abortController.signal, customHandlers: handlers, requestBody: { @@ -512,10 +486,6 @@ const createResponse = async (req, res) => { thread_id: conversationId, user_id: userId, user: createSafeUser(req.user), - requestBody: { - messageId: responseId, - conversationId, - }, ...(userMCPAuthMap != null && { userMCPAuthMap }), }, signal: abortController.signal, @@ -535,18 +505,12 @@ const createResponse = async (req, res) => { const balanceConfig = getBalanceConfig(req.config); const transactionsConfig = getTransactionsConfig(req.config); recordCollectedUsage( - { - spendTokens: db.spendTokens, - spendStructuredTokens: db.spendStructuredTokens, - pricing: { getMultiplier: db.getMultiplier, getCacheMultiplier: db.getCacheMultiplier }, - bulkWriteOps: { insertMany: db.bulkInsertTransactions, updateBalance: db.updateBalance }, - }, + { spendTokens, spendStructuredTokens }, { user: userId, conversationId, collectedUsage, context: 'message', - messageId: responseId, balance: balanceConfig, transactions: transactionsConfig, model: primaryConfig.model || agent.model_parameters?.model, @@ -611,7 +575,6 @@ const createResponse = async (req, res) => { toolRegistry: primaryConfig.toolRegistry, userMCPAuthMap: primaryConfig.userMCPAuthMap, tool_resources: primaryConfig.tool_resources, - actionsEnabled: primaryConfig.actionsEnabled, }); }, toolEndCallback, @@ -623,12 +586,11 @@ const createResponse = async (req, res) => { on_run_step: aggregatorHandlers.on_run_step, on_run_step_delta: aggregatorHandlers.on_run_step_delta, on_chat_model_end: { - handle: (event, data, metadata) => { + handle: (event, data) => { aggregatorHandlers.on_chat_model_end.handle(event, data); const usage = data?.output?.usage_metadata; if (usage) { - const taggedUsage = markSummarizationUsage(usage, metadata); - collectedUsage.push(taggedUsage); + collectedUsage.push(usage); } }, }, @@ -639,10 +601,6 @@ const createResponse = async (req, res) => { on_agent_update: { handle: () => {} }, on_custom_event: { handle: () => {} }, on_tool_execute: createToolExecuteHandler(toolExecuteOptions), - on_agent_log: agentLogHandlerObj, - ...(summarizationConfig?.enabled !== false - ? buildSummarizationHandlers({ isStreaming: false, res }) - : {}), }; const userId = req.user?.id ?? 'api-user'; @@ -652,9 +610,7 @@ const createResponse = async (req, res) => { agents: [primaryConfig], messages: formattedMessages, indexTokenCountMap, - initialSummary, runId: responseId, - summarizationConfig, signal: abortController.signal, customHandlers: handlers, requestBody: { @@ -674,10 +630,6 @@ const createResponse = async (req, res) => { thread_id: conversationId, user_id: userId, user: createSafeUser(req.user), - requestBody: { - messageId: responseId, - conversationId, - }, ...(userMCPAuthMap != null && { userMCPAuthMap }), }, signal: abortController.signal, @@ -697,18 +649,12 @@ const createResponse = async (req, res) => { const balanceConfig = getBalanceConfig(req.config); const transactionsConfig = getTransactionsConfig(req.config); recordCollectedUsage( - { - spendTokens: db.spendTokens, - spendStructuredTokens: db.spendStructuredTokens, - pricing: { getMultiplier: db.getMultiplier, getCacheMultiplier: db.getCacheMultiplier }, - bulkWriteOps: { insertMany: db.bulkInsertTransactions, updateBalance: db.updateBalance }, - }, + { spendTokens, spendStructuredTokens }, { user: userId, conversationId, collectedUsage, context: 'message', - messageId: responseId, balance: balanceConfig, transactions: transactionsConfig, model: primaryConfig.model || agent.model_parameters?.model, @@ -800,7 +746,7 @@ const listModels = async (req, res) => { // Get the accessible agents let agents = []; if (accessibleAgentIds.length > 0) { - agents = await db.getAgents({ _id: { $in: accessibleAgentIds } }); + agents = await getAgents({ _id: { $in: accessibleAgentIds } }); } // Convert to models format @@ -850,7 +796,7 @@ const getResponse = async (req, res) => { // The responseId could be either the response ID or the conversation ID // Try to find a conversation with this ID - const conversation = await db.getConvo(userId, responseId); + const conversation = await getConvo(userId, responseId); if (!conversation) { return sendResponsesErrorResponse( diff --git a/api/server/controllers/agents/v1.js b/api/server/controllers/agents/v1.js index e365b232e4..34078b2250 100644 --- a/api/server/controllers/agents/v1.js +++ b/api/server/controllers/agents/v1.js @@ -3,11 +3,9 @@ const fs = require('fs').promises; const { nanoid } = require('nanoid'); const { logger } = require('@librechat/data-schemas'); const { - refreshS3Url, agentCreateSchema, agentUpdateSchema, refreshListAvatars, - collectEdgeAgentIds, mergeAgentOcrConversion, MAX_AVATAR_REFRESH_AGENTS, convertOcrToContextInPlace, @@ -26,22 +24,30 @@ const { actionDelimiter, removeNullishValues, } = require('librechat-data-provider'); +const { + getListAgentsByAccess, + countPromotedAgents, + revertAgentVersion, + createAgent, + updateAgent, + deleteAgent, + getAgent, +} = require('~/models/Agent'); const { findPubliclyAccessibleResources, - getResourcePermissionsMap, findAccessibleResources, hasPublicPermission, grantPermission, } = require('~/server/services/PermissionService'); const { getStrategyFunctions } = require('~/server/services/Files/strategies'); +const { getCategoriesWithCounts, deleteFileByFilter } = require('~/models'); const { resizeAvatar } = require('~/server/services/Files/images/avatar'); const { getFileStrategy } = require('~/server/utils/getFileStrategy'); +const { refreshS3Url } = require('~/server/services/Files/S3/crud'); const { filterFile } = require('~/server/services/Files/process'); +const { updateAction, getActions } = require('~/models/Action'); const { getCachedTools } = require('~/server/services/Config'); -const { resolveConfigServers } = require('~/server/services/MCP'); -const { getMCPServersRegistry } = require('~/config'); const { getLogStores } = require('~/cache'); -const db = require('~/models'); const systemTools = { [Tools.execute_code]: true, @@ -52,122 +58,6 @@ const systemTools = { const MAX_SEARCH_LEN = 100; const escapeRegex = (str = '') => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); -/** - * Validates that the requesting user has VIEW access to every agent referenced in edges. - * Agents that do not exist in the database are skipped — at create time, the `from` field - * often references the agent being built, which has no DB record yet. - * @param {import('librechat-data-provider').GraphEdge[]} edges - * @param {string} userId - * @param {string} userRole - Used for group/role principal resolution - * @returns {Promise} Agent IDs the user cannot VIEW (empty if all accessible) - */ -const validateEdgeAgentAccess = async (edges, userId, userRole) => { - const edgeAgentIds = collectEdgeAgentIds(edges); - if (edgeAgentIds.size === 0) { - return []; - } - - const agents = await db.getAgents({ id: { $in: [...edgeAgentIds] } }); - - if (agents.length === 0) { - return []; - } - - const permissionsMap = await getResourcePermissionsMap({ - userId, - role: userRole, - resourceType: ResourceType.AGENT, - resourceIds: agents.map((a) => a._id), - }); - - return agents - .filter((a) => { - const bits = permissionsMap.get(a._id.toString()) ?? 0; - return (bits & PermissionBits.VIEW) === 0; - }) - .map((a) => a.id); -}; - -/** - * Filters tools to only include those the user is authorized to use. - * MCP tools must match the exact format `{toolName}_mcp_{serverName}` (exactly 2 segments). - * Multi-delimiter keys are rejected to prevent authorization/execution mismatch. - * Non-MCP tools must appear in availableTools (global tool cache) or systemTools. - * - * When `existingTools` is provided and the MCP registry is unavailable (e.g. server restart), - * tools already present on the agent are preserved rather than stripped — they were validated - * when originally added, and we cannot re-verify them without the registry. - * @param {object} params - * @param {string[]} params.tools - Raw tool strings from the request - * @param {string} params.userId - Requesting user ID for MCP server access check - * @param {Record} params.availableTools - Global non-MCP tool cache - * @param {string[]} [params.existingTools] - Tools already persisted on the agent document - * @param {Record} [params.configServers] - Config-source MCP servers resolved from appConfig overrides - * @returns {Promise} Only the authorized subset of tools - */ -const filterAuthorizedTools = async ({ - tools, - userId, - availableTools, - existingTools, - configServers, -}) => { - const filteredTools = []; - let mcpServerConfigs; - let registryUnavailable = false; - const existingToolSet = existingTools?.length ? new Set(existingTools) : null; - - for (const tool of tools) { - if (availableTools[tool] || systemTools[tool]) { - filteredTools.push(tool); - continue; - } - - if (!tool?.includes(Constants.mcp_delimiter)) { - continue; - } - - if (mcpServerConfigs === undefined) { - try { - mcpServerConfigs = - (await getMCPServersRegistry().getAllServerConfigs(userId, configServers)) ?? {}; - } catch (e) { - logger.warn( - '[filterAuthorizedTools] MCP registry unavailable, filtering all MCP tools', - e.message, - ); - mcpServerConfigs = {}; - registryUnavailable = true; - } - } - - const parts = tool.split(Constants.mcp_delimiter); - if (parts.length !== 2) { - logger.warn( - `[filterAuthorizedTools] Rejected malformed MCP tool key "${tool}" for user ${userId}`, - ); - continue; - } - - if (registryUnavailable && existingToolSet?.has(tool)) { - filteredTools.push(tool); - continue; - } - - const [, serverName] = parts; - if (!serverName || !Object.hasOwn(mcpServerConfigs, serverName)) { - logger.warn( - `[filterAuthorizedTools] Rejected MCP tool "${tool}" — server "${serverName}" not accessible to user ${userId}`, - ); - continue; - } - - filteredTools.push(tool); - } - - return filteredTools; -}; - /** * Creates an Agent. * @route POST /Agents @@ -185,35 +75,24 @@ const createAgentHandler = async (req, res) => { agentData.model_parameters = removeNullishValues(agentData.model_parameters, true); } - const { id: userId, role: userRole } = req.user; - - if (agentData.edges?.length) { - const unauthorized = await validateEdgeAgentAccess(agentData.edges, userId, userRole); - if (unauthorized.length > 0) { - return res.status(403).json({ - error: 'You do not have access to one or more agents referenced in edges', - agent_ids: unauthorized, - }); - } - } + const { id: userId } = req.user; agentData.id = `agent_${nanoid()}`; agentData.author = userId; agentData.tools = []; - const hasMCPTools = tools.some((t) => t?.includes(Constants.mcp_delimiter)); - const [availableTools, configServers] = await Promise.all([ - getCachedTools().then((t) => t ?? {}), - hasMCPTools ? resolveConfigServers(req) : Promise.resolve(undefined), - ]); - agentData.tools = await filterAuthorizedTools({ - tools, - userId, - availableTools, - configServers, - }); + const availableTools = (await getCachedTools()) ?? {}; + for (const tool of tools) { + if (availableTools[tool]) { + agentData.tools.push(tool); + } else if (systemTools[tool]) { + agentData.tools.push(tool); + } else if (tool.includes(Constants.mcp_delimiter)) { + agentData.tools.push(tool); + } + } - const agent = await db.createAgent(agentData); + const agent = await createAgent(agentData); try { await Promise.all([ @@ -273,7 +152,7 @@ const getAgentHandler = async (req, res, expandProperties = false) => { // Permissions are validated by middleware before calling this function // Simply load the agent by ID - const agent = await db.getAgent({ id }); + const agent = await getAgent({ id }); if (!agent) { return res.status(404).json({ error: 'Agent not found' }); @@ -294,6 +173,9 @@ const getAgentHandler = async (req, res, expandProperties = false) => { agent.author = agent.author.toString(); + // @deprecated - isCollaborative replaced by ACL permissions + agent.isCollaborative = !!agent.isCollaborative; + // Check if agent is public const isPublic = await hasPublicPermission({ resourceType: ResourceType.AGENT, @@ -317,6 +199,9 @@ const getAgentHandler = async (req, res, expandProperties = false) => { author: agent.author, provider: agent.provider, model: agent.model, + projectIds: agent.projectIds, + // @deprecated - isCollaborative replaced by ACL permissions + isCollaborative: agent.isCollaborative, isPublic: agent.isPublic, version: agent.version, // Safe metadata @@ -358,21 +243,10 @@ const updateAgentHandler = async (req, res) => { updateData.avatar = avatarField; } - if (updateData.edges?.length) { - const { id: userId, role: userRole } = req.user; - const unauthorized = await validateEdgeAgentAccess(updateData.edges, userId, userRole); - if (unauthorized.length > 0) { - return res.status(403).json({ - error: 'You do not have access to one or more agents referenced in edges', - agent_ids: unauthorized, - }); - } - } - // Convert OCR to context in incoming updateData convertOcrToContextInPlace(updateData); - const existingAgent = await db.getAgent({ id }); + const existingAgent = await getAgent({ id }); if (!existingAgent) { return res.status(404).json({ error: 'Agent not found' }); @@ -387,33 +261,9 @@ const updateAgentHandler = async (req, res) => { updateData.tools = ocrConversion.tools; } - if (updateData.tools) { - const existingToolSet = new Set(existingAgent.tools ?? []); - const newMCPTools = updateData.tools.filter( - (t) => !existingToolSet.has(t) && t?.includes(Constants.mcp_delimiter), - ); - - if (newMCPTools.length > 0) { - const [availableTools, configServers] = await Promise.all([ - getCachedTools().then((t) => t ?? {}), - resolveConfigServers(req), - ]); - const approvedNew = await filterAuthorizedTools({ - tools: newMCPTools, - userId: req.user.id, - availableTools, - configServers, - }); - const rejectedSet = new Set(newMCPTools.filter((t) => !approvedNew.includes(t))); - if (rejectedSet.size > 0) { - updateData.tools = updateData.tools.filter((t) => !rejectedSet.has(t)); - } - } - } - let updatedAgent = Object.keys(updateData).length > 0 - ? await db.updateAgent({ id }, updateData, { + ? await updateAgent({ id }, updateData, { updatingUserId: req.user.id, }) : existingAgent; @@ -463,7 +313,7 @@ const duplicateAgentHandler = async (req, res) => { const sensitiveFields = ['api_key', 'oauth_client_id', 'oauth_client_secret']; try { - const agent = await db.getAgent({ id }); + const agent = await getAgent({ id }); if (!agent) { return res.status(404).json({ error: 'Agent not found', @@ -511,7 +361,7 @@ const duplicateAgentHandler = async (req, res) => { }); const newActionsList = []; - const originalActions = (await db.getActions({ agent_id: id }, true)) ?? []; + const originalActions = (await getActions({ agent_id: id }, true)) ?? []; const promises = []; /** @@ -521,7 +371,7 @@ const duplicateAgentHandler = async (req, res) => { */ const duplicateAction = async (action) => { const newActionId = nanoid(); - const { domain } = action.metadata; + const [domain] = action.action_id.split(actionDelimiter); const fullActionId = `${domain}${actionDelimiter}${newActionId}`; // Sanitize sensitive metadata before persisting @@ -530,8 +380,8 @@ const duplicateAgentHandler = async (req, res) => { delete filteredMetadata[field]; } - const newAction = await db.updateAction( - { action_id: newActionId, agent_id: newAgentId }, + const newAction = await updateAction( + { action_id: newActionId }, { metadata: filteredMetadata, agent_id: newAgentId, @@ -553,22 +403,7 @@ const duplicateAgentHandler = async (req, res) => { const agentActions = await Promise.all(promises); newAgentData.actions = agentActions; - - if (newAgentData.tools?.length) { - const [availableTools, configServers] = await Promise.all([ - getCachedTools().then((t) => t ?? {}), - resolveConfigServers(req), - ]); - newAgentData.tools = await filterAuthorizedTools({ - tools: newAgentData.tools, - userId, - availableTools, - existingTools: newAgentData.tools, - configServers, - }); - } - - const newAgent = await db.createAgent(newAgentData); + const newAgent = await createAgent(newAgentData); try { await Promise.all([ @@ -621,11 +456,11 @@ const duplicateAgentHandler = async (req, res) => { const deleteAgentHandler = async (req, res) => { try { const id = req.params.id; - const agent = await db.getAgent({ id }); + const agent = await getAgent({ id }); if (!agent) { return res.status(404).json({ error: 'Agent not found' }); } - await db.deleteAgent({ id }); + await deleteAgent({ id }); return res.json({ message: 'Agent deleted' }); } catch (error) { logger.error('[/Agents/:id] Error deleting Agent', error); @@ -695,34 +530,31 @@ const getListAgentsHandler = async (req, res) => { */ const cache = getLogStores(CacheKeys.S3_EXPIRY_INTERVAL); const refreshKey = `${userId}:agents_avatar_refresh`; - let cachedRefresh = await cache.get(refreshKey); - const isValidCachedRefresh = - cachedRefresh != null && typeof cachedRefresh === 'object' && cachedRefresh.urlCache != null; - if (!isValidCachedRefresh) { + const alreadyChecked = await cache.get(refreshKey); + if (alreadyChecked) { + logger.debug('[/Agents] S3 avatar refresh already checked, skipping'); + } else { try { - const fullList = await db.getListAgentsByAccess({ + const fullList = await getListAgentsByAccess({ accessibleIds, otherParams: {}, limit: MAX_AVATAR_REFRESH_AGENTS, after: null, }); - const { urlCache } = await refreshListAvatars({ + await refreshListAvatars({ agents: fullList?.data ?? [], userId, refreshS3Url, - updateAgent: db.updateAgent, + updateAgent, }); - cachedRefresh = { urlCache }; - await cache.set(refreshKey, cachedRefresh, Time.THIRTY_MINUTES); + await cache.set(refreshKey, true, Time.THIRTY_MINUTES); } catch (err) { logger.error('[/Agents] Error refreshing avatars for full list: %o', err); } - } else { - logger.debug('[/Agents] S3 avatar refresh already checked, skipping'); } // Use the new ACL-aware function - const data = await db.getListAgentsByAccess({ + const data = await getListAgentsByAccess({ accessibleIds, otherParams: filter, limit, @@ -736,20 +568,11 @@ const getListAgentsHandler = async (req, res) => { const publicSet = new Set(publiclyAccessibleIds.map((oid) => oid.toString())); - const urlCache = cachedRefresh?.urlCache; data.data = agents.map((agent) => { try { if (agent?._id && publicSet.has(agent._id.toString())) { agent.isPublic = true; } - if ( - urlCache && - agent?.id && - agent?.avatar?.source === FileSources.s3 && - urlCache[agent.id] - ) { - agent.avatar = { ...agent.avatar, filepath: urlCache[agent.id] }; - } } catch (e) { // Silently ignore mapping errors void e; @@ -787,7 +610,7 @@ const uploadAgentAvatarHandler = async (req, res) => { return res.status(400).json({ message: 'Agent ID is required' }); } - const existingAgent = await db.getAgent({ id: agent_id }); + const existingAgent = await getAgent({ id: agent_id }); if (!existingAgent) { return res.status(404).json({ error: 'Agent not found' }); @@ -819,7 +642,7 @@ const uploadAgentAvatarHandler = async (req, res) => { const { deleteFile } = getStrategyFunctions(_avatar.source); try { await deleteFile(req, { filepath: _avatar.filepath }); - await db.deleteFileByFilter({ user: req.user.id, filepath: _avatar.filepath }); + await deleteFileByFilter({ user: req.user.id, filepath: _avatar.filepath }); } catch (error) { logger.error('[/:agent_id/avatar] Error deleting old avatar', error); } @@ -832,17 +655,9 @@ const uploadAgentAvatarHandler = async (req, res) => { }, }; - const updatedAgent = await db.updateAgent({ id: agent_id }, data, { + const updatedAgent = await updateAgent({ id: agent_id }, data, { updatingUserId: req.user.id, }); - - try { - const avatarCache = getLogStores(CacheKeys.S3_EXPIRY_INTERVAL); - await avatarCache.delete(`${req.user.id}:agents_avatar_refresh`); - } catch (cacheErr) { - logger.error('[/:agent_id/avatar] Error invalidating avatar refresh cache', cacheErr); - } - res.status(201).json(updatedAgent); } catch (error) { const message = 'An error occurred while updating the Agent Avatar'; @@ -888,7 +703,7 @@ const revertAgentVersionHandler = async (req, res) => { return res.status(400).json({ error: 'version_index is required' }); } - const existingAgent = await db.getAgent({ id }); + const existingAgent = await getAgent({ id }); if (!existingAgent) { return res.status(404).json({ error: 'Agent not found' }); @@ -896,28 +711,7 @@ const revertAgentVersionHandler = async (req, res) => { // Permissions are enforced via route middleware (ACL EDIT) - let updatedAgent = await db.revertAgentVersion({ id }, version_index); - - if (updatedAgent.tools?.length) { - const [availableTools, configServers] = await Promise.all([ - getCachedTools().then((t) => t ?? {}), - resolveConfigServers(req), - ]); - const filteredTools = await filterAuthorizedTools({ - tools: updatedAgent.tools, - userId: req.user.id, - availableTools, - existingTools: updatedAgent.tools, - configServers, - }); - if (filteredTools.length !== updatedAgent.tools.length) { - updatedAgent = await db.updateAgent( - { id }, - { tools: filteredTools }, - { updatingUserId: req.user.id }, - ); - } - } + const updatedAgent = await revertAgentVersion({ id }, version_index); if (updatedAgent.author) { updatedAgent.author = updatedAgent.author.toString(); @@ -941,8 +735,8 @@ const revertAgentVersionHandler = async (req, res) => { */ const getAgentCategories = async (_req, res) => { try { - const categories = await db.getCategoriesWithCounts(); - const promotedCount = await db.countPromotedAgents(); + const categories = await getCategoriesWithCounts(); + const promotedCount = await countPromotedAgents(); const formattedCategories = categories.map((category) => ({ value: category.value, label: category.label, @@ -985,5 +779,4 @@ module.exports = { uploadAgentAvatar: uploadAgentAvatarHandler, revertAgentVersion: revertAgentVersionHandler, getAgentCategories, - filterAuthorizedTools, }; diff --git a/api/server/controllers/agents/v1.spec.js b/api/server/controllers/agents/v1.spec.js index 455cea2e7c..8b2a57d903 100644 --- a/api/server/controllers/agents/v1.spec.js +++ b/api/server/controllers/agents/v1.spec.js @@ -2,7 +2,7 @@ const mongoose = require('mongoose'); const { nanoid } = require('nanoid'); const { v4: uuidv4 } = require('uuid'); const { agentSchema } = require('@librechat/data-schemas'); -const { FileSources, PermissionBits } = require('librechat-data-provider'); +const { FileSources } = require('librechat-data-provider'); const { MongoMemoryServer } = require('mongodb-memory-server'); // Only mock the dependencies that are not database-related @@ -14,6 +14,10 @@ jest.mock('~/server/services/Config', () => ({ }), })); +jest.mock('~/models/Project', () => ({ + getProjectByName: jest.fn().mockResolvedValue(null), +})); + jest.mock('~/server/services/Files/strategies', () => ({ getStrategyFunctions: jest.fn(), })); @@ -22,16 +26,7 @@ jest.mock('~/server/services/Files/images/avatar', () => ({ resizeAvatar: jest.fn(), })); -jest.mock('sharp', () => - jest.fn(() => ({ - metadata: jest.fn().mockResolvedValue({}), - toFormat: jest.fn().mockReturnThis(), - toBuffer: jest.fn().mockResolvedValue(Buffer.alloc(0)), - })), -); - -jest.mock('@librechat/api', () => ({ - ...jest.requireActual('@librechat/api'), +jest.mock('~/server/services/Files/S3/crud', () => ({ refreshS3Url: jest.fn(), })); @@ -39,32 +34,31 @@ jest.mock('~/server/services/Files/process', () => ({ filterFile: jest.fn(), })); +jest.mock('~/models/Action', () => ({ + updateAction: jest.fn(), + getActions: jest.fn().mockResolvedValue([]), +})); + +jest.mock('~/models/File', () => ({ + deleteFileByFilter: jest.fn(), +})); + jest.mock('~/server/services/PermissionService', () => ({ findAccessibleResources: jest.fn().mockResolvedValue([]), findPubliclyAccessibleResources: jest.fn().mockResolvedValue([]), - getResourcePermissionsMap: jest.fn().mockResolvedValue(new Map()), grantPermission: jest.fn(), hasPublicPermission: jest.fn().mockResolvedValue(false), + checkPermission: jest.fn().mockResolvedValue(true), })); -jest.mock('~/models', () => { - const mongoose = require('mongoose'); - const { createMethods } = require('@librechat/data-schemas'); - const methods = createMethods(mongoose, { - removeAllPermissions: jest.fn().mockResolvedValue(undefined), - }); - return { - ...methods, - getCategoriesWithCounts: jest.fn(), - deleteFileByFilter: jest.fn(), - }; -}); +jest.mock('~/models', () => ({ + getCategoriesWithCounts: jest.fn(), +})); // Mock cache for S3 avatar refresh tests const mockCache = { get: jest.fn(), set: jest.fn(), - delete: jest.fn(), }; jest.mock('~/cache', () => ({ getLogStores: jest.fn(() => mockCache), @@ -79,10 +73,9 @@ const { const { findAccessibleResources, findPubliclyAccessibleResources, - getResourcePermissionsMap, } = require('~/server/services/PermissionService'); -const { refreshS3Url } = require('@librechat/api'); +const { refreshS3Url } = require('~/server/services/Files/S3/crud'); /** * @type {import('mongoose').Model} @@ -181,6 +174,7 @@ describe('Agent Controllers - Mass Assignment Protection', () => { // Unauthorized fields that should be stripped author: new mongoose.Types.ObjectId().toString(), // Should not be able to set author authorName: 'Hacker', // Should be stripped + isCollaborative: true, // Should be stripped on creation versions: [], // Should be stripped _id: new mongoose.Types.ObjectId(), // Should be stripped id: 'custom_agent_id', // Should be overridden @@ -199,6 +193,7 @@ describe('Agent Controllers - Mass Assignment Protection', () => { // Verify unauthorized fields were not set expect(createdAgent.author.toString()).toBe(mockReq.user.id); // Should be the request user, not the malicious value expect(createdAgent.authorName).toBeUndefined(); + expect(createdAgent.isCollaborative).toBeFalsy(); expect(createdAgent.versions).toHaveLength(1); // Should have exactly 1 version from creation expect(createdAgent.id).not.toBe('custom_agent_id'); // Should have generated ID expect(createdAgent.id).toMatch(/^agent_/); // Should have proper prefix @@ -449,6 +444,7 @@ describe('Agent Controllers - Mass Assignment Protection', () => { model: 'gpt-3.5-turbo', author: existingAgentAuthorId, description: 'Original description', + isCollaborative: false, versions: [ { name: 'Original Agent', @@ -470,6 +466,7 @@ describe('Agent Controllers - Mass Assignment Protection', () => { name: 'Updated Agent', description: 'Updated description', model: 'gpt-4', + isCollaborative: true, // This IS allowed in updates }; await updateAgentHandler(mockReq, mockRes); @@ -482,11 +479,13 @@ describe('Agent Controllers - Mass Assignment Protection', () => { expect(updatedAgent.name).toBe('Updated Agent'); expect(updatedAgent.description).toBe('Updated description'); expect(updatedAgent.model).toBe('gpt-4'); + expect(updatedAgent.isCollaborative).toBe(true); expect(updatedAgent.author).toBe(existingAgentAuthorId.toString()); // Verify in database const agentInDb = await Agent.findOne({ id: existingAgentId }); expect(agentInDb.name).toBe('Updated Agent'); + expect(agentInDb.isCollaborative).toBe(true); }); test('should reject update with unauthorized fields (mass assignment protection)', async () => { @@ -541,6 +540,26 @@ describe('Agent Controllers - Mass Assignment Protection', () => { expect(updatedAgent.name).toBe('Admin Update'); }); + test('should handle projectIds updates', async () => { + mockReq.user.id = existingAgentAuthorId.toString(); + mockReq.params.id = existingAgentId; + + const projectId1 = new mongoose.Types.ObjectId().toString(); + const projectId2 = new mongoose.Types.ObjectId().toString(); + + mockReq.body = { + projectIds: [projectId1, projectId2], + }; + + await updateAgentHandler(mockReq, mockRes); + + expect(mockRes.json).toHaveBeenCalled(); + + const updatedAgent = mockRes.json.mock.calls[0][0]; + expect(updatedAgent).toBeDefined(); + // Note: updateAgentProjects requires more setup, so we just verify the handler doesn't crash + }); + test('should validate tool_resources in updates', async () => { mockReq.user.id = existingAgentAuthorId.toString(); mockReq.params.id = existingAgentId; @@ -1290,7 +1309,7 @@ describe('Agent Controllers - Mass Assignment Protection', () => { }); test('should skip avatar refresh if cache hit', async () => { - mockCache.get.mockResolvedValue({ urlCache: {} }); + mockCache.get.mockResolvedValue(true); findAccessibleResources.mockResolvedValue([agentWithS3Avatar._id]); findPubliclyAccessibleResources.mockResolvedValue([]); @@ -1329,12 +1348,8 @@ describe('Agent Controllers - Mass Assignment Protection', () => { // Verify S3 URL was refreshed expect(refreshS3Url).toHaveBeenCalled(); - // Verify cache was set with urlCache map, not a plain boolean - expect(mockCache.set).toHaveBeenCalledWith( - expect.any(String), - expect.objectContaining({ urlCache: expect.any(Object) }), - expect.any(Number), - ); + // Verify cache was set + expect(mockCache.set).toHaveBeenCalled(); // Verify response was returned expect(mockRes.json).toHaveBeenCalled(); @@ -1548,191 +1563,5 @@ describe('Agent Controllers - Mass Assignment Protection', () => { // Verify that the handler completed successfully expect(mockRes.json).toHaveBeenCalled(); }); - - test('should treat legacy boolean cache entry as a miss and run refresh', async () => { - // Simulate a cache entry written by the pre-fix code - mockCache.get.mockResolvedValue(true); - findAccessibleResources.mockResolvedValue([agentWithS3Avatar._id]); - findPubliclyAccessibleResources.mockResolvedValue([]); - refreshS3Url.mockResolvedValue('new-s3-path.jpg'); - - const mockReq = { - user: { id: userA.toString(), role: 'USER' }, - query: {}, - }; - const mockRes = { - status: jest.fn().mockReturnThis(), - json: jest.fn().mockReturnThis(), - }; - - await getListAgentsHandler(mockReq, mockRes); - - // Boolean true fails the shape guard, so refresh must run - expect(refreshS3Url).toHaveBeenCalled(); - // Cache is overwritten with the proper format - expect(mockCache.set).toHaveBeenCalledWith( - expect.any(String), - expect.objectContaining({ urlCache: expect.any(Object) }), - expect.any(Number), - ); - }); - - test('should apply cached urlCache filepath to paginated response on cache hit', async () => { - const agentId = agentWithS3Avatar.id; - const cachedUrl = 'cached-presigned-url.jpg'; - - mockCache.get.mockResolvedValue({ urlCache: { [agentId]: cachedUrl } }); - findAccessibleResources.mockResolvedValue([agentWithS3Avatar._id]); - findPubliclyAccessibleResources.mockResolvedValue([]); - - const mockReq = { - user: { id: userA.toString(), role: 'USER' }, - query: {}, - }; - const mockRes = { - status: jest.fn().mockReturnThis(), - json: jest.fn().mockReturnThis(), - }; - - await getListAgentsHandler(mockReq, mockRes); - - expect(refreshS3Url).not.toHaveBeenCalled(); - - const responseData = mockRes.json.mock.calls[0][0]; - const agent = responseData.data.find((a) => a.id === agentId); - // Cached URL is served, not the stale DB value 'old-s3-path.jpg' - expect(agent.avatar.filepath).toBe(cachedUrl); - }); - - test('should preserve DB filepath for agents absent from urlCache on cache hit', async () => { - mockCache.get.mockResolvedValue({ urlCache: {} }); - findAccessibleResources.mockResolvedValue([agentWithS3Avatar._id]); - findPubliclyAccessibleResources.mockResolvedValue([]); - - const mockReq = { - user: { id: userA.toString(), role: 'USER' }, - query: {}, - }; - const mockRes = { - status: jest.fn().mockReturnThis(), - json: jest.fn().mockReturnThis(), - }; - - await getListAgentsHandler(mockReq, mockRes); - - expect(refreshS3Url).not.toHaveBeenCalled(); - - const responseData = mockRes.json.mock.calls[0][0]; - const agent = responseData.data.find((a) => a.id === agentWithS3Avatar.id); - expect(agent.avatar.filepath).toBe('old-s3-path.jpg'); - }); - }); - - describe('Edge ACL validation', () => { - let targetAgent; - - beforeEach(async () => { - targetAgent = await Agent.create({ - id: `agent_${nanoid()}`, - author: new mongoose.Types.ObjectId().toString(), - name: 'Target Agent', - provider: 'openai', - model: 'gpt-4', - tools: [], - }); - }); - - test('createAgentHandler should return 403 when user lacks VIEW on an edge-referenced agent', async () => { - const permMap = new Map(); - getResourcePermissionsMap.mockResolvedValueOnce(permMap); - - mockReq.body = { - name: 'Attacker Agent', - provider: 'openai', - model: 'gpt-4', - edges: [{ from: 'self_placeholder', to: targetAgent.id, edgeType: 'handoff' }], - }; - - await createAgentHandler(mockReq, mockRes); - - expect(mockRes.status).toHaveBeenCalledWith(403); - const response = mockRes.json.mock.calls[0][0]; - expect(response.agent_ids).toContain(targetAgent.id); - }); - - test('createAgentHandler should succeed when user has VIEW on all edge-referenced agents', async () => { - const permMap = new Map([[targetAgent._id.toString(), 1]]); - getResourcePermissionsMap.mockResolvedValueOnce(permMap); - - mockReq.body = { - name: 'Legit Agent', - provider: 'openai', - model: 'gpt-4', - edges: [{ from: 'self_placeholder', to: targetAgent.id, edgeType: 'handoff' }], - }; - - await createAgentHandler(mockReq, mockRes); - - expect(mockRes.status).toHaveBeenCalledWith(201); - }); - - test('createAgentHandler should allow edges referencing non-existent agents (self-reference at create time)', async () => { - mockReq.body = { - name: 'Self-Ref Agent', - provider: 'openai', - model: 'gpt-4', - edges: [{ from: 'agent_does_not_exist_yet', to: 'agent_also_new', edgeType: 'handoff' }], - }; - - await createAgentHandler(mockReq, mockRes); - - expect(mockRes.status).toHaveBeenCalledWith(201); - }); - - test('updateAgentHandler should return 403 when user lacks VIEW on an edge-referenced agent', async () => { - const ownedAgent = await Agent.create({ - id: `agent_${nanoid()}`, - author: mockReq.user.id, - name: 'Owned Agent', - provider: 'openai', - model: 'gpt-4', - tools: [], - }); - - const permMap = new Map([[ownedAgent._id.toString(), PermissionBits.VIEW]]); - getResourcePermissionsMap.mockResolvedValueOnce(permMap); - - mockReq.params = { id: ownedAgent.id }; - mockReq.body = { - edges: [{ from: ownedAgent.id, to: targetAgent.id, edgeType: 'handoff' }], - }; - - await updateAgentHandler(mockReq, mockRes); - - expect(mockRes.status).toHaveBeenCalledWith(403); - const response = mockRes.json.mock.calls[0][0]; - expect(response.agent_ids).toContain(targetAgent.id); - expect(response.agent_ids).not.toContain(ownedAgent.id); - }); - - test('updateAgentHandler should succeed when edges field is absent from payload', async () => { - const ownedAgent = await Agent.create({ - id: `agent_${nanoid()}`, - author: mockReq.user.id, - name: 'Owned Agent', - provider: 'openai', - model: 'gpt-4', - tools: [], - }); - - mockReq.params = { id: ownedAgent.id }; - mockReq.body = { name: 'Renamed Agent' }; - - await updateAgentHandler(mockReq, mockRes); - - expect(mockRes.status).not.toHaveBeenCalledWith(403); - const response = mockRes.json.mock.calls[0][0]; - expect(response.name).toBe('Renamed Agent'); - }); }); }); diff --git a/api/server/controllers/assistants/chatV1.js b/api/server/controllers/assistants/chatV1.js index 631831e617..804594d0bf 100644 --- a/api/server/controllers/assistants/chatV1.js +++ b/api/server/controllers/assistants/chatV1.js @@ -1,13 +1,7 @@ const { v4 } = require('uuid'); const { sleep } = require('@librechat/agents'); const { logger } = require('@librechat/data-schemas'); -const { - sendEvent, - countTokens, - checkBalance, - getBalanceConfig, - getModelMaxTokens, -} = require('@librechat/api'); +const { sendEvent, getBalanceConfig, getModelMaxTokens, countTokens } = require('@librechat/api'); const { Time, Constants, @@ -37,15 +31,10 @@ const { createRun, StreamRunManager } = require('~/server/services/Runs'); const { addTitle } = require('~/server/services/Endpoints/assistants'); const { createRunBody } = require('~/server/services/createRunBody'); const { sendResponse } = require('~/server/middleware/error'); -const { - createAutoRefillTransaction, - findBalanceByUser, - upsertBalanceFields, - getTransactions, - getMultiplier, - getConvo, -} = require('~/models'); -const { logViolation, getLogStores } = require('~/cache'); +const { getTransactions } = require('~/models/Transaction'); +const { checkBalance } = require('~/models/balanceMethods'); +const { getConvo } = require('~/models/Conversation'); +const getLogStores = require('~/cache/getLogStores'); const { getOpenAIClient } = require('./helpers'); /** @@ -286,26 +275,16 @@ const chatV1 = async (req, res) => { // Count tokens up to the current context window promptTokens = Math.min(promptTokens, getModelMaxTokens(model)); - await checkBalance( - { - req, - res, - txData: { - model, - user: req.user.id, - tokenType: 'prompt', - amount: promptTokens, - }, + await checkBalance({ + req, + res, + txData: { + model, + user: req.user.id, + tokenType: 'prompt', + amount: promptTokens, }, - { - findBalanceByUser, - getMultiplier, - createAutoRefillTransaction, - logViolation, - balanceConfig, - upsertBalanceFields, - }, - ); + }); }; const { openai: _openai } = await getOpenAIClient({ diff --git a/api/server/controllers/assistants/chatV2.js b/api/server/controllers/assistants/chatV2.js index 237af1b11a..414681d6dc 100644 --- a/api/server/controllers/assistants/chatV2.js +++ b/api/server/controllers/assistants/chatV2.js @@ -1,13 +1,7 @@ const { v4 } = require('uuid'); const { sleep } = require('@librechat/agents'); const { logger } = require('@librechat/data-schemas'); -const { - sendEvent, - countTokens, - checkBalance, - getBalanceConfig, - getModelMaxTokens, -} = require('@librechat/api'); +const { sendEvent, getBalanceConfig, getModelMaxTokens, countTokens } = require('@librechat/api'); const { Time, Constants, @@ -32,15 +26,10 @@ const validateAuthor = require('~/server/middleware/assistants/validateAuthor'); const { createRun, StreamRunManager } = require('~/server/services/Runs'); const { addTitle } = require('~/server/services/Endpoints/assistants'); const { createRunBody } = require('~/server/services/createRunBody'); -const { - getConvo, - getMultiplier, - getTransactions, - findBalanceByUser, - upsertBalanceFields, - createAutoRefillTransaction, -} = require('~/models'); -const { logViolation, getLogStores } = require('~/cache'); +const { getTransactions } = require('~/models/Transaction'); +const { checkBalance } = require('~/models/balanceMethods'); +const { getConvo } = require('~/models/Conversation'); +const getLogStores = require('~/cache/getLogStores'); const { getOpenAIClient } = require('./helpers'); /** @@ -159,26 +148,16 @@ const chatV2 = async (req, res) => { // Count tokens up to the current context window promptTokens = Math.min(promptTokens, getModelMaxTokens(model)); - await checkBalance( - { - req, - res, - txData: { - model, - user: req.user.id, - tokenType: 'prompt', - amount: promptTokens, - }, + await checkBalance({ + req, + res, + txData: { + model, + user: req.user.id, + tokenType: 'prompt', + amount: promptTokens, }, - { - findBalanceByUser, - getMultiplier, - createAutoRefillTransaction, - logViolation, - balanceConfig, - upsertBalanceFields, - }, - ); + }); }; const { openai: _openai } = await getOpenAIClient({ diff --git a/api/server/controllers/assistants/errors.js b/api/server/controllers/assistants/errors.js index f8dcf39f2b..1ae12ea3d5 100644 --- a/api/server/controllers/assistants/errors.js +++ b/api/server/controllers/assistants/errors.js @@ -3,8 +3,8 @@ const { logger } = require('@librechat/data-schemas'); const { CacheKeys, ViolationTypes, ContentTypes } = require('librechat-data-provider'); const { recordUsage, checkMessageGaps } = require('~/server/services/Threads'); const { sendResponse } = require('~/server/middleware/error'); +const { getConvo } = require('~/models/Conversation'); const getLogStores = require('~/cache/getLogStores'); -const { getConvo } = require('~/models'); /** * @typedef {Object} ErrorHandlerContext diff --git a/api/server/controllers/assistants/helpers.js b/api/server/controllers/assistants/helpers.js index 4630bfe7ef..9183680f1e 100644 --- a/api/server/controllers/assistants/helpers.js +++ b/api/server/controllers/assistants/helpers.js @@ -1,14 +1,13 @@ const { + SystemRoles, EModelEndpoint, defaultOrderQuery, defaultAssistantsVersion, } = require('librechat-data-provider'); -const { logger, SystemCapabilities } = require('@librechat/data-schemas'); const { initializeClient: initAzureClient, } = require('~/server/services/Endpoints/azureAssistants'); const { initializeClient } = require('~/server/services/Endpoints/assistants'); -const { hasCapability } = require('~/server/middleware/roles/capabilities'); const { getEndpointsConfig } = require('~/server/services/Config'); /** @@ -237,19 +236,9 @@ const fetchAssistants = async ({ req, res, overrideEndpoint }) => { body = await listAssistantsForAzure({ req, res, version, azureConfig, query }); } - if (!appConfig.endpoints?.[endpoint]) { + if (req.user.role === SystemRoles.ADMIN) { return body; - } - - let canManageAssistants = false; - try { - canManageAssistants = await hasCapability(req.user, SystemCapabilities.MANAGE_ASSISTANTS); - } catch (err) { - logger.warn(`[fetchAssistants] capability check failed, denying bypass: ${err.message}`); - } - - if (canManageAssistants) { - logger.debug(`[fetchAssistants] MANAGE_ASSISTANTS bypass for user ${req.user.id}`); + } else if (!appConfig.endpoints?.[endpoint]) { return body; } diff --git a/api/server/controllers/assistants/v1.js b/api/server/controllers/assistants/v1.js index c441b7ec59..5d13d30334 100644 --- a/api/server/controllers/assistants/v1.js +++ b/api/server/controllers/assistants/v1.js @@ -1,14 +1,15 @@ const fs = require('fs').promises; const { logger } = require('@librechat/data-schemas'); const { FileContext } = require('librechat-data-provider'); -const { deleteFileByFilter, updateAssistantDoc, getAssistants } = require('~/models'); const { uploadImageBuffer, filterFile } = require('~/server/services/Files/process'); const validateAuthor = require('~/server/middleware/assistants/validateAuthor'); const { getStrategyFunctions } = require('~/server/services/Files/strategies'); const { deleteAssistantActions } = require('~/server/services/ActionService'); +const { updateAssistantDoc, getAssistants } = require('~/models/Assistant'); const { getOpenAIClient, fetchAssistants } = require('./helpers'); const { getCachedTools } = require('~/server/services/Config'); const { manifestToolMap } = require('~/app/clients/tools'); +const { deleteFileByFilter } = require('~/models'); /** * Create an assistant. diff --git a/api/server/controllers/assistants/v2.js b/api/server/controllers/assistants/v2.js index cc0e03916d..b9c5cd709f 100644 --- a/api/server/controllers/assistants/v2.js +++ b/api/server/controllers/assistants/v2.js @@ -3,8 +3,8 @@ const { ToolCallTypes } = require('librechat-data-provider'); const validateAuthor = require('~/server/middleware/assistants/validateAuthor'); const { validateAndUpdateTool } = require('~/server/services/ActionService'); const { getCachedTools } = require('~/server/services/Config'); +const { updateAssistantDoc } = require('~/models/Assistant'); const { manifestToolMap } = require('~/app/clients/tools'); -const { updateAssistantDoc } = require('~/models'); const { getOpenAIClient } = require('./helpers'); /** diff --git a/api/server/controllers/auth/LogoutController.js b/api/server/controllers/auth/LogoutController.js index 381bfc58b2..0b3cf262b8 100644 --- a/api/server/controllers/auth/LogoutController.js +++ b/api/server/controllers/auth/LogoutController.js @@ -4,36 +4,17 @@ 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 refresh token from session; for others, use cookie */ let refreshToken; - let idToken; if (isOpenIdUser && req.session?.openidTokens) { refreshToken = req.session.openidTokens.refreshToken; - idToken = req.session.openidTokens.idToken; delete req.session.openidTokens; } refreshToken = refreshToken || parsedCookies.refreshToken; - idToken = idToken || parsedCookies.openid_id_token; try { const logout = await logoutUser(req, refreshToken); @@ -50,76 +31,21 @@ const logoutController = async (req, res) => { isEnabled(process.env.OPENID_USE_END_SESSION_ENDPOINT) && process.env.OPENID_ISSUER ) { - let openIdConfig; - try { - openIdConfig = getOpenIdConfig(); - } catch (err) { - logger.warn('[logoutController] OpenID config not available:', err.message); - } - if (openIdConfig) { - const endSessionEndpoint = openIdConfig.serverMetadata().end_session_endpoint; + const openIdConfig = getOpenIdConfig(); + if (!openIdConfig) { + logger.warn( + '[logoutController] OpenID config not found. Please verify that the open id configuration and initialization are correct.', + ); + } else { + const endSessionEndpoint = openIdConfig + ? openIdConfig.serverMetadata().end_session_endpoint + : null; 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); - - /** - * 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 (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(); } else { logger.warn( diff --git a/api/server/controllers/auth/LogoutController.spec.js b/api/server/controllers/auth/LogoutController.spec.js deleted file mode 100644 index c9294fdcec..0000000000 --- a/api/server/controllers/auth/LogoutController.spec.js +++ /dev/null @@ -1,567 +0,0 @@ -const cookies = require('cookie'); - -const mockLogoutUser = jest.fn(); -const mockLogger = { warn: jest.fn(), error: jest.fn(), debug: jest.fn() }; -const mockIsEnabled = jest.fn(); -const mockGetOpenIdConfig = jest.fn(); - -jest.mock('cookie'); -jest.mock('@librechat/api', () => ({ isEnabled: (...args) => mockIsEnabled(...args) })); -jest.mock('@librechat/data-schemas', () => ({ logger: mockLogger })); -jest.mock('~/server/services/AuthService', () => ({ - logoutUser: (...args) => mockLogoutUser(...args), -})); -jest.mock('~/strategies', () => ({ getOpenIdConfig: () => mockGetOpenIdConfig() })); - -const { logoutController } = require('./LogoutController'); - -function buildReq(overrides = {}) { - return { - user: { _id: 'user1', openidId: 'oid1', provider: 'openid' }, - headers: { cookie: 'refreshToken=rt1' }, - session: { - openidTokens: { refreshToken: 'srt', idToken: 'small-id-token' }, - destroy: jest.fn(), - }, - ...overrides, - }; -} - -function buildRes() { - const res = { - status: jest.fn().mockReturnThis(), - send: jest.fn().mockReturnThis(), - json: jest.fn().mockReturnThis(), - clearCookie: jest.fn(), - }; - return res; -} - -const ORIGINAL_ENV = process.env; - -beforeEach(() => { - jest.clearAllMocks(); - process.env = { - ...ORIGINAL_ENV, - OPENID_USE_END_SESSION_ENDPOINT: 'true', - OPENID_ISSUER: 'https://idp.example.com', - OPENID_CLIENT_ID: 'my-client-id', - DOMAIN_CLIENT: 'https://app.example.com', - }; - cookies.parse.mockReturnValue({ refreshToken: 'cookie-rt' }); - mockLogoutUser.mockResolvedValue({ status: 200, message: 'Logout successful' }); - mockIsEnabled.mockReturnValue(true); - mockGetOpenIdConfig.mockReturnValue({ - serverMetadata: () => ({ - end_session_endpoint: 'https://idp.example.com/logout', - }), - }); -}); - -afterAll(() => { - process.env = ORIGINAL_ENV; -}); - -describe('LogoutController', () => { - describe('id_token_hint from session', () => { - it('sets id_token_hint when session has idToken', async () => { - const req = buildReq(); - const res = buildRes(); - - await logoutController(req, res); - - const body = res.send.mock.calls[0][0]; - expect(body.redirect).toContain('id_token_hint=small-id-token'); - expect(body.redirect).not.toContain('client_id='); - }); - }); - - describe('id_token_hint from cookie fallback', () => { - it('uses cookie id_token when session has no tokens', async () => { - cookies.parse.mockReturnValue({ - refreshToken: 'cookie-rt', - openid_id_token: 'cookie-id-token', - }); - const req = buildReq({ session: { 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=cookie-id-token'); - }); - }); - - describe('client_id fallback', () => { - it('falls back to client_id when no idToken is available', async () => { - cookies.parse.mockReturnValue({ refreshToken: 'cookie-rt' }); - const req = buildReq({ session: { destroy: jest.fn() } }); - const res = buildRes(); - - await logoutController(req, res); - - const body = res.send.mock.calls[0][0]; - expect(body.redirect).toContain('client_id=my-client-id'); - expect(body.redirect).not.toContain('id_token_hint='); - }); - - it('does not produce client_id=undefined when OPENID_CLIENT_ID is unset', async () => { - delete process.env.OPENID_CLIENT_ID; - cookies.parse.mockReturnValue({ refreshToken: 'cookie-rt' }); - const req = buildReq({ session: { destroy: jest.fn() } }); - const res = buildRes(); - - await logoutController(req, res); - - const body = res.send.mock.calls[0][0]; - expect(body.redirect).not.toContain('client_id='); - expect(body.redirect).not.toContain('undefined'); - expect(mockLogger.warn).toHaveBeenCalledWith( - expect.stringContaining('Neither id_token_hint nor OPENID_CLIENT_ID'), - ); - }); - }); - - describe('OPENID_USE_END_SESSION_ENDPOINT disabled', () => { - it('does not include redirect when disabled', async () => { - mockIsEnabled.mockReturnValue(false); - const req = buildReq(); - const res = buildRes(); - - await logoutController(req, res); - - const body = res.send.mock.calls[0][0]; - expect(body.redirect).toBeUndefined(); - }); - }); - - describe('OPENID_ISSUER unset', () => { - it('does not include redirect when OPENID_ISSUER is missing', async () => { - delete process.env.OPENID_ISSUER; - const req = buildReq(); - const res = buildRes(); - - await logoutController(req, res); - - const body = res.send.mock.calls[0][0]; - expect(body.redirect).toBeUndefined(); - }); - }); - - describe('non-OpenID user', () => { - it('does not include redirect for non-OpenID users', async () => { - const req = buildReq({ - user: { _id: 'user1', provider: 'local' }, - }); - const res = buildRes(); - - await logoutController(req, res); - - const body = res.send.mock.calls[0][0]; - expect(body.redirect).toBeUndefined(); - }); - }); - - describe('post_logout_redirect_uri', () => { - it('uses OPENID_POST_LOGOUT_REDIRECT_URI when set', async () => { - process.env.OPENID_POST_LOGOUT_REDIRECT_URI = 'https://custom.example.com/logged-out'; - const req = buildReq(); - const res = buildRes(); - - await logoutController(req, res); - - const body = res.send.mock.calls[0][0]; - const url = new URL(body.redirect); - expect(url.searchParams.get('post_logout_redirect_uri')).toBe( - 'https://custom.example.com/logged-out', - ); - }); - - it('defaults to DOMAIN_CLIENT/login when OPENID_POST_LOGOUT_REDIRECT_URI is unset', async () => { - delete process.env.OPENID_POST_LOGOUT_REDIRECT_URI; - const req = buildReq(); - const res = buildRes(); - - await logoutController(req, res); - - const body = res.send.mock.calls[0][0]; - const url = new URL(body.redirect); - expect(url.searchParams.get('post_logout_redirect_uri')).toBe( - 'https://app.example.com/login', - ); - }); - }); - - describe('OpenID config not available', () => { - it('warns and returns no redirect when getOpenIdConfig throws', async () => { - mockGetOpenIdConfig.mockImplementation(() => { - throw new Error('OpenID configuration has not been initialized'); - }); - const req = buildReq(); - const res = buildRes(); - - await logoutController(req, res); - - const body = res.send.mock.calls[0][0]; - expect(body.redirect).toBeUndefined(); - expect(mockLogger.warn).toHaveBeenCalledWith( - expect.stringContaining('OpenID config not available'), - 'OpenID configuration has not been initialized', - ); - }); - }); - - describe('end_session_endpoint not in metadata', () => { - it('warns and returns no redirect when end_session_endpoint is missing', async () => { - mockGetOpenIdConfig.mockReturnValue({ - serverMetadata: () => ({}), - }); - const req = buildReq(); - const res = buildRes(); - - await logoutController(req, res); - - const body = res.send.mock.calls[0][0]; - expect(body.redirect).toBeUndefined(); - expect(mockLogger.warn).toHaveBeenCalledWith( - expect.stringContaining('end_session_endpoint not found'), - ); - }); - }); - - describe('error handling', () => { - it('returns 500 on logoutUser error', async () => { - mockLogoutUser.mockRejectedValue(new Error('session error')); - const req = buildReq(); - const res = buildRes(); - - await logoutController(req, res); - - expect(res.status).toHaveBeenCalledWith(500); - expect(res.json).toHaveBeenCalledWith({ message: 'session error' }); - }); - }); - - describe('cookie clearing', () => { - it('clears all auth cookies on successful logout', async () => { - const req = buildReq(); - const res = buildRes(); - - await logoutController(req, res); - - expect(res.clearCookie).toHaveBeenCalledWith('refreshToken'); - expect(res.clearCookie).toHaveBeenCalledWith('openid_access_token'); - expect(res.clearCookie).toHaveBeenCalledWith('openid_id_token'); - expect(res.clearCookie).toHaveBeenCalledWith('openid_user_id'); - 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'); - }); - }); -}); diff --git a/api/server/controllers/auth/oauth.js b/api/server/controllers/auth/oauth.js index 917e9e2bef..80c2ced002 100644 --- a/api/server/controllers/auth/oauth.js +++ b/api/server/controllers/auth/oauth.js @@ -47,15 +47,9 @@ function createOAuthHandler(redirectUri = domains.client) { const refreshToken = req.user.tokenset?.refresh_token || req.user.federatedTokens?.refresh_token; + const exchangeCode = await generateAdminExchangeCode(cache, req.user, token, refreshToken); + const callbackUrl = new URL(redirectUri); - const exchangeCode = await generateAdminExchangeCode( - cache, - req.user, - token, - refreshToken, - callbackUrl.origin, - req.pkceChallenge, - ); callbackUrl.searchParams.set('code', exchangeCode); logger.info(`[OAuth] Admin panel redirect with exchange code for user: ${req.user.email}`); return res.redirect(callbackUrl.toString()); diff --git a/api/server/controllers/mcp.js b/api/server/controllers/mcp.js index e31bb93bc6..e5dfff61ca 100644 --- a/api/server/controllers/mcp.js +++ b/api/server/controllers/mcp.js @@ -7,14 +7,11 @@ */ const { logger } = require('@librechat/data-schemas'); const { - MCPErrorCodes, - redactServerSecrets, - redactAllServerSecrets, isMCPDomainNotAllowedError, isMCPInspectionFailedError, + MCPErrorCodes, } = require('@librechat/api'); const { Constants, MCPServerUserInputSchema } = require('librechat-data-provider'); -const { resolveConfigServers, resolveAllMcpConfigs } = require('~/server/services/MCP'); const { cacheMCPServerTools, getMCPServerTools } = require('~/server/services/Config'); const { getMCPManager, getMCPServersRegistry } = require('~/config'); @@ -58,7 +55,7 @@ function handleMCPError(error, res) { } /** - * Get all MCP tools available to the user. + * Get all MCP tools available to the user */ const getMCPTools = async (req, res) => { try { @@ -68,10 +65,10 @@ const getMCPTools = async (req, res) => { return res.status(401).json({ message: 'Unauthorized' }); } - const mcpConfig = await resolveAllMcpConfigs(userId, req.user); - const configuredServers = Object.keys(mcpConfig); + const mcpConfig = await getMCPServersRegistry().getAllServerConfigs(userId); + const configuredServers = mcpConfig ? Object.keys(mcpConfig) : []; - if (!configuredServers.length) { + if (!mcpConfig || Object.keys(mcpConfig).length == 0) { return res.status(200).json({ servers: {} }); } @@ -116,11 +113,14 @@ const getMCPTools = async (req, res) => { try { const serverTools = serverToolsMap.get(serverName); + // Get server config once const serverConfig = mcpConfig[serverName]; + const rawServerConfig = await getMCPServersRegistry().getServerConfig(serverName, userId); + // Initialize server object with all server-level data const server = { name: serverName, - icon: serverConfig?.iconPath || '', + icon: rawServerConfig?.iconPath || '', authenticated: true, authConfig: [], tools: [], @@ -181,8 +181,10 @@ const getMCPServersList = async (req, res) => { return res.status(401).json({ message: 'Unauthorized' }); } - const serverConfigs = await resolveAllMcpConfigs(userId, req.user); - return res.json(redactAllServerSecrets(serverConfigs)); + // 2. Get all server configs from registry (YAML + DB) + const serverConfigs = await getMCPServersRegistry().getAllServerConfigs(userId); + + return res.json(serverConfigs); } catch (error) { logger.error('[getMCPServersList]', error); res.status(500).json({ error: error.message }); @@ -213,7 +215,7 @@ const createMCPServerController = async (req, res) => { ); res.status(201).json({ serverName: result.serverName, - ...redactServerSecrets(result.config), + ...result.config, }); } catch (error) { logger.error('[createMCPServer]', error); @@ -235,18 +237,13 @@ const getMCPServerById = async (req, res) => { if (!serverName) { return res.status(400).json({ message: 'Server name is required' }); } - const configServers = await resolveConfigServers(req); - const parsedConfig = await getMCPServersRegistry().getServerConfig( - serverName, - userId, - configServers, - ); + const parsedConfig = await getMCPServersRegistry().getServerConfig(serverName, userId); if (!parsedConfig) { return res.status(404).json({ message: 'MCP server not found' }); } - res.status(200).json(redactServerSecrets(parsedConfig)); + res.status(200).json(parsedConfig); } catch (error) { logger.error('[getMCPServerById]', error); res.status(500).json({ message: error.message }); @@ -277,7 +274,7 @@ const updateMCPServerController = async (req, res) => { userId, ); - res.status(200).json(redactServerSecrets(parsedConfig)); + res.status(200).json(parsedConfig); } catch (error) { logger.error('[updateMCPServer]', error); const mcpErrorResponse = handleMCPError(error, res); diff --git a/api/server/controllers/tools.js b/api/server/controllers/tools.js index 1df11b1059..14a757e2bc 100644 --- a/api/server/controllers/tools.js +++ b/api/server/controllers/tools.js @@ -9,11 +9,13 @@ const { ToolCallTypes, PermissionTypes, } = require('librechat-data-provider'); -const { getRoleByName, createToolCall, getToolCallsByConvo, getMessage } = require('~/models'); const { processFileURL, uploadImageBuffer } = require('~/server/services/Files/process'); const { processCodeOutput } = require('~/server/services/Files/Code/process'); +const { createToolCall, getToolCallsByConvo } = require('~/models/ToolCall'); const { loadAuthValues } = require('~/server/services/Tools/credentials'); const { loadTools } = require('~/app/clients/tools/util'); +const { getRoleByName } = require('~/models/Role'); +const { getMessage } = require('~/models/Message'); const fieldsMap = { [Tools.execute_code]: [EnvVar.CODE_API_KEY], diff --git a/api/server/experimental.js b/api/server/experimental.js index ff023b4504..4a457abf61 100644 --- a/api/server/experimental.js +++ b/api/server/experimental.js @@ -14,26 +14,23 @@ const { logger } = require('@librechat/data-schemas'); const mongoSanitize = require('express-mongo-sanitize'); const { isEnabled, - apiNotFound, ErrorController, performStartupChecks, handleJsonParseError, initializeFileStorage, - preAuthTenantMiddleware, } = require('@librechat/api'); const { connectDb, indexSync } = require('~/db'); const initializeOAuthReconnectManager = require('./services/initializeOAuthReconnectManager'); const createValidateImageRequest = require('./middleware/validateImageRequest'); const { jwtLogin, ldapLogin, passportLogin } = require('~/strategies'); -const { updateInterfacePermissions: updateInterfacePerms } = require('@librechat/api'); -const { getRoleByName, updateAccessPermissions, seedDatabase } = require('~/models'); +const { updateInterfacePermissions } = require('~/models/interface'); const { checkMigrations } = require('./services/start/migration'); const initializeMCPs = require('./services/initializeMCPs'); const configureSocialLogins = require('./socialLogins'); const { getAppConfig } = require('./services/Config'); const staticCache = require('./utils/staticCache'); -const optionalJwtAuth = require('./middleware/optionalJwtAuth'); const noIndex = require('./middleware/noIndex'); +const { seedDatabase } = require('~/models'); const routes = require('./routes'); const { PORT, HOST, ALLOW_SOCIAL_LOGIN, DISABLE_COMPRESSION, TRUST_PROXY } = process.env ?? {}; @@ -224,7 +221,7 @@ if (cluster.isMaster) { const appConfig = await getAppConfig(); initializeFileStorage(appConfig); await performStartupChecks(appConfig); - await updateInterfacePerms({ appConfig, getRoleByName, updateAccessPermissions }); + await updateInterfacePermissions(appConfig); /** Load index.html for SPA serving */ const indexPath = path.join(appConfig.paths.dist, 'index.html'); @@ -300,7 +297,6 @@ if (cluster.isMaster) { /** Routes */ app.use('/oauth', routes.oauth); app.use('/api/auth', routes.auth); - app.use('/api/admin', routes.adminAuth); app.use('/api/actions', routes.actions); app.use('/api/keys', routes.keys); app.use('/api/api-keys', routes.apiKeys); @@ -314,7 +310,8 @@ if (cluster.isMaster) { app.use('/api/endpoints', routes.endpoints); app.use('/api/balance', routes.balance); app.use('/api/models', routes.models); - app.use('/api/config', preAuthTenantMiddleware, optionalJwtAuth, routes.config); + app.use('/api/plugins', routes.plugins); + app.use('/api/config', routes.config); app.use('/api/assistants', routes.assistants); app.use('/api/files', await routes.files.initialize()); app.use('/images/', createValidateImageRequest(appConfig.secureImageLinks), routes.staticRoute); @@ -327,8 +324,8 @@ if (cluster.isMaster) { app.use('/api/tags', routes.tags); app.use('/api/mcp', routes.mcp); - /** 404 for unmatched API routes */ - app.use('/api', apiNotFound); + /** Error handler */ + app.use(ErrorController); /** SPA fallback - serve index.html for all unmatched routes */ app.use((req, res) => { @@ -346,9 +343,6 @@ if (cluster.isMaster) { res.send(updatedIndexHtml); }); - /** Error handler (must be last - Express identifies error middleware by its 4-arg signature) */ - app.use(ErrorController); - /** Start listening on shared port (cluster will distribute connections) */ app.listen(port, host, async (err) => { if (err) { diff --git a/api/server/index.js b/api/server/index.js index d26a203c0a..193eb423ad 100644 --- a/api/server/index.js +++ b/api/server/index.js @@ -8,34 +8,29 @@ const express = require('express'); const passport = require('passport'); const compression = require('compression'); const cookieParser = require('cookie-parser'); +const { logger } = require('@librechat/data-schemas'); const mongoSanitize = require('express-mongo-sanitize'); -const { logger, runAsSystem } = require('@librechat/data-schemas'); const { isEnabled, - apiNotFound, ErrorController, - memoryDiagnostics, performStartupChecks, handleJsonParseError, + initializeFileStorage, GenerationJobManager, createStreamServices, - initializeFileStorage, - updateInterfacePermissions, - preAuthTenantMiddleware, } = require('@librechat/api'); const { connectDb, indexSync } = require('~/db'); const initializeOAuthReconnectManager = require('./services/initializeOAuthReconnectManager'); -const { getRoleByName, updateAccessPermissions, seedDatabase } = require('~/models'); -const { capabilityContextMiddleware } = require('./middleware/roles/capabilities'); const createValidateImageRequest = require('./middleware/validateImageRequest'); const { jwtLogin, ldapLogin, passportLogin } = require('~/strategies'); +const { updateInterfacePermissions } = require('~/models/interface'); const { checkMigrations } = require('./services/start/migration'); const initializeMCPs = require('./services/initializeMCPs'); const configureSocialLogins = require('./socialLogins'); const { getAppConfig } = require('./services/Config'); const staticCache = require('./utils/staticCache'); -const optionalJwtAuth = require('./middleware/optionalJwtAuth'); const noIndex = require('./middleware/noIndex'); +const { seedDatabase } = require('~/models'); const routes = require('./routes'); const { PORT, HOST, ALLOW_SOCIAL_LOGIN, DISABLE_COMPRESSION, TRUST_PROXY } = process.env ?? {}; @@ -61,20 +56,11 @@ const startServer = async () => { app.disable('x-powered-by'); app.set('trust proxy', trusted_proxy); - if (isEnabled(process.env.TENANT_ISOLATION_STRICT)) { - logger.warn( - '[Security] TENANT_ISOLATION_STRICT is active. Ensure your reverse proxy strips or sets ' + - 'the X-Tenant-Id header — untrusted clients must not be able to set it directly.', - ); - } - - await runAsSystem(seedDatabase); - const appConfig = await getAppConfig({ baseOnly: true }); + await seedDatabase(); + const appConfig = await getAppConfig(); initializeFileStorage(appConfig); - await runAsSystem(async () => { - await performStartupChecks(appConfig); - await updateInterfacePermissions({ appConfig, getRoleByName, updateAccessPermissions }); - }); + await performStartupChecks(appConfig); + await updateInterfacePermissions(appConfig); const indexPath = path.join(appConfig.paths.dist, 'index.html'); let indexHTML = fs.readFileSync(indexPath, 'utf8'); @@ -145,20 +131,10 @@ const startServer = async () => { await configureSocialLogins(app); } - /* Per-request capability cache — must be registered before any route that calls hasCapability */ - app.use(capabilityContextMiddleware); - - /* Pre-auth tenant context for unauthenticated routes that need tenant scoping. - * The reverse proxy / auth gateway sets `X-Tenant-Id` header for multi-tenant deployments. */ - app.use('/oauth', preAuthTenantMiddleware, routes.oauth); + app.use('/oauth', routes.oauth); /* API Endpoints */ - app.use('/api/auth', preAuthTenantMiddleware, routes.auth); + app.use('/api/auth', routes.auth); app.use('/api/admin', routes.adminAuth); - app.use('/api/admin/config', routes.adminConfig); - app.use('/api/admin/grants', routes.adminGrants); - app.use('/api/admin/groups', routes.adminGroups); - app.use('/api/admin/roles', routes.adminRoles); - app.use('/api/admin/users', routes.adminUsers); app.use('/api/actions', routes.actions); app.use('/api/keys', routes.keys); app.use('/api/api-keys', routes.apiKeys); @@ -172,11 +148,11 @@ const startServer = async () => { app.use('/api/endpoints', routes.endpoints); app.use('/api/balance', routes.balance); app.use('/api/models', routes.models); - app.use('/api/config', preAuthTenantMiddleware, optionalJwtAuth, routes.config); + app.use('/api/config', routes.config); app.use('/api/assistants', routes.assistants); app.use('/api/files', await routes.files.initialize()); app.use('/images/', createValidateImageRequest(appConfig.secureImageLinks), routes.staticRoute); - app.use('/api/share', preAuthTenantMiddleware, routes.share); + app.use('/api/share', routes.share); app.use('/api/roles', routes.roles); app.use('/api/agents', routes.agents); app.use('/api/banner', routes.banner); @@ -186,10 +162,8 @@ const startServer = async () => { app.use('/api/tags', routes.tags); app.use('/api/mcp', routes.mcp); - /** 404 for unmatched API routes */ - app.use('/api', apiNotFound); + app.use(ErrorController); - /** SPA fallback - serve index.html for all unmatched routes */ app.use((req, res) => { res.set({ 'Cache-Control': process.env.INDEX_CACHE_CONTROL || 'no-cache, no-store, must-revalidate', @@ -205,9 +179,6 @@ const startServer = async () => { res.send(updatedIndexHtml); }); - /** Error handler (must be last - Express identifies error middleware by its 4-arg signature) */ - app.use(ErrorController); - app.listen(port, host, async (err) => { if (err) { logger.error('Failed to start server:', err); @@ -222,21 +193,14 @@ const startServer = async () => { logger.info(`Server listening at http://${host == '0.0.0.0' ? 'localhost' : host}:${port}`); } - await runAsSystem(async () => { - await initializeMCPs(); - await initializeOAuthReconnectManager(); - }); + await initializeMCPs(); + await initializeOAuthReconnectManager(); await checkMigrations(); // Configure stream services (auto-detects Redis from USE_REDIS env var) const streamServices = createStreamServices(); GenerationJobManager.configure(streamServices); GenerationJobManager.initialize(); - - const inspectFlags = process.execArgv.some((arg) => arg.startsWith('--inspect')); - if (inspectFlags || isEnabled(process.env.MEM_DIAG)) { - memoryDiagnostics.start(); - } }); }; diff --git a/api/server/index.spec.js b/api/server/index.spec.js index 7b3d062fce..c73c605518 100644 --- a/api/server/index.spec.js +++ b/api/server/index.spec.js @@ -100,40 +100,6 @@ describe('Server Configuration', () => { expect(response.headers['expires']).toBe('0'); }); - it('should return 404 JSON for undefined API routes', async () => { - const response = await request(app).get('/api/nonexistent'); - expect(response.status).toBe(404); - expect(response.body).toEqual({ message: 'Endpoint not found' }); - }); - - it('should return 404 JSON for nested undefined API routes', async () => { - const response = await request(app).get('/api/nonexistent/nested/path'); - expect(response.status).toBe(404); - expect(response.body).toEqual({ message: 'Endpoint not found' }); - }); - - it('should return 404 JSON for non-GET methods on undefined API routes', async () => { - const post = await request(app).post('/api/nonexistent'); - expect(post.status).toBe(404); - expect(post.body).toEqual({ message: 'Endpoint not found' }); - - const del = await request(app).delete('/api/nonexistent'); - expect(del.status).toBe(404); - expect(del.body).toEqual({ message: 'Endpoint not found' }); - }); - - it('should return 404 JSON for the /api root path', async () => { - const response = await request(app).get('/api'); - expect(response.status).toBe(404); - expect(response.body).toEqual({ message: 'Endpoint not found' }); - }); - - it('should serve SPA HTML for non-API unmatched routes', async () => { - const response = await request(app).get('/this/does/not/exist'); - expect(response.status).toBe(200); - expect(response.headers['content-type']).toMatch(/html/); - }); - it('should return 500 for unknown errors via ErrorController', async () => { // Testing the error handling here on top of unit tests to ensure the middleware is correctly integrated diff --git a/api/server/middleware/__tests__/requireJwtAuth.spec.js b/api/server/middleware/__tests__/requireJwtAuth.spec.js deleted file mode 100644 index bc288e5dab..0000000000 --- a/api/server/middleware/__tests__/requireJwtAuth.spec.js +++ /dev/null @@ -1,116 +0,0 @@ -/** - * Integration test: verifies that requireJwtAuth chains tenantContextMiddleware - * after successful passport authentication, so ALS tenant context is set for - * all downstream middleware and route handlers. - * - * requireJwtAuth must chain tenantContextMiddleware after passport populates - * req.user (not at global app.use() scope where req.user is undefined). - * If the chaining is removed, these tests fail. - */ - -const { getTenantId } = require('@librechat/data-schemas'); - -// ── Mocks ────────────────────────────────────────────────────────────── - -let mockPassportError = null; - -jest.mock('passport', () => ({ - authenticate: jest.fn(() => { - return (req, _res, done) => { - if (mockPassportError) { - return done(mockPassportError); - } - if (req._mockUser) { - req.user = req._mockUser; - } - done(); - }; - }), -})); - -// Mock @librechat/api — the real tenantContextMiddleware is TS and cannot be -// required directly from CJS tests. This thin wrapper mirrors the real logic -// (read req.user.tenantId, call tenantStorage.run) using the same data-schemas -// primitives. The real implementation is covered by packages/api tenant.spec.ts. -jest.mock('@librechat/api', () => { - const { tenantStorage } = require('@librechat/data-schemas'); - return { - isEnabled: jest.fn(() => false), - tenantContextMiddleware: (req, res, next) => { - const tenantId = req.user?.tenantId; - if (!tenantId) { - return next(); - } - return tenantStorage.run({ tenantId }, async () => next()); - }, - }; -}); - -// ── Helpers ───────────────────────────────────────────────────────────── - -const requireJwtAuth = require('../requireJwtAuth'); - -function mockReq(user) { - return { headers: {}, _mockUser: user }; -} - -function mockRes() { - return { status: jest.fn().mockReturnThis(), json: jest.fn().mockReturnThis() }; -} - -/** Runs requireJwtAuth and returns the tenantId observed inside next(). */ -function runAuth(user) { - return new Promise((resolve) => { - const req = mockReq(user); - const res = mockRes(); - requireJwtAuth(req, res, () => { - resolve(getTenantId()); - }); - }); -} - -// ── Tests ────────────────────────────────────────────────────────────── - -describe('requireJwtAuth tenant context chaining', () => { - afterEach(() => { - mockPassportError = null; - }); - - it('forwards passport errors to next() without entering tenant middleware', async () => { - mockPassportError = new Error('JWT signature invalid'); - const req = mockReq(undefined); - const res = mockRes(); - const err = await new Promise((resolve) => { - requireJwtAuth(req, res, (e) => resolve(e)); - }); - expect(err).toBeInstanceOf(Error); - expect(err.message).toBe('JWT signature invalid'); - expect(getTenantId()).toBeUndefined(); - }); - - it('sets ALS tenant context after passport auth succeeds', async () => { - const tenantId = await runAuth({ tenantId: 'tenant-abc', role: 'user' }); - expect(tenantId).toBe('tenant-abc'); - }); - - it('ALS tenant context is NOT set when user has no tenantId', async () => { - const tenantId = await runAuth({ role: 'user' }); - expect(tenantId).toBeUndefined(); - }); - - it('ALS tenant context is NOT set when user is undefined', async () => { - const tenantId = await runAuth(undefined); - expect(tenantId).toBeUndefined(); - }); - - it('concurrent requests get isolated tenant contexts', async () => { - const results = await Promise.all( - ['tenant-1', 'tenant-2', 'tenant-3'].map((tid) => runAuth({ tenantId: tid, role: 'user' })), - ); - expect(results).toEqual(['tenant-1', 'tenant-2', 'tenant-3']); - }); - - it('ALS context is not set at top-level scope (outside any request)', () => { - expect(getTenantId()).toBeUndefined(); - }); -}); diff --git a/api/server/middleware/__tests__/validateModel.spec.js b/api/server/middleware/__tests__/validateModel.spec.js deleted file mode 100644 index 634baeed11..0000000000 --- a/api/server/middleware/__tests__/validateModel.spec.js +++ /dev/null @@ -1,178 +0,0 @@ -const { ViolationTypes } = require('librechat-data-provider'); - -jest.mock('@librechat/api', () => ({ - handleError: jest.fn(), -})); - -jest.mock('~/server/controllers/ModelController', () => ({ - getModelsConfig: jest.fn(), -})); - -jest.mock('~/server/services/Config', () => ({ - getEndpointsConfig: jest.fn(), -})); - -jest.mock('~/cache', () => ({ - logViolation: jest.fn(), -})); - -const { handleError } = require('@librechat/api'); -const { getModelsConfig } = require('~/server/controllers/ModelController'); -const { getEndpointsConfig } = require('~/server/services/Config'); -const { logViolation } = require('~/cache'); -const validateModel = require('../validateModel'); - -describe('validateModel', () => { - let req, res, next; - - beforeEach(() => { - jest.clearAllMocks(); - req = { body: { model: 'gpt-4o', endpoint: 'openAI' } }; - res = {}; - next = jest.fn(); - getEndpointsConfig.mockResolvedValue({ - openAI: { userProvide: false }, - }); - getModelsConfig.mockResolvedValue({ - openAI: ['gpt-4o', 'gpt-4o-mini'], - }); - }); - - describe('format validation', () => { - it('rejects missing model', async () => { - req.body.model = undefined; - await validateModel(req, res, next); - expect(handleError).toHaveBeenCalledWith(res, { text: 'Model not provided' }); - expect(next).not.toHaveBeenCalled(); - }); - - it('rejects non-string model', async () => { - req.body.model = 12345; - await validateModel(req, res, next); - expect(handleError).toHaveBeenCalledWith(res, { text: 'Model not provided' }); - expect(next).not.toHaveBeenCalled(); - }); - - it('rejects model exceeding 256 chars', async () => { - req.body.model = 'a'.repeat(257); - await validateModel(req, res, next); - expect(handleError).toHaveBeenCalledWith(res, { text: 'Invalid model identifier' }); - }); - - it('rejects model with leading special character', async () => { - req.body.model = '.bad-model'; - await validateModel(req, res, next); - expect(handleError).toHaveBeenCalledWith(res, { text: 'Invalid model identifier' }); - }); - - it('rejects model with script injection', async () => { - req.body.model = ''; - await validateModel(req, res, next); - expect(handleError).toHaveBeenCalledWith(res, { text: 'Invalid model identifier' }); - }); - - it('trims whitespace before validation', async () => { - req.body.model = ' gpt-4o '; - getModelsConfig.mockResolvedValue({ openAI: ['gpt-4o'] }); - await validateModel(req, res, next); - expect(next).toHaveBeenCalled(); - expect(handleError).not.toHaveBeenCalled(); - }); - - it('rejects model with spaces in the middle', async () => { - req.body.model = 'gpt 4o'; - await validateModel(req, res, next); - expect(handleError).toHaveBeenCalledWith(res, { text: 'Invalid model identifier' }); - }); - - it('accepts standard model IDs', async () => { - const validModels = [ - 'gpt-4o', - 'claude-3-5-sonnet-20241022', - 'us.amazon.nova-pro-v1:0', - 'qwen/qwen3.6-plus-preview:free', - 'Meta-Llama-3-8B-Instruct-4bit', - ]; - for (const model of validModels) { - jest.clearAllMocks(); - req.body.model = model; - getEndpointsConfig.mockResolvedValue({ openAI: { userProvide: false } }); - getModelsConfig.mockResolvedValue({ openAI: [model] }); - next.mockClear(); - - await validateModel(req, res, next); - expect(next).toHaveBeenCalled(); - expect(handleError).not.toHaveBeenCalled(); - } - }); - }); - - describe('userProvide early-return', () => { - it('calls next() immediately for userProvide endpoints without checking model list', async () => { - getEndpointsConfig.mockResolvedValue({ - openAI: { userProvide: true }, - }); - req.body.model = 'any-model-from-user-key'; - - await validateModel(req, res, next); - - expect(next).toHaveBeenCalled(); - expect(getModelsConfig).not.toHaveBeenCalled(); - }); - - it('does not call getModelsConfig for userProvide endpoints', async () => { - getEndpointsConfig.mockResolvedValue({ - CustomEndpoint: { userProvide: true }, - }); - req.body = { model: 'custom-model', endpoint: 'CustomEndpoint' }; - - await validateModel(req, res, next); - - expect(getModelsConfig).not.toHaveBeenCalled(); - expect(next).toHaveBeenCalled(); - }); - }); - - describe('system endpoint list validation', () => { - it('rejects a model not in the available list', async () => { - req.body.model = 'not-in-list'; - - await validateModel(req, res, next); - - expect(logViolation).toHaveBeenCalledWith( - req, - res, - ViolationTypes.ILLEGAL_MODEL_REQUEST, - expect.any(Object), - expect.anything(), - ); - expect(handleError).toHaveBeenCalledWith(res, { text: 'Illegal model request' }); - expect(next).not.toHaveBeenCalled(); - }); - - it('accepts a model in the available list', async () => { - req.body.model = 'gpt-4o'; - - await validateModel(req, res, next); - - expect(next).toHaveBeenCalled(); - expect(handleError).not.toHaveBeenCalled(); - }); - - it('rejects when endpoint has no models loaded', async () => { - getModelsConfig.mockResolvedValue({ openAI: undefined }); - - await validateModel(req, res, next); - - expect(handleError).toHaveBeenCalledWith(res, { text: 'Endpoint models not loaded' }); - }); - - it('rejects when modelsConfig is null', async () => { - getModelsConfig.mockResolvedValue(null); - - await validateModel(req, res, next); - - expect(handleError).toHaveBeenCalledWith(res, { text: 'Models not loaded' }); - }); - }); -}); diff --git a/api/server/middleware/abortMiddleware.js b/api/server/middleware/abortMiddleware.js index e0c5ae0ff0..d07a09682d 100644 --- a/api/server/middleware/abortMiddleware.js +++ b/api/server/middleware/abortMiddleware.js @@ -1,18 +1,18 @@ const { logger } = require('@librechat/data-schemas'); -const { isAssistantsEndpoint, ErrorTypes } = require('librechat-data-provider'); const { + countTokens, isEnabled, sendEvent, - countTokens, GenerationJobManager, - recordCollectedUsage, sanitizeMessageForTransmit, } = require('@librechat/api'); +const { isAssistantsEndpoint, ErrorTypes } = require('librechat-data-provider'); +const { spendTokens, spendStructuredTokens } = require('~/models/spendTokens'); const { truncateText, smartTruncateText } = require('~/app/clients/prompts'); const clearPendingReq = require('~/cache/clearPendingReq'); const { sendError } = require('~/server/middleware/error'); +const { saveMessage, getConvo } = require('~/models'); const { abortRun } = require('./abortRun'); -const db = require('~/models'); /** * Spend tokens for all models from collected usage. @@ -27,35 +27,62 @@ const db = require('~/models'); * @param {string} params.conversationId - Conversation ID * @param {Array} params.collectedUsage - Usage metadata from all models * @param {string} [params.fallbackModel] - Fallback model name if not in usage - * @param {string} [params.messageId] - The response message ID for transaction correlation */ -async function spendCollectedUsage({ - userId, - conversationId, - collectedUsage, - fallbackModel, - messageId, -}) { +async function spendCollectedUsage({ userId, conversationId, collectedUsage, fallbackModel }) { if (!collectedUsage || collectedUsage.length === 0) { return; } - await recordCollectedUsage( - { - spendTokens: db.spendTokens, - spendStructuredTokens: db.spendStructuredTokens, - pricing: { getMultiplier: db.getMultiplier, getCacheMultiplier: db.getCacheMultiplier }, - bulkWriteOps: { insertMany: db.bulkInsertTransactions, updateBalance: db.updateBalance }, - }, - { - user: userId, - conversationId, - collectedUsage, + const spendPromises = []; + + for (const usage of collectedUsage) { + if (!usage) { + continue; + } + + // Support both OpenAI format (input_token_details) and Anthropic format (cache_*_input_tokens) + const cache_creation = + Number(usage.input_token_details?.cache_creation) || + Number(usage.cache_creation_input_tokens) || + 0; + const cache_read = + Number(usage.input_token_details?.cache_read) || Number(usage.cache_read_input_tokens) || 0; + + const txMetadata = { context: 'abort', - messageId, - model: fallbackModel, - }, - ); + conversationId, + user: userId, + model: usage.model ?? fallbackModel, + }; + + if (cache_creation > 0 || cache_read > 0) { + spendPromises.push( + spendStructuredTokens(txMetadata, { + promptTokens: { + input: usage.input_tokens, + write: cache_creation, + read: cache_read, + }, + completionTokens: usage.output_tokens, + }).catch((err) => { + logger.error('[abortMiddleware] Error spending structured tokens for abort', err); + }), + ); + continue; + } + + spendPromises.push( + spendTokens(txMetadata, { + promptTokens: usage.input_tokens, + completionTokens: usage.output_tokens, + }).catch((err) => { + logger.error('[abortMiddleware] Error spending tokens for abort', err); + }), + ); + } + + // Wait for all token spending to complete + await Promise.all(spendPromises); // Clear the array to prevent double-spending from the AgentClient finally block. // The collectedUsage array is shared by reference with AgentClient.collectedUsage, @@ -117,28 +144,23 @@ async function abortMessage(req, res) { conversationId: jobData?.conversationId, collectedUsage, fallbackModel: jobData?.model, - messageId: jobData?.responseMessageId, }); } else { // Fallback: no collected usage, use text-based token counting for primary model only - await db.spendTokens( + await spendTokens( { ...responseMessage, context: 'incomplete', user: userId }, { promptTokens, completionTokens }, ); } - await db.saveMessage( - { - userId: req?.user?.id, - isTemporary: req?.body?.isTemporary, - interfaceConfig: req?.config?.interfaceConfig, - }, + await saveMessage( + req, { ...responseMessage, user: userId }, { context: 'api/server/middleware/abortMiddleware.js' }, ); // Get conversation for title - const conversation = await db.getConvo(userId, conversationId); + const conversation = await getConvo(userId, conversationId); const finalEvent = { title: conversation && !conversation.title ? null : conversation?.title || 'New Chat', @@ -270,5 +292,4 @@ const handleAbortError = async (res, req, error, data) => { module.exports = { handleAbort, handleAbortError, - spendCollectedUsage, }; diff --git a/api/server/middleware/abortMiddleware.spec.js b/api/server/middleware/abortMiddleware.spec.js index a4ce85674b..93f2ce558b 100644 --- a/api/server/middleware/abortMiddleware.spec.js +++ b/api/server/middleware/abortMiddleware.spec.js @@ -4,21 +4,15 @@ * This tests the token spending logic for abort scenarios, * particularly for parallel agents (addedConvo) where multiple * models need their tokens spent. - * - * spendCollectedUsage delegates to recordCollectedUsage from @librechat/api, - * passing pricing + bulkWriteOps deps, with context: 'abort'. - * After spending, it clears the collectedUsage array to prevent double-spending - * from the AgentClient finally block (which shares the same array reference). */ const mockSpendTokens = jest.fn().mockResolvedValue(); const mockSpendStructuredTokens = jest.fn().mockResolvedValue(); -const mockRecordCollectedUsage = jest - .fn() - .mockResolvedValue({ input_tokens: 100, output_tokens: 50 }); -const mockGetMultiplier = jest.fn().mockReturnValue(1); -const mockGetCacheMultiplier = jest.fn().mockReturnValue(null); +jest.mock('~/models/spendTokens', () => ({ + spendTokens: (...args) => mockSpendTokens(...args), + spendStructuredTokens: (...args) => mockSpendStructuredTokens(...args), +})); jest.mock('@librechat/data-schemas', () => ({ logger: { @@ -36,7 +30,6 @@ jest.mock('@librechat/api', () => ({ GenerationJobManager: { abortJob: jest.fn(), }, - recordCollectedUsage: mockRecordCollectedUsage, sanitizeMessageForTransmit: jest.fn((msg) => msg), })); @@ -56,31 +49,94 @@ jest.mock('~/server/middleware/error', () => ({ sendError: jest.fn(), })); -const mockUpdateBalance = jest.fn().mockResolvedValue({}); -const mockBulkInsertTransactions = jest.fn().mockResolvedValue(undefined); jest.mock('~/models', () => ({ saveMessage: jest.fn().mockResolvedValue(), getConvo: jest.fn().mockResolvedValue({ title: 'Test Chat' }), - updateBalance: mockUpdateBalance, - bulkInsertTransactions: mockBulkInsertTransactions, - spendTokens: (...args) => mockSpendTokens(...args), - spendStructuredTokens: (...args) => mockSpendStructuredTokens(...args), - getMultiplier: mockGetMultiplier, - getCacheMultiplier: mockGetCacheMultiplier, })); jest.mock('./abortRun', () => ({ abortRun: jest.fn(), })); -const { spendCollectedUsage } = require('./abortMiddleware'); +// Import the module after mocks are set up +// We need to extract the spendCollectedUsage function for testing +// Since it's not exported, we'll test it through the handleAbort flow describe('abortMiddleware - spendCollectedUsage', () => { beforeEach(() => { jest.clearAllMocks(); }); - describe('spendCollectedUsage delegation', () => { + describe('spendCollectedUsage logic', () => { + // Since spendCollectedUsage is not exported, we test the logic directly + // by replicating the function here for unit testing + + const spendCollectedUsage = async ({ + userId, + conversationId, + collectedUsage, + fallbackModel, + }) => { + if (!collectedUsage || collectedUsage.length === 0) { + return; + } + + const spendPromises = []; + + for (const usage of collectedUsage) { + if (!usage) { + continue; + } + + const cache_creation = + Number(usage.input_token_details?.cache_creation) || + Number(usage.cache_creation_input_tokens) || + 0; + const cache_read = + Number(usage.input_token_details?.cache_read) || + Number(usage.cache_read_input_tokens) || + 0; + + const txMetadata = { + context: 'abort', + conversationId, + user: userId, + model: usage.model ?? fallbackModel, + }; + + if (cache_creation > 0 || cache_read > 0) { + spendPromises.push( + mockSpendStructuredTokens(txMetadata, { + promptTokens: { + input: usage.input_tokens, + write: cache_creation, + read: cache_read, + }, + completionTokens: usage.output_tokens, + }).catch(() => { + // Log error but don't throw + }), + ); + continue; + } + + spendPromises.push( + mockSpendTokens(txMetadata, { + promptTokens: usage.input_tokens, + completionTokens: usage.output_tokens, + }).catch(() => { + // Log error but don't throw + }), + ); + } + + // Wait for all token spending to complete + await Promise.all(spendPromises); + + // Clear the array to prevent double-spending + collectedUsage.length = 0; + }; + it('should return early if collectedUsage is empty', async () => { await spendCollectedUsage({ userId: 'user-123', @@ -89,7 +145,8 @@ describe('abortMiddleware - spendCollectedUsage', () => { fallbackModel: 'gpt-4', }); - expect(mockRecordCollectedUsage).not.toHaveBeenCalled(); + expect(mockSpendTokens).not.toHaveBeenCalled(); + expect(mockSpendStructuredTokens).not.toHaveBeenCalled(); }); it('should return early if collectedUsage is null', async () => { @@ -100,10 +157,28 @@ describe('abortMiddleware - spendCollectedUsage', () => { fallbackModel: 'gpt-4', }); - expect(mockRecordCollectedUsage).not.toHaveBeenCalled(); + expect(mockSpendTokens).not.toHaveBeenCalled(); + expect(mockSpendStructuredTokens).not.toHaveBeenCalled(); }); - it('should call recordCollectedUsage with abort context and full deps', async () => { + it('should skip null entries in collectedUsage', async () => { + const collectedUsage = [ + { input_tokens: 100, output_tokens: 50, model: 'gpt-4' }, + null, + { input_tokens: 200, output_tokens: 60, model: 'gpt-4' }, + ]; + + await spendCollectedUsage({ + userId: 'user-123', + conversationId: 'convo-123', + collectedUsage, + fallbackModel: 'gpt-4', + }); + + expect(mockSpendTokens).toHaveBeenCalledTimes(2); + }); + + it('should spend tokens for single model', async () => { const collectedUsage = [{ input_tokens: 100, output_tokens: 50, model: 'gpt-4' }]; await spendCollectedUsage({ @@ -111,35 +186,21 @@ describe('abortMiddleware - spendCollectedUsage', () => { conversationId: 'convo-123', collectedUsage, fallbackModel: 'gpt-4', - messageId: 'msg-123', }); - expect(mockRecordCollectedUsage).toHaveBeenCalledTimes(1); - expect(mockRecordCollectedUsage).toHaveBeenCalledWith( - { - spendTokens: expect.any(Function), - spendStructuredTokens: expect.any(Function), - pricing: { - getMultiplier: mockGetMultiplier, - getCacheMultiplier: mockGetCacheMultiplier, - }, - bulkWriteOps: { - insertMany: mockBulkInsertTransactions, - updateBalance: mockUpdateBalance, - }, - }, - { - user: 'user-123', - conversationId: 'convo-123', - collectedUsage, + expect(mockSpendTokens).toHaveBeenCalledTimes(1); + expect(mockSpendTokens).toHaveBeenCalledWith( + expect.objectContaining({ context: 'abort', - messageId: 'msg-123', + conversationId: 'convo-123', + user: 'user-123', model: 'gpt-4', - }, + }), + { promptTokens: 100, completionTokens: 50 }, ); }); - it('should pass context abort for multiple models (parallel agents)', async () => { + it('should spend tokens for multiple models (parallel agents)', async () => { const collectedUsage = [ { input_tokens: 100, output_tokens: 50, model: 'gpt-4' }, { input_tokens: 80, output_tokens: 40, model: 'claude-3' }, @@ -153,17 +214,136 @@ describe('abortMiddleware - spendCollectedUsage', () => { fallbackModel: 'gpt-4', }); - expect(mockRecordCollectedUsage).toHaveBeenCalledTimes(1); - expect(mockRecordCollectedUsage).toHaveBeenCalledWith( - expect.any(Object), - expect.objectContaining({ - context: 'abort', - collectedUsage, - }), + expect(mockSpendTokens).toHaveBeenCalledTimes(3); + + // Verify each model was called + expect(mockSpendTokens).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ model: 'gpt-4' }), + { promptTokens: 100, completionTokens: 50 }, + ); + expect(mockSpendTokens).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ model: 'claude-3' }), + { promptTokens: 80, completionTokens: 40 }, + ); + expect(mockSpendTokens).toHaveBeenNthCalledWith( + 3, + expect.objectContaining({ model: 'gemini-pro' }), + { promptTokens: 120, completionTokens: 60 }, ); }); + it('should use fallbackModel when usage.model is missing', async () => { + const collectedUsage = [{ input_tokens: 100, output_tokens: 50 }]; + + await spendCollectedUsage({ + userId: 'user-123', + conversationId: 'convo-123', + collectedUsage, + fallbackModel: 'fallback-model', + }); + + expect(mockSpendTokens).toHaveBeenCalledWith( + expect.objectContaining({ model: 'fallback-model' }), + expect.any(Object), + ); + }); + + it('should use spendStructuredTokens for OpenAI format cache tokens', async () => { + const collectedUsage = [ + { + input_tokens: 100, + output_tokens: 50, + model: 'gpt-4', + input_token_details: { + cache_creation: 20, + cache_read: 10, + }, + }, + ]; + + await spendCollectedUsage({ + userId: 'user-123', + conversationId: 'convo-123', + collectedUsage, + fallbackModel: 'gpt-4', + }); + + expect(mockSpendStructuredTokens).toHaveBeenCalledTimes(1); + expect(mockSpendTokens).not.toHaveBeenCalled(); + expect(mockSpendStructuredTokens).toHaveBeenCalledWith( + expect.objectContaining({ model: 'gpt-4', context: 'abort' }), + { + promptTokens: { + input: 100, + write: 20, + read: 10, + }, + completionTokens: 50, + }, + ); + }); + + it('should use spendStructuredTokens for Anthropic format cache tokens', async () => { + const collectedUsage = [ + { + input_tokens: 100, + output_tokens: 50, + model: 'claude-3', + cache_creation_input_tokens: 25, + cache_read_input_tokens: 15, + }, + ]; + + await spendCollectedUsage({ + userId: 'user-123', + conversationId: 'convo-123', + collectedUsage, + fallbackModel: 'claude-3', + }); + + expect(mockSpendStructuredTokens).toHaveBeenCalledTimes(1); + expect(mockSpendTokens).not.toHaveBeenCalled(); + expect(mockSpendStructuredTokens).toHaveBeenCalledWith( + expect.objectContaining({ model: 'claude-3' }), + { + promptTokens: { + input: 100, + write: 25, + read: 15, + }, + completionTokens: 50, + }, + ); + }); + + it('should handle mixed cache and non-cache entries', async () => { + const collectedUsage = [ + { input_tokens: 100, output_tokens: 50, model: 'gpt-4' }, + { + input_tokens: 150, + output_tokens: 30, + model: 'claude-3', + cache_creation_input_tokens: 20, + cache_read_input_tokens: 10, + }, + { input_tokens: 200, output_tokens: 20, model: 'gemini-pro' }, + ]; + + await spendCollectedUsage({ + userId: 'user-123', + conversationId: 'convo-123', + collectedUsage, + fallbackModel: 'gpt-4', + }); + + expect(mockSpendTokens).toHaveBeenCalledTimes(2); + expect(mockSpendStructuredTokens).toHaveBeenCalledTimes(1); + }); + it('should handle real-world parallel agent abort scenario', async () => { + // Simulates: Primary agent (gemini) + addedConvo agent (gpt-5) aborted mid-stream const collectedUsage = [ { input_tokens: 31596, output_tokens: 151, model: 'gemini-3-flash-preview' }, { input_tokens: 28000, output_tokens: 120, model: 'gpt-5.2' }, @@ -176,24 +356,27 @@ describe('abortMiddleware - spendCollectedUsage', () => { fallbackModel: 'gemini-3-flash-preview', }); - expect(mockRecordCollectedUsage).toHaveBeenCalledTimes(1); - expect(mockRecordCollectedUsage).toHaveBeenCalledWith( - expect.any(Object), - expect.objectContaining({ - user: 'user-123', - conversationId: 'convo-123', - context: 'abort', - model: 'gemini-3-flash-preview', - }), + expect(mockSpendTokens).toHaveBeenCalledTimes(2); + + // Primary model + expect(mockSpendTokens).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ model: 'gemini-3-flash-preview' }), + { promptTokens: 31596, completionTokens: 151 }, + ); + + // Parallel model (addedConvo) + expect(mockSpendTokens).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ model: 'gpt-5.2' }), + { promptTokens: 28000, completionTokens: 120 }, ); }); - /** - * Race condition prevention: after abort middleware spends tokens, - * the collectedUsage array is cleared so AgentClient.recordCollectedUsage() - * (which shares the same array reference) sees an empty array and returns early. - */ it('should clear collectedUsage array after spending to prevent double-spending', async () => { + // This tests the race condition fix: after abort middleware spends tokens, + // the collectedUsage array is cleared so AgentClient.recordCollectedUsage() + // (which shares the same array reference) sees an empty array and returns early. const collectedUsage = [ { input_tokens: 100, output_tokens: 50, model: 'gpt-4' }, { input_tokens: 80, output_tokens: 40, model: 'claude-3' }, @@ -208,16 +391,19 @@ describe('abortMiddleware - spendCollectedUsage', () => { fallbackModel: 'gpt-4', }); - expect(mockRecordCollectedUsage).toHaveBeenCalledTimes(1); + expect(mockSpendTokens).toHaveBeenCalledTimes(2); + + // The array should be cleared after spending expect(collectedUsage.length).toBe(0); }); - it('should await recordCollectedUsage before clearing array', async () => { - let resolved = false; - mockRecordCollectedUsage.mockImplementation(async () => { + it('should await all token spending operations before clearing array', async () => { + // Ensure we don't clear the array before spending completes + let spendCallCount = 0; + mockSpendTokens.mockImplementation(async () => { + spendCallCount++; + // Simulate async delay await new Promise((resolve) => setTimeout(resolve, 10)); - resolved = true; - return { input_tokens: 100, output_tokens: 50 }; }); const collectedUsage = [ @@ -232,7 +418,10 @@ describe('abortMiddleware - spendCollectedUsage', () => { fallbackModel: 'gpt-4', }); - expect(resolved).toBe(true); + // Both spend calls should have completed + expect(spendCallCount).toBe(2); + + // Array should be cleared after awaiting expect(collectedUsage.length).toBe(0); }); }); diff --git a/api/server/middleware/abortRun.js b/api/server/middleware/abortRun.js index 318693fe15..44375f5024 100644 --- a/api/server/middleware/abortRun.js +++ b/api/server/middleware/abortRun.js @@ -3,7 +3,8 @@ const { logger } = require('@librechat/data-schemas'); const { CacheKeys, RunStatus, isUUID } = require('librechat-data-provider'); const { initializeClient } = require('~/server/services/Endpoints/assistants'); const { checkMessageGaps, recordUsage } = require('~/server/services/Threads'); -const { deleteMessages, getConvo } = require('~/models'); +const { deleteMessages } = require('~/models/Message'); +const { getConvo } = require('~/models/Conversation'); const getLogStores = require('~/cache/getLogStores'); const three_minutes = 1000 * 60 * 3; diff --git a/api/server/middleware/accessResources/canAccessAgentFromBody.js b/api/server/middleware/accessResources/canAccessAgentFromBody.js index 5ade76bb77..f8112af14d 100644 --- a/api/server/middleware/accessResources/canAccessAgentFromBody.js +++ b/api/server/middleware/accessResources/canAccessAgentFromBody.js @@ -1,145 +1,42 @@ const { logger } = require('@librechat/data-schemas'); const { Constants, - Permissions, ResourceType, - SystemRoles, - PermissionTypes, isAgentsEndpoint, isEphemeralAgentId, } = require('librechat-data-provider'); -const { checkPermission } = require('~/server/services/PermissionService'); const { canAccessResource } = require('./canAccessResource'); -const db = require('~/models'); - -const { getRoleByName, getAgent } = db; +const { getAgent } = require('~/models/Agent'); /** - * Resolves custom agent ID (e.g., "agent_abc123") to a MongoDB document. + * Agent ID resolver function for agent_id from request body + * Resolves custom agent ID (e.g., "agent_abc123") to MongoDB ObjectId + * This is used specifically for chat routes where agent_id comes from request body + * * @param {string} agentCustomId - Custom agent ID from request body - * @returns {Promise} Agent document with _id field, or null if ephemeral/not found + * @returns {Promise} Agent document with _id field, or null if not found */ const resolveAgentIdFromBody = async (agentCustomId) => { + // Handle ephemeral agents - they don't need permission checks + // Real agent IDs always start with "agent_", so anything else is ephemeral if (isEphemeralAgentId(agentCustomId)) { - return null; + return null; // No permission check needed for ephemeral agents } - return getAgent({ id: agentCustomId }); + + return await getAgent({ id: agentCustomId }); }; /** - * Creates a `canAccessResource` middleware for the given agent ID - * and chains to the provided continuation on success. - * - * @param {string} agentId - The agent's custom string ID (e.g., "agent_abc123") - * @param {number} requiredPermission - Permission bit(s) required - * @param {import('express').Request} req - * @param {import('express').Response} res - Written on deny; continuation called on allow - * @param {Function} continuation - Called when the permission check passes - * @returns {Promise} - */ -const checkAgentResourceAccess = (agentId, requiredPermission, req, res, continuation) => { - const middleware = canAccessResource({ - resourceType: ResourceType.AGENT, - requiredPermission, - resourceIdParam: 'agent_id', - idResolver: () => resolveAgentIdFromBody(agentId), - }); - - const tempReq = { - ...req, - params: { ...req.params, agent_id: agentId }, - }; - - return middleware(tempReq, res, continuation); -}; - -/** - * Middleware factory that validates MULTI_CONVO:USE role permission and, when - * addedConvo.agent_id is a non-ephemeral agent, the same resource-level permission - * required for the primary agent (`requiredPermission`). Caches the resolved agent - * document on `req.resolvedAddedAgent` to avoid a duplicate DB fetch in `loadAddedAgent`. - * - * @param {number} requiredPermission - Permission bit(s) to check on the added agent resource - * @returns {(req: import('express').Request, res: import('express').Response, next: Function) => Promise} - */ -const checkAddedConvoAccess = (requiredPermission) => async (req, res, next) => { - const addedConvo = req.body?.addedConvo; - if (!addedConvo || typeof addedConvo !== 'object' || Array.isArray(addedConvo)) { - return next(); - } - - try { - if (!req.user?.role) { - return res.status(403).json({ - error: 'Forbidden', - message: 'Insufficient permissions for multi-conversation', - }); - } - - if (req.user.role !== SystemRoles.ADMIN) { - const role = await getRoleByName(req.user.role); - const hasMultiConvo = role?.permissions?.[PermissionTypes.MULTI_CONVO]?.[Permissions.USE]; - if (!hasMultiConvo) { - return res.status(403).json({ - error: 'Forbidden', - message: 'Multi-conversation feature is not enabled', - }); - } - } - - const addedAgentId = addedConvo.agent_id; - if (!addedAgentId || typeof addedAgentId !== 'string' || isEphemeralAgentId(addedAgentId)) { - return next(); - } - - if (req.user.role === SystemRoles.ADMIN) { - return next(); - } - - const agent = await resolveAgentIdFromBody(addedAgentId); - if (!agent) { - return res.status(404).json({ - error: 'Not Found', - message: `${ResourceType.AGENT} not found`, - }); - } - - const hasPermission = await checkPermission({ - userId: req.user.id, - role: req.user.role, - resourceType: ResourceType.AGENT, - resourceId: agent._id, - requiredPermission, - }); - - if (!hasPermission) { - return res.status(403).json({ - error: 'Forbidden', - message: `Insufficient permissions to access this ${ResourceType.AGENT}`, - }); - } - - req.resolvedAddedAgent = agent; - return next(); - } catch (error) { - logger.error('Failed to validate addedConvo access permissions', error); - return res.status(500).json({ - error: 'Internal Server Error', - message: 'Failed to validate addedConvo access permissions', - }); - } -}; - -/** - * Middleware factory that checks agent access permissions from request body. - * Validates both the primary agent_id and, when present, addedConvo.agent_id - * (which also requires MULTI_CONVO:USE role permission). + * Middleware factory that creates middleware to check agent access permissions from request body. + * This middleware is specifically designed for chat routes where the agent_id comes from req.body + * instead of route parameters. * * @param {Object} options - Configuration options * @param {number} options.requiredPermission - The permission bit required (1=view, 2=edit, 4=delete, 8=share) * @returns {Function} Express middleware function * * @example + * // Basic usage for agent chat (requires VIEW permission) * router.post('/chat', * canAccessAgentFromBody({ requiredPermission: PermissionBits.VIEW }), * buildEndpointOption, @@ -149,12 +46,11 @@ const checkAddedConvoAccess = (requiredPermission) => async (req, res, next) => const canAccessAgentFromBody = (options) => { const { requiredPermission } = options; + // Validate required options if (!requiredPermission || typeof requiredPermission !== 'number') { throw new Error('canAccessAgentFromBody: requiredPermission is required and must be a number'); } - const addedConvoMiddleware = checkAddedConvoAccess(requiredPermission); - return async (req, res, next) => { try { const { endpoint, agent_id } = req.body; @@ -171,13 +67,28 @@ const canAccessAgentFromBody = (options) => { }); } - const afterPrimaryCheck = () => addedConvoMiddleware(req, res, next); - + // Skip permission checks for ephemeral agents + // Real agent IDs always start with "agent_", so anything else is ephemeral if (isEphemeralAgentId(agentId)) { - return afterPrimaryCheck(); + return next(); } - return checkAgentResourceAccess(agentId, requiredPermission, req, res, afterPrimaryCheck); + const agentAccessMiddleware = canAccessResource({ + resourceType: ResourceType.AGENT, + requiredPermission, + resourceIdParam: 'agent_id', // This will be ignored since we use custom resolver + idResolver: () => resolveAgentIdFromBody(agentId), + }); + + const tempReq = { + ...req, + params: { + ...req.params, + agent_id: agentId, + }, + }; + + return agentAccessMiddleware(tempReq, res, next); } catch (error) { logger.error('Failed to validate agent access permissions', error); return res.status(500).json({ diff --git a/api/server/middleware/accessResources/canAccessAgentFromBody.spec.js b/api/server/middleware/accessResources/canAccessAgentFromBody.spec.js deleted file mode 100644 index 9e5e0b093a..0000000000 --- a/api/server/middleware/accessResources/canAccessAgentFromBody.spec.js +++ /dev/null @@ -1,509 +0,0 @@ -const mongoose = require('mongoose'); -const { - ResourceType, - SystemRoles, - PrincipalType, - PrincipalModel, -} = require('librechat-data-provider'); -const { MongoMemoryServer } = require('mongodb-memory-server'); -const { canAccessAgentFromBody } = require('./canAccessAgentFromBody'); -const { User, Role, AclEntry } = require('~/db/models'); -const { createAgent } = require('~/models'); - -describe('canAccessAgentFromBody middleware', () => { - let mongoServer; - let req, res, next; - let testUser, otherUser; - - beforeAll(async () => { - mongoServer = await MongoMemoryServer.create(); - await mongoose.connect(mongoServer.getUri()); - }); - - afterAll(async () => { - await mongoose.disconnect(); - await mongoServer.stop(); - }); - - beforeEach(async () => { - await mongoose.connection.dropDatabase(); - - await Role.create({ - name: 'test-role', - permissions: { - AGENTS: { USE: true, CREATE: true, SHARE: true }, - MULTI_CONVO: { USE: true }, - }, - }); - - await Role.create({ - name: 'no-multi-convo', - permissions: { - AGENTS: { USE: true, CREATE: true, SHARE: true }, - MULTI_CONVO: { USE: false }, - }, - }); - - await Role.create({ - name: SystemRoles.ADMIN, - permissions: { - AGENTS: { USE: true, CREATE: true, SHARE: true }, - MULTI_CONVO: { USE: true }, - }, - }); - - testUser = await User.create({ - email: 'test@example.com', - name: 'Test User', - username: 'testuser', - role: 'test-role', - }); - - otherUser = await User.create({ - email: 'other@example.com', - name: 'Other User', - username: 'otheruser', - role: 'test-role', - }); - - req = { - user: { id: testUser._id, role: testUser.role }, - params: {}, - body: { - endpoint: 'agents', - agent_id: 'ephemeral_primary', - }, - }; - res = { - status: jest.fn().mockReturnThis(), - json: jest.fn(), - }; - next = jest.fn(); - - jest.clearAllMocks(); - }); - - describe('middleware factory', () => { - test('throws if requiredPermission is missing', () => { - expect(() => canAccessAgentFromBody({})).toThrow( - 'canAccessAgentFromBody: requiredPermission is required and must be a number', - ); - }); - - test('throws if requiredPermission is not a number', () => { - expect(() => canAccessAgentFromBody({ requiredPermission: '1' })).toThrow( - 'canAccessAgentFromBody: requiredPermission is required and must be a number', - ); - }); - - test('returns a middleware function', () => { - const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); - expect(typeof middleware).toBe('function'); - expect(middleware.length).toBe(3); - }); - }); - - describe('primary agent checks', () => { - test('returns 400 when agent_id is missing on agents endpoint', async () => { - req.body.agent_id = undefined; - const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); - await middleware(req, res, next); - - expect(next).not.toHaveBeenCalled(); - expect(res.status).toHaveBeenCalledWith(400); - }); - - test('proceeds for ephemeral primary agent without addedConvo', async () => { - const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); - await middleware(req, res, next); - - expect(next).toHaveBeenCalled(); - expect(res.status).not.toHaveBeenCalled(); - }); - - test('proceeds for non-agents endpoint (ephemeral fallback)', async () => { - req.body.endpoint = 'openAI'; - req.body.agent_id = undefined; - const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); - await middleware(req, res, next); - - expect(next).toHaveBeenCalled(); - }); - }); - - describe('addedConvo — absent or invalid shape', () => { - test('calls next when addedConvo is absent', async () => { - const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); - await middleware(req, res, next); - - expect(next).toHaveBeenCalled(); - }); - - test('calls next when addedConvo is a string', async () => { - req.body.addedConvo = 'not-an-object'; - const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); - await middleware(req, res, next); - - expect(next).toHaveBeenCalled(); - }); - - test('calls next when addedConvo is an array', async () => { - req.body.addedConvo = [{ agent_id: 'agent_something' }]; - const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); - await middleware(req, res, next); - - expect(next).toHaveBeenCalled(); - }); - }); - - describe('addedConvo — MULTI_CONVO permission gate', () => { - test('returns 403 when user lacks MULTI_CONVO:USE', async () => { - req.user.role = 'no-multi-convo'; - req.body.addedConvo = { agent_id: 'agent_x', endpoint: 'agents', model: 'gpt-4' }; - - const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); - await middleware(req, res, next); - - expect(next).not.toHaveBeenCalled(); - expect(res.status).toHaveBeenCalledWith(403); - expect(res.json).toHaveBeenCalledWith( - expect.objectContaining({ message: 'Multi-conversation feature is not enabled' }), - ); - }); - - test('returns 403 when user.role is missing', async () => { - req.user = { id: testUser._id }; - req.body.addedConvo = { agent_id: 'agent_x', endpoint: 'agents', model: 'gpt-4' }; - - const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); - await middleware(req, res, next); - - expect(next).not.toHaveBeenCalled(); - expect(res.status).toHaveBeenCalledWith(403); - }); - - test('ADMIN bypasses MULTI_CONVO check', async () => { - req.user.role = SystemRoles.ADMIN; - req.body.addedConvo = { agent_id: 'ephemeral_x', endpoint: 'agents', model: 'gpt-4' }; - - const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); - await middleware(req, res, next); - - expect(next).toHaveBeenCalled(); - expect(res.status).not.toHaveBeenCalled(); - }); - }); - - describe('addedConvo — agent_id shape validation', () => { - test('calls next when agent_id is ephemeral', async () => { - req.body.addedConvo = { agent_id: 'ephemeral_xyz', endpoint: 'agents', model: 'gpt-4' }; - - const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); - await middleware(req, res, next); - - expect(next).toHaveBeenCalled(); - }); - - test('calls next when agent_id is absent', async () => { - req.body.addedConvo = { endpoint: 'agents', model: 'gpt-4' }; - - const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); - await middleware(req, res, next); - - expect(next).toHaveBeenCalled(); - }); - - test('calls next when agent_id is not a string (object injection)', async () => { - req.body.addedConvo = { agent_id: { $gt: '' }, endpoint: 'agents', model: 'gpt-4' }; - - const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); - await middleware(req, res, next); - - expect(next).toHaveBeenCalled(); - }); - }); - - describe('addedConvo — agent resource ACL (IDOR prevention)', () => { - let addedAgent; - - beforeEach(async () => { - addedAgent = await createAgent({ - id: `agent_added_${Date.now()}`, - name: 'Private Agent', - provider: 'openai', - model: 'gpt-4', - author: otherUser._id, - }); - - await AclEntry.create({ - principalType: PrincipalType.USER, - principalId: otherUser._id, - principalModel: PrincipalModel.USER, - resourceType: ResourceType.AGENT, - resourceId: addedAgent._id, - permBits: 15, - grantedBy: otherUser._id, - }); - }); - - test('returns 403 when requester has no ACL for the added agent', async () => { - req.body.addedConvo = { agent_id: addedAgent.id, endpoint: 'agents', model: 'gpt-4' }; - - const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); - await middleware(req, res, next); - - expect(next).not.toHaveBeenCalled(); - expect(res.status).toHaveBeenCalledWith(403); - expect(res.json).toHaveBeenCalledWith( - expect.objectContaining({ - message: 'Insufficient permissions to access this agent', - }), - ); - }); - - test('returns 404 when added agent does not exist', async () => { - req.body.addedConvo = { - agent_id: 'agent_nonexistent_999', - endpoint: 'agents', - model: 'gpt-4', - }; - - const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); - await middleware(req, res, next); - - expect(next).not.toHaveBeenCalled(); - expect(res.status).toHaveBeenCalledWith(404); - }); - - test('proceeds when requester has ACL for the added agent', async () => { - await AclEntry.create({ - principalType: PrincipalType.USER, - principalId: testUser._id, - principalModel: PrincipalModel.USER, - resourceType: ResourceType.AGENT, - resourceId: addedAgent._id, - permBits: 1, - grantedBy: otherUser._id, - }); - - req.body.addedConvo = { agent_id: addedAgent.id, endpoint: 'agents', model: 'gpt-4' }; - - const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); - await middleware(req, res, next); - - expect(next).toHaveBeenCalled(); - expect(res.status).not.toHaveBeenCalled(); - }); - - test('denies when ACL permission bits are insufficient', async () => { - await AclEntry.create({ - principalType: PrincipalType.USER, - principalId: testUser._id, - principalModel: PrincipalModel.USER, - resourceType: ResourceType.AGENT, - resourceId: addedAgent._id, - permBits: 1, - grantedBy: otherUser._id, - }); - - req.body.addedConvo = { agent_id: addedAgent.id, endpoint: 'agents', model: 'gpt-4' }; - - const middleware = canAccessAgentFromBody({ requiredPermission: 2 }); - await middleware(req, res, next); - - expect(next).not.toHaveBeenCalled(); - expect(res.status).toHaveBeenCalledWith(403); - }); - - test('caches resolved agent on req.resolvedAddedAgent', async () => { - await AclEntry.create({ - principalType: PrincipalType.USER, - principalId: testUser._id, - principalModel: PrincipalModel.USER, - resourceType: ResourceType.AGENT, - resourceId: addedAgent._id, - permBits: 1, - grantedBy: otherUser._id, - }); - - req.body.addedConvo = { agent_id: addedAgent.id, endpoint: 'agents', model: 'gpt-4' }; - - const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); - await middleware(req, res, next); - - expect(next).toHaveBeenCalled(); - expect(req.resolvedAddedAgent).toBeDefined(); - expect(req.resolvedAddedAgent._id.toString()).toBe(addedAgent._id.toString()); - }); - - test('ADMIN bypasses agent resource ACL for addedConvo', async () => { - req.user.role = SystemRoles.ADMIN; - req.body.addedConvo = { agent_id: addedAgent.id, endpoint: 'agents', model: 'gpt-4' }; - - const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); - await middleware(req, res, next); - - expect(next).toHaveBeenCalled(); - expect(res.status).not.toHaveBeenCalled(); - expect(req.resolvedAddedAgent).toBeUndefined(); - }); - }); - - describe('end-to-end: primary real agent + addedConvo real agent', () => { - let primaryAgent, addedAgent; - - beforeEach(async () => { - primaryAgent = await createAgent({ - id: `agent_primary_${Date.now()}`, - name: 'Primary Agent', - provider: 'openai', - model: 'gpt-4', - author: testUser._id, - }); - - await AclEntry.create({ - principalType: PrincipalType.USER, - principalId: testUser._id, - principalModel: PrincipalModel.USER, - resourceType: ResourceType.AGENT, - resourceId: primaryAgent._id, - permBits: 15, - grantedBy: testUser._id, - }); - - addedAgent = await createAgent({ - id: `agent_added_${Date.now()}`, - name: 'Added Agent', - provider: 'openai', - model: 'gpt-4', - author: otherUser._id, - }); - - await AclEntry.create({ - principalType: PrincipalType.USER, - principalId: otherUser._id, - principalModel: PrincipalModel.USER, - resourceType: ResourceType.AGENT, - resourceId: addedAgent._id, - permBits: 15, - grantedBy: otherUser._id, - }); - - req.body.agent_id = primaryAgent.id; - }); - - test('both checks pass when user has ACL for both agents', async () => { - await AclEntry.create({ - principalType: PrincipalType.USER, - principalId: testUser._id, - principalModel: PrincipalModel.USER, - resourceType: ResourceType.AGENT, - resourceId: addedAgent._id, - permBits: 1, - grantedBy: otherUser._id, - }); - - req.body.addedConvo = { agent_id: addedAgent.id, endpoint: 'agents', model: 'gpt-4' }; - - const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); - await middleware(req, res, next); - - expect(next).toHaveBeenCalled(); - expect(res.status).not.toHaveBeenCalled(); - expect(req.resolvedAddedAgent).toBeDefined(); - }); - - test('primary passes but addedConvo denied → 403', async () => { - req.body.addedConvo = { agent_id: addedAgent.id, endpoint: 'agents', model: 'gpt-4' }; - - const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); - await middleware(req, res, next); - - expect(next).not.toHaveBeenCalled(); - expect(res.status).toHaveBeenCalledWith(403); - }); - - test('primary denied → 403 without reaching addedConvo check', async () => { - const foreignAgent = await createAgent({ - id: `agent_foreign_${Date.now()}`, - name: 'Foreign Agent', - provider: 'openai', - model: 'gpt-4', - author: otherUser._id, - }); - - await AclEntry.create({ - principalType: PrincipalType.USER, - principalId: otherUser._id, - principalModel: PrincipalModel.USER, - resourceType: ResourceType.AGENT, - resourceId: foreignAgent._id, - permBits: 15, - grantedBy: otherUser._id, - }); - - req.body.agent_id = foreignAgent.id; - req.body.addedConvo = { agent_id: addedAgent.id, endpoint: 'agents', model: 'gpt-4' }; - - const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); - await middleware(req, res, next); - - expect(next).not.toHaveBeenCalled(); - expect(res.status).toHaveBeenCalledWith(403); - }); - }); - - describe('ephemeral primary + real addedConvo agent', () => { - let addedAgent; - - beforeEach(async () => { - addedAgent = await createAgent({ - id: `agent_added_${Date.now()}`, - name: 'Added Agent', - provider: 'openai', - model: 'gpt-4', - author: otherUser._id, - }); - - await AclEntry.create({ - principalType: PrincipalType.USER, - principalId: otherUser._id, - principalModel: PrincipalModel.USER, - resourceType: ResourceType.AGENT, - resourceId: addedAgent._id, - permBits: 15, - grantedBy: otherUser._id, - }); - }); - - test('runs full addedConvo ACL check even when primary is ephemeral', async () => { - req.body.addedConvo = { agent_id: addedAgent.id, endpoint: 'agents', model: 'gpt-4' }; - - const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); - await middleware(req, res, next); - - expect(next).not.toHaveBeenCalled(); - expect(res.status).toHaveBeenCalledWith(403); - }); - - test('proceeds when user has ACL for added agent (ephemeral primary)', async () => { - await AclEntry.create({ - principalType: PrincipalType.USER, - principalId: testUser._id, - principalModel: PrincipalModel.USER, - resourceType: ResourceType.AGENT, - resourceId: addedAgent._id, - permBits: 1, - grantedBy: otherUser._id, - }); - - req.body.addedConvo = { agent_id: addedAgent.id, endpoint: 'agents', model: 'gpt-4' }; - - const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); - await middleware(req, res, next); - - expect(next).toHaveBeenCalled(); - expect(res.status).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/api/server/middleware/accessResources/canAccessAgentResource.js b/api/server/middleware/accessResources/canAccessAgentResource.js index 4c00ab4982..62d9f248c0 100644 --- a/api/server/middleware/accessResources/canAccessAgentResource.js +++ b/api/server/middleware/accessResources/canAccessAgentResource.js @@ -1,6 +1,6 @@ const { ResourceType } = require('librechat-data-provider'); const { canAccessResource } = require('./canAccessResource'); -const { getAgent } = require('~/models'); +const { getAgent } = require('~/models/Agent'); /** * Agent ID resolver function diff --git a/api/server/middleware/accessResources/canAccessAgentResource.spec.js b/api/server/middleware/accessResources/canAccessAgentResource.spec.js index 786636ee74..1106390e72 100644 --- a/api/server/middleware/accessResources/canAccessAgentResource.spec.js +++ b/api/server/middleware/accessResources/canAccessAgentResource.spec.js @@ -3,7 +3,7 @@ const { ResourceType, PrincipalType, PrincipalModel } = require('librechat-data- const { MongoMemoryServer } = require('mongodb-memory-server'); const { canAccessAgentResource } = require('./canAccessAgentResource'); const { User, Role, AclEntry } = require('~/db/models'); -const { createAgent } = require('~/models'); +const { createAgent } = require('~/models/Agent'); describe('canAccessAgentResource middleware', () => { let mongoServer; @@ -373,7 +373,7 @@ describe('canAccessAgentResource middleware', () => { jest.clearAllMocks(); // Update the agent - const { updateAgent } = require('~/models'); + const { updateAgent } = require('~/models/Agent'); await updateAgent({ id: agentId }, { description: 'Updated description' }); // Test edit access diff --git a/api/server/middleware/accessResources/canAccessMCPServerResource.spec.js b/api/server/middleware/accessResources/canAccessMCPServerResource.spec.js index 6f7e4ab506..77508be2d1 100644 --- a/api/server/middleware/accessResources/canAccessMCPServerResource.spec.js +++ b/api/server/middleware/accessResources/canAccessMCPServerResource.spec.js @@ -1,9 +1,8 @@ const mongoose = require('mongoose'); const { ResourceType, PrincipalType, PrincipalModel } = require('librechat-data-provider'); -const { SystemCapabilities } = require('@librechat/data-schemas'); const { MongoMemoryServer } = require('mongodb-memory-server'); const { canAccessMCPServerResource } = require('./canAccessMCPServerResource'); -const { User, Role, AclEntry, SystemGrant } = require('~/db/models'); +const { User, Role, AclEntry } = require('~/db/models'); const { createMCPServer } = require('~/models'); describe('canAccessMCPServerResource middleware', () => { @@ -512,7 +511,7 @@ describe('canAccessMCPServerResource middleware', () => { }); }); - test('should allow users with MANAGE_MCP_SERVERS capability to bypass permission checks', async () => { + test('should allow admin users to bypass permission checks', async () => { const { SystemRoles } = require('librechat-data-provider'); // Create an MCP server owned by another user @@ -532,14 +531,6 @@ describe('canAccessMCPServerResource middleware', () => { author: otherUser._id, }); - // Seed MANAGE_MCP_SERVERS capability for the ADMIN role - await SystemGrant.create({ - principalType: PrincipalType.ROLE, - principalId: SystemRoles.ADMIN, - capability: SystemCapabilities.MANAGE_MCP_SERVERS, - grantedAt: new Date(), - }); - // Set user as admin req.user = { id: testUser._id, role: SystemRoles.ADMIN }; req.params.serverName = mcpServer.serverName; diff --git a/api/server/middleware/accessResources/canAccessPromptGroupResource.js b/api/server/middleware/accessResources/canAccessPromptGroupResource.js index 9da1994a77..90aa280772 100644 --- a/api/server/middleware/accessResources/canAccessPromptGroupResource.js +++ b/api/server/middleware/accessResources/canAccessPromptGroupResource.js @@ -1,6 +1,6 @@ const { ResourceType } = require('librechat-data-provider'); const { canAccessResource } = require('./canAccessResource'); -const { getPromptGroup } = require('~/models'); +const { getPromptGroup } = require('~/models/Prompt'); /** * PromptGroup ID resolver function diff --git a/api/server/middleware/accessResources/canAccessPromptViaGroup.js b/api/server/middleware/accessResources/canAccessPromptViaGroup.js index 534db3d6c6..0bb0a804a9 100644 --- a/api/server/middleware/accessResources/canAccessPromptViaGroup.js +++ b/api/server/middleware/accessResources/canAccessPromptViaGroup.js @@ -1,6 +1,6 @@ const { ResourceType } = require('librechat-data-provider'); const { canAccessResource } = require('./canAccessResource'); -const { getPrompt } = require('~/models'); +const { getPrompt } = require('~/models/Prompt'); /** * Prompt to PromptGroup ID resolver function diff --git a/api/server/middleware/accessResources/canAccessResource.js b/api/server/middleware/accessResources/canAccessResource.js index 2431971b2f..c8bd15ffc2 100644 --- a/api/server/middleware/accessResources/canAccessResource.js +++ b/api/server/middleware/accessResources/canAccessResource.js @@ -1,5 +1,5 @@ -const { logger, ResourceCapabilityMap } = require('@librechat/data-schemas'); -const { hasCapability } = require('~/server/middleware/roles/capabilities'); +const { logger } = require('@librechat/data-schemas'); +const { SystemRoles } = require('librechat-data-provider'); const { checkPermission } = require('~/server/services/PermissionService'); /** @@ -71,17 +71,8 @@ const canAccessResource = (options) => { message: 'Authentication required', }); } - const cap = ResourceCapabilityMap[resourceType]; - let hasCap = false; - try { - hasCap = cap != null && (await hasCapability(req.user, cap)); - } catch (err) { - logger.warn(`[canAccessResource] capability check failed, denying bypass: ${err.message}`); - } - if (hasCap) { - logger.debug( - `[canAccessResource] ${cap} bypass for user ${req.user.id} on ${resourceType} ${rawResourceId}`, - ); + // if system admin let through + if (req.user.role === SystemRoles.ADMIN) { return next(); } const userId = req.user.id; diff --git a/api/server/middleware/accessResources/fileAccess.js b/api/server/middleware/accessResources/fileAccess.js index 0f77a61175..25d41e7c02 100644 --- a/api/server/middleware/accessResources/fileAccess.js +++ b/api/server/middleware/accessResources/fileAccess.js @@ -1,7 +1,8 @@ const { logger } = require('@librechat/data-schemas'); const { PermissionBits, hasPermissions, ResourceType } = require('librechat-data-provider'); const { getEffectivePermissions } = require('~/server/services/PermissionService'); -const { getAgents, getFiles } = require('~/models'); +const { getAgents } = require('~/models/Agent'); +const { getFiles } = require('~/models'); /** * Checks if user has access to a file through agent permissions diff --git a/api/server/middleware/accessResources/fileAccess.spec.js b/api/server/middleware/accessResources/fileAccess.spec.js index 72896b0629..cc0d57ac48 100644 --- a/api/server/middleware/accessResources/fileAccess.spec.js +++ b/api/server/middleware/accessResources/fileAccess.spec.js @@ -3,7 +3,8 @@ const { ResourceType, PrincipalType, PrincipalModel } = require('librechat-data- const { MongoMemoryServer } = require('mongodb-memory-server'); const { fileAccess } = require('./fileAccess'); const { User, Role, AclEntry } = require('~/db/models'); -const { createAgent, createFile } = require('~/models'); +const { createAgent } = require('~/models/Agent'); +const { createFile } = require('~/models'); describe('fileAccess middleware', () => { let mongoServer; diff --git a/api/server/middleware/assistants/validateAuthor.js b/api/server/middleware/assistants/validateAuthor.js index 024d6abbe3..03936444e0 100644 --- a/api/server/middleware/assistants/validateAuthor.js +++ b/api/server/middleware/assistants/validateAuthor.js @@ -1,6 +1,5 @@ -const { logger, SystemCapabilities } = require('@librechat/data-schemas'); -const { hasCapability } = require('~/server/middleware/roles/capabilities'); -const { getAssistant } = require('~/models'); +const { SystemRoles } = require('librechat-data-provider'); +const { getAssistant } = require('~/models/Assistant'); /** * Checks if the assistant is supported or excluded @@ -13,6 +12,10 @@ const { getAssistant } = require('~/models'); * @returns {Promise} */ const validateAuthor = async ({ req, openai, overrideEndpoint, overrideAssistantId }) => { + if (req.user.role === SystemRoles.ADMIN) { + return; + } + const endpoint = overrideEndpoint ?? req.body.endpoint ?? req.query.endpoint; const assistant_id = overrideAssistantId ?? req.params.id ?? req.body.assistant_id ?? req.query.assistant_id; @@ -28,18 +31,6 @@ const validateAuthor = async ({ req, openai, overrideEndpoint, overrideAssistant return; } - let canManageAssistants = false; - try { - canManageAssistants = await hasCapability(req.user, SystemCapabilities.MANAGE_ASSISTANTS); - } catch (err) { - logger.warn(`[validateAuthor] capability check failed, denying bypass: ${err.message}`); - } - - if (canManageAssistants) { - logger.debug(`[validateAuthor] MANAGE_ASSISTANTS bypass for user ${req.user.id}`); - return; - } - const assistantDoc = await getAssistant({ assistant_id, user: req.user.id }); if (assistantDoc) { return; diff --git a/api/server/middleware/canDeleteAccount.js b/api/server/middleware/canDeleteAccount.js index 3c08745d76..a913495287 100644 --- a/api/server/middleware/canDeleteAccount.js +++ b/api/server/middleware/canDeleteAccount.js @@ -1,6 +1,6 @@ const { isEnabled } = require('@librechat/api'); -const { logger, SystemCapabilities } = require('@librechat/data-schemas'); -const { hasCapability } = require('~/server/middleware/roles/capabilities'); +const { logger } = require('@librechat/data-schemas'); +const { SystemRoles } = require('librechat-data-provider'); /** * Checks if the user can delete their account @@ -17,29 +17,12 @@ const { hasCapability } = require('~/server/middleware/roles/capabilities'); const canDeleteAccount = async (req, res, next = () => {}) => { const { user } = req; const { ALLOW_ACCOUNT_DELETION = true } = process.env; - if (isEnabled(ALLOW_ACCOUNT_DELETION)) { + if (user?.role === SystemRoles.ADMIN || isEnabled(ALLOW_ACCOUNT_DELETION)) { return next(); + } else { + logger.error(`[User] [Delete Account] [User cannot delete account] [User: ${user?.id}]`); + return res.status(403).send({ message: 'You do not have permission to delete this account' }); } - let hasAdminAccess = false; - if (user) { - try { - const id = user.id ?? user._id?.toString(); - if (id) { - hasAdminAccess = await hasCapability( - { id, role: user.role ?? '', tenantId: user.tenantId }, - SystemCapabilities.ACCESS_ADMIN, - ); - } - } catch (err) { - logger.warn(`[canDeleteAccount] capability check failed, denying: ${err.message}`); - } - } - if (hasAdminAccess) { - logger.debug(`[canDeleteAccount] ACCESS_ADMIN bypass for user ${user.id}`); - return next(); - } - logger.error(`[User] [Delete Account] [User cannot delete account] [User: ${user?.id}]`); - return res.status(403).send({ message: 'You do not have permission to delete this account' }); }; module.exports = canDeleteAccount; diff --git a/api/server/middleware/canDeleteAccount.spec.js b/api/server/middleware/canDeleteAccount.spec.js deleted file mode 100644 index abb888c4a4..0000000000 --- a/api/server/middleware/canDeleteAccount.spec.js +++ /dev/null @@ -1,180 +0,0 @@ -const mongoose = require('mongoose'); -const { MongoMemoryServer } = require('mongodb-memory-server'); -const { SystemRoles, PrincipalType } = require('librechat-data-provider'); -const { SystemCapabilities } = require('@librechat/data-schemas'); - -jest.mock('@librechat/data-schemas', () => ({ - ...jest.requireActual('@librechat/data-schemas'), - logger: { error: jest.fn(), warn: jest.fn(), debug: jest.fn(), info: jest.fn() }, -})); - -jest.mock('~/cache', () => ({ - getLogStores: jest.fn(() => ({ - get: jest.fn(), - set: jest.fn(), - })), -})); - -const { User, SystemGrant } = require('~/db/models'); -const canDeleteAccount = require('./canDeleteAccount'); - -let mongoServer; - -beforeAll(async () => { - mongoServer = await MongoMemoryServer.create(); - await mongoose.connect(mongoServer.getUri()); -}); - -afterAll(async () => { - await mongoose.disconnect(); - await mongoServer.stop(); -}); - -beforeEach(async () => { - await mongoose.connection.dropDatabase(); - delete process.env.ALLOW_ACCOUNT_DELETION; -}); - -const makeRes = () => { - const send = jest.fn(); - const status = jest.fn().mockReturnValue({ send }); - return { status, send }; -}; - -describe('canDeleteAccount', () => { - describe('ALLOW_ACCOUNT_DELETION=true (default)', () => { - it('calls next without hitting the DB', async () => { - process.env.ALLOW_ACCOUNT_DELETION = 'true'; - const next = jest.fn(); - const req = { user: { id: 'user-1', role: SystemRoles.USER } }; - - await canDeleteAccount(req, makeRes(), next); - - expect(next).toHaveBeenCalled(); - }); - - it('skips capability check entirely when deletion is allowed', async () => { - process.env.ALLOW_ACCOUNT_DELETION = 'true'; - const next = jest.fn(); - const req = { user: { id: 'user-1', role: SystemRoles.USER } }; - - await canDeleteAccount(req, makeRes(), next); - - expect(next).toHaveBeenCalled(); - const grantCount = await SystemGrant.countDocuments(); - expect(grantCount).toBe(0); - }); - }); - - describe('ALLOW_ACCOUNT_DELETION=false', () => { - beforeEach(() => { - process.env.ALLOW_ACCOUNT_DELETION = 'false'; - }); - - it('allows admin with ACCESS_ADMIN grant (real DB check)', async () => { - const admin = await User.create({ - name: 'Admin', - email: 'admin@test.com', - password: 'password123', - provider: 'local', - role: SystemRoles.ADMIN, - }); - - await SystemGrant.create({ - principalType: PrincipalType.ROLE, - principalId: SystemRoles.ADMIN, - capability: SystemCapabilities.ACCESS_ADMIN, - grantedAt: new Date(), - }); - - const next = jest.fn(); - const req = { user: { id: admin._id.toString(), role: SystemRoles.ADMIN } }; - - await canDeleteAccount(req, makeRes(), next); - - expect(next).toHaveBeenCalled(); - }); - - it('blocks regular user without ACCESS_ADMIN grant', async () => { - const user = await User.create({ - name: 'Regular', - email: 'user@test.com', - password: 'password123', - provider: 'local', - role: SystemRoles.USER, - }); - - const next = jest.fn(); - const res = makeRes(); - const req = { user: { id: user._id.toString(), role: SystemRoles.USER } }; - - await canDeleteAccount(req, res, next); - - expect(next).not.toHaveBeenCalled(); - expect(res.status).toHaveBeenCalledWith(403); - }); - - it('blocks admin role WITHOUT the ACCESS_ADMIN grant', async () => { - const admin = await User.create({ - name: 'Admin No Grant', - email: 'admin2@test.com', - password: 'password123', - provider: 'local', - role: SystemRoles.ADMIN, - }); - - const next = jest.fn(); - const res = makeRes(); - const req = { user: { id: admin._id.toString(), role: SystemRoles.ADMIN } }; - - await canDeleteAccount(req, res, next); - - expect(next).not.toHaveBeenCalled(); - expect(res.status).toHaveBeenCalledWith(403); - }); - - it('allows user-level grant (not just role-level)', async () => { - const user = await User.create({ - name: 'Privileged User', - email: 'priv@test.com', - password: 'password123', - provider: 'local', - role: SystemRoles.USER, - }); - - await SystemGrant.create({ - principalType: PrincipalType.USER, - principalId: user._id, - capability: SystemCapabilities.ACCESS_ADMIN, - grantedAt: new Date(), - }); - - const next = jest.fn(); - const req = { user: { id: user._id.toString(), role: SystemRoles.USER } }; - - await canDeleteAccount(req, makeRes(), next); - - expect(next).toHaveBeenCalled(); - }); - - it('blocks when user is undefined — does not throw', async () => { - const next = jest.fn(); - const res = makeRes(); - - await canDeleteAccount({ user: undefined }, res, next); - - expect(next).not.toHaveBeenCalled(); - expect(res.status).toHaveBeenCalledWith(403); - }); - - it('blocks when user is null — does not throw', async () => { - const next = jest.fn(); - const res = makeRes(); - - await canDeleteAccount({ user: null }, res, next); - - expect(next).not.toHaveBeenCalled(); - expect(res.status).toHaveBeenCalledWith(403); - }); - }); -}); diff --git a/api/server/middleware/checkBan.js b/api/server/middleware/checkBan.js index 5d1b60297f..79804a84e1 100644 --- a/api/server/middleware/checkBan.js +++ b/api/server/middleware/checkBan.js @@ -1,23 +1,16 @@ 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 { isEnabled, keyvMongo, removePorts } = require('@librechat/api'); -const { getLogStores } = require('~/cache'); +const { removePorts } = require('~/server/utils'); const denyRequest = require('./denyRequest'); +const { getLogStores } = require('~/cache'); 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. * @@ -71,16 +64,25 @@ const checkBan = async (req, res, next = () => {}) => { return next(); } - const useRedis = isEnabled(process.env.USE_REDIS); - const ipKey = getBanCacheKey('ip', req.ip, useRedis); - const userKey = getBanCacheKey('user', userId, useRedis); + let cachedIPBan; + let cachedUserBan; - const [cachedIPBan, cachedUserBan] = await Promise.all([ - ipKey ? banCache.get(ipKey) : undefined, - userKey ? banCache.get(userKey) : undefined, - ]); + let ipKey = ''; + let userKey = ''; - if (cachedIPBan || cachedUserBan) { + 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) { req.banned = true; return await banResponse(req, res); } @@ -92,47 +94,41 @@ const checkBan = async (req, res, next = () => {}) => { return next(); } - const [ipBan, userBan] = await Promise.all([ - req.ip ? banLogs.get(req.ip) : undefined, - userId ? banLogs.get(userId) : undefined, - ]); + let ipBan; + let userBan; - const banData = ipBan || userBan; + if (req.ip) { + ipBan = await banLogs.get(req.ip); + } - if (!banData) { + if (userId) { + userBan = await banLogs.get(userId); + } + + const isBanned = !!(ipBan || userBan); + + if (!isBanned) { return next(); } - const expiresAt = Number(banData.expiresAt); - if (!banData.expiresAt || isNaN(expiresAt)) { - req.banned = true; - return await banResponse(req, res); + const timeLeft = Number(isBanned.expiresAt) - Date.now(); + + if (timeLeft <= 0 && ipKey) { + await banLogs.delete(ipKey); } - 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); + if (timeLeft <= 0 && userKey) { + await banLogs.delete(userKey); return next(); } - const cacheWrites = []; if (ipKey) { - cacheWrites.push(banCache.set(ipKey, banData, timeLeft)); + banCache.set(ipKey, isBanned, timeLeft); } + if (userKey) { - cacheWrites.push(banCache.set(userKey, banData, timeLeft)); + banCache.set(userKey, isBanned, 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/server/middleware/checkDomainAllowed.js b/api/server/middleware/checkDomainAllowed.js index f7a3f00e68..754eb9c127 100644 --- a/api/server/middleware/checkDomainAllowed.js +++ b/api/server/middleware/checkDomainAllowed.js @@ -18,7 +18,6 @@ const checkDomainAllowed = async (req, res, next) => { const email = req?.user?.email; const appConfig = await getAppConfig({ role: req?.user?.role, - tenantId: req?.user?.tenantId, }); if (email && !isEmailDomainAllowed(email, appConfig?.registration?.allowedDomains)) { diff --git a/api/server/middleware/checkInviteUser.js b/api/server/middleware/checkInviteUser.js index 22f2824ffc..42e1faba5b 100644 --- a/api/server/middleware/checkInviteUser.js +++ b/api/server/middleware/checkInviteUser.js @@ -1,8 +1,5 @@ -const { getInvite: getInviteFn } = require('@librechat/api'); -const { createToken, findToken, deleteTokens } = require('~/models'); - -const getInvite = (encodedToken, email) => - getInviteFn(encodedToken, email, { createToken, findToken }); +const { getInvite } = require('~/models/inviteUser'); +const { deleteTokens } = require('~/models'); async function checkInviteUser(req, res, next) { const token = req.body.token; diff --git a/api/server/middleware/checkPeoplePickerAccess.js b/api/server/middleware/checkPeoplePickerAccess.js index 50f137285e..0e604272db 100644 --- a/api/server/middleware/checkPeoplePickerAccess.js +++ b/api/server/middleware/checkPeoplePickerAccess.js @@ -1,21 +1,14 @@ const { logger } = require('@librechat/data-schemas'); const { PrincipalType, PermissionTypes, Permissions } = require('librechat-data-provider'); -const { getRoleByName } = require('~/models'); - -const VALID_PRINCIPAL_TYPES = new Set([ - PrincipalType.USER, - PrincipalType.GROUP, - PrincipalType.ROLE, -]); +const { getRoleByName } = require('~/models/Role'); /** - * Middleware to check if user has permission to access people picker functionality. - * Validates requested principal types via `type` (singular) and `types` (comma-separated or array) - * query parameters against the caller's role permissions: - * - user: requires VIEW_USERS permission - * - group: requires VIEW_GROUPS permission - * - role: requires VIEW_ROLES permission - * - no type filter (mixed search): requires at least one of the above + * Middleware to check if user has permission to access people picker functionality + * Checks specific permission based on the 'type' query parameter: + * - type=user: requires VIEW_USERS permission + * - type=group: requires VIEW_GROUPS permission + * - type=role: requires VIEW_ROLES permission + * - no type (mixed search): requires either VIEW_USERS OR VIEW_GROUPS OR VIEW_ROLES */ const checkPeoplePickerAccess = async (req, res, next) => { try { @@ -35,7 +28,7 @@ const checkPeoplePickerAccess = async (req, res, next) => { }); } - const { type, types } = req.query; + const { type } = req.query; const peoplePickerPerms = role.permissions[PermissionTypes.PEOPLE_PICKER] || {}; const canViewUsers = peoplePickerPerms[Permissions.VIEW_USERS] === true; const canViewGroups = peoplePickerPerms[Permissions.VIEW_GROUPS] === true; @@ -56,32 +49,15 @@ const checkPeoplePickerAccess = async (req, res, next) => { }, }; - const requestedTypes = new Set(); - - if (type && VALID_PRINCIPAL_TYPES.has(type)) { - requestedTypes.add(type); + const check = permissionChecks[type]; + if (check && !check.hasPermission) { + return res.status(403).json({ + error: 'Forbidden', + message: check.message, + }); } - if (types) { - const typesArray = Array.isArray(types) ? types : types.split(','); - for (const t of typesArray) { - if (VALID_PRINCIPAL_TYPES.has(t)) { - requestedTypes.add(t); - } - } - } - - for (const requested of requestedTypes) { - const check = permissionChecks[requested]; - if (!check.hasPermission) { - return res.status(403).json({ - error: 'Forbidden', - message: check.message, - }); - } - } - - if (requestedTypes.size === 0 && !canViewUsers && !canViewGroups && !canViewRoles) { + if (!type && !canViewUsers && !canViewGroups && !canViewRoles) { return res.status(403).json({ error: 'Forbidden', message: 'Insufficient permissions to search for users, groups, or roles', @@ -91,7 +67,7 @@ const checkPeoplePickerAccess = async (req, res, next) => { next(); } catch (error) { logger.error( - `[checkPeoplePickerAccess][${req.user?.id}] error for type=${req.query.type}, types=${req.query.types}`, + `[checkPeoplePickerAccess][${req.user?.id}] checkPeoplePickerAccess error for req.query.type = ${req.query.type}`, error, ); return res.status(500).json({ diff --git a/api/server/middleware/checkPeoplePickerAccess.spec.js b/api/server/middleware/checkPeoplePickerAccess.spec.js index c394bbae65..52bf0e6724 100644 --- a/api/server/middleware/checkPeoplePickerAccess.spec.js +++ b/api/server/middleware/checkPeoplePickerAccess.spec.js @@ -1,9 +1,9 @@ const { logger } = require('@librechat/data-schemas'); const { PrincipalType, PermissionTypes, Permissions } = require('librechat-data-provider'); const { checkPeoplePickerAccess } = require('./checkPeoplePickerAccess'); -const { getRoleByName } = require('~/models'); +const { getRoleByName } = require('~/models/Role'); -jest.mock('~/models'); +jest.mock('~/models/Role'); jest.mock('@librechat/data-schemas', () => ({ ...jest.requireActual('@librechat/data-schemas'), logger: { @@ -173,171 +173,6 @@ describe('checkPeoplePickerAccess', () => { expect(next).not.toHaveBeenCalled(); }); - it('should deny access when using types param to bypass type-specific check', async () => { - req.query.types = PrincipalType.GROUP; - getRoleByName.mockResolvedValue({ - permissions: { - [PermissionTypes.PEOPLE_PICKER]: { - [Permissions.VIEW_USERS]: true, - [Permissions.VIEW_GROUPS]: false, - [Permissions.VIEW_ROLES]: false, - }, - }, - }); - - await checkPeoplePickerAccess(req, res, next); - - expect(res.status).toHaveBeenCalledWith(403); - expect(res.json).toHaveBeenCalledWith({ - error: 'Forbidden', - message: 'Insufficient permissions to search for groups', - }); - expect(next).not.toHaveBeenCalled(); - }); - - it('should deny access when types contains any unpermitted type', async () => { - req.query.types = `${PrincipalType.USER},${PrincipalType.ROLE}`; - getRoleByName.mockResolvedValue({ - permissions: { - [PermissionTypes.PEOPLE_PICKER]: { - [Permissions.VIEW_USERS]: true, - [Permissions.VIEW_GROUPS]: false, - [Permissions.VIEW_ROLES]: false, - }, - }, - }); - - await checkPeoplePickerAccess(req, res, next); - - expect(res.status).toHaveBeenCalledWith(403); - expect(res.json).toHaveBeenCalledWith({ - error: 'Forbidden', - message: 'Insufficient permissions to search for roles', - }); - expect(next).not.toHaveBeenCalled(); - }); - - it('should allow access when all requested types are permitted', async () => { - req.query.types = `${PrincipalType.USER},${PrincipalType.GROUP}`; - getRoleByName.mockResolvedValue({ - permissions: { - [PermissionTypes.PEOPLE_PICKER]: { - [Permissions.VIEW_USERS]: true, - [Permissions.VIEW_GROUPS]: true, - [Permissions.VIEW_ROLES]: false, - }, - }, - }); - - await checkPeoplePickerAccess(req, res, next); - - expect(next).toHaveBeenCalled(); - expect(res.status).not.toHaveBeenCalled(); - }); - - it('should validate types when provided as array (Express qs parsing)', async () => { - req.query.types = [PrincipalType.GROUP, PrincipalType.ROLE]; - getRoleByName.mockResolvedValue({ - permissions: { - [PermissionTypes.PEOPLE_PICKER]: { - [Permissions.VIEW_USERS]: true, - [Permissions.VIEW_GROUPS]: false, - [Permissions.VIEW_ROLES]: true, - }, - }, - }); - - await checkPeoplePickerAccess(req, res, next); - - expect(res.status).toHaveBeenCalledWith(403); - expect(res.json).toHaveBeenCalledWith({ - error: 'Forbidden', - message: 'Insufficient permissions to search for groups', - }); - expect(next).not.toHaveBeenCalled(); - }); - - it('should enforce permissions for combined type and types params', async () => { - req.query.type = PrincipalType.USER; - req.query.types = PrincipalType.GROUP; - getRoleByName.mockResolvedValue({ - permissions: { - [PermissionTypes.PEOPLE_PICKER]: { - [Permissions.VIEW_USERS]: true, - [Permissions.VIEW_GROUPS]: false, - [Permissions.VIEW_ROLES]: false, - }, - }, - }); - - await checkPeoplePickerAccess(req, res, next); - - expect(res.status).toHaveBeenCalledWith(403); - expect(res.json).toHaveBeenCalledWith({ - error: 'Forbidden', - message: 'Insufficient permissions to search for groups', - }); - expect(next).not.toHaveBeenCalled(); - }); - - it('should treat all-invalid types values as mixed search', async () => { - req.query.types = 'foobar'; - getRoleByName.mockResolvedValue({ - permissions: { - [PermissionTypes.PEOPLE_PICKER]: { - [Permissions.VIEW_USERS]: true, - [Permissions.VIEW_GROUPS]: false, - [Permissions.VIEW_ROLES]: false, - }, - }, - }); - - await checkPeoplePickerAccess(req, res, next); - - expect(next).toHaveBeenCalled(); - expect(res.status).not.toHaveBeenCalled(); - }); - - it('should deny when types is empty string and user has no permissions', async () => { - req.query.types = ''; - getRoleByName.mockResolvedValue({ - permissions: { - [PermissionTypes.PEOPLE_PICKER]: { - [Permissions.VIEW_USERS]: false, - [Permissions.VIEW_GROUPS]: false, - [Permissions.VIEW_ROLES]: false, - }, - }, - }); - - await checkPeoplePickerAccess(req, res, next); - - expect(res.status).toHaveBeenCalledWith(403); - expect(res.json).toHaveBeenCalledWith({ - error: 'Forbidden', - message: 'Insufficient permissions to search for users, groups, or roles', - }); - expect(next).not.toHaveBeenCalled(); - }); - - it('should treat types=public as mixed search since PUBLIC is not a searchable principal type', async () => { - req.query.types = PrincipalType.PUBLIC; - getRoleByName.mockResolvedValue({ - permissions: { - [PermissionTypes.PEOPLE_PICKER]: { - [Permissions.VIEW_USERS]: true, - [Permissions.VIEW_GROUPS]: false, - [Permissions.VIEW_ROLES]: false, - }, - }, - }); - - await checkPeoplePickerAccess(req, res, next); - - expect(next).toHaveBeenCalled(); - expect(res.status).not.toHaveBeenCalled(); - }); - it('should allow mixed search when user has at least one permission', async () => { // No type specified = mixed search req.query.type = undefined; @@ -387,7 +222,7 @@ describe('checkPeoplePickerAccess', () => { await checkPeoplePickerAccess(req, res, next); expect(logger.error).toHaveBeenCalledWith( - '[checkPeoplePickerAccess][user123] error for type=undefined, types=undefined', + '[checkPeoplePickerAccess][user123] checkPeoplePickerAccess error for req.query.type = undefined', error, ); expect(res.status).toHaveBeenCalledWith(500); diff --git a/api/server/middleware/checkSharePublicAccess.js b/api/server/middleware/checkSharePublicAccess.js index c7b65a077e..0e95b9f6f8 100644 --- a/api/server/middleware/checkSharePublicAccess.js +++ b/api/server/middleware/checkSharePublicAccess.js @@ -1,6 +1,6 @@ const { logger } = require('@librechat/data-schemas'); const { ResourceType, PermissionTypes, Permissions } = require('librechat-data-provider'); -const { getRoleByName } = require('~/models'); +const { getRoleByName } = require('~/models/Role'); /** * Maps resource types to their corresponding permission types diff --git a/api/server/middleware/checkSharePublicAccess.spec.js b/api/server/middleware/checkSharePublicAccess.spec.js index 605de2049e..c73e71693b 100644 --- a/api/server/middleware/checkSharePublicAccess.spec.js +++ b/api/server/middleware/checkSharePublicAccess.spec.js @@ -1,8 +1,8 @@ const { ResourceType, PermissionTypes, Permissions } = require('librechat-data-provider'); const { checkSharePublicAccess } = require('./checkSharePublicAccess'); -const { getRoleByName } = require('~/models'); +const { getRoleByName } = require('~/models/Role'); -jest.mock('~/models'); +jest.mock('~/models/Role'); describe('checkSharePublicAccess middleware', () => { let mockReq; diff --git a/api/server/middleware/config/app.js b/api/server/middleware/config/app.js index fb5f89b229..bca3c8f71d 100644 --- a/api/server/middleware/config/app.js +++ b/api/server/middleware/config/app.js @@ -4,9 +4,7 @@ const { getAppConfig } = require('~/server/services/Config'); const configMiddleware = async (req, res, next) => { try { const userRole = req.user?.role; - const userId = req.user?.id; - const tenantId = req.user?.tenantId; - req.config = await getAppConfig({ role: userRole, userId, tenantId }); + req.config = await getAppConfig({ role: userRole }); next(); } catch (error) { diff --git a/api/server/middleware/denyRequest.js b/api/server/middleware/denyRequest.js index 86054d0a23..20360519cf 100644 --- a/api/server/middleware/denyRequest.js +++ b/api/server/middleware/denyRequest.js @@ -43,11 +43,7 @@ const denyRequest = async (req, res, errorMessage) => { if (shouldSaveMessage) { await saveMessage( - { - userId: req?.user?.id, - isTemporary: req?.body?.isTemporary, - interfaceConfig: req?.config?.interfaceConfig, - }, + req, { ...userMessage, user: req.user.id }, { context: `api/server/middleware/denyRequest.js - ${responseText}` }, ); diff --git a/api/server/middleware/error.js b/api/server/middleware/error.js index 5fa3562c30..fef7e60ef7 100644 --- a/api/server/middleware/error.js +++ b/api/server/middleware/error.js @@ -2,7 +2,8 @@ const crypto = require('crypto'); const { logger } = require('@librechat/data-schemas'); const { parseConvo } = require('librechat-data-provider'); const { sendEvent, handleError, sanitizeMessageForTransmit } = require('@librechat/api'); -const { saveMessage, getMessages, getConvo } = require('~/models'); +const { saveMessage, getMessages } = require('~/models/Message'); +const { getConvo } = require('~/models/Conversation'); /** * Processes an error with provided options, saves the error message and sends a corresponding SSE response @@ -48,11 +49,7 @@ const sendError = async (req, res, options, callback) => { if (shouldSaveMessage) { await saveMessage( - { - userId: req?.user?.id, - isTemporary: req?.body?.isTemporary, - interfaceConfig: req?.config?.interfaceConfig, - }, + req, { ...errorMessage, user }, { context: 'api/server/utils/streamResponse.js - sendError', diff --git a/api/server/middleware/limiters/forkLimiters.js b/api/server/middleware/limiters/forkLimiters.js index 6d05cedad5..e0aa65700c 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 = () => { @@ -48,7 +48,7 @@ const createForkHandler = (ip = true) => { }; await logViolation(req, res, type, errorMessage, forkViolationScore); - res.status(429).json({ message: 'Too many requests. Try again later' }); + res.status(429).json({ message: 'Too many conversation fork requests. Try again later' }); }; }; @@ -59,7 +59,6 @@ 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 22b7013558..f383e99563 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,7 +60,6 @@ const createImportLimiters = () => { windowMs: importIpWindowMs, max: importIpMax, handler: createImportHandler(), - keyGenerator: removePorts, store: limiterCache('import_ip_limiter'), }; const userLimiterOptions = { @@ -68,7 +67,7 @@ const createImportLimiters = () => { max: importUserMax, handler: createImportHandler(false), keyGenerator: function (req) { - return req.user?.id; + return req.user?.id; // Use the user ID or NULL if not available }, store: limiterCache('import_user_limiter'), }; diff --git a/api/server/middleware/limiters/index.js b/api/server/middleware/limiters/index.js index a38188d2a6..ab110443dc 100644 --- a/api/server/middleware/limiters/index.js +++ b/api/server/middleware/limiters/index.js @@ -8,7 +8,6 @@ const forkLimiters = require('./forkLimiters'); const registerLimiter = require('./registerLimiter'); const toolCallLimiter = require('./toolCallLimiter'); const messageLimiters = require('./messageLimiters'); -const promptUsageLimiter = require('./promptUsageLimiter'); const verifyEmailLimiter = require('./verifyEmailLimiter'); const resetPasswordLimiter = require('./resetPasswordLimiter'); @@ -17,7 +16,6 @@ module.exports = { ...importLimiters, ...messageLimiters, ...forkLimiters, - ...promptUsageLimiter, loginLimiter, registerLimiter, toolCallLimiter, diff --git a/api/server/middleware/limiters/loginLimiter.js b/api/server/middleware/limiters/loginLimiter.js index c178b68a25..eef0c56bfc 100644 --- a/api/server/middleware/limiters/loginLimiter.js +++ b/api/server/middleware/limiters/loginLimiter.js @@ -1,6 +1,7 @@ const rateLimit = require('express-rate-limit'); +const { limiterCache } = require('@librechat/api'); const { ViolationTypes } = require('librechat-data-provider'); -const { limiterCache, removePorts } = require('@librechat/api'); +const { removePorts } = require('~/server/utils'); 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 4f1d72076f..50f4dbc644 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,7 +50,6 @@ const ipLimiterOptions = { windowMs: ipWindowMs, max: ipMax, handler: createHandler(), - keyGenerator: removePorts, store: limiterCache('message_ip_limiter'), }; @@ -59,7 +58,7 @@ const userLimiterOptions = { max: userMax, handler: createHandler(false), keyGenerator: function (req) { - return req.user?.id; + return req.user?.id; // Use the user ID or NULL if not available }, store: limiterCache('message_user_limiter'), }; diff --git a/api/server/middleware/limiters/promptUsageLimiter.js b/api/server/middleware/limiters/promptUsageLimiter.js deleted file mode 100644 index 38bdeed636..0000000000 --- a/api/server/middleware/limiters/promptUsageLimiter.js +++ /dev/null @@ -1,17 +0,0 @@ -const rateLimit = require('express-rate-limit'); -const { limiterCache } = require('@librechat/api'); - -const PROMPT_USAGE_WINDOW_MS = 60 * 1000; // 1 minute -const PROMPT_USAGE_MAX = 30; // 30 usage increments per user per minute - -const promptUsageLimiter = rateLimit({ - windowMs: PROMPT_USAGE_WINDOW_MS, - max: PROMPT_USAGE_MAX, - handler: (_req, res) => { - res.status(429).json({ message: 'Too many prompt usage requests. Try again later' }); - }, - keyGenerator: (req) => req.user?.id, - store: limiterCache('prompt_usage_limiter'), -}); - -module.exports = { promptUsageLimiter }; diff --git a/api/server/middleware/limiters/registerLimiter.js b/api/server/middleware/limiters/registerLimiter.js index 91ea027376..eeebebdb42 100644 --- a/api/server/middleware/limiters/registerLimiter.js +++ b/api/server/middleware/limiters/registerLimiter.js @@ -1,6 +1,7 @@ const rateLimit = require('express-rate-limit'); +const { limiterCache } = require('@librechat/api'); const { ViolationTypes } = require('librechat-data-provider'); -const { limiterCache, removePorts } = require('@librechat/api'); +const { removePorts } = require('~/server/utils'); 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 7feca47ca5..d1dfe52a98 100644 --- a/api/server/middleware/limiters/resetPasswordLimiter.js +++ b/api/server/middleware/limiters/resetPasswordLimiter.js @@ -1,6 +1,7 @@ const rateLimit = require('express-rate-limit'); +const { limiterCache } = require('@librechat/api'); const { ViolationTypes } = require('librechat-data-provider'); -const { limiterCache, removePorts } = require('@librechat/api'); +const { removePorts } = require('~/server/utils'); const { logViolation } = require('~/cache'); const { diff --git a/api/server/middleware/limiters/sttLimiters.js b/api/server/middleware/limiters/sttLimiters.js index ded9040033..f2f47cf680 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,7 +54,6 @@ const createSTTLimiters = () => { windowMs: sttIpWindowMs, max: sttIpMax, handler: createSTTHandler(), - keyGenerator: removePorts, store: limiterCache('stt_ip_limiter'), }; @@ -63,7 +62,7 @@ const createSTTLimiters = () => { max: sttUserMax, handler: createSTTHandler(false), keyGenerator: function (req) { - return req.user?.id; + return req.user?.id; // Use the user ID or NULL if not available }, store: limiterCache('stt_user_limiter'), }; diff --git a/api/server/middleware/limiters/ttsLimiters.js b/api/server/middleware/limiters/ttsLimiters.js index 7ded475230..41dd9a6ba5 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,7 +54,6 @@ const createTTSLimiters = () => { windowMs: ttsIpWindowMs, max: ttsIpMax, handler: createTTSHandler(), - keyGenerator: removePorts, store: limiterCache('tts_ip_limiter'), }; @@ -62,10 +61,10 @@ const createTTSLimiters = () => { windowMs: ttsUserWindowMs, max: ttsUserMax, handler: createTTSHandler(false), - keyGenerator: function (req) { - return req.user?.id; - }, store: limiterCache('tts_user_limiter'), + keyGenerator: function (req) { + return req.user?.id; // Use the user ID or NULL if not available + }, }; const ttsIpLimiter = rateLimit(ipLimiterOptions); diff --git a/api/server/middleware/limiters/uploadLimiters.js b/api/server/middleware/limiters/uploadLimiters.js index 8c878cfa86..df6987877c 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,7 +60,6 @@ const createFileLimiters = () => { windowMs: fileUploadIpWindowMs, max: fileUploadIpMax, handler: createFileUploadHandler(), - keyGenerator: removePorts, store: limiterCache('file_upload_ip_limiter'), }; @@ -69,7 +68,7 @@ const createFileLimiters = () => { max: fileUploadUserMax, handler: createFileUploadHandler(false), keyGenerator: function (req) { - return req.user?.id; + return req.user?.id; // Use the user ID or NULL if not available }, store: limiterCache('file_upload_user_limiter'), }; diff --git a/api/server/middleware/limiters/verifyEmailLimiter.js b/api/server/middleware/limiters/verifyEmailLimiter.js index 5844686bf0..006c4df656 100644 --- a/api/server/middleware/limiters/verifyEmailLimiter.js +++ b/api/server/middleware/limiters/verifyEmailLimiter.js @@ -1,6 +1,7 @@ const rateLimit = require('express-rate-limit'); +const { limiterCache } = require('@librechat/api'); const { ViolationTypes } = require('librechat-data-provider'); -const { limiterCache, removePorts } = require('@librechat/api'); +const { removePorts } = require('~/server/utils'); const { logViolation } = require('~/cache'); const { diff --git a/api/server/middleware/optionalJwtAuth.js b/api/server/middleware/optionalJwtAuth.js index d46478d36e..2f59fdda4a 100644 --- a/api/server/middleware/optionalJwtAuth.js +++ b/api/server/middleware/optionalJwtAuth.js @@ -1,10 +1,9 @@ const cookies = require('cookie'); const passport = require('passport'); -const { isEnabled, tenantContextMiddleware } = require('@librechat/api'); +const { isEnabled } = require('@librechat/api'); // This middleware does not require authentication, -// but if the user is authenticated, it will set the user object -// and establish tenant ALS context. +// but if the user is authenticated, it will set the user object. const optionalJwtAuth = (req, res, next) => { const cookieHeader = req.headers.cookie; const tokenProvider = cookieHeader ? cookies.parse(cookieHeader).token_provider : null; @@ -14,7 +13,6 @@ const optionalJwtAuth = (req, res, next) => { } if (user) { req.user = user; - return tenantContextMiddleware(req, res, next); } next(); }; diff --git a/api/server/middleware/requireJwtAuth.js b/api/server/middleware/requireJwtAuth.js index b13e991b23..16b107aefc 100644 --- a/api/server/middleware/requireJwtAuth.js +++ b/api/server/middleware/requireJwtAuth.js @@ -1,29 +1,20 @@ const cookies = require('cookie'); const passport = require('passport'); -const { isEnabled, tenantContextMiddleware } = require('@librechat/api'); +const { isEnabled } = require('@librechat/api'); /** - * Custom Middleware to handle JWT authentication, with support for OpenID token reuse. - * Switches between JWT and OpenID authentication based on cookies and environment settings. - * - * After successful authentication (req.user populated), automatically chains into - * `tenantContextMiddleware` to propagate `req.user.tenantId` into AsyncLocalStorage - * for downstream Mongoose tenant isolation. + * Custom Middleware to handle JWT authentication, with support for OpenID token reuse + * Switches between JWT and OpenID authentication based on cookies and environment settings */ const requireJwtAuth = (req, res, next) => { const cookieHeader = req.headers.cookie; const tokenProvider = cookieHeader ? cookies.parse(cookieHeader).token_provider : null; - const strategy = - tokenProvider === 'openid' && isEnabled(process.env.OPENID_REUSE_TOKENS) ? 'openidJwt' : 'jwt'; + if (tokenProvider === 'openid' && isEnabled(process.env.OPENID_REUSE_TOKENS)) { + return passport.authenticate('openidJwt', { session: false })(req, res, next); + } - passport.authenticate(strategy, { session: false })(req, res, (err) => { - if (err) { - return next(err); - } - // req.user is now populated by passport — set up tenant ALS context - tenantContextMiddleware(req, res, next); - }); + return passport.authenticate('jwt', { session: false })(req, res, next); }; module.exports = requireJwtAuth; diff --git a/api/server/middleware/roles/access.spec.js b/api/server/middleware/roles/access.spec.js index 16fb6df138..9de840819d 100644 --- a/api/server/middleware/roles/access.spec.js +++ b/api/server/middleware/roles/access.spec.js @@ -2,7 +2,7 @@ const mongoose = require('mongoose'); const { MongoMemoryServer } = require('mongodb-memory-server'); const { checkAccess, generateCheckAccess } = require('@librechat/api'); const { PermissionTypes, Permissions } = require('librechat-data-provider'); -const { getRoleByName } = require('~/models'); +const { getRoleByName } = require('~/models/Role'); const { Role } = require('~/db/models'); // Mock the logger from @librechat/data-schemas diff --git a/api/server/middleware/roles/capabilities.js b/api/server/middleware/roles/capabilities.js deleted file mode 100644 index 6f2aa43e96..0000000000 --- a/api/server/middleware/roles/capabilities.js +++ /dev/null @@ -1,14 +0,0 @@ -const { generateCapabilityCheck, capabilityContextMiddleware } = require('@librechat/api'); -const { getUserPrincipals, hasCapabilityForPrincipals } = require('~/models'); - -const { hasCapability, requireCapability, hasConfigCapability } = generateCapabilityCheck({ - getUserPrincipals, - hasCapabilityForPrincipals, -}); - -module.exports = { - hasCapability, - requireCapability, - hasConfigCapability, - capabilityContextMiddleware, -}; diff --git a/api/server/middleware/roles/index.js b/api/server/middleware/roles/index.js index f97d4b72b4..f01b884e5a 100644 --- a/api/server/middleware/roles/index.js +++ b/api/server/middleware/roles/index.js @@ -1,15 +1,3 @@ -/** - * NOTE: hasCapability, requireCapability, hasConfigCapability, and - * capabilityContextMiddleware are intentionally NOT re-exported here. - * - * capabilities.js depends on ~/models, and the middleware barrel - * (middleware/index.js) is frequently required by modules that are - * themselves loaded while the barrel is still initialising — creating - * a circular-require that silently returns an empty exports object. - * - * Always import capability helpers directly: - * require('~/server/middleware/roles/capabilities') - */ const checkAdmin = require('./admin'); module.exports = { diff --git a/api/server/middleware/validate/convoAccess.js b/api/server/middleware/validate/convoAccess.js index ef1eea8f37..127bfdc530 100644 --- a/api/server/middleware/validate/convoAccess.js +++ b/api/server/middleware/validate/convoAccess.js @@ -1,8 +1,8 @@ const { isEnabled } = require('@librechat/api'); const { Constants, ViolationTypes, Time } = require('librechat-data-provider'); +const { searchConversation } = require('~/models/Conversation'); const denyRequest = require('~/server/middleware/denyRequest'); const { logViolation, getLogStores } = require('~/cache'); -const { searchConversation } = require('~/models'); const { USE_REDIS, CONVO_ACCESS_VIOLATION_SCORE: score = 0 } = process.env ?? {}; diff --git a/api/server/middleware/validateModel.js b/api/server/middleware/validateModel.js index 71a931f0d1..40f6e67bfb 100644 --- a/api/server/middleware/validateModel.js +++ b/api/server/middleware/validateModel.js @@ -1,12 +1,7 @@ const { handleError } = require('@librechat/api'); const { ViolationTypes } = require('librechat-data-provider'); const { getModelsConfig } = require('~/server/controllers/ModelController'); -const { getEndpointsConfig } = require('~/server/services/Config'); const { logViolation } = require('~/cache'); - -const MAX_MODEL_STRING_LENGTH = 256; -const MODEL_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_.:/@+-]*$/; - /** * Validates the model of the request. * @@ -16,27 +11,11 @@ const MODEL_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_.:/@+-]*$/; * @param {Function} next - The Express next function. */ const validateModel = async (req, res, next) => { - const { endpoint } = req.body; - const rawModel = req.body.model; - - if (!rawModel || typeof rawModel !== 'string') { + const { model, endpoint } = req.body; + if (!model) { return handleError(res, { text: 'Model not provided' }); } - const model = rawModel.trim(); - if (!model || model.length > MAX_MODEL_STRING_LENGTH || !MODEL_PATTERN.test(model)) { - return handleError(res, { text: 'Invalid model identifier' }); - } - - req.body.model = model; - - const endpointsConfig = await getEndpointsConfig(req); - const endpointConfig = endpointsConfig?.[endpoint]; - - if (endpointConfig?.userProvide) { - return next(); - } - const modelsConfig = await getModelsConfig(req); if (!modelsConfig) { diff --git a/api/server/routes/__test-utils__/convos-route-mocks.js b/api/server/routes/__test-utils__/convos-route-mocks.js deleted file mode 100644 index 0929e0759d..0000000000 --- a/api/server/routes/__test-utils__/convos-route-mocks.js +++ /dev/null @@ -1,98 +0,0 @@ -module.exports = { - agents: () => ({ sleep: jest.fn() }), - - api: (overrides = {}) => ({ - isEnabled: jest.fn(), - resolveImportMaxFileSize: jest.fn(() => 262144000), - createAxiosInstance: jest.fn(() => ({ - get: jest.fn(), - post: jest.fn(), - put: jest.fn(), - delete: jest.fn(), - })), - logAxiosError: jest.fn(), - ...overrides, - }), - - dataSchemas: () => ({ - logger: { - debug: jest.fn(), - info: jest.fn(), - warn: jest.fn(), - error: jest.fn(), - }, - createModels: jest.fn(() => ({ - User: {}, - Conversation: {}, - Message: {}, - SharedLink: {}, - })), - }), - - dataProvider: (overrides = {}) => ({ - CacheKeys: { GEN_TITLE: 'GEN_TITLE' }, - EModelEndpoint: { - azureAssistants: 'azureAssistants', - assistants: 'assistants', - }, - ...overrides, - }), - - conversationModel: () => ({ - getConvosByCursor: jest.fn(), - getConvo: jest.fn(), - deleteConvos: jest.fn(), - saveConvo: jest.fn(), - }), - - toolCallModel: () => ({ deleteToolCalls: jest.fn() }), - - sharedModels: () => ({ - getConvosByCursor: jest.fn(), - getConvo: jest.fn(), - deleteConvos: jest.fn(), - saveConvo: jest.fn(), - deleteAllSharedLinks: jest.fn(), - deleteConvoSharedLink: jest.fn(), - deleteToolCalls: jest.fn(), - }), - - requireJwtAuth: () => (req, res, next) => next(), - - middlewarePassthrough: () => ({ - createImportLimiters: jest.fn(() => ({ - importIpLimiter: (req, res, next) => next(), - importUserLimiter: (req, res, next) => next(), - })), - createForkLimiters: jest.fn(() => ({ - forkIpLimiter: (req, res, next) => next(), - forkUserLimiter: (req, res, next) => next(), - })), - configMiddleware: (req, res, next) => next(), - validateConvoAccess: (req, res, next) => next(), - }), - - forkUtils: () => ({ - forkConversation: jest.fn(), - duplicateConversation: jest.fn(), - }), - - importUtils: () => ({ importConversations: jest.fn() }), - - logStores: () => jest.fn(), - - multerSetup: () => ({ - storage: {}, - importFileFilter: jest.fn(), - }), - - multerLib: () => - jest.fn(() => ({ - single: jest.fn(() => (req, res, next) => { - req.file = { path: '/tmp/test-file.json' }; - next(); - }), - })), - - assistantEndpoint: () => ({ initializeClient: jest.fn() }), -}; diff --git a/api/server/routes/__tests__/config.spec.js b/api/server/routes/__tests__/config.spec.js index 54315a7798..7d7d3ea13a 100644 --- a/api/server/routes/__tests__/config.spec.js +++ b/api/server/routes/__tests__/config.spec.js @@ -1,73 +1,25 @@ jest.mock('~/cache/getLogStores'); - -const mockGetAppConfig = jest.fn(); -jest.mock('~/server/services/Config/app', () => ({ - getAppConfig: (...args) => mockGetAppConfig(...args), -})); - -jest.mock('~/server/services/Config/ldap', () => ({ - getLdapConfig: jest.fn(() => null), -})); - -const mockGetTenantId = jest.fn(() => undefined); -jest.mock('@librechat/data-schemas', () => ({ - ...jest.requireActual('@librechat/data-schemas'), - getTenantId: (...args) => mockGetTenantId(...args), -})); - const request = require('supertest'); const express = require('express'); const configRoute = require('../config'); - -function createApp(user) { - const app = express(); - app.disable('x-powered-by'); - if (user) { - app.use((req, _res, next) => { - req.user = user; - next(); - }); - } - app.use('/api/config', configRoute); - return app; -} - -const baseAppConfig = { - registration: { socialLogins: ['google', 'github'] }, - interfaceConfig: { - privacyPolicy: { externalUrl: 'https://example.com/privacy' }, - termsOfService: { externalUrl: 'https://example.com/tos' }, - modelSelect: true, - }, - turnstileConfig: { siteKey: 'test-key' }, - modelSpecs: { list: [{ name: 'test-spec' }] }, - webSearch: { searchProvider: 'tavily' }, -}; - -const mockUser = { - id: 'user123', - role: 'USER', - tenantId: undefined, -}; +// file deepcode ignore UseCsurfForExpress/test: test +const app = express(); +app.disable('x-powered-by'); +app.use('/api/config', configRoute); afterEach(() => { - jest.resetAllMocks(); delete process.env.APP_TITLE; - delete process.env.CHECK_BALANCE; - delete process.env.START_BALANCE; - delete process.env.SANDPACK_BUNDLER_URL; - delete process.env.SANDPACK_STATIC_BUNDLER_URL; - delete process.env.CONVERSATION_IMPORT_MAX_FILE_SIZE_BYTES; - delete process.env.ALLOW_REGISTRATION; - delete process.env.ALLOW_SOCIAL_LOGIN; - delete process.env.ALLOW_PASSWORD_RESET; - delete process.env.DOMAIN_SERVER; delete process.env.GOOGLE_CLIENT_ID; delete process.env.GOOGLE_CLIENT_SECRET; + delete process.env.FACEBOOK_CLIENT_ID; + delete process.env.FACEBOOK_CLIENT_SECRET; delete process.env.OPENID_CLIENT_ID; delete process.env.OPENID_CLIENT_SECRET; delete process.env.OPENID_ISSUER; delete process.env.OPENID_SESSION_SECRET; + delete process.env.OPENID_BUTTON_LABEL; + delete process.env.OPENID_AUTO_REDIRECT; + delete process.env.OPENID_AUTH_URL; delete process.env.GITHUB_CLIENT_ID; delete process.env.GITHUB_CLIENT_SECRET; delete process.env.DISCORD_CLIENT_ID; @@ -76,215 +28,78 @@ afterEach(() => { delete process.env.SAML_ISSUER; delete process.env.SAML_CERT; delete process.env.SAML_SESSION_SECRET; + delete process.env.SAML_BUTTON_LABEL; + delete process.env.SAML_IMAGE_URL; + delete process.env.DOMAIN_SERVER; + delete process.env.ALLOW_REGISTRATION; + delete process.env.ALLOW_SOCIAL_LOGIN; + delete process.env.ALLOW_PASSWORD_RESET; + delete process.env.LDAP_URL; + delete process.env.LDAP_BIND_DN; + delete process.env.LDAP_BIND_CREDENTIALS; + delete process.env.LDAP_USER_SEARCH_BASE; + delete process.env.LDAP_SEARCH_FILTER; }); -describe('GET /api/config', () => { - describe('unauthenticated (no req.user)', () => { - it('should call getAppConfig with baseOnly when no tenant context', async () => { - mockGetAppConfig.mockResolvedValue(baseAppConfig); - mockGetTenantId.mockReturnValue(undefined); - const app = createApp(null); +//TODO: This works/passes locally but http request tests fail with 404 in CI. Need to figure out why. - await request(app).get('/api/config'); +describe.skip('GET /', () => { + it('should return 200 and the correct body', async () => { + process.env.APP_TITLE = 'Test Title'; + process.env.GOOGLE_CLIENT_ID = 'Test Google Client Id'; + process.env.GOOGLE_CLIENT_SECRET = 'Test Google Client Secret'; + process.env.FACEBOOK_CLIENT_ID = 'Test Facebook Client Id'; + process.env.FACEBOOK_CLIENT_SECRET = 'Test Facebook Client Secret'; + process.env.OPENID_CLIENT_ID = 'Test OpenID Id'; + process.env.OPENID_CLIENT_SECRET = 'Test OpenID Secret'; + process.env.OPENID_ISSUER = 'Test OpenID Issuer'; + process.env.OPENID_SESSION_SECRET = 'Test Secret'; + process.env.OPENID_BUTTON_LABEL = 'Test OpenID'; + process.env.OPENID_AUTH_URL = 'http://test-server.com'; + process.env.GITHUB_CLIENT_ID = 'Test Github client Id'; + process.env.GITHUB_CLIENT_SECRET = 'Test Github client Secret'; + process.env.DISCORD_CLIENT_ID = 'Test Discord client Id'; + process.env.DISCORD_CLIENT_SECRET = 'Test Discord client Secret'; + process.env.SAML_ENTRY_POINT = 'http://test-server.com'; + process.env.SAML_ISSUER = 'Test SAML Issuer'; + process.env.SAML_CERT = 'saml.pem'; + process.env.SAML_SESSION_SECRET = 'Test Secret'; + process.env.SAML_BUTTON_LABEL = 'Test SAML'; + process.env.SAML_IMAGE_URL = 'http://test-server.com'; + process.env.DOMAIN_SERVER = 'http://test-server.com'; + process.env.ALLOW_REGISTRATION = 'true'; + process.env.ALLOW_SOCIAL_LOGIN = 'true'; + process.env.ALLOW_PASSWORD_RESET = 'true'; + process.env.LDAP_URL = 'Test LDAP URL'; + process.env.LDAP_BIND_DN = 'Test LDAP Bind DN'; + process.env.LDAP_BIND_CREDENTIALS = 'Test LDAP Bind Credentials'; + process.env.LDAP_USER_SEARCH_BASE = 'Test LDAP User Search Base'; + process.env.LDAP_SEARCH_FILTER = 'Test LDAP Search Filter'; - expect(mockGetAppConfig).toHaveBeenCalledWith({ baseOnly: true }); - }); + const response = await request(app).get('/'); - it('should call getAppConfig with tenantId when tenant context is present', async () => { - mockGetAppConfig.mockResolvedValue(baseAppConfig); - mockGetTenantId.mockReturnValue('tenant-abc'); - const app = createApp(null); - - await request(app).get('/api/config'); - - expect(mockGetAppConfig).toHaveBeenCalledWith({ tenantId: 'tenant-abc' }); - }); - - it('should map tenant-scoped config fields in unauthenticated response', async () => { - const tenantConfig = { - ...baseAppConfig, - registration: { socialLogins: ['saml'] }, - turnstileConfig: { siteKey: 'tenant-key' }, - }; - mockGetAppConfig.mockResolvedValue(tenantConfig); - mockGetTenantId.mockReturnValue('tenant-abc'); - const app = createApp(null); - - const response = await request(app).get('/api/config'); - - expect(response.statusCode).toBe(200); - expect(response.body.socialLogins).toEqual(['saml']); - expect(response.body.turnstile).toEqual({ siteKey: 'tenant-key' }); - expect(response.body).not.toHaveProperty('modelSpecs'); - }); - - it('should return minimal payload without authenticated-only fields', async () => { - mockGetAppConfig.mockResolvedValue(baseAppConfig); - const app = createApp(null); - - const response = await request(app).get('/api/config'); - - expect(response.statusCode).toBe(200); - expect(response.body).not.toHaveProperty('modelSpecs'); - expect(response.body).not.toHaveProperty('balance'); - expect(response.body).not.toHaveProperty('webSearch'); - expect(response.body).not.toHaveProperty('bundlerURL'); - expect(response.body).not.toHaveProperty('staticBundlerURL'); - expect(response.body).not.toHaveProperty('sharePointFilePickerEnabled'); - expect(response.body).not.toHaveProperty('conversationImportMaxFileSize'); - }); - - it('should include socialLogins and turnstile from base config', async () => { - mockGetAppConfig.mockResolvedValue(baseAppConfig); - const app = createApp(null); - - const response = await request(app).get('/api/config'); - - expect(response.body.socialLogins).toEqual(['google', 'github']); - expect(response.body.turnstile).toEqual({ siteKey: 'test-key' }); - }); - - it('should include only privacyPolicy and termsOfService from interface config', async () => { - mockGetAppConfig.mockResolvedValue(baseAppConfig); - const app = createApp(null); - - const response = await request(app).get('/api/config'); - - expect(response.body.interface).toEqual({ - privacyPolicy: { externalUrl: 'https://example.com/privacy' }, - termsOfService: { externalUrl: 'https://example.com/tos' }, - }); - expect(response.body.interface).not.toHaveProperty('modelSelect'); - }); - - it('should not include interface if no privacyPolicy or termsOfService', async () => { - mockGetAppConfig.mockResolvedValue({ - ...baseAppConfig, - interfaceConfig: { modelSelect: true }, - }); - const app = createApp(null); - - const response = await request(app).get('/api/config'); - - expect(response.body).not.toHaveProperty('interface'); - }); - - it('should include shared env var fields', async () => { - mockGetAppConfig.mockResolvedValue(baseAppConfig); - process.env.APP_TITLE = 'Test App'; - const app = createApp(null); - - const response = await request(app).get('/api/config'); - - expect(response.body.appTitle).toBe('Test App'); - expect(response.body).toHaveProperty('emailLoginEnabled'); - expect(response.body).toHaveProperty('serverDomain'); - }); - - it('should return 500 when getAppConfig throws', async () => { - mockGetAppConfig.mockRejectedValue(new Error('Config service failure')); - const app = createApp(null); - - const response = await request(app).get('/api/config'); - - expect(response.statusCode).toBe(500); - expect(response.body).toHaveProperty('error'); - }); - }); - - describe('authenticated (req.user exists)', () => { - it('should call getAppConfig with role, userId, and tenantId', async () => { - mockGetAppConfig.mockResolvedValue(baseAppConfig); - mockGetTenantId.mockReturnValue('fallback-tenant'); - const app = createApp(mockUser); - - await request(app).get('/api/config'); - - expect(mockGetAppConfig).toHaveBeenCalledWith({ - role: 'USER', - userId: 'user123', - tenantId: 'fallback-tenant', - }); - }); - - it('should prefer user tenantId over getTenantId fallback', async () => { - mockGetAppConfig.mockResolvedValue(baseAppConfig); - mockGetTenantId.mockReturnValue('fallback-tenant'); - const app = createApp({ ...mockUser, tenantId: 'user-tenant' }); - - await request(app).get('/api/config'); - - expect(mockGetAppConfig).toHaveBeenCalledWith({ - role: 'USER', - userId: 'user123', - tenantId: 'user-tenant', - }); - }); - - it('should include modelSpecs, balance, and webSearch', async () => { - mockGetAppConfig.mockResolvedValue(baseAppConfig); - process.env.CHECK_BALANCE = 'true'; - process.env.START_BALANCE = '10000'; - const app = createApp(mockUser); - - const response = await request(app).get('/api/config'); - - expect(response.body.modelSpecs).toEqual({ list: [{ name: 'test-spec' }] }); - expect(response.body.balance).toEqual({ enabled: true, startBalance: 10000 }); - expect(response.body.webSearch).toEqual({ searchProvider: 'tavily' }); - }); - - it('should include full interface config', async () => { - mockGetAppConfig.mockResolvedValue(baseAppConfig); - const app = createApp(mockUser); - - const response = await request(app).get('/api/config'); - - expect(response.body.interface).toEqual(baseAppConfig.interfaceConfig); - }); - - it('should include authenticated-only env var fields', async () => { - mockGetAppConfig.mockResolvedValue(baseAppConfig); - process.env.SANDPACK_BUNDLER_URL = 'https://bundler.test'; - process.env.SANDPACK_STATIC_BUNDLER_URL = 'https://static-bundler.test'; - process.env.CONVERSATION_IMPORT_MAX_FILE_SIZE_BYTES = '5000000'; - const app = createApp(mockUser); - - const response = await request(app).get('/api/config'); - - expect(response.body.bundlerURL).toBe('https://bundler.test'); - expect(response.body.staticBundlerURL).toBe('https://static-bundler.test'); - expect(response.body.conversationImportMaxFileSize).toBe(5000000); - }); - - it('should merge per-user balance override into config', async () => { - mockGetAppConfig.mockResolvedValue({ - ...baseAppConfig, - balance: { - enabled: true, - startBalance: 50000, - }, - }); - const app = createApp(mockUser); - - const response = await request(app).get('/api/config'); - - expect(response.body.balance).toEqual( - expect.objectContaining({ - enabled: true, - startBalance: 50000, - }), - ); - }); - - it('should return 500 when getAppConfig throws', async () => { - mockGetAppConfig.mockRejectedValue(new Error('Config service failure')); - const app = createApp(mockUser); - - const response = await request(app).get('/api/config'); - - expect(response.statusCode).toBe(500); - expect(response.body).toHaveProperty('error'); + expect(response.statusCode).toBe(200); + expect(response.body).toEqual({ + appTitle: 'Test Title', + socialLogins: ['google', 'facebook', 'openid', 'github', 'discord', 'saml'], + discordLoginEnabled: true, + facebookLoginEnabled: true, + githubLoginEnabled: true, + googleLoginEnabled: true, + openidLoginEnabled: true, + openidLabel: 'Test OpenID', + openidImageUrl: 'http://test-server.com', + samlLoginEnabled: true, + samlLabel: 'Test SAML', + samlImageUrl: 'http://test-server.com', + ldap: { + enabled: true, + }, + serverDomain: 'http://test-server.com', + emailLoginEnabled: 'true', + registrationEnabled: 'true', + passwordResetEnabled: 'true', + socialLoginEnabled: 'true', }); }); }); diff --git a/api/server/routes/__tests__/convos-duplicate-ratelimit.spec.js b/api/server/routes/__tests__/convos-duplicate-ratelimit.spec.js deleted file mode 100644 index a75c11ccba..0000000000 --- a/api/server/routes/__tests__/convos-duplicate-ratelimit.spec.js +++ /dev/null @@ -1,137 +0,0 @@ -const express = require('express'); -const request = require('supertest'); - -const MOCKS = '../__test-utils__/convos-route-mocks'; - -jest.mock('@librechat/agents', () => require(MOCKS).agents()); -jest.mock('@librechat/api', () => require(MOCKS).api({ limiterCache: jest.fn(() => undefined) })); -jest.mock('@librechat/data-schemas', () => require(MOCKS).dataSchemas()); -jest.mock('librechat-data-provider', () => - require(MOCKS).dataProvider({ ViolationTypes: { FILE_UPLOAD_LIMIT: 'file_upload_limit' } }), -); - -jest.mock('~/cache/logViolation', () => jest.fn().mockResolvedValue(undefined)); -jest.mock('~/cache/getLogStores', () => require(MOCKS).logStores()); -jest.mock('~/models', () => ({ - ...require(MOCKS).sharedModels(), - ...require(MOCKS).conversationModel(), - ...require(MOCKS).toolCallModel(), -})); -jest.mock('~/server/middleware/requireJwtAuth', () => require(MOCKS).requireJwtAuth()); - -jest.mock('~/server/middleware', () => { - const { createForkLimiters } = jest.requireActual('~/server/middleware/limiters/forkLimiters'); - return { - createImportLimiters: jest.fn(() => ({ - importIpLimiter: (req, res, next) => next(), - importUserLimiter: (req, res, next) => next(), - })), - createForkLimiters, - configMiddleware: (req, res, next) => next(), - validateConvoAccess: (req, res, next) => next(), - }; -}); - -jest.mock('~/server/utils/import/fork', () => require(MOCKS).forkUtils()); -jest.mock('~/server/utils/import', () => require(MOCKS).importUtils()); -jest.mock('~/server/routes/files/multer', () => require(MOCKS).multerSetup()); -jest.mock('multer', () => require(MOCKS).multerLib()); -jest.mock('~/server/services/Endpoints/azureAssistants', () => require(MOCKS).assistantEndpoint()); -jest.mock('~/server/services/Endpoints/assistants', () => require(MOCKS).assistantEndpoint()); - -describe('POST /api/convos/duplicate - Rate Limiting', () => { - let app; - let duplicateConversation; - const savedEnv = {}; - - beforeAll(() => { - savedEnv.FORK_USER_MAX = process.env.FORK_USER_MAX; - savedEnv.FORK_USER_WINDOW = process.env.FORK_USER_WINDOW; - savedEnv.FORK_IP_MAX = process.env.FORK_IP_MAX; - savedEnv.FORK_IP_WINDOW = process.env.FORK_IP_WINDOW; - }); - - afterAll(() => { - for (const key of Object.keys(savedEnv)) { - if (savedEnv[key] === undefined) { - delete process.env[key]; - } else { - process.env[key] = savedEnv[key]; - } - } - }); - - const setupApp = () => { - jest.clearAllMocks(); - jest.isolateModules(() => { - const convosRouter = require('../convos'); - ({ duplicateConversation } = require('~/server/utils/import/fork')); - - app = express(); - app.use(express.json()); - app.use((req, res, next) => { - req.user = { id: 'rate-limit-test-user' }; - next(); - }); - app.use('/api/convos', convosRouter); - }); - - duplicateConversation.mockResolvedValue({ - conversation: { conversationId: 'duplicated-conv' }, - }); - }; - - describe('user limit', () => { - beforeEach(() => { - process.env.FORK_USER_MAX = '2'; - process.env.FORK_USER_WINDOW = '1'; - process.env.FORK_IP_MAX = '100'; - process.env.FORK_IP_WINDOW = '1'; - setupApp(); - }); - - it('should return 429 after exceeding the user rate limit', async () => { - const userMax = parseInt(process.env.FORK_USER_MAX, 10); - - for (let i = 0; i < userMax; i++) { - const res = await request(app) - .post('/api/convos/duplicate') - .send({ conversationId: 'conv-123' }); - expect(res.status).toBe(201); - } - - const res = await request(app) - .post('/api/convos/duplicate') - .send({ conversationId: 'conv-123' }); - expect(res.status).toBe(429); - expect(res.body.message).toMatch(/too many/i); - }); - }); - - describe('IP limit', () => { - beforeEach(() => { - process.env.FORK_USER_MAX = '100'; - process.env.FORK_USER_WINDOW = '1'; - process.env.FORK_IP_MAX = '2'; - process.env.FORK_IP_WINDOW = '1'; - setupApp(); - }); - - it('should return 429 after exceeding the IP rate limit', async () => { - const ipMax = parseInt(process.env.FORK_IP_MAX, 10); - - for (let i = 0; i < ipMax; i++) { - const res = await request(app) - .post('/api/convos/duplicate') - .send({ conversationId: 'conv-123' }); - expect(res.status).toBe(201); - } - - const res = await request(app) - .post('/api/convos/duplicate') - .send({ conversationId: 'conv-123' }); - expect(res.status).toBe(429); - expect(res.body.message).toMatch(/too many/i); - }); - }); -}); diff --git a/api/server/routes/__tests__/convos-import.spec.js b/api/server/routes/__tests__/convos-import.spec.js deleted file mode 100644 index c4ea139931..0000000000 --- a/api/server/routes/__tests__/convos-import.spec.js +++ /dev/null @@ -1,98 +0,0 @@ -const express = require('express'); -const request = require('supertest'); -const multer = require('multer'); - -const importFileFilter = (req, file, cb) => { - if (file.mimetype === 'application/json') { - cb(null, true); - } else { - cb(new Error('Only JSON files are allowed'), false); - } -}; - -/** Proxy app that mirrors the production multer + error-handling pattern */ -function createImportApp(fileSize) { - const app = express(); - const upload = multer({ - storage: multer.memoryStorage(), - fileFilter: importFileFilter, - limits: { fileSize }, - }); - const uploadSingle = upload.single('file'); - - function handleUpload(req, res, next) { - uploadSingle(req, res, (err) => { - if (err && err.code === 'LIMIT_FILE_SIZE') { - return res.status(413).json({ message: 'File exceeds the maximum allowed size' }); - } - if (err) { - return next(err); - } - next(); - }); - } - - app.post('/import', handleUpload, (req, res) => { - res.status(201).json({ message: 'success', size: req.file.size }); - }); - - app.use((err, _req, res, _next) => { - res.status(400).json({ error: err.message }); - }); - - return app; -} - -describe('Conversation Import - Multer File Size Limits', () => { - describe('multer rejects files exceeding the configured limit', () => { - it('returns 413 for files larger than the limit', async () => { - const limit = 1024; - const app = createImportApp(limit); - const oversized = Buffer.alloc(limit + 512, 'x'); - - const res = await request(app) - .post('/import') - .attach('file', oversized, { filename: 'import.json', contentType: 'application/json' }); - - expect(res.status).toBe(413); - expect(res.body.message).toBe('File exceeds the maximum allowed size'); - }); - - it('accepts files within the limit', async () => { - const limit = 4096; - const app = createImportApp(limit); - const valid = Buffer.from(JSON.stringify({ title: 'test' })); - - const res = await request(app) - .post('/import') - .attach('file', valid, { filename: 'import.json', contentType: 'application/json' }); - - expect(res.status).toBe(201); - expect(res.body.message).toBe('success'); - }); - - it('rejects at the exact boundary (limit + 1 byte)', async () => { - const limit = 512; - const app = createImportApp(limit); - const boundary = Buffer.alloc(limit + 1, 'a'); - - const res = await request(app) - .post('/import') - .attach('file', boundary, { filename: 'import.json', contentType: 'application/json' }); - - expect(res.status).toBe(413); - }); - - it('accepts a file just under the limit', async () => { - const limit = 512; - const app = createImportApp(limit); - const underLimit = Buffer.alloc(limit - 1, 'b'); - - const res = await request(app) - .post('/import') - .attach('file', underLimit, { filename: 'import.json', contentType: 'application/json' }); - - expect(res.status).toBe(201); - }); - }); -}); diff --git a/api/server/routes/__tests__/convos.spec.js b/api/server/routes/__tests__/convos.spec.js index 23978f28e9..931ef006d0 100644 --- a/api/server/routes/__tests__/convos.spec.js +++ b/api/server/routes/__tests__/convos.spec.js @@ -1,33 +1,116 @@ const express = require('express'); const request = require('supertest'); -const MOCKS = '../__test-utils__/convos-route-mocks'; +jest.mock('@librechat/agents', () => ({ + sleep: jest.fn(), +})); -jest.mock('@librechat/agents', () => require(MOCKS).agents()); -jest.mock('@librechat/api', () => require(MOCKS).api()); -jest.mock('@librechat/data-schemas', () => require(MOCKS).dataSchemas()); -jest.mock('librechat-data-provider', () => require(MOCKS).dataProvider()); -jest.mock('~/models', () => require(MOCKS).sharedModels()); -jest.mock('~/server/middleware/requireJwtAuth', () => require(MOCKS).requireJwtAuth()); -jest.mock('~/server/middleware', () => require(MOCKS).middlewarePassthrough()); -jest.mock('~/server/utils/import/fork', () => require(MOCKS).forkUtils()); -jest.mock('~/server/utils/import', () => require(MOCKS).importUtils()); -jest.mock('~/cache/getLogStores', () => require(MOCKS).logStores()); -jest.mock('~/server/routes/files/multer', () => require(MOCKS).multerSetup()); -jest.mock('multer', () => require(MOCKS).multerLib()); -jest.mock('~/server/services/Endpoints/azureAssistants', () => require(MOCKS).assistantEndpoint()); -jest.mock('~/server/services/Endpoints/assistants', () => require(MOCKS).assistantEndpoint()); +jest.mock('@librechat/api', () => ({ + isEnabled: jest.fn(), + createAxiosInstance: jest.fn(() => ({ + get: jest.fn(), + post: jest.fn(), + put: jest.fn(), + delete: jest.fn(), + })), + logAxiosError: jest.fn(), +})); + +jest.mock('@librechat/data-schemas', () => ({ + logger: { + debug: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }, + createModels: jest.fn(() => ({ + User: {}, + Conversation: {}, + Message: {}, + SharedLink: {}, + })), +})); + +jest.mock('~/models/Conversation', () => ({ + getConvosByCursor: jest.fn(), + getConvo: jest.fn(), + deleteConvos: jest.fn(), + saveConvo: jest.fn(), +})); + +jest.mock('~/models/ToolCall', () => ({ + deleteToolCalls: jest.fn(), +})); + +jest.mock('~/models', () => ({ + deleteAllSharedLinks: jest.fn(), + deleteConvoSharedLink: jest.fn(), +})); + +jest.mock('~/server/middleware/requireJwtAuth', () => (req, res, next) => next()); + +jest.mock('~/server/middleware', () => ({ + createImportLimiters: jest.fn(() => ({ + importIpLimiter: (req, res, next) => next(), + importUserLimiter: (req, res, next) => next(), + })), + createForkLimiters: jest.fn(() => ({ + forkIpLimiter: (req, res, next) => next(), + forkUserLimiter: (req, res, next) => next(), + })), + configMiddleware: (req, res, next) => next(), + validateConvoAccess: (req, res, next) => next(), +})); + +jest.mock('~/server/utils/import/fork', () => ({ + forkConversation: jest.fn(), + duplicateConversation: jest.fn(), +})); + +jest.mock('~/server/utils/import', () => ({ + importConversations: jest.fn(), +})); + +jest.mock('~/cache/getLogStores', () => jest.fn()); + +jest.mock('~/server/routes/files/multer', () => ({ + storage: {}, + importFileFilter: jest.fn(), +})); + +jest.mock('multer', () => { + return jest.fn(() => ({ + single: jest.fn(() => (req, res, next) => { + req.file = { path: '/tmp/test-file.json' }; + next(); + }), + })); +}); + +jest.mock('librechat-data-provider', () => ({ + CacheKeys: { + GEN_TITLE: 'GEN_TITLE', + }, + EModelEndpoint: { + azureAssistants: 'azureAssistants', + assistants: 'assistants', + }, +})); + +jest.mock('~/server/services/Endpoints/azureAssistants', () => ({ + initializeClient: jest.fn(), +})); + +jest.mock('~/server/services/Endpoints/assistants', () => ({ + initializeClient: jest.fn(), +})); describe('Convos Routes', () => { let app; let convosRouter; - const { - deleteAllSharedLinks, - deleteConvoSharedLink, - deleteToolCalls, - deleteConvos, - saveConvo, - } = require('~/models'); + const { deleteAllSharedLinks, deleteConvoSharedLink } = require('~/models'); + const { deleteConvos, saveConvo } = require('~/models/Conversation'); + const { deleteToolCalls } = require('~/models/ToolCall'); beforeAll(() => { convosRouter = require('../convos'); @@ -437,7 +520,7 @@ describe('Convos Routes', () => { expect(response.status).toBe(200); expect(response.body).toEqual(mockArchivedConvo); expect(saveConvo).toHaveBeenCalledWith( - expect.objectContaining({ userId: 'test-user-123' }), + expect.objectContaining({ user: { id: 'test-user-123' } }), { conversationId: mockConversationId, isArchived: true }, { context: `POST /api/convos/archive ${mockConversationId}` }, ); @@ -466,7 +549,7 @@ describe('Convos Routes', () => { expect(response.status).toBe(200); expect(response.body).toEqual(mockUnarchivedConvo); expect(saveConvo).toHaveBeenCalledWith( - expect.objectContaining({ userId: 'test-user-123' }), + expect.objectContaining({ user: { id: 'test-user-123' } }), { conversationId: mockConversationId, isArchived: false }, { context: `POST /api/convos/archive ${mockConversationId}` }, ); diff --git a/api/server/routes/__tests__/grants.spec.js b/api/server/routes/__tests__/grants.spec.js deleted file mode 100644 index c7b5b6bdda..0000000000 --- a/api/server/routes/__tests__/grants.spec.js +++ /dev/null @@ -1,185 +0,0 @@ -const express = require('express'); -const request = require('supertest'); -const mongoose = require('mongoose'); -const { MongoMemoryServer } = require('mongodb-memory-server'); -const { createModels, createMethods } = require('@librechat/data-schemas'); -const { PrincipalType, SystemRoles } = require('librechat-data-provider'); - -/** - * Integration test for the admin grants routes. - * - * Validates the full Express wiring: route registration → middleware → - * handler → real MongoDB. Auth middleware is injected (matching the repo - * pattern in keys.spec.js) so we can control the caller identity without - * a real JWT, while the handler DI deps use real DB methods. - */ - -jest.mock('~/server/middleware', () => ({ - requireJwtAuth: (_req, _res, next) => next(), -})); - -jest.mock('~/server/middleware/roles/capabilities', () => ({ - requireCapability: () => (_req, _res, next) => next(), -})); - -let mongoServer; -let db; - -beforeAll(async () => { - mongoServer = await MongoMemoryServer.create(); - await mongoose.connect(mongoServer.getUri()); - createModels(mongoose); - db = createMethods(mongoose); - await db.seedSystemGrants(); - await db.initializeRoles(); - await db.seedDefaultRoles(); -}); - -afterAll(async () => { - await mongoose.disconnect(); - await mongoServer.stop(); -}); - -afterEach(async () => { - const SystemGrant = mongoose.models.SystemGrant; - // Clean non-seed grants (keep admin seed) - await SystemGrant.deleteMany({ - $or: [ - { principalId: { $ne: SystemRoles.ADMIN } }, - { principalType: { $ne: PrincipalType.ROLE } }, - ], - }); -}); - -function createApp(user) { - const { createAdminGrantsHandlers, getCachedPrincipals } = require('@librechat/api'); - - const handlers = createAdminGrantsHandlers({ - listGrants: db.listGrants, - countGrants: db.countGrants, - getCapabilitiesForPrincipal: db.getCapabilitiesForPrincipal, - getCapabilitiesForPrincipals: db.getCapabilitiesForPrincipals, - grantCapability: db.grantCapability, - revokeCapability: db.revokeCapability, - getUserPrincipals: db.getUserPrincipals, - hasCapabilityForPrincipals: db.hasCapabilityForPrincipals, - getHeldCapabilities: db.getHeldCapabilities, - getCachedPrincipals, - checkRoleExists: async (name) => (await db.getRoleByName(name)) != null, - }); - - const app = express(); - app.use(express.json()); - app.use((req, _res, next) => { - req.user = user; - next(); - }); - - const router = express.Router(); - router.get('/', handlers.listGrants); - router.get('/effective', handlers.getEffectiveCapabilities); - router.get('/:principalType/:principalId', handlers.getPrincipalGrants); - router.post('/', handlers.assignGrant); - router.delete('/:principalType/:principalId/:capability', handlers.revokeGrant); - app.use('/api/admin/grants', router); - - return app; -} - -describe('Admin Grants Routes — Integration', () => { - const adminUserId = new mongoose.Types.ObjectId(); - const adminUser = { - _id: adminUserId, - id: adminUserId.toString(), - role: SystemRoles.ADMIN, - }; - - it('GET / returns seeded admin grants', async () => { - const app = createApp(adminUser); - const res = await request(app).get('/api/admin/grants').expect(200); - - expect(res.body).toHaveProperty('grants'); - expect(res.body).toHaveProperty('total'); - expect(res.body.grants.length).toBeGreaterThan(0); - // Seeded grants are for the ADMIN role - expect(res.body.grants[0].principalType).toBe(PrincipalType.ROLE); - }); - - it('GET /effective returns capabilities for admin', async () => { - const app = createApp(adminUser); - const res = await request(app).get('/api/admin/grants/effective').expect(200); - - expect(res.body).toHaveProperty('capabilities'); - expect(res.body.capabilities).toContain('access:admin'); - expect(res.body.capabilities).toContain('manage:roles'); - }); - - it('POST / assigns a grant and DELETE / revokes it', async () => { - const app = createApp(adminUser); - - // Assign - const assignRes = await request(app) - .post('/api/admin/grants') - .send({ - principalType: PrincipalType.ROLE, - principalId: SystemRoles.USER, - capability: 'read:users', - }) - .expect(201); - - expect(assignRes.body.grant).toMatchObject({ - principalType: PrincipalType.ROLE, - principalId: SystemRoles.USER, - capability: 'read:users', - }); - - // Verify via GET - const getRes = await request(app) - .get(`/api/admin/grants/${PrincipalType.ROLE}/${SystemRoles.USER}`) - .expect(200); - - expect(getRes.body.grants.some((g) => g.capability === 'read:users')).toBe(true); - - // Revoke - await request(app) - .delete(`/api/admin/grants/${PrincipalType.ROLE}/${SystemRoles.USER}/read:users`) - .expect(200); - - // Verify revoked - const afterRes = await request(app) - .get(`/api/admin/grants/${PrincipalType.ROLE}/${SystemRoles.USER}`) - .expect(200); - - expect(afterRes.body.grants.some((g) => g.capability === 'read:users')).toBe(false); - }); - - it('POST / returns 400 for non-existent role when checkRoleExists is wired', async () => { - const app = createApp(adminUser); - - const res = await request(app) - .post('/api/admin/grants') - .send({ - principalType: PrincipalType.ROLE, - principalId: 'nonexistent-role', - capability: 'read:users', - }) - .expect(400); - - expect(res.body.error).toBe('Role not found'); - }); - - it('POST / returns 401 without authenticated user', async () => { - const app = createApp(undefined); - - const res = await request(app) - .post('/api/admin/grants') - .send({ - principalType: PrincipalType.ROLE, - principalId: SystemRoles.USER, - capability: 'read:users', - }) - .expect(401); - - expect(res.body).toHaveProperty('error', 'Authentication required'); - }); -}); diff --git a/api/server/routes/__tests__/mcp.spec.js b/api/server/routes/__tests__/mcp.spec.js index f194f361d3..e87fcf8f15 100644 --- a/api/server/routes/__tests__/mcp.spec.js +++ b/api/server/routes/__tests__/mcp.spec.js @@ -18,7 +18,6 @@ const mockRegistryInstance = { getServerConfig: jest.fn(), getOAuthServers: jest.fn(), getAllServerConfigs: jest.fn(), - ensureConfigServers: jest.fn().mockResolvedValue({}), addServer: jest.fn(), updateServer: jest.fn(), removeServer: jest.fn(), @@ -33,9 +32,6 @@ jest.mock('@librechat/api', () => { getFlowState: jest.fn(), completeOAuthFlow: jest.fn(), generateFlowId: jest.fn(), - resolveStateToFlowId: jest.fn(async (state) => state), - storeStateMapping: jest.fn(), - deleteStateMapping: jest.fn(), }, MCPTokenStorage: { storeTokens: jest.fn(), @@ -59,7 +55,6 @@ jest.mock('@librechat/api', () => { }); jest.mock('@librechat/data-schemas', () => ({ - getTenantId: jest.fn(), logger: { debug: jest.fn(), info: jest.fn(), @@ -95,18 +90,14 @@ jest.mock('~/server/services/Config', () => ({ getCachedTools: jest.fn(), getMCPServerTools: jest.fn(), loadCustomConfig: jest.fn(), - getAppConfig: jest.fn().mockResolvedValue({ mcpConfig: {} }), })); jest.mock('~/server/services/Config/mcp', () => ({ updateMCPServerTools: jest.fn(), })); -const mockResolveAllMcpConfigs = jest.fn().mockResolvedValue({}); jest.mock('~/server/services/MCP', () => ({ getMCPSetupData: jest.fn(), - resolveConfigServers: jest.fn().mockResolvedValue({}), - resolveAllMcpConfigs: (...args) => mockResolveAllMcpConfigs(...args), getServerConnectionStatus: jest.fn(), })); @@ -189,10 +180,7 @@ describe('MCP Routes', () => { MCPOAuthHandler.initiateOAuthFlow.mockResolvedValue({ authorizationUrl: 'https://oauth.example.com/auth', flowId: 'test-user-id:test-server', - flowMetadata: { state: 'random-state-value' }, }); - MCPOAuthHandler.storeStateMapping.mockResolvedValue(); - mockFlowManager.initFlow = jest.fn().mockResolvedValue(); const response = await request(app).get('/api/mcp/test-server/oauth/initiate').query({ userId: 'test-user-id', @@ -379,121 +367,6 @@ describe('MCP Routes', () => { expect(response.headers.location).toBe(`${basePath}/oauth/error?error=invalid_state`); }); - describe('CSRF fallback via active PENDING flow', () => { - it('should proceed when a fresh PENDING flow exists and no cookies are present', async () => { - const flowId = 'test-user-id:test-server'; - const mockFlowManager = { - getFlowState: jest.fn().mockResolvedValue({ - status: 'PENDING', - createdAt: Date.now(), - }), - completeFlow: jest.fn().mockResolvedValue(true), - deleteFlow: jest.fn().mockResolvedValue(true), - }; - const mockFlowState = { - serverName: 'test-server', - userId: 'test-user-id', - metadata: {}, - clientInfo: {}, - codeVerifier: 'test-verifier', - }; - - getLogStores.mockReturnValue({}); - require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager); - MCPOAuthHandler.getFlowState.mockResolvedValue(mockFlowState); - MCPOAuthHandler.completeOAuthFlow.mockResolvedValue({ - access_token: 'test-token', - }); - MCPTokenStorage.storeTokens.mockResolvedValue(); - mockRegistryInstance.getServerConfig.mockResolvedValue({}); - - const mockMcpManager = { - getUserConnection: jest.fn().mockResolvedValue({ - fetchTools: jest.fn().mockResolvedValue([]), - }), - }; - require('~/config').getMCPManager.mockReturnValue(mockMcpManager); - require('~/config').getOAuthReconnectionManager.mockReturnValue({ - clearReconnection: jest.fn(), - }); - require('~/server/services/Config/mcp').updateMCPServerTools.mockResolvedValue(); - - const response = await request(app) - .get('/api/mcp/test-server/oauth/callback') - .query({ code: 'test-code', state: flowId }); - - const basePath = getBasePath(); - expect(response.status).toBe(302); - expect(response.headers.location).toContain(`${basePath}/oauth/success`); - }); - - it('should reject when no PENDING flow exists and no cookies are present', async () => { - const flowId = 'test-user-id:test-server'; - const mockFlowManager = { - getFlowState: jest.fn().mockResolvedValue(null), - }; - - getLogStores.mockReturnValue({}); - require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager); - - const response = await request(app) - .get('/api/mcp/test-server/oauth/callback') - .query({ code: 'test-code', state: flowId }); - - const basePath = getBasePath(); - expect(response.status).toBe(302); - expect(response.headers.location).toBe( - `${basePath}/oauth/error?error=csrf_validation_failed`, - ); - }); - - it('should reject when only a COMPLETED flow exists (not PENDING)', async () => { - const flowId = 'test-user-id:test-server'; - const mockFlowManager = { - getFlowState: jest.fn().mockResolvedValue({ - status: 'COMPLETED', - createdAt: Date.now(), - }), - }; - - getLogStores.mockReturnValue({}); - require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager); - - const response = await request(app) - .get('/api/mcp/test-server/oauth/callback') - .query({ code: 'test-code', state: flowId }); - - const basePath = getBasePath(); - expect(response.status).toBe(302); - expect(response.headers.location).toBe( - `${basePath}/oauth/error?error=csrf_validation_failed`, - ); - }); - - it('should reject when PENDING flow is stale (older than PENDING_STALE_MS)', async () => { - const flowId = 'test-user-id:test-server'; - const mockFlowManager = { - getFlowState: jest.fn().mockResolvedValue({ - status: 'PENDING', - createdAt: Date.now() - 3 * 60 * 1000, - }), - }; - - getLogStores.mockReturnValue({}); - require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager); - - const response = await request(app) - .get('/api/mcp/test-server/oauth/callback') - .query({ code: 'test-code', state: flowId }); - - const basePath = getBasePath(); - expect(response.status).toBe(302); - expect(response.headers.location).toBe( - `${basePath}/oauth/error?error=csrf_validation_failed`, - ); - }); - }); - it('should handle OAuth callback successfully', async () => { // mockRegistryInstance is defined at the top of the file const mockFlowManager = { @@ -585,112 +458,6 @@ describe('MCP Routes', () => { ); }); - it('should use oauthHeaders from flow state when present', async () => { - const mockFlowManager = { - getFlowState: jest.fn().mockResolvedValue({ status: 'PENDING' }), - completeFlow: jest.fn().mockResolvedValue(), - deleteFlow: jest.fn().mockResolvedValue(true), - }; - const mockFlowState = { - serverName: 'test-server', - userId: 'test-user-id', - metadata: { toolFlowId: 'tool-flow-123' }, - clientInfo: {}, - codeVerifier: 'test-verifier', - oauthHeaders: { 'X-Custom-Auth': 'header-value' }, - }; - const mockTokens = { access_token: 'tok', refresh_token: 'ref' }; - - MCPOAuthHandler.getFlowState.mockResolvedValue(mockFlowState); - MCPOAuthHandler.completeOAuthFlow.mockResolvedValue(mockTokens); - MCPTokenStorage.storeTokens.mockResolvedValue(); - getLogStores.mockReturnValue({}); - require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager); - require('~/config').getOAuthReconnectionManager.mockReturnValue({ - clearReconnection: jest.fn(), - }); - require('~/config').getMCPManager.mockReturnValue({ - getUserConnection: jest.fn().mockResolvedValue({ - fetchTools: jest.fn().mockResolvedValue([]), - }), - }); - const { getCachedTools, setCachedTools } = require('~/server/services/Config'); - getCachedTools.mockResolvedValue({}); - setCachedTools.mockResolvedValue(); - - const flowId = 'test-user-id:test-server'; - const csrfToken = generateTestCsrfToken(flowId); - - await request(app) - .get('/api/mcp/test-server/oauth/callback') - .set('Cookie', [`oauth_csrf=${csrfToken}`]) - .query({ code: 'auth-code', state: flowId }); - - expect(MCPOAuthHandler.completeOAuthFlow).toHaveBeenCalledWith( - flowId, - 'auth-code', - mockFlowManager, - { 'X-Custom-Auth': 'header-value' }, - ); - expect(mockRegistryInstance.getServerConfig).not.toHaveBeenCalled(); - }); - - it('should fall back to registry oauth_headers when flow state lacks them', async () => { - const mockFlowManager = { - getFlowState: jest.fn().mockResolvedValue({ status: 'PENDING' }), - completeFlow: jest.fn().mockResolvedValue(), - deleteFlow: jest.fn().mockResolvedValue(true), - }; - const mockFlowState = { - serverName: 'test-server', - userId: 'test-user-id', - metadata: { toolFlowId: 'tool-flow-123' }, - clientInfo: {}, - codeVerifier: 'test-verifier', - }; - const mockTokens = { access_token: 'tok', refresh_token: 'ref' }; - - MCPOAuthHandler.getFlowState.mockResolvedValue(mockFlowState); - MCPOAuthHandler.completeOAuthFlow.mockResolvedValue(mockTokens); - MCPTokenStorage.storeTokens.mockResolvedValue(); - mockRegistryInstance.getServerConfig.mockResolvedValue({ - oauth_headers: { 'X-Registry-Header': 'from-registry' }, - }); - getLogStores.mockReturnValue({}); - require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager); - require('~/config').getOAuthReconnectionManager.mockReturnValue({ - clearReconnection: jest.fn(), - }); - require('~/config').getMCPManager.mockReturnValue({ - getUserConnection: jest.fn().mockResolvedValue({ - fetchTools: jest.fn().mockResolvedValue([]), - }), - }); - const { getCachedTools, setCachedTools } = require('~/server/services/Config'); - getCachedTools.mockResolvedValue({}); - setCachedTools.mockResolvedValue(); - - const flowId = 'test-user-id:test-server'; - const csrfToken = generateTestCsrfToken(flowId); - - await request(app) - .get('/api/mcp/test-server/oauth/callback') - .set('Cookie', [`oauth_csrf=${csrfToken}`]) - .query({ code: 'auth-code', state: flowId }); - - expect(MCPOAuthHandler.completeOAuthFlow).toHaveBeenCalledWith( - flowId, - 'auth-code', - mockFlowManager, - { 'X-Registry-Header': 'from-registry' }, - ); - expect(mockRegistryInstance.getServerConfig).toHaveBeenCalledWith( - 'test-server', - 'test-user-id', - undefined, - ); - }); - it('should redirect to error page when callback processing fails', async () => { MCPOAuthHandler.getFlowState.mockRejectedValue(new Error('Callback error')); const flowId = 'test-user-id:test-server'; @@ -1462,10 +1229,19 @@ describe('MCP Routes', () => { }, }); - expect(getMCPSetupData).toHaveBeenCalledWith('test-user-id', expect.any(Object)); + expect(getMCPSetupData).toHaveBeenCalledWith('test-user-id'); expect(getServerConnectionStatus).toHaveBeenCalledTimes(2); }); + it('should return 404 when MCP config is not found', async () => { + getMCPSetupData.mockRejectedValue(new Error('MCP config not found')); + + const response = await request(app).get('/api/mcp/connection/status'); + + expect(response.status).toBe(404); + expect(response.body).toEqual({ error: 'MCP config not found' }); + }); + it('should return 500 when connection status check fails', async () => { getMCPSetupData.mockRejectedValue(new Error('Database error')); @@ -1540,6 +1316,15 @@ describe('MCP Routes', () => { }); }); + it('should return 404 when MCP config is not found', async () => { + getMCPSetupData.mockRejectedValue(new Error('MCP config not found')); + + const response = await request(app).get('/api/mcp/connection/status/test-server'); + + expect(response.status).toBe(404); + expect(response.body).toEqual({ error: 'MCP config not found' }); + }); + it('should return 500 when connection status check fails', async () => { getMCPSetupData.mockRejectedValue(new Error('Database connection failed')); @@ -1787,42 +1572,26 @@ describe('MCP Routes', () => { it('should return all server configs for authenticated user', async () => { const mockServerConfigs = { 'server-1': { - type: 'sse', - url: 'http://server1.com/sse', - title: 'Server 1', + endpoint: 'http://server1.com', + name: 'Server 1', }, 'server-2': { - type: 'sse', - url: 'http://server2.com/sse', - title: 'Server 2', + endpoint: 'http://server2.com', + name: 'Server 2', }, }; - mockResolveAllMcpConfigs.mockResolvedValue(mockServerConfigs); + mockRegistryInstance.getAllServerConfigs.mockResolvedValue(mockServerConfigs); const response = await request(app).get('/api/mcp/servers'); expect(response.status).toBe(200); - expect(response.body['server-1']).toMatchObject({ - type: 'sse', - url: 'http://server1.com/sse', - title: 'Server 1', - }); - expect(response.body['server-2']).toMatchObject({ - type: 'sse', - url: 'http://server2.com/sse', - title: 'Server 2', - }); - expect(response.body['server-1'].headers).toBeUndefined(); - expect(response.body['server-2'].headers).toBeUndefined(); - expect(mockResolveAllMcpConfigs).toHaveBeenCalledWith( - 'test-user-id', - expect.objectContaining({ id: 'test-user-id' }), - ); + expect(response.body).toEqual(mockServerConfigs); + expect(mockRegistryInstance.getAllServerConfigs).toHaveBeenCalledWith('test-user-id'); }); it('should return empty object when no servers are configured', async () => { - mockResolveAllMcpConfigs.mockResolvedValue({}); + mockRegistryInstance.getAllServerConfigs.mockResolvedValue({}); const response = await request(app).get('/api/mcp/servers'); @@ -1846,7 +1615,7 @@ describe('MCP Routes', () => { }); it('should return 500 when server config retrieval fails', async () => { - mockResolveAllMcpConfigs.mockRejectedValue(new Error('Database error')); + mockRegistryInstance.getAllServerConfigs.mockRejectedValue(new Error('Database error')); const response = await request(app).get('/api/mcp/servers'); @@ -1872,10 +1641,10 @@ describe('MCP Routes', () => { const response = await request(app).post('/api/mcp/servers').send({ config: validConfig }); expect(response.status).toBe(201); - expect(response.body.serverName).toBe('test-sse-server'); - expect(response.body.type).toBe('sse'); - expect(response.body.url).toBe('https://mcp-server.example.com/sse'); - expect(response.body.title).toBe('Test SSE Server'); + expect(response.body).toEqual({ + serverName: 'test-sse-server', + ...validConfig, + }); expect(mockRegistryInstance.addServer).toHaveBeenCalledWith( 'temp_server_name', expect.objectContaining({ @@ -1929,78 +1698,6 @@ describe('MCP Routes', () => { expect(response.body.message).toBe('Invalid configuration'); }); - it('should reject SSE URL containing env variable references', async () => { - const response = await request(app) - .post('/api/mcp/servers') - .send({ - config: { - type: 'sse', - url: 'http://attacker.com/?secret=${JWT_SECRET}', - }, - }); - - expect(response.status).toBe(400); - expect(response.body.message).toBe('Invalid configuration'); - expect(mockRegistryInstance.addServer).not.toHaveBeenCalled(); - }); - - it('should reject streamable-http URL containing env variable references', async () => { - const response = await request(app) - .post('/api/mcp/servers') - .send({ - config: { - type: 'streamable-http', - url: 'http://attacker.com/?key=${CREDS_KEY}&iv=${CREDS_IV}', - }, - }); - - expect(response.status).toBe(400); - expect(response.body.message).toBe('Invalid configuration'); - expect(mockRegistryInstance.addServer).not.toHaveBeenCalled(); - }); - - it('should reject websocket URL containing env variable references', async () => { - const response = await request(app) - .post('/api/mcp/servers') - .send({ - config: { - type: 'websocket', - url: 'ws://attacker.com/?secret=${MONGO_URI}', - }, - }); - - expect(response.status).toBe(400); - expect(response.body.message).toBe('Invalid configuration'); - expect(mockRegistryInstance.addServer).not.toHaveBeenCalled(); - }); - - it('should redact secrets from create response', async () => { - const validConfig = { - type: 'sse', - url: 'https://mcp-server.example.com/sse', - title: 'Test Server', - }; - - mockRegistryInstance.addServer.mockResolvedValue({ - serverName: 'test-server', - config: { - ...validConfig, - apiKey: { source: 'admin', authorization_type: 'bearer', key: 'admin-secret-key' }, - oauth: { client_id: 'cid', client_secret: 'admin-oauth-secret' }, - headers: { Authorization: 'Bearer leaked-token' }, - }, - }); - - const response = await request(app).post('/api/mcp/servers').send({ config: validConfig }); - - expect(response.status).toBe(201); - expect(response.body.apiKey?.key).toBeUndefined(); - expect(response.body.oauth?.client_secret).toBeUndefined(); - expect(response.body.headers).toBeUndefined(); - expect(response.body.apiKey?.source).toBe('admin'); - expect(response.body.oauth?.client_id).toBe('cid'); - }); - it('should return 500 when registry throws error', async () => { const validConfig = { type: 'sse', @@ -2030,18 +1727,15 @@ describe('MCP Routes', () => { const response = await request(app).get('/api/mcp/servers/test-server'); expect(response.status).toBe(200); - expect(response.body.type).toBe('sse'); - expect(response.body.url).toBe('https://mcp-server.example.com/sse'); - expect(response.body.title).toBe('Test Server'); + expect(response.body).toEqual(mockConfig); expect(mockRegistryInstance.getServerConfig).toHaveBeenCalledWith( 'test-server', 'test-user-id', - {}, ); }); it('should return 404 when server not found', async () => { - mockRegistryInstance.getServerConfig.mockResolvedValue(undefined); + mockRegistryInstance.getServerConfig.mockResolvedValue(null); const response = await request(app).get('/api/mcp/servers/non-existent-server'); @@ -2049,29 +1743,6 @@ describe('MCP Routes', () => { expect(response.body).toEqual({ message: 'MCP server not found' }); }); - it('should redact secrets from get response', async () => { - mockRegistryInstance.getServerConfig.mockResolvedValue({ - type: 'sse', - url: 'https://mcp-server.example.com/sse', - title: 'Secret Server', - apiKey: { source: 'admin', authorization_type: 'bearer', key: 'decrypted-admin-key' }, - oauth: { client_id: 'cid', client_secret: 'decrypted-oauth-secret' }, - headers: { Authorization: 'Bearer internal-token' }, - oauth_headers: { 'X-OAuth': 'secret-value' }, - }); - - const response = await request(app).get('/api/mcp/servers/secret-server'); - - expect(response.status).toBe(200); - expect(response.body.title).toBe('Secret Server'); - expect(response.body.apiKey?.key).toBeUndefined(); - expect(response.body.apiKey?.source).toBe('admin'); - expect(response.body.oauth?.client_secret).toBeUndefined(); - expect(response.body.oauth?.client_id).toBe('cid'); - expect(response.body.headers).toBeUndefined(); - expect(response.body.oauth_headers).toBeUndefined(); - }); - it('should return 500 when registry throws error', async () => { mockRegistryInstance.getServerConfig.mockRejectedValue(new Error('Database error')); @@ -2098,9 +1769,7 @@ describe('MCP Routes', () => { .send({ config: updatedConfig }); expect(response.status).toBe(200); - expect(response.body.type).toBe('sse'); - expect(response.body.url).toBe('https://updated-mcp-server.example.com/sse'); - expect(response.body.title).toBe('Updated Server'); + expect(response.body).toEqual(updatedConfig); expect(mockRegistryInstance.updateServer).toHaveBeenCalledWith( 'test-server', expect.objectContaining({ @@ -2112,35 +1781,6 @@ describe('MCP Routes', () => { ); }); - it('should redact secrets from update response', async () => { - const validConfig = { - type: 'sse', - url: 'https://mcp-server.example.com/sse', - title: 'Updated Server', - }; - - mockRegistryInstance.updateServer.mockResolvedValue({ - ...validConfig, - apiKey: { source: 'admin', authorization_type: 'bearer', key: 'preserved-admin-key' }, - oauth: { client_id: 'cid', client_secret: 'preserved-oauth-secret' }, - headers: { Authorization: 'Bearer internal-token' }, - env: { DATABASE_URL: 'postgres://admin:pass@localhost/db' }, - }); - - const response = await request(app) - .patch('/api/mcp/servers/test-server') - .send({ config: validConfig }); - - expect(response.status).toBe(200); - expect(response.body.title).toBe('Updated Server'); - expect(response.body.apiKey?.key).toBeUndefined(); - expect(response.body.apiKey?.source).toBe('admin'); - expect(response.body.oauth?.client_secret).toBeUndefined(); - expect(response.body.oauth?.client_id).toBe('cid'); - expect(response.body.headers).toBeUndefined(); - expect(response.body.env).toBeUndefined(); - }); - it('should return 400 for invalid configuration', async () => { const invalidConfig = { type: 'sse', @@ -2157,51 +1797,6 @@ describe('MCP Routes', () => { expect(response.body.errors).toBeDefined(); }); - it('should reject SSE URL containing env variable references', async () => { - const response = await request(app) - .patch('/api/mcp/servers/test-server') - .send({ - config: { - type: 'sse', - url: 'http://attacker.com/?secret=${JWT_SECRET}', - }, - }); - - expect(response.status).toBe(400); - expect(response.body.message).toBe('Invalid configuration'); - expect(mockRegistryInstance.updateServer).not.toHaveBeenCalled(); - }); - - it('should reject streamable-http URL containing env variable references', async () => { - const response = await request(app) - .patch('/api/mcp/servers/test-server') - .send({ - config: { - type: 'streamable-http', - url: 'http://attacker.com/?key=${CREDS_KEY}', - }, - }); - - expect(response.status).toBe(400); - expect(response.body.message).toBe('Invalid configuration'); - expect(mockRegistryInstance.updateServer).not.toHaveBeenCalled(); - }); - - it('should reject websocket URL containing env variable references', async () => { - const response = await request(app) - .patch('/api/mcp/servers/test-server') - .send({ - config: { - type: 'websocket', - url: 'ws://attacker.com/?secret=${MONGO_URI}', - }, - }); - - expect(response.status).toBe(400); - expect(response.body.message).toBe('Invalid configuration'); - expect(mockRegistryInstance.updateServer).not.toHaveBeenCalled(); - }); - it('should return 500 when registry throws error', async () => { const validConfig = { type: 'sse', diff --git a/api/server/routes/__tests__/messages-delete.spec.js b/api/server/routes/__tests__/messages-delete.spec.js deleted file mode 100644 index 714d497719..0000000000 --- a/api/server/routes/__tests__/messages-delete.spec.js +++ /dev/null @@ -1,199 +0,0 @@ -const mongoose = require('mongoose'); -const express = require('express'); -const request = require('supertest'); -const { v4: uuidv4 } = require('uuid'); -const { MongoMemoryServer } = require('mongodb-memory-server'); - -jest.mock('@librechat/agents', () => ({ - sleep: jest.fn(), -})); - -jest.mock('@librechat/api', () => ({ - unescapeLaTeX: jest.fn((x) => x), - countTokens: jest.fn().mockResolvedValue(10), -})); - -jest.mock('@librechat/data-schemas', () => ({ - ...jest.requireActual('@librechat/data-schemas'), - logger: { - debug: jest.fn(), - info: jest.fn(), - warn: jest.fn(), - error: jest.fn(), - }, -})); - -jest.mock('librechat-data-provider', () => ({ - ...jest.requireActual('librechat-data-provider'), -})); - -jest.mock('~/models', () => ({ - saveConvo: jest.fn(), - getMessage: jest.fn(), - saveMessage: jest.fn(), - getMessages: jest.fn(), - updateMessage: jest.fn(), - deleteMessages: jest.fn(), - getConvosQueried: jest.fn(), - searchMessages: jest.fn(), - getMessagesByCursor: jest.fn(), -})); - -jest.mock('~/server/services/Artifacts/update', () => ({ - findAllArtifacts: jest.fn(), - replaceArtifactContent: jest.fn(), -})); - -jest.mock('~/server/middleware/requireJwtAuth', () => (req, res, next) => next()); - -jest.mock('~/server/middleware', () => ({ - requireJwtAuth: (req, res, next) => next(), - validateMessageReq: (req, res, next) => next(), -})); - -jest.mock('~/db/models', () => ({ - Message: { - findOne: jest.fn(), - find: jest.fn(), - meiliSearch: jest.fn(), - }, -})); - -/* ─── Model-level tests: real MongoDB, proves cross-user deletion is prevented ─── */ - -const { messageSchema } = require('@librechat/data-schemas'); - -describe('deleteMessages – model-level IDOR prevention', () => { - let mongoServer; - let Message; - - const ownerUserId = 'user-owner-111'; - const attackerUserId = 'user-attacker-222'; - - beforeAll(async () => { - mongoServer = await MongoMemoryServer.create(); - Message = mongoose.models.Message || mongoose.model('Message', messageSchema); - await mongoose.connect(mongoServer.getUri()); - }); - - afterAll(async () => { - await mongoose.disconnect(); - await mongoServer.stop(); - }); - - beforeEach(async () => { - await Message.deleteMany({}); - }); - - it("should NOT delete another user's message when attacker supplies victim messageId", async () => { - const conversationId = uuidv4(); - const victimMsgId = 'victim-msg-001'; - - await Message.create({ - messageId: victimMsgId, - conversationId, - user: ownerUserId, - text: 'Sensitive owner data', - }); - - await Message.deleteMany({ messageId: victimMsgId, user: attackerUserId }); - - const victimMsg = await Message.findOne({ messageId: victimMsgId }).lean(); - expect(victimMsg).not.toBeNull(); - expect(victimMsg.user).toBe(ownerUserId); - expect(victimMsg.text).toBe('Sensitive owner data'); - }); - - it("should delete the user's own message", async () => { - const conversationId = uuidv4(); - const ownMsgId = 'own-msg-001'; - - await Message.create({ - messageId: ownMsgId, - conversationId, - user: ownerUserId, - text: 'My message', - }); - - const result = await Message.deleteMany({ messageId: ownMsgId, user: ownerUserId }); - expect(result.deletedCount).toBe(1); - - const deleted = await Message.findOne({ messageId: ownMsgId }).lean(); - expect(deleted).toBeNull(); - }); - - it('should scope deletion by conversationId, messageId, and user together', async () => { - const convoA = uuidv4(); - const convoB = uuidv4(); - - await Message.create([ - { messageId: 'msg-a1', conversationId: convoA, user: ownerUserId, text: 'A1' }, - { messageId: 'msg-b1', conversationId: convoB, user: ownerUserId, text: 'B1' }, - ]); - - await Message.deleteMany({ messageId: 'msg-a1', conversationId: convoA, user: attackerUserId }); - - const remaining = await Message.find({ user: ownerUserId }).lean(); - expect(remaining).toHaveLength(2); - }); -}); - -/* ─── Route-level tests: supertest + mocked deleteMessages ─── */ - -describe('DELETE /:conversationId/:messageId – route handler', () => { - let app; - const { deleteMessages } = require('~/models'); - - const authenticatedUserId = 'user-owner-123'; - - beforeAll(() => { - const messagesRouter = require('../messages'); - - app = express(); - app.use(express.json()); - app.use((req, res, next) => { - req.user = { id: authenticatedUserId }; - next(); - }); - app.use('/api/messages', messagesRouter); - }); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('should pass user and conversationId in the deleteMessages filter', async () => { - deleteMessages.mockResolvedValue({ deletedCount: 1 }); - - await request(app).delete('/api/messages/convo-1/msg-1'); - - expect(deleteMessages).toHaveBeenCalledTimes(1); - expect(deleteMessages).toHaveBeenCalledWith({ - messageId: 'msg-1', - conversationId: 'convo-1', - user: authenticatedUserId, - }); - }); - - it('should return 204 on successful deletion', async () => { - deleteMessages.mockResolvedValue({ deletedCount: 1 }); - - const response = await request(app).delete('/api/messages/convo-1/msg-owned'); - - expect(response.status).toBe(204); - expect(deleteMessages).toHaveBeenCalledWith({ - messageId: 'msg-owned', - conversationId: 'convo-1', - user: authenticatedUserId, - }); - }); - - it('should return 500 when deleteMessages throws', async () => { - deleteMessages.mockRejectedValue(new Error('DB failure')); - - const response = await request(app).delete('/api/messages/convo-1/msg-1'); - - expect(response.status).toBe(500); - expect(response.body).toEqual({ error: 'Internal server error' }); - }); -}); diff --git a/api/server/routes/accessPermissions.test.js b/api/server/routes/accessPermissions.test.js index ddbe702f15..81c21c8667 100644 --- a/api/server/routes/accessPermissions.test.js +++ b/api/server/routes/accessPermissions.test.js @@ -5,7 +5,7 @@ const { v4: uuidv4 } = require('uuid'); const { createMethods } = require('@librechat/data-schemas'); const { MongoMemoryServer } = require('mongodb-memory-server'); const { ResourceType, PermissionBits } = require('librechat-data-provider'); -const { createAgent } = require('~/models'); +const { createAgent } = require('~/models/Agent'); /** * Mock the PermissionsController to isolate route testing diff --git a/api/server/routes/admin/auth.js b/api/server/routes/admin/auth.js index 72f23b7d52..291b5eaaf8 100644 --- a/api/server/routes/admin/auth.js +++ b/api/server/routes/admin/auth.js @@ -1,62 +1,41 @@ const express = require('express'); const passport = require('passport'); -const crypto = require('node:crypto'); +const { randomState } = require('openid-client'); +const { logger } = require('@librechat/data-schemas'); const { CacheKeys } = require('librechat-data-provider'); -const { logger, SystemCapabilities } = require('@librechat/data-schemas'); -const { getAdminPanelUrl, exchangeAdminCode, createSetBalanceConfig } = require('@librechat/api'); +const { + requireAdmin, + getAdminPanelUrl, + exchangeAdminCode, + createSetBalanceConfig, +} = require('@librechat/api'); const { loginController } = require('~/server/controllers/auth/LoginController'); -const { requireCapability } = require('~/server/middleware/roles/capabilities'); const { createOAuthHandler } = require('~/server/controllers/auth/oauth'); -const { findBalanceByUser, upsertBalanceFields } = require('~/models'); const { getAppConfig } = require('~/server/services/Config'); const getLogStores = require('~/cache/getLogStores'); const { getOpenIdConfig } = require('~/strategies'); const middleware = require('~/server/middleware'); - -const requireAdminAccess = requireCapability(SystemCapabilities.ACCESS_ADMIN); +const { Balance } = require('~/db/models'); const setBalanceConfig = createSetBalanceConfig({ getAppConfig, - findBalanceByUser, - upsertBalanceFields, + Balance, }); const router = express.Router(); -function resolveRequestOrigin(req) { - const originHeader = req.get('origin'); - if (originHeader) { - try { - return new URL(originHeader).origin; - } catch { - return undefined; - } - } - - const refererHeader = req.get('referer'); - if (!refererHeader) { - return undefined; - } - - try { - return new URL(refererHeader).origin; - } catch { - return undefined; - } -} - router.post( '/login/local', middleware.logHeaders, middleware.loginLimiter, middleware.checkBan, middleware.requireLocalAuth, - requireAdminAccess, + requireAdmin, setBalanceConfig, loginController, ); -router.get('/verify', middleware.requireJwtAuth, requireAdminAccess, (req, res) => { +router.get('/verify', middleware.requireJwtAuth, requireAdmin, (req, res) => { const { password: _p, totpSecret: _t, __v, ...user } = req.user; user.id = user._id.toString(); res.status(200).json({ user }); @@ -73,340 +52,28 @@ router.get('/oauth/openid/check', (req, res) => { res.status(200).json({ message: 'OpenID check successful' }); }); -/** PKCE challenge cache TTL: 5 minutes (enough for user to authenticate with IdP) */ -const PKCE_CHALLENGE_TTL = 5 * 60 * 1000; -/** Regex pattern for valid PKCE challenges: 64 hex characters (SHA-256 hex digest) */ -const PKCE_CHALLENGE_PATTERN = /^[a-f0-9]{64}$/; - -/** - * Generates a random hex state string for OAuth flows. - * @returns {string} A 32-byte random hex string. - */ -function generateState() { - return crypto.randomBytes(32).toString('hex'); -} - -/** - * Stores a PKCE challenge in cache keyed by state. - * @param {string} state - The OAuth state value. - * @param {string | undefined} codeChallenge - The PKCE code_challenge from query params. - * @param {string} provider - Provider name for logging. - * @returns {Promise} True if stored successfully or no challenge provided. - */ -async function storePkceChallenge(state, codeChallenge, provider) { - if (typeof codeChallenge !== 'string' || !PKCE_CHALLENGE_PATTERN.test(codeChallenge)) { - return true; - } - try { - const cache = getLogStores(CacheKeys.ADMIN_OAUTH_EXCHANGE); - await cache.set(`pkce:${state}`, codeChallenge, PKCE_CHALLENGE_TTL); - return true; - } catch (err) { - logger.error(`[admin/oauth/${provider}] Failed to store PKCE challenge:`, err); - return false; - } -} - -/** - * Middleware to retrieve PKCE challenge from cache using the OAuth state. - * Reads state from req.oauthState (set by a preceding middleware). - * @param {string} provider - Provider name for logging. - * @returns {Function} Express middleware. - */ -function retrievePkceChallenge(provider) { - return async (req, res, next) => { - if (!req.oauthState) { - return next(); - } - try { - const cache = getLogStores(CacheKeys.ADMIN_OAUTH_EXCHANGE); - const challenge = await cache.get(`pkce:${req.oauthState}`); - if (challenge) { - req.pkceChallenge = challenge; - await cache.delete(`pkce:${req.oauthState}`); - } else { - logger.warn( - `[admin/oauth/${provider}/callback] State present but no PKCE challenge found; PKCE will not be enforced for this request`, - ); - } - } catch (err) { - logger.error( - `[admin/oauth/${provider}/callback] Failed to retrieve PKCE challenge, aborting:`, - err, - ); - return res.redirect( - `${getAdminPanelUrl()}/auth/${provider}/callback?error=pkce_retrieval_failed&error_description=Failed+to+retrieve+PKCE+challenge`, - ); - } - next(); - }; -} - -/* ────────────────────────────────────────────── - * OpenID Admin Routes - * ────────────────────────────────────────────── */ - -router.get('/oauth/openid', async (req, res, next) => { - const state = generateState(); - const stored = await storePkceChallenge(state, req.query.code_challenge, 'openid'); - if (!stored) { - return res.redirect( - `${getAdminPanelUrl()}/auth/openid/callback?error=pkce_store_failed&error_description=Failed+to+store+PKCE+challenge`, - ); - } - +router.get('/oauth/openid', (req, res, next) => { return passport.authenticate('openidAdmin', { session: false, - state, + state: randomState(), })(req, res, next); }); router.get( '/oauth/openid/callback', - (req, res, next) => { - req.oauthState = typeof req.query.state === 'string' ? req.query.state : undefined; - next(); - }, passport.authenticate('openidAdmin', { failureRedirect: `${getAdminPanelUrl()}/auth/openid/callback?error=auth_failed&error_description=Authentication+failed`, failureMessage: true, session: false, }), - retrievePkceChallenge('openid'), - requireAdminAccess, + requireAdmin, setBalanceConfig, middleware.checkDomainAllowed, createOAuthHandler(`${getAdminPanelUrl()}/auth/openid/callback`), ); -/* ────────────────────────────────────────────── - * SAML Admin Routes - * ────────────────────────────────────────────── */ - -router.get('/oauth/saml', async (req, res, next) => { - const state = generateState(); - const stored = await storePkceChallenge(state, req.query.code_challenge, 'saml'); - if (!stored) { - return res.redirect( - `${getAdminPanelUrl()}/auth/saml/callback?error=pkce_store_failed&error_description=Failed+to+store+PKCE+challenge`, - ); - } - - return passport.authenticate('samlAdmin', { - session: false, - additionalParams: { RelayState: state }, - })(req, res, next); -}); - -router.post( - '/oauth/saml/callback', - (req, res, next) => { - req.oauthState = typeof req.body.RelayState === 'string' ? req.body.RelayState : undefined; - next(); - }, - passport.authenticate('samlAdmin', { - failureRedirect: `${getAdminPanelUrl()}/auth/saml/callback?error=auth_failed&error_description=Authentication+failed`, - failureMessage: true, - session: false, - }), - retrievePkceChallenge('saml'), - requireAdminAccess, - setBalanceConfig, - middleware.checkDomainAllowed, - createOAuthHandler(`${getAdminPanelUrl()}/auth/saml/callback`), -); - -/* ────────────────────────────────────────────── - * Google Admin Routes - * ────────────────────────────────────────────── */ - -router.get('/oauth/google', async (req, res, next) => { - const state = generateState(); - const stored = await storePkceChallenge(state, req.query.code_challenge, 'google'); - if (!stored) { - return res.redirect( - `${getAdminPanelUrl()}/auth/google/callback?error=pkce_store_failed&error_description=Failed+to+store+PKCE+challenge`, - ); - } - - return passport.authenticate('googleAdmin', { - scope: ['openid', 'profile', 'email'], - session: false, - state, - })(req, res, next); -}); - -router.get( - '/oauth/google/callback', - (req, res, next) => { - req.oauthState = typeof req.query.state === 'string' ? req.query.state : undefined; - next(); - }, - passport.authenticate('googleAdmin', { - failureRedirect: `${getAdminPanelUrl()}/auth/google/callback?error=auth_failed&error_description=Authentication+failed`, - failureMessage: true, - session: false, - }), - retrievePkceChallenge('google'), - requireAdminAccess, - setBalanceConfig, - middleware.checkDomainAllowed, - createOAuthHandler(`${getAdminPanelUrl()}/auth/google/callback`), -); - -/* ────────────────────────────────────────────── - * GitHub Admin Routes - * ────────────────────────────────────────────── */ - -router.get('/oauth/github', async (req, res, next) => { - const state = generateState(); - const stored = await storePkceChallenge(state, req.query.code_challenge, 'github'); - if (!stored) { - return res.redirect( - `${getAdminPanelUrl()}/auth/github/callback?error=pkce_store_failed&error_description=Failed+to+store+PKCE+challenge`, - ); - } - - return passport.authenticate('githubAdmin', { - scope: ['user:email', 'read:user'], - session: false, - state, - })(req, res, next); -}); - -router.get( - '/oauth/github/callback', - (req, res, next) => { - req.oauthState = typeof req.query.state === 'string' ? req.query.state : undefined; - next(); - }, - passport.authenticate('githubAdmin', { - failureRedirect: `${getAdminPanelUrl()}/auth/github/callback?error=auth_failed&error_description=Authentication+failed`, - failureMessage: true, - session: false, - }), - retrievePkceChallenge('github'), - requireAdminAccess, - setBalanceConfig, - middleware.checkDomainAllowed, - createOAuthHandler(`${getAdminPanelUrl()}/auth/github/callback`), -); - -/* ────────────────────────────────────────────── - * Discord Admin Routes - * ────────────────────────────────────────────── */ - -router.get('/oauth/discord', async (req, res, next) => { - const state = generateState(); - const stored = await storePkceChallenge(state, req.query.code_challenge, 'discord'); - if (!stored) { - return res.redirect( - `${getAdminPanelUrl()}/auth/discord/callback?error=pkce_store_failed&error_description=Failed+to+store+PKCE+challenge`, - ); - } - - return passport.authenticate('discordAdmin', { - scope: ['identify', 'email'], - session: false, - state, - })(req, res, next); -}); - -router.get( - '/oauth/discord/callback', - (req, res, next) => { - req.oauthState = typeof req.query.state === 'string' ? req.query.state : undefined; - next(); - }, - passport.authenticate('discordAdmin', { - failureRedirect: `${getAdminPanelUrl()}/auth/discord/callback?error=auth_failed&error_description=Authentication+failed`, - failureMessage: true, - session: false, - }), - retrievePkceChallenge('discord'), - requireAdminAccess, - setBalanceConfig, - middleware.checkDomainAllowed, - createOAuthHandler(`${getAdminPanelUrl()}/auth/discord/callback`), -); - -/* ────────────────────────────────────────────── - * Facebook Admin Routes - * ────────────────────────────────────────────── */ - -router.get('/oauth/facebook', async (req, res, next) => { - const state = generateState(); - const stored = await storePkceChallenge(state, req.query.code_challenge, 'facebook'); - if (!stored) { - return res.redirect( - `${getAdminPanelUrl()}/auth/facebook/callback?error=pkce_store_failed&error_description=Failed+to+store+PKCE+challenge`, - ); - } - - return passport.authenticate('facebookAdmin', { - scope: ['public_profile'], - session: false, - state, - })(req, res, next); -}); - -router.get( - '/oauth/facebook/callback', - (req, res, next) => { - req.oauthState = typeof req.query.state === 'string' ? req.query.state : undefined; - next(); - }, - passport.authenticate('facebookAdmin', { - failureRedirect: `${getAdminPanelUrl()}/auth/facebook/callback?error=auth_failed&error_description=Authentication+failed`, - failureMessage: true, - session: false, - }), - retrievePkceChallenge('facebook'), - requireAdminAccess, - setBalanceConfig, - middleware.checkDomainAllowed, - createOAuthHandler(`${getAdminPanelUrl()}/auth/facebook/callback`), -); - -/* ────────────────────────────────────────────── - * Apple Admin Routes (POST callback) - * ────────────────────────────────────────────── */ - -router.get('/oauth/apple', async (req, res, next) => { - const state = generateState(); - const stored = await storePkceChallenge(state, req.query.code_challenge, 'apple'); - if (!stored) { - return res.redirect( - `${getAdminPanelUrl()}/auth/apple/callback?error=pkce_store_failed&error_description=Failed+to+store+PKCE+challenge`, - ); - } - - return passport.authenticate('appleAdmin', { - session: false, - state, - })(req, res, next); -}); - -router.post( - '/oauth/apple/callback', - (req, res, next) => { - req.oauthState = typeof req.body.state === 'string' ? req.body.state : undefined; - next(); - }, - passport.authenticate('appleAdmin', { - failureRedirect: `${getAdminPanelUrl()}/auth/apple/callback?error=auth_failed&error_description=Authentication+failed`, - failureMessage: true, - session: false, - }), - retrievePkceChallenge('apple'), - requireAdminAccess, - setBalanceConfig, - middleware.checkDomainAllowed, - createOAuthHandler(`${getAdminPanelUrl()}/auth/apple/callback`), -); - /** Regex pattern for valid exchange codes: 64 hex characters */ -const EXCHANGE_CODE_PATTERN = /^[a-f0-9]{64}$/; +const EXCHANGE_CODE_PATTERN = /^[a-f0-9]{64}$/i; /** * Exchange OAuth authorization code for tokens. @@ -414,12 +81,12 @@ const EXCHANGE_CODE_PATTERN = /^[a-f0-9]{64}$/; * The code is one-time-use and expires in 30 seconds. * * POST /api/admin/oauth/exchange - * Body: { code: string, code_verifier?: string } + * Body: { code: string } * Response: { token: string, refreshToken: string, user: object } */ router.post('/oauth/exchange', middleware.loginLimiter, async (req, res) => { try { - const { code, code_verifier: codeVerifier } = req.body; + const { code } = req.body; if (!code) { logger.warn('[admin/oauth/exchange] Missing authorization code'); @@ -437,20 +104,8 @@ router.post('/oauth/exchange', middleware.loginLimiter, async (req, res) => { }); } - if ( - codeVerifier !== undefined && - (typeof codeVerifier !== 'string' || codeVerifier.length < 1 || codeVerifier.length > 512) - ) { - logger.warn('[admin/oauth/exchange] Invalid code_verifier format'); - return res.status(400).json({ - error: 'Invalid code_verifier', - error_code: 'INVALID_VERIFIER', - }); - } - const cache = getLogStores(CacheKeys.ADMIN_OAUTH_EXCHANGE); - const requestOrigin = resolveRequestOrigin(req); - const result = await exchangeAdminCode(cache, code, requestOrigin, codeVerifier); + const result = await exchangeAdminCode(cache, code); if (!result) { return res.status(401).json({ diff --git a/api/server/routes/admin/config.js b/api/server/routes/admin/config.js deleted file mode 100644 index 0632077ea9..0000000000 --- a/api/server/routes/admin/config.js +++ /dev/null @@ -1,40 +0,0 @@ -const express = require('express'); -const { createAdminConfigHandlers } = require('@librechat/api'); -const { SystemCapabilities } = require('@librechat/data-schemas'); -const { - hasConfigCapability, - requireCapability, -} = require('~/server/middleware/roles/capabilities'); -const { getAppConfig, invalidateConfigCaches } = require('~/server/services/Config'); -const { requireJwtAuth } = require('~/server/middleware'); -const db = require('~/models'); - -const router = express.Router(); - -const requireAdminAccess = requireCapability(SystemCapabilities.ACCESS_ADMIN); - -const handlers = createAdminConfigHandlers({ - listAllConfigs: db.listAllConfigs, - findConfigByPrincipal: db.findConfigByPrincipal, - upsertConfig: db.upsertConfig, - patchConfigFields: db.patchConfigFields, - unsetConfigField: db.unsetConfigField, - deleteConfig: db.deleteConfig, - toggleConfigActive: db.toggleConfigActive, - hasConfigCapability, - getAppConfig, - invalidateConfigCaches, -}); - -router.use(requireJwtAuth, requireAdminAccess); - -router.get('/', handlers.listConfigs); -router.get('/base', handlers.getBaseConfig); -router.get('/:principalType/:principalId', handlers.getConfig); -router.put('/:principalType/:principalId', handlers.upsertConfigOverrides); -router.patch('/:principalType/:principalId/fields', handlers.patchConfigField); -router.delete('/:principalType/:principalId/fields', handlers.deleteConfigField); -router.delete('/:principalType/:principalId', handlers.deleteConfigOverrides); -router.patch('/:principalType/:principalId/active', handlers.toggleConfig); - -module.exports = router; diff --git a/api/server/routes/admin/grants.js b/api/server/routes/admin/grants.js deleted file mode 100644 index a0fa73dc43..0000000000 --- a/api/server/routes/admin/grants.js +++ /dev/null @@ -1,35 +0,0 @@ -const express = require('express'); -const { createAdminGrantsHandlers, getCachedPrincipals } = require('@librechat/api'); -const { SystemCapabilities } = require('@librechat/data-schemas'); -const { requireCapability } = require('~/server/middleware/roles/capabilities'); -const { requireJwtAuth } = require('~/server/middleware'); -const db = require('~/models'); - -const router = express.Router(); - -const requireAdminAccess = requireCapability(SystemCapabilities.ACCESS_ADMIN); - -const handlers = createAdminGrantsHandlers({ - listGrants: db.listGrants, - countGrants: db.countGrants, - getCapabilitiesForPrincipal: db.getCapabilitiesForPrincipal, - getCapabilitiesForPrincipals: db.getCapabilitiesForPrincipals, - grantCapability: db.grantCapability, - revokeCapability: db.revokeCapability, - getUserPrincipals: db.getUserPrincipals, - hasCapabilityForPrincipals: db.hasCapabilityForPrincipals, - getHeldCapabilities: db.getHeldCapabilities, - getCachedPrincipals, - checkRoleExists: async (name) => (await db.getRoleByName(name)) != null, -}); - -router.use(requireJwtAuth, requireAdminAccess); - -router.get('/', handlers.listGrants); -router.get('/effective', handlers.getEffectiveCapabilities); -router.get('/:principalType/:principalId', handlers.getPrincipalGrants); -router.post('/', handlers.assignGrant); -/** Callers should encodeURIComponent the capability for client compatibility (e.g. manage%3Aconfigs%3Aendpoints). */ -router.delete('/:principalType/:principalId/:capability', handlers.revokeGrant); - -module.exports = router; diff --git a/api/server/routes/admin/groups.js b/api/server/routes/admin/groups.js deleted file mode 100644 index 11ed59737e..0000000000 --- a/api/server/routes/admin/groups.js +++ /dev/null @@ -1,40 +0,0 @@ -const express = require('express'); -const { createAdminGroupsHandlers } = require('@librechat/api'); -const { SystemCapabilities } = require('@librechat/data-schemas'); -const { requireCapability } = require('~/server/middleware/roles/capabilities'); -const { requireJwtAuth } = require('~/server/middleware'); -const db = require('~/models'); - -const router = express.Router(); - -const requireAdminAccess = requireCapability(SystemCapabilities.ACCESS_ADMIN); -const requireReadGroups = requireCapability(SystemCapabilities.READ_GROUPS); -const requireManageGroups = requireCapability(SystemCapabilities.MANAGE_GROUPS); - -const handlers = createAdminGroupsHandlers({ - listGroups: db.listGroups, - countGroups: db.countGroups, - findGroupById: db.findGroupById, - createGroup: db.createGroup, - updateGroupById: db.updateGroupById, - deleteGroup: db.deleteGroup, - addUserToGroup: db.addUserToGroup, - removeUserFromGroup: db.removeUserFromGroup, - removeMemberById: db.removeMemberById, - findUsers: db.findUsers, - deleteConfig: db.deleteConfig, - deleteAclEntries: db.deleteAclEntries, -}); - -router.use(requireJwtAuth, requireAdminAccess); - -router.get('/', requireReadGroups, handlers.listGroups); -router.post('/', requireManageGroups, handlers.createGroup); -router.get('/:id', requireReadGroups, handlers.getGroup); -router.patch('/:id', requireManageGroups, handlers.updateGroup); -router.delete('/:id', requireManageGroups, handlers.deleteGroup); -router.get('/:id/members', requireReadGroups, handlers.getGroupMembers); -router.post('/:id/members', requireManageGroups, handlers.addGroupMember); -router.delete('/:id/members/:userId', requireManageGroups, handlers.removeGroupMember); - -module.exports = router; diff --git a/api/server/routes/admin/roles.js b/api/server/routes/admin/roles.js deleted file mode 100644 index f2bbd7f7ea..0000000000 --- a/api/server/routes/admin/roles.js +++ /dev/null @@ -1,46 +0,0 @@ -const express = require('express'); -const { createAdminRolesHandlers } = require('@librechat/api'); -const { SystemCapabilities } = require('@librechat/data-schemas'); -const { requireCapability } = require('~/server/middleware/roles/capabilities'); -const { requireJwtAuth } = require('~/server/middleware'); -const db = require('~/models'); - -const router = express.Router(); - -const requireAdminAccess = requireCapability(SystemCapabilities.ACCESS_ADMIN); -const requireReadRoles = requireCapability(SystemCapabilities.READ_ROLES); -const requireManageRoles = requireCapability(SystemCapabilities.MANAGE_ROLES); - -const handlers = createAdminRolesHandlers({ - listRoles: db.listRoles, - countRoles: db.countRoles, - getRoleByName: db.getRoleByName, - createRoleByName: db.createRoleByName, - updateRoleByName: db.updateRoleByName, - updateAccessPermissions: db.updateAccessPermissions, - deleteRoleByName: db.deleteRoleByName, - findUser: db.findUser, - updateUser: db.updateUser, - updateUsersByRole: db.updateUsersByRole, - findUserIdsByRole: db.findUserIdsByRole, - updateUsersRoleByIds: db.updateUsersRoleByIds, - listUsersByRole: db.listUsersByRole, - countUsersByRole: db.countUsersByRole, - deleteConfig: db.deleteConfig, - deleteAclEntries: db.deleteAclEntries, - deleteGrantsForPrincipal: db.deleteGrantsForPrincipal, -}); - -router.use(requireJwtAuth, requireAdminAccess); - -router.get('/', requireReadRoles, handlers.listRoles); -router.post('/', requireManageRoles, handlers.createRole); -router.get('/:name', requireReadRoles, handlers.getRole); -router.patch('/:name', requireManageRoles, handlers.updateRole); -router.delete('/:name', requireManageRoles, handlers.deleteRole); -router.patch('/:name/permissions', requireManageRoles, handlers.updateRolePermissions); -router.get('/:name/members', requireReadRoles, handlers.getRoleMembers); -router.post('/:name/members', requireManageRoles, handlers.addRoleMember); -router.delete('/:name/members/:userId', requireManageRoles, handlers.removeRoleMember); - -module.exports = router; diff --git a/api/server/routes/admin/users.js b/api/server/routes/admin/users.js deleted file mode 100644 index 20d4eb1797..0000000000 --- a/api/server/routes/admin/users.js +++ /dev/null @@ -1,28 +0,0 @@ -const express = require('express'); -const { createAdminUsersHandlers } = require('@librechat/api'); -const { SystemCapabilities } = require('@librechat/data-schemas'); -const { requireCapability } = require('~/server/middleware/roles/capabilities'); -const { requireJwtAuth } = require('~/server/middleware'); -const db = require('~/models'); - -const router = express.Router(); - -const requireAdminAccess = requireCapability(SystemCapabilities.ACCESS_ADMIN); -const requireReadUsers = requireCapability(SystemCapabilities.READ_USERS); -// const requireManageUsers = requireCapability(SystemCapabilities.MANAGE_USERS); - -const handlers = createAdminUsersHandlers({ - findUsers: db.findUsers, - countUsers: db.countUsers, - deleteUserById: db.deleteUserById, - deleteConfig: db.deleteConfig, - deleteAclEntries: db.deleteAclEntries, -}); - -router.use(requireJwtAuth, requireAdminAccess); - -router.get('/', requireReadUsers, handlers.listUsers); -router.get('/search', requireReadUsers, handlers.searchUsers); -// router.delete('/:id', requireManageUsers, handlers.deleteUser); - -module.exports = router; diff --git a/api/server/routes/agents/__tests__/streamTenant.spec.js b/api/server/routes/agents/__tests__/streamTenant.spec.js deleted file mode 100644 index 1f89953186..0000000000 --- a/api/server/routes/agents/__tests__/streamTenant.spec.js +++ /dev/null @@ -1,186 +0,0 @@ -const express = require('express'); -const request = require('supertest'); - -const mockGenerationJobManager = { - getJob: jest.fn(), - subscribe: jest.fn(), - getResumeState: jest.fn(), - abortJob: jest.fn(), - getActiveJobIdsForUser: jest.fn().mockResolvedValue([]), -}; - -jest.mock('@librechat/data-schemas', () => ({ - ...jest.requireActual('@librechat/data-schemas'), - logger: { - debug: jest.fn(), - warn: jest.fn(), - error: jest.fn(), - info: jest.fn(), - }, -})); - -jest.mock('@librechat/api', () => ({ - ...jest.requireActual('@librechat/api'), - isEnabled: jest.fn().mockReturnValue(false), - GenerationJobManager: mockGenerationJobManager, -})); - -jest.mock('~/models', () => ({ - saveMessage: jest.fn(), -})); - -let mockUserId = 'user-123'; -let mockTenantId; - -jest.mock('~/server/middleware', () => ({ - uaParser: (req, res, next) => next(), - checkBan: (req, res, next) => next(), - requireJwtAuth: (req, res, next) => { - req.user = { id: mockUserId, tenantId: mockTenantId }; - next(); - }, - messageIpLimiter: (req, res, next) => next(), - configMiddleware: (req, res, next) => next(), - messageUserLimiter: (req, res, next) => next(), -})); - -jest.mock('~/server/routes/agents/chat', () => require('express').Router()); -jest.mock('~/server/routes/agents/v1', () => ({ - v1: require('express').Router(), -})); -jest.mock('~/server/routes/agents/openai', () => require('express').Router()); -jest.mock('~/server/routes/agents/responses', () => require('express').Router()); - -const agentsRouter = require('../index'); -const app = express(); -app.use(express.json()); -app.use('/agents', agentsRouter); - -function mockSubscribeSuccess() { - mockGenerationJobManager.subscribe.mockImplementation((_streamId, _writeEvent, onDone) => { - process.nextTick(() => onDone({ done: true })); - return { unsubscribe: jest.fn() }; - }); -} - -describe('SSE stream tenant isolation', () => { - beforeEach(() => { - jest.clearAllMocks(); - mockUserId = 'user-123'; - mockTenantId = undefined; - }); - - describe('GET /chat/stream/:streamId', () => { - it('returns 403 when a user from a different tenant accesses a stream', async () => { - mockUserId = 'user-456'; - mockTenantId = 'tenant-b'; - - mockGenerationJobManager.getJob.mockResolvedValue({ - metadata: { userId: 'user-456', tenantId: 'tenant-a' }, - status: 'running', - }); - - const res = await request(app).get('/agents/chat/stream/stream-123'); - expect(res.status).toBe(403); - expect(res.body.error).toBe('Unauthorized'); - }); - - it('returns 404 when stream does not exist', async () => { - mockGenerationJobManager.getJob.mockResolvedValue(null); - - const res = await request(app).get('/agents/chat/stream/nonexistent'); - expect(res.status).toBe(404); - }); - - it('proceeds past tenant guard when tenant matches', async () => { - mockUserId = 'user-123'; - mockTenantId = 'tenant-a'; - mockSubscribeSuccess(); - - mockGenerationJobManager.getJob.mockResolvedValue({ - metadata: { userId: 'user-123', tenantId: 'tenant-a' }, - status: 'running', - }); - - const res = await request(app).get('/agents/chat/stream/stream-123'); - expect(res.status).toBe(200); - expect(mockGenerationJobManager.subscribe).toHaveBeenCalledTimes(1); - }); - - it('proceeds past tenant guard when job has no tenantId (single-tenant mode)', async () => { - mockUserId = 'user-123'; - mockTenantId = undefined; - mockSubscribeSuccess(); - - mockGenerationJobManager.getJob.mockResolvedValue({ - metadata: { userId: 'user-123' }, - status: 'running', - }); - - const res = await request(app).get('/agents/chat/stream/stream-123'); - expect(res.status).toBe(200); - expect(mockGenerationJobManager.subscribe).toHaveBeenCalledTimes(1); - }); - - it('returns 403 when job has tenantId but user has no tenantId', async () => { - mockUserId = 'user-123'; - mockTenantId = undefined; - - mockGenerationJobManager.getJob.mockResolvedValue({ - metadata: { userId: 'user-123', tenantId: 'some-tenant' }, - status: 'running', - }); - - const res = await request(app).get('/agents/chat/stream/stream-123'); - expect(res.status).toBe(403); - }); - }); - - describe('GET /chat/status/:conversationId', () => { - it('returns 403 when tenant does not match', async () => { - mockUserId = 'user-123'; - mockTenantId = 'tenant-b'; - - mockGenerationJobManager.getJob.mockResolvedValue({ - metadata: { userId: 'user-123', tenantId: 'tenant-a' }, - status: 'running', - }); - - const res = await request(app).get('/agents/chat/status/conv-123'); - expect(res.status).toBe(403); - expect(res.body.error).toBe('Unauthorized'); - }); - - it('returns status when tenant matches', async () => { - mockUserId = 'user-123'; - mockTenantId = 'tenant-a'; - - mockGenerationJobManager.getJob.mockResolvedValue({ - metadata: { userId: 'user-123', tenantId: 'tenant-a' }, - status: 'running', - createdAt: Date.now(), - }); - mockGenerationJobManager.getResumeState.mockResolvedValue(null); - - const res = await request(app).get('/agents/chat/status/conv-123'); - expect(res.status).toBe(200); - expect(res.body.active).toBe(true); - }); - }); - - describe('POST /chat/abort', () => { - it('returns 403 when tenant does not match', async () => { - mockUserId = 'user-123'; - mockTenantId = 'tenant-b'; - - mockGenerationJobManager.getJob.mockResolvedValue({ - metadata: { userId: 'user-123', tenantId: 'tenant-a' }, - status: 'running', - }); - - const res = await request(app).post('/agents/chat/abort').send({ streamId: 'stream-123' }); - expect(res.status).toBe(403); - expect(res.body.error).toBe('Unauthorized'); - }); - }); -}); diff --git a/api/server/routes/agents/actions.js b/api/server/routes/agents/actions.js index b3b34f3f1c..12168ba28a 100644 --- a/api/server/routes/agents/actions.js +++ b/api/server/routes/agents/actions.js @@ -12,21 +12,19 @@ const { validateActionDomain, validateAndParseOpenAPISpec, } = require('librechat-data-provider'); -const { - legacyDomainEncode, - encryptMetadata, - domainParser, -} = require('~/server/services/ActionService'); +const { encryptMetadata, domainParser } = require('~/server/services/ActionService'); const { findAccessibleResources } = require('~/server/services/PermissionService'); -const db = require('~/models'); +const { getAgent, updateAgent, getListAgentsByAccess } = require('~/models/Agent'); +const { updateAction, getActions, deleteAction } = require('~/models/Action'); const { canAccessAgentResource } = require('~/server/middleware'); +const { getRoleByName } = require('~/models/Role'); const router = express.Router(); const checkAgentCreate = generateCheckAccess({ permissionType: PermissionTypes.AGENTS, permissions: [Permissions.USE, Permissions.CREATE], - getRoleByName: db.getRoleByName, + getRoleByName, }); /** @@ -45,15 +43,13 @@ router.get('/', async (req, res) => { requiredPermissions: PermissionBits.EDIT, }); - const agentsResponse = await db.getListAgentsByAccess({ + const agentsResponse = await getListAgentsByAccess({ accessibleIds: editableAgentObjectIds, }); const editableAgentIds = agentsResponse.data.map((agent) => agent.id); const actions = - editableAgentIds.length > 0 - ? await db.getActions({ agent_id: { $in: editableAgentIds } }) - : []; + editableAgentIds.length > 0 ? await getActions({ agent_id: { $in: editableAgentIds } }) : []; res.json(actions); } catch (error) { @@ -123,21 +119,20 @@ router.post( return res.status(400).json({ message: 'Domain not allowed' }); } - const encodedDomain = await domainParser(metadata.domain, true); + let { domain } = metadata; + domain = await domainParser(domain, true); - if (!encodedDomain) { + if (!domain) { return res.status(400).json({ message: 'No domain provided' }); } - const legacyDomain = legacyDomainEncode(metadata.domain); - const action_id = _action_id ?? nanoid(); const initialPromises = []; // Permissions already validated by middleware - load agent directly - initialPromises.push(db.getAgent({ id: agent_id })); + initialPromises.push(getAgent({ id: agent_id })); if (_action_id) { - initialPromises.push(db.getActions({ action_id }, true)); + initialPromises.push(getActions({ action_id }, true)); } /** @type {[Agent, [Action|undefined]]} */ @@ -148,9 +143,6 @@ router.post( if (actions_result && actions_result.length) { const action = actions_result[0]; - if (action.agent_id !== agent_id) { - return res.status(403).json({ message: 'Action does not belong to this agent' }); - } metadata = { ...action.metadata, ...metadata }; } @@ -165,26 +157,17 @@ router.post( actions.push(action); } - actions.push(`${encodedDomain}${actionDelimiter}${action_id}`); + actions.push(`${domain}${actionDelimiter}${action_id}`); /** @type {string[]}} */ const { tools: _tools = [] } = agent; - const shouldRemoveAgentTool = (tool) => { - if (!tool) { - return false; - } - return ( - tool.includes(encodedDomain) || tool.includes(legacyDomain) || tool.includes(action_id) - ); - }; - const tools = _tools - .filter((tool) => !shouldRemoveAgentTool(tool)) - .concat(functions.map((tool) => `${tool.function.name}${actionDelimiter}${encodedDomain}`)); + .filter((tool) => !(tool && (tool.includes(domain) || tool.includes(action_id)))) + .concat(functions.map((tool) => `${tool.function.name}${actionDelimiter}${domain}`)); // Force version update since actions are changing - const updatedAgent = await db.updateAgent( + const updatedAgent = await updateAgent( { id: agent_id }, { tools, actions }, { @@ -201,7 +184,7 @@ router.post( } /** @type {[Action]} */ - const updatedAction = await db.updateAction({ action_id, agent_id }, actionUpdateData); + const updatedAction = await updateAction({ action_id }, actionUpdateData); const sensitiveFields = ['api_key', 'oauth_client_id', 'oauth_client_secret']; for (let field of sensitiveFields) { @@ -238,43 +221,37 @@ router.delete( const { agent_id, action_id } = req.params; // Permissions already validated by middleware - load agent directly - const agent = await db.getAgent({ id: agent_id }); + const agent = await getAgent({ id: agent_id }); if (!agent) { return res.status(404).json({ message: 'Agent not found for deleting action' }); } const { tools = [], actions = [] } = agent; - let storedDomain = ''; + let domain = ''; const updatedActions = actions.filter((action) => { if (action.includes(action_id)) { - [storedDomain] = action.split(actionDelimiter); + [domain] = action.split(actionDelimiter); return false; } return true; }); - if (!storedDomain) { + domain = await domainParser(domain, true); + + if (!domain) { return res.status(400).json({ message: 'No domain provided' }); } - const updatedTools = tools.filter( - (tool) => !(tool && (tool.includes(storedDomain) || tool.includes(action_id))), - ); + const updatedTools = tools.filter((tool) => !(tool && tool.includes(domain))); // Force version update since actions are being removed - await db.updateAgent( + await updateAgent( { id: agent_id }, { tools: updatedTools, actions: updatedActions }, { updatingUserId: req.user.id, forceVersion: true }, ); - const deleted = await db.deleteAction({ action_id, agent_id }); - if (!deleted) { - logger.warn('[Agent Action Delete] No matching action document found', { - action_id, - agent_id, - }); - } + await deleteAction({ action_id }); res.status(200).json({ message: 'Action deleted successfully' }); } catch (error) { const message = 'Trouble deleting the Agent Action'; diff --git a/api/server/routes/agents/chat.js b/api/server/routes/agents/chat.js index 0543b0b1aa..37b83f4f54 100644 --- a/api/server/routes/agents/chat.js +++ b/api/server/routes/agents/chat.js @@ -11,7 +11,7 @@ const { const { initializeClient } = require('~/server/services/Endpoints/agents'); const AgentController = require('~/server/controllers/agents/request'); const addTitle = require('~/server/services/Endpoints/agents/title'); -const { getRoleByName } = require('~/models'); +const { getRoleByName } = require('~/models/Role'); const router = express.Router(); diff --git a/api/server/routes/agents/index.js b/api/server/routes/agents/index.js index eb42046bed..f8d39cb4d8 100644 --- a/api/server/routes/agents/index.js +++ b/api/server/routes/agents/index.js @@ -10,18 +10,13 @@ const { messageUserLimiter, } = require('~/server/middleware'); const { saveMessage } = require('~/models'); -const responses = require('./responses'); const openai = require('./openai'); +const responses = require('./responses'); const { v1 } = require('./v1'); const chat = require('./chat'); const { LIMIT_MESSAGE_IP, LIMIT_MESSAGE_USER } = process.env ?? {}; -/** Untenanted jobs (pre-multi-tenancy) remain accessible if the userId check passes. */ -function hasTenantMismatch(job, user) { - return job.metadata?.tenantId != null && job.metadata.tenantId !== user.tenantId; -} - const router = express.Router(); /** @@ -72,10 +67,6 @@ router.get('/chat/stream/:streamId', async (req, res) => { return res.status(403).json({ error: 'Unauthorized' }); } - if (hasTenantMismatch(job, req.user)) { - return res.status(403).json({ error: 'Unauthorized' }); - } - res.setHeader('Content-Encoding', 'identity'); res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache, no-transform'); @@ -85,62 +76,52 @@ router.get('/chat/stream/:streamId', async (req, res) => { logger.debug(`[AgentStream] Client subscribed to ${streamId}, resume: ${isResume}`); - const writeEvent = (event) => { - if (!res.writableEnded) { - res.write(`event: message\ndata: ${JSON.stringify(event)}\n\n`); - if (typeof res.flush === 'function') { - res.flush(); - } - } - }; - - const onDone = (event) => { - writeEvent(event); - res.end(); - }; - - const onError = (error) => { - if (!res.writableEnded) { - res.write(`event: error\ndata: ${JSON.stringify({ error })}\n\n`); - if (typeof res.flush === 'function') { - res.flush(); - } - res.end(); - } - }; - - let result; - + // Send sync event with resume state for ALL reconnecting clients + // This supports multi-tab scenarios where each tab needs run step data if (isResume) { - const { subscription, resumeState, pendingEvents } = - await GenerationJobManager.subscribeWithResume(streamId, writeEvent, onDone, onError); + const resumeState = await GenerationJobManager.getResumeState(streamId); + if (resumeState && !res.writableEnded) { + // Send sync event with run steps AND aggregatedContent + // Client will use aggregatedContent to initialize message state + res.write(`event: message\ndata: ${JSON.stringify({ sync: true, resumeState })}\n\n`); + if (typeof res.flush === 'function') { + res.flush(); + } + logger.debug( + `[AgentStream] Sent sync event for ${streamId} with ${resumeState.runSteps.length} run steps`, + ); + } + } - if (!res.writableEnded) { - if (resumeState) { - res.write( - `event: message\ndata: ${JSON.stringify({ sync: true, resumeState, pendingEvents })}\n\n`, - ); + const result = await GenerationJobManager.subscribe( + streamId, + (event) => { + if (!res.writableEnded) { + res.write(`event: message\ndata: ${JSON.stringify(event)}\n\n`); if (typeof res.flush === 'function') { res.flush(); } - GenerationJobManager.markSyncSent(streamId); - logger.debug( - `[AgentStream] Sent sync event for ${streamId} with ${resumeState.runSteps.length} run steps, ${pendingEvents.length} pending events`, - ); - } else if (pendingEvents.length > 0) { - for (const event of pendingEvents) { - writeEvent(event); - } - logger.warn( - `[AgentStream] Resume state null for ${streamId}, replayed ${pendingEvents.length} gap events directly`, - ); } - } - - result = subscription; - } else { - result = await GenerationJobManager.subscribe(streamId, writeEvent, onDone, onError); - } + }, + (event) => { + if (!res.writableEnded) { + res.write(`event: message\ndata: ${JSON.stringify(event)}\n\n`); + if (typeof res.flush === 'function') { + res.flush(); + } + res.end(); + } + }, + (error) => { + if (!res.writableEnded) { + res.write(`event: error\ndata: ${JSON.stringify({ error })}\n\n`); + if (typeof res.flush === 'function') { + res.flush(); + } + res.end(); + } + }, + ); if (!result) { return res.status(404).json({ error: 'Failed to subscribe to stream' }); @@ -159,10 +140,7 @@ router.get('/chat/stream/:streamId', async (req, res) => { * @returns { activeJobIds: string[] } */ router.get('/chat/active', async (req, res) => { - const activeJobIds = await GenerationJobManager.getActiveJobIdsForUser( - req.user.id, - req.user.tenantId, - ); + const activeJobIds = await GenerationJobManager.getActiveJobIdsForUser(req.user.id); res.json({ activeJobIds }); }); @@ -186,10 +164,6 @@ router.get('/chat/status/:conversationId', async (req, res) => { return res.status(403).json({ error: 'Unauthorized' }); } - if (hasTenantMismatch(job, req.user)) { - return res.status(403).json({ error: 'Unauthorized' }); - } - // Get resume state which contains aggregatedContent // Avoid calling both getStreamInfo and getResumeState (both fetch content) const resumeState = await GenerationJobManager.getResumeState(conversationId); @@ -229,10 +203,7 @@ router.post('/chat/abort', async (req, res) => { // This handles the case where frontend sends "new" but job was created with a UUID if (!job && userId) { logger.debug(`[AgentStream] Job not found by ID, checking active jobs for user: ${userId}`); - const activeJobIds = await GenerationJobManager.getActiveJobIdsForUser( - userId, - req.user.tenantId, - ); + const activeJobIds = await GenerationJobManager.getActiveJobIdsForUser(userId); if (activeJobIds.length > 0) { // Abort the most recent active job for this user jobStreamId = activeJobIds[0]; @@ -249,10 +220,6 @@ router.post('/chat/abort', async (req, res) => { return res.status(403).json({ error: 'Unauthorized' }); } - if (hasTenantMismatch(job, req.user)) { - return res.status(403).json({ error: 'Unauthorized' }); - } - logger.debug(`[AgentStream] Job found, aborting: ${jobStreamId}`); const abortResult = await GenerationJobManager.abortJob(jobStreamId); logger.debug(`[AgentStream] Job aborted successfully: ${jobStreamId}`, { @@ -286,15 +253,9 @@ router.post('/chat/abort', async (req, res) => { }; try { - await saveMessage( - { - userId: req?.user?.id, - isTemporary: req?.body?.isTemporary, - interfaceConfig: req?.config?.interfaceConfig, - }, - responseMessage, - { context: 'api/server/routes/agents/index.js - abort endpoint' }, - ); + await saveMessage(req, responseMessage, { + context: 'api/server/routes/agents/index.js - abort endpoint', + }); logger.debug(`[AgentStream] Saved partial response for: ${jobStreamId}`); } catch (saveError) { logger.error(`[AgentStream] Failed to save partial response: ${saveError.message}`); diff --git a/api/server/routes/agents/openai.js b/api/server/routes/agents/openai.js index 72e3da6c5a..9a0d9a3564 100644 --- a/api/server/routes/agents/openai.js +++ b/api/server/routes/agents/openai.js @@ -29,24 +29,26 @@ const { GetModelController, } = require('~/server/controllers/agents/openai'); const { getEffectivePermissions } = require('~/server/services/PermissionService'); +const { validateAgentApiKey, findUser } = require('~/models'); const { configMiddleware } = require('~/server/middleware'); -const db = require('~/models'); +const { getRoleByName } = require('~/models/Role'); +const { getAgent } = require('~/models/Agent'); const router = express.Router(); const requireApiKeyAuth = createRequireApiKeyAuth({ - validateAgentApiKey: db.validateAgentApiKey, - findUser: db.findUser, + validateAgentApiKey, + findUser, }); const checkRemoteAgentsFeature = generateCheckAccess({ permissionType: PermissionTypes.REMOTE_AGENTS, permissions: [Permissions.USE], - getRoleByName: db.getRoleByName, + getRoleByName, }); const checkAgentPermission = createCheckRemoteAgentAccess({ - getAgent: db.getAgent, + getAgent, getEffectivePermissions, }); diff --git a/api/server/routes/agents/responses.js b/api/server/routes/agents/responses.js index 2c118e0597..431942e921 100644 --- a/api/server/routes/agents/responses.js +++ b/api/server/routes/agents/responses.js @@ -32,24 +32,26 @@ const { listModels, } = require('~/server/controllers/agents/responses'); const { getEffectivePermissions } = require('~/server/services/PermissionService'); +const { validateAgentApiKey, findUser } = require('~/models'); const { configMiddleware } = require('~/server/middleware'); -const db = require('~/models'); +const { getRoleByName } = require('~/models/Role'); +const { getAgent } = require('~/models/Agent'); const router = express.Router(); const requireApiKeyAuth = createRequireApiKeyAuth({ - validateAgentApiKey: db.validateAgentApiKey, - findUser: db.findUser, + validateAgentApiKey, + findUser, }); const checkRemoteAgentsFeature = generateCheckAccess({ permissionType: PermissionTypes.REMOTE_AGENTS, permissions: [Permissions.USE], - getRoleByName: db.getRoleByName, + getRoleByName, }); const checkAgentPermission = createCheckRemoteAgentAccess({ - getAgent: db.getAgent, + getAgent, getEffectivePermissions, }); diff --git a/api/server/routes/agents/v1.js b/api/server/routes/agents/v1.js index c4f90d0bd5..682a9c795f 100644 --- a/api/server/routes/agents/v1.js +++ b/api/server/routes/agents/v1.js @@ -3,7 +3,7 @@ const { generateCheckAccess } = require('@librechat/api'); const { PermissionTypes, Permissions, PermissionBits } = require('librechat-data-provider'); const { requireJwtAuth, configMiddleware, canAccessAgentResource } = require('~/server/middleware'); const v1 = require('~/server/controllers/agents/v1'); -const { getRoleByName } = require('~/models'); +const { getRoleByName } = require('~/models/Role'); const actions = require('./actions'); const tools = require('./tools'); @@ -21,6 +21,15 @@ const checkAgentCreate = generateCheckAccess({ getRoleByName, }); +const checkGlobalAgentShare = generateCheckAccess({ + permissionType: PermissionTypes.AGENTS, + permissions: [Permissions.USE, Permissions.CREATE], + bodyProps: { + [Permissions.SHARE]: ['projectIds', 'removeProjectIds'], + }, + getRoleByName, +}); + router.use(requireJwtAuth); /** @@ -90,7 +99,7 @@ router.get( */ router.patch( '/:id', - checkAgentCreate, + checkGlobalAgentShare, canAccessAgentResource({ requiredPermission: PermissionBits.EDIT, resourceIdParam: 'id', @@ -108,7 +117,7 @@ router.post( '/:id/duplicate', checkAgentCreate, canAccessAgentResource({ - requiredPermission: PermissionBits.EDIT, + requiredPermission: PermissionBits.VIEW, resourceIdParam: 'id', }), v1.duplicateAgent, @@ -139,7 +148,7 @@ router.delete( */ router.post( '/:id/revert', - checkAgentCreate, + checkGlobalAgentShare, canAccessAgentResource({ requiredPermission: PermissionBits.EDIT, resourceIdParam: 'id', diff --git a/api/server/routes/apiKeys.js b/api/server/routes/apiKeys.js index ee11a8b0dd..29dcc326f5 100644 --- a/api/server/routes/apiKeys.js +++ b/api/server/routes/apiKeys.js @@ -6,9 +6,9 @@ const { createAgentApiKey, deleteAgentApiKey, listAgentApiKeys, - getRoleByName, } = require('~/models'); const { requireJwtAuth } = require('~/server/middleware'); +const { getRoleByName } = require('~/models/Role'); const router = express.Router(); diff --git a/api/server/routes/assistants/actions.js b/api/server/routes/assistants/actions.js index 977d3f92a7..57975d32a7 100644 --- a/api/server/routes/assistants/actions.js +++ b/api/server/routes/assistants/actions.js @@ -3,13 +3,10 @@ const { nanoid } = require('nanoid'); const { logger } = require('@librechat/data-schemas'); const { isActionDomainAllowed } = require('@librechat/api'); const { actionDelimiter, EModelEndpoint, removeNullishValues } = require('librechat-data-provider'); -const { - legacyDomainEncode, - encryptMetadata, - domainParser, -} = require('~/server/services/ActionService'); +const { encryptMetadata, domainParser } = require('~/server/services/ActionService'); const { getOpenAIClient } = require('~/server/controllers/assistants/helpers'); -const db = require('~/models'); +const { updateAction, getActions, deleteAction } = require('~/models/Action'); +const { updateAssistantDoc, getAssistant } = require('~/models/Assistant'); const router = express.Router(); @@ -42,31 +39,27 @@ router.post('/:assistant_id', async (req, res) => { return res.status(400).json({ message: 'Domain not allowed' }); } - const encodedDomain = await domainParser(metadata.domain, true); + let { domain } = metadata; + domain = await domainParser(domain, true); - if (!encodedDomain) { + if (!domain) { return res.status(400).json({ message: 'No domain provided' }); } - const legacyDomain = legacyDomainEncode(metadata.domain); - const action_id = _action_id ?? nanoid(); const initialPromises = []; const { openai } = await getOpenAIClient({ req, res }); - initialPromises.push(db.getAssistant({ assistant_id })); + initialPromises.push(getAssistant({ assistant_id })); initialPromises.push(openai.beta.assistants.retrieve(assistant_id)); - !!_action_id && initialPromises.push(db.getActions({ action_id }, true)); + !!_action_id && initialPromises.push(getActions({ action_id }, true)); /** @type {[AssistantDocument, Assistant, [Action|undefined]]} */ const [assistant_data, assistant, actions_result] = await Promise.all(initialPromises); if (actions_result && actions_result.length) { const action = actions_result[0]; - if (action.assistant_id !== assistant_id) { - return res.status(403).json({ message: 'Action does not belong to this assistant' }); - } metadata = { ...action.metadata, ...metadata }; } @@ -85,29 +78,25 @@ router.post('/:assistant_id', async (req, res) => { actions.push(action); } - actions.push(`${encodedDomain}${actionDelimiter}${action_id}`); + actions.push(`${domain}${actionDelimiter}${action_id}`); /** @type {{ tools: FunctionTool[] | { type: 'code_interpreter'|'retrieval'}[]}} */ const { tools: _tools = [] } = assistant; - const shouldRemoveAssistantTool = (tool) => { - if (!tool.function) { - return false; - } - const name = tool.function.name; - return ( - name.includes(encodedDomain) || name.includes(legacyDomain) || name.includes(action_id) - ); - }; - const tools = _tools - .filter((tool) => !shouldRemoveAssistantTool(tool)) + .filter( + (tool) => + !( + tool.function && + (tool.function.name.includes(domain) || tool.function.name.includes(action_id)) + ), + ) .concat( functions.map((tool) => ({ ...tool, function: { ...tool.function, - name: `${tool.function.name}${actionDelimiter}${encodedDomain}`, + name: `${tool.function.name}${actionDelimiter}${domain}`, }, })), ); @@ -120,7 +109,7 @@ router.post('/:assistant_id', async (req, res) => { if (!assistant_data) { assistantUpdateData.user = req.user.id; } - promises.push(db.updateAssistantDoc({ assistant_id }, assistantUpdateData)); + promises.push(updateAssistantDoc({ assistant_id }, assistantUpdateData)); // Only update user field for new actions const actionUpdateData = { metadata, assistant_id }; @@ -128,7 +117,7 @@ router.post('/:assistant_id', async (req, res) => { // For new actions, use the assistant owner's user ID actionUpdateData.user = assistant_user || req.user.id; } - promises.push(db.updateAction({ action_id, assistant_id }, actionUpdateData)); + promises.push(updateAction({ action_id }, actionUpdateData)); /** @type {[AssistantDocument, Action]} */ let [assistantDocument, updatedAction] = await Promise.all(promises); @@ -170,7 +159,7 @@ router.delete('/:assistant_id/:action_id/:model', async (req, res) => { const { openai } = await getOpenAIClient({ req, res }); const initialPromises = []; - initialPromises.push(db.getAssistant({ assistant_id })); + initialPromises.push(getAssistant({ assistant_id })); initialPromises.push(openai.beta.assistants.retrieve(assistant_id)); /** @type {[AssistantDocument, Assistant]} */ @@ -179,25 +168,23 @@ router.delete('/:assistant_id/:action_id/:model', async (req, res) => { const { actions = [] } = assistant_data ?? {}; const { tools = [] } = assistant ?? {}; - let storedDomain = ''; + let domain = ''; const updatedActions = actions.filter((action) => { if (action.includes(action_id)) { - [storedDomain] = action.split(actionDelimiter); + [domain] = action.split(actionDelimiter); return false; } return true; }); - if (!storedDomain) { + domain = await domainParser(domain, true); + + if (!domain) { return res.status(400).json({ message: 'No domain provided' }); } const updatedTools = tools.filter( - (tool) => - !( - tool.function && - (tool.function.name.includes(storedDomain) || tool.function.name.includes(action_id)) - ), + (tool) => !(tool.function && tool.function.name.includes(domain)), ); await openai.beta.assistants.update(assistant_id, { tools: updatedTools }); @@ -208,16 +195,10 @@ router.delete('/:assistant_id/:action_id/:model', async (req, res) => { if (!assistant_data) { assistantUpdateData.user = req.user.id; } - promises.push(db.updateAssistantDoc({ assistant_id }, assistantUpdateData)); - promises.push(db.deleteAction({ action_id, assistant_id })); + promises.push(updateAssistantDoc({ assistant_id }, assistantUpdateData)); + promises.push(deleteAction({ action_id })); - const [, deletedAction] = await Promise.all(promises); - if (!deletedAction) { - logger.warn('[Assistant Action Delete] No matching action document found', { - action_id, - assistant_id, - }); - } + await Promise.all(promises); res.status(200).json({ message: 'Action deleted successfully' }); } catch (error) { const message = 'Trouble deleting the Assistant Action'; diff --git a/api/server/routes/auth.js b/api/server/routes/auth.js index c660e6f99d..e84442f65f 100644 --- a/api/server/routes/auth.js +++ b/api/server/routes/auth.js @@ -17,14 +17,13 @@ const { const { verify2FAWithTempToken } = require('~/server/controllers/auth/TwoFactorAuthController'); const { logoutController } = require('~/server/controllers/auth/LogoutController'); const { loginController } = require('~/server/controllers/auth/LoginController'); -const { findBalanceByUser, upsertBalanceFields } = require('~/models'); const { getAppConfig } = require('~/server/services/Config'); const middleware = require('~/server/middleware'); +const { Balance } = require('~/db/models'); const setBalanceConfig = createSetBalanceConfig({ getAppConfig, - findBalanceByUser, - upsertBalanceFields, + Balance, }); const router = express.Router(); @@ -64,7 +63,7 @@ router.post( resetPasswordController, ); -router.post('/2fa/enable', middleware.requireJwtAuth, enable2FA); +router.get('/2fa/enable', middleware.requireJwtAuth, enable2FA); router.post('/2fa/verify', middleware.requireJwtAuth, verify2FA); router.post('/2fa/verify-temp', middleware.checkBan, verify2FAWithTempToken); router.post('/2fa/confirm', middleware.requireJwtAuth, confirm2FA); diff --git a/api/server/routes/banner.js b/api/server/routes/banner.js index ad949fd2ca..cf7eafd017 100644 --- a/api/server/routes/banner.js +++ b/api/server/routes/banner.js @@ -1,15 +1,13 @@ const express = require('express'); -const { logger } = require('@librechat/data-schemas'); -const optionalJwtAuth = require('~/server/middleware/optionalJwtAuth'); -const { getBanner } = require('~/models'); +const { getBanner } = require('~/models/Banner'); +const optionalJwtAuth = require('~/server/middleware/optionalJwtAuth'); const router = express.Router(); router.get('/', optionalJwtAuth, async (req, res) => { try { res.status(200).send(await getBanner(req.user)); } catch (error) { - logger.error('[getBanner] Error getting banner', error); res.status(500).json({ message: 'Error getting banner' }); } }); diff --git a/api/server/routes/categories.js b/api/server/routes/categories.js index 612bc37860..da1828b3ce 100644 --- a/api/server/routes/categories.js +++ b/api/server/routes/categories.js @@ -1,7 +1,7 @@ const express = require('express'); const router = express.Router(); const { requireJwtAuth } = require('~/server/middleware'); -const { getCategories } = require('~/models'); +const { getCategories } = require('~/models/Categories'); router.get('/', requireJwtAuth, async (req, res) => { try { diff --git a/api/server/routes/config.js b/api/server/routes/config.js index a57e4bd958..a2dc5b79d2 100644 --- a/api/server/routes/config.js +++ b/api/server/routes/config.js @@ -1,9 +1,11 @@ const express = require('express'); +const { logger } = require('@librechat/data-schemas'); const { isEnabled, getBalanceConfig } = require('@librechat/api'); -const { defaultSocialLogins } = require('librechat-data-provider'); -const { logger, getTenantId } = require('@librechat/data-schemas'); +const { Constants, CacheKeys, defaultSocialLogins } = require('librechat-data-provider'); const { getLdapConfig } = require('~/server/services/Config/ldap'); const { getAppConfig } = require('~/server/services/Config/app'); +const { getProjectByName } = require('~/models/Project'); +const { getLogStores } = require('~/cache'); const router = express.Router(); const emailLoginEnabled = @@ -14,164 +16,138 @@ const sharedLinksEnabled = process.env.ALLOW_SHARED_LINKS === undefined || isEnabled(process.env.ALLOW_SHARED_LINKS); const publicSharedLinksEnabled = - sharedLinksEnabled && isEnabled(process.env.ALLOW_SHARED_LINKS_PUBLIC); + sharedLinksEnabled && + (process.env.ALLOW_SHARED_LINKS_PUBLIC === undefined || + isEnabled(process.env.ALLOW_SHARED_LINKS_PUBLIC)); const sharePointFilePickerEnabled = isEnabled(process.env.ENABLE_SHAREPOINT_FILEPICKER); const openidReuseTokens = isEnabled(process.env.OPENID_REUSE_TOKENS); -function isBirthday() { - const today = new Date(); - return today.getMonth() === 1 && today.getDate() === 11; -} +router.get('/', async function (req, res) { + const cache = getLogStores(CacheKeys.CONFIG_STORE); -function buildSharedPayload() { - const isOpenIdEnabled = - !!process.env.OPENID_CLIENT_ID && - !!process.env.OPENID_CLIENT_SECRET && - !!process.env.OPENID_ISSUER && - !!process.env.OPENID_SESSION_SECRET; + const cachedStartupConfig = await cache.get(CacheKeys.STARTUP_CONFIG); + if (cachedStartupConfig) { + res.send(cachedStartupConfig); + return; + } - const isSamlEnabled = - !!process.env.SAML_ENTRY_POINT && - !!process.env.SAML_ISSUER && - !!process.env.SAML_CERT && - !!process.env.SAML_SESSION_SECRET; + const isBirthday = () => { + const today = new Date(); + return today.getMonth() === 1 && today.getDate() === 11; + }; + + const instanceProject = await getProjectByName(Constants.GLOBAL_PROJECT_NAME, '_id'); const ldap = getLdapConfig(); - /** @type {Partial} */ - const payload = { - appTitle: process.env.APP_TITLE || 'LibreChat', - discordLoginEnabled: !!process.env.DISCORD_CLIENT_ID && !!process.env.DISCORD_CLIENT_SECRET, - facebookLoginEnabled: !!process.env.FACEBOOK_CLIENT_ID && !!process.env.FACEBOOK_CLIENT_SECRET, - githubLoginEnabled: !!process.env.GITHUB_CLIENT_ID && !!process.env.GITHUB_CLIENT_SECRET, - googleLoginEnabled: !!process.env.GOOGLE_CLIENT_ID && !!process.env.GOOGLE_CLIENT_SECRET, - appleLoginEnabled: - !!process.env.APPLE_CLIENT_ID && - !!process.env.APPLE_TEAM_ID && - !!process.env.APPLE_KEY_ID && - !!process.env.APPLE_PRIVATE_KEY_PATH, - openidLoginEnabled: isOpenIdEnabled, - openidLabel: process.env.OPENID_BUTTON_LABEL || 'Continue with OpenID', - openidImageUrl: process.env.OPENID_IMAGE_URL, - openidAutoRedirect: isEnabled(process.env.OPENID_AUTO_REDIRECT), - samlLoginEnabled: !isOpenIdEnabled && isSamlEnabled, - samlLabel: process.env.SAML_BUTTON_LABEL, - samlImageUrl: process.env.SAML_IMAGE_URL, - serverDomain: process.env.DOMAIN_SERVER || 'http://localhost:3080', - emailLoginEnabled, - registrationEnabled: !ldap?.enabled && isEnabled(process.env.ALLOW_REGISTRATION), - socialLoginEnabled: isEnabled(process.env.ALLOW_SOCIAL_LOGIN), - emailEnabled: - (!!process.env.EMAIL_SERVICE || !!process.env.EMAIL_HOST) && - !!process.env.EMAIL_USERNAME && - !!process.env.EMAIL_PASSWORD && - !!process.env.EMAIL_FROM, - passwordResetEnabled, - showBirthdayIcon: - isBirthday() || - isEnabled(process.env.SHOW_BIRTHDAY_ICON) || - process.env.SHOW_BIRTHDAY_ICON === '', - helpAndFaqURL: process.env.HELP_AND_FAQ_URL || 'https://librechat.ai', - sharedLinksEnabled, - publicSharedLinksEnabled, - analyticsGtmId: process.env.ANALYTICS_GTM_ID, - openidReuseTokens, - }; - - const minPasswordLength = parseInt(process.env.MIN_PASSWORD_LENGTH, 10); - if (minPasswordLength && !isNaN(minPasswordLength)) { - payload.minPasswordLength = minPasswordLength; - } - - if (ldap) { - payload.ldap = ldap; - } - - if (typeof process.env.CUSTOM_FOOTER === 'string') { - payload.customFooter = process.env.CUSTOM_FOOTER; - } - - return payload; -} - -function buildWebSearchConfig(appConfig) { - const ws = appConfig?.webSearch; - if (!ws) { - return undefined; - } - const { searchProvider, scraperProvider, rerankerType } = ws; - if (!searchProvider && !scraperProvider && !rerankerType) { - return undefined; - } - return { - ...(searchProvider && { searchProvider }), - ...(scraperProvider && { scraperProvider }), - ...(rerankerType && { rerankerType }), - }; -} - -router.get('/', async function (req, res) { try { - const sharedPayload = buildSharedPayload(); + const appConfig = await getAppConfig({ role: req.user?.role }); - if (!req.user) { - const tenantId = getTenantId(); - const baseConfig = await getAppConfig(tenantId ? { tenantId } : { baseOnly: true }); + const isOpenIdEnabled = + !!process.env.OPENID_CLIENT_ID && + !!process.env.OPENID_CLIENT_SECRET && + !!process.env.OPENID_ISSUER && + !!process.env.OPENID_SESSION_SECRET; - /** @type {Partial} */ - const payload = { - ...sharedPayload, - socialLogins: baseConfig?.registration?.socialLogins ?? defaultSocialLogins, - turnstile: baseConfig?.turnstileConfig, - }; - - const interfaceConfig = baseConfig?.interfaceConfig; - if (interfaceConfig?.privacyPolicy || interfaceConfig?.termsOfService) { - payload.interface = {}; - if (interfaceConfig.privacyPolicy) { - payload.interface.privacyPolicy = interfaceConfig.privacyPolicy; - } - if (interfaceConfig.termsOfService) { - payload.interface.termsOfService = interfaceConfig.termsOfService; - } - } - - return res.status(200).send(payload); - } - - const appConfig = await getAppConfig({ - role: req.user.role, - userId: req.user.id, - tenantId: req.user.tenantId || getTenantId(), - }); + const isSamlEnabled = + !!process.env.SAML_ENTRY_POINT && + !!process.env.SAML_ISSUER && + !!process.env.SAML_CERT && + !!process.env.SAML_SESSION_SECRET; const balanceConfig = getBalanceConfig(appConfig); /** @type {TStartupConfig} */ const payload = { - ...sharedPayload, + appTitle: process.env.APP_TITLE || 'LibreChat', socialLogins: appConfig?.registration?.socialLogins ?? defaultSocialLogins, + discordLoginEnabled: !!process.env.DISCORD_CLIENT_ID && !!process.env.DISCORD_CLIENT_SECRET, + facebookLoginEnabled: + !!process.env.FACEBOOK_CLIENT_ID && !!process.env.FACEBOOK_CLIENT_SECRET, + githubLoginEnabled: !!process.env.GITHUB_CLIENT_ID && !!process.env.GITHUB_CLIENT_SECRET, + googleLoginEnabled: !!process.env.GOOGLE_CLIENT_ID && !!process.env.GOOGLE_CLIENT_SECRET, + appleLoginEnabled: + !!process.env.APPLE_CLIENT_ID && + !!process.env.APPLE_TEAM_ID && + !!process.env.APPLE_KEY_ID && + !!process.env.APPLE_PRIVATE_KEY_PATH, + openidLoginEnabled: isOpenIdEnabled, + openidLabel: process.env.OPENID_BUTTON_LABEL || 'Continue with OpenID', + openidImageUrl: process.env.OPENID_IMAGE_URL, + openidAutoRedirect: isEnabled(process.env.OPENID_AUTO_REDIRECT), + samlLoginEnabled: !isOpenIdEnabled && isSamlEnabled, + samlLabel: process.env.SAML_BUTTON_LABEL, + samlImageUrl: process.env.SAML_IMAGE_URL, + serverDomain: process.env.DOMAIN_SERVER || 'http://localhost:3080', + emailLoginEnabled, + registrationEnabled: !ldap?.enabled && isEnabled(process.env.ALLOW_REGISTRATION), + socialLoginEnabled: isEnabled(process.env.ALLOW_SOCIAL_LOGIN), + emailEnabled: + (!!process.env.EMAIL_SERVICE || !!process.env.EMAIL_HOST) && + !!process.env.EMAIL_USERNAME && + !!process.env.EMAIL_PASSWORD && + !!process.env.EMAIL_FROM, + passwordResetEnabled, + showBirthdayIcon: + isBirthday() || + isEnabled(process.env.SHOW_BIRTHDAY_ICON) || + process.env.SHOW_BIRTHDAY_ICON === '', + helpAndFaqURL: process.env.HELP_AND_FAQ_URL || 'https://librechat.ai', interface: appConfig?.interfaceConfig, turnstile: appConfig?.turnstileConfig, modelSpecs: appConfig?.modelSpecs, balance: balanceConfig, + sharedLinksEnabled, + publicSharedLinksEnabled, + analyticsGtmId: process.env.ANALYTICS_GTM_ID, + instanceProjectId: instanceProject._id.toString(), bundlerURL: process.env.SANDPACK_BUNDLER_URL, staticBundlerURL: process.env.SANDPACK_STATIC_BUNDLER_URL, sharePointFilePickerEnabled, sharePointBaseUrl: process.env.SHAREPOINT_BASE_URL, sharePointPickerGraphScope: process.env.SHAREPOINT_PICKER_GRAPH_SCOPE, sharePointPickerSharePointScope: process.env.SHAREPOINT_PICKER_SHAREPOINT_SCOPE, + openidReuseTokens, conversationImportMaxFileSize: process.env.CONVERSATION_IMPORT_MAX_FILE_SIZE_BYTES ? parseInt(process.env.CONVERSATION_IMPORT_MAX_FILE_SIZE_BYTES, 10) : 0, }; - const webSearch = buildWebSearchConfig(appConfig); - if (webSearch) { - payload.webSearch = webSearch; + const minPasswordLength = parseInt(process.env.MIN_PASSWORD_LENGTH, 10); + if (minPasswordLength && !isNaN(minPasswordLength)) { + payload.minPasswordLength = minPasswordLength; } + const webSearchConfig = appConfig?.webSearch; + if ( + webSearchConfig != null && + (webSearchConfig.searchProvider || + webSearchConfig.scraperProvider || + webSearchConfig.rerankerType) + ) { + payload.webSearch = {}; + } + + if (webSearchConfig?.searchProvider) { + payload.webSearch.searchProvider = webSearchConfig.searchProvider; + } + if (webSearchConfig?.scraperProvider) { + payload.webSearch.scraperProvider = webSearchConfig.scraperProvider; + } + if (webSearchConfig?.rerankerType) { + payload.webSearch.rerankerType = webSearchConfig.rerankerType; + } + + if (ldap) { + payload.ldap = ldap; + } + + if (typeof process.env.CUSTOM_FOOTER === 'string') { + payload.customFooter = process.env.CUSTOM_FOOTER; + } + + await cache.set(CacheKeys.STARTUP_CONFIG, payload); return res.status(200).send(payload); } catch (err) { logger.error('Error in startup config', err); diff --git a/api/server/routes/convos.js b/api/server/routes/convos.js index ded7d835d7..bb9c4ebea9 100644 --- a/api/server/routes/convos.js +++ b/api/server/routes/convos.js @@ -1,7 +1,7 @@ const multer = require('multer'); const express = require('express'); const { sleep } = require('@librechat/agents'); -const { isEnabled, resolveImportMaxFileSize } = require('@librechat/api'); +const { isEnabled } = require('@librechat/api'); const { logger } = require('@librechat/data-schemas'); const { CacheKeys, EModelEndpoint } = require('librechat-data-provider'); const { @@ -10,12 +10,14 @@ const { createForkLimiters, configMiddleware, } = require('~/server/middleware'); +const { getConvosByCursor, deleteConvos, getConvo, saveConvo } = require('~/models/Conversation'); const { forkConversation, duplicateConversation } = require('~/server/utils/import/fork'); const { storage, importFileFilter } = require('~/server/routes/files/multer'); +const { deleteAllSharedLinks, deleteConvoSharedLink } = require('~/models'); const requireJwtAuth = require('~/server/middleware/requireJwtAuth'); const { importConversations } = require('~/server/utils/import'); +const { deleteToolCalls } = require('~/models/ToolCall'); const getLogStores = require('~/cache/getLogStores'); -const db = require('~/models'); const assistantClients = { [EModelEndpoint.azureAssistants]: require('~/server/services/Endpoints/azureAssistants'), @@ -39,7 +41,7 @@ router.get('/', async (req, res) => { } try { - const result = await db.getConvosByCursor(req.user.id, { + const result = await getConvosByCursor(req.user.id, { cursor, limit, isArchived, @@ -57,7 +59,7 @@ router.get('/', async (req, res) => { router.get('/:conversationId', async (req, res) => { const { conversationId } = req.params; - const convo = await db.getConvo(req.user.id, conversationId); + const convo = await getConvo(req.user.id, conversationId); if (convo) { res.status(200).json(convo); @@ -126,10 +128,10 @@ router.delete('/', async (req, res) => { } try { - const dbResponse = await db.deleteConvos(req.user.id, filter); + const dbResponse = await deleteConvos(req.user.id, filter); if (filter.conversationId) { - await db.deleteToolCalls(req.user.id, filter.conversationId); - await db.deleteConvoSharedLink(req.user.id, filter.conversationId); + await deleteToolCalls(req.user.id, filter.conversationId); + await deleteConvoSharedLink(req.user.id, filter.conversationId); } res.status(201).json(dbResponse); } catch (error) { @@ -140,9 +142,9 @@ router.delete('/', async (req, res) => { router.delete('/all', async (req, res) => { try { - const dbResponse = await db.deleteConvos(req.user.id, {}); - await db.deleteToolCalls(req.user.id); - await db.deleteAllSharedLinks(req.user.id); + const dbResponse = await deleteConvos(req.user.id, {}); + await deleteToolCalls(req.user.id); + await deleteAllSharedLinks(req.user.id); res.status(201).json(dbResponse); } catch (error) { logger.error('Error clearing conversations', error); @@ -169,12 +171,8 @@ router.post('/archive', validateConvoAccess, async (req, res) => { } try { - const dbResponse = await db.saveConvo( - { - userId: req?.user?.id, - isTemporary: req?.body?.isTemporary, - interfaceConfig: req?.config?.interfaceConfig, - }, + const dbResponse = await saveConvo( + req, { conversationId, isArchived }, { context: `POST /api/convos/archive ${conversationId}` }, ); @@ -213,12 +211,8 @@ router.post('/update', validateConvoAccess, async (req, res) => { const sanitizedTitle = title.trim().slice(0, MAX_CONVO_TITLE_LENGTH); try { - const dbResponse = await db.saveConvo( - { - userId: req?.user?.id, - isTemporary: req?.body?.isTemporary, - interfaceConfig: req?.config?.interfaceConfig, - }, + const dbResponse = await saveConvo( + req, { conversationId, title: sanitizedTitle }, { context: `POST /api/convos/update ${conversationId}` }, ); @@ -230,27 +224,8 @@ router.post('/update', validateConvoAccess, async (req, res) => { }); const { importIpLimiter, importUserLimiter } = createImportLimiters(); -/** Fork and duplicate share one rate-limit budget (same "clone" operation class) */ const { forkIpLimiter, forkUserLimiter } = createForkLimiters(); -const importMaxFileSize = resolveImportMaxFileSize(); -const upload = multer({ - storage, - fileFilter: importFileFilter, - limits: { fileSize: importMaxFileSize }, -}); -const uploadSingle = upload.single('file'); - -function handleUpload(req, res, next) { - uploadSingle(req, res, (err) => { - if (err && err.code === 'LIMIT_FILE_SIZE') { - return res.status(413).json({ message: 'File exceeds the maximum allowed size' }); - } - if (err) { - return next(err); - } - next(); - }); -} +const upload = multer({ storage: storage, fileFilter: importFileFilter }); /** * Imports a conversation from a JSON file and saves it to the database. @@ -263,15 +238,11 @@ router.post( importIpLimiter, importUserLimiter, configMiddleware, - handleUpload, + upload.single('file'), async (req, res) => { try { /* TODO: optimize to return imported conversations and add manually */ - await importConversations({ - filepath: req.file.path, - requestUserId: req.user.id, - userRole: req.user.role, - }); + await importConversations({ filepath: req.file.path, requestUserId: req.user.id }); res.status(201).json({ message: 'Conversation(s) imported successfully' }); } catch (error) { logger.error('Error processing file', error); @@ -309,7 +280,7 @@ router.post('/fork', forkIpLimiter, forkUserLimiter, async (req, res) => { } }); -router.post('/duplicate', forkIpLimiter, forkUserLimiter, async (req, res) => { +router.post('/duplicate', async (req, res) => { const { conversationId, title } = req.body; try { diff --git a/api/server/routes/endpoints.js b/api/server/routes/endpoints.js index e7ff1c7000..794abde0c2 100644 --- a/api/server/routes/endpoints.js +++ b/api/server/routes/endpoints.js @@ -1,9 +1,7 @@ const express = require('express'); -const requireJwtAuth = require('~/server/middleware/requireJwtAuth'); const endpointController = require('~/server/controllers/EndpointController'); const router = express.Router(); -/** Auth required for role/tenant-scoped endpoint config resolution. */ -router.get('/', requireJwtAuth, endpointController); +router.get('/', endpointController); module.exports = router; diff --git a/api/server/routes/files/files.agents.test.js b/api/server/routes/files/files.agents.test.js index cb0e4ff3d2..7c21e95234 100644 --- a/api/server/routes/files/files.agents.test.js +++ b/api/server/routes/files/files.agents.test.js @@ -2,15 +2,16 @@ const express = require('express'); const request = require('supertest'); const mongoose = require('mongoose'); const { v4: uuidv4 } = require('uuid'); +const { createMethods } = require('@librechat/data-schemas'); const { MongoMemoryServer } = require('mongodb-memory-server'); -const { createMethods, SystemCapabilities } = require('@librechat/data-schemas'); const { SystemRoles, AccessRoleIds, ResourceType, PrincipalType, } = require('librechat-data-provider'); -const { createAgent, createFile } = require('~/models'); +const { createAgent } = require('~/models/Agent'); +const { createFile } = require('~/models'); // Only mock the external dependencies that we don't want to test jest.mock('~/server/services/Files/process', () => ({ @@ -38,16 +39,7 @@ jest.mock('~/server/services/Tools/credentials', () => ({ loadAuthValues: jest.fn(), })); -jest.mock('sharp', () => - jest.fn(() => ({ - metadata: jest.fn().mockResolvedValue({}), - toFormat: jest.fn().mockReturnThis(), - toBuffer: jest.fn().mockResolvedValue(Buffer.alloc(0)), - })), -); - -jest.mock('@librechat/api', () => ({ - ...jest.requireActual('@librechat/api'), +jest.mock('~/server/services/Files/S3/crud', () => ({ refreshS3FileUrls: jest.fn(), })); @@ -91,7 +83,6 @@ describe('File Routes - Agent Files Endpoint', () => { let AclEntry; // eslint-disable-next-line no-unused-vars let AccessRole; - let SystemGrant; let modelsToCleanup = []; beforeAll(async () => { @@ -118,7 +109,6 @@ describe('File Routes - Agent Files Endpoint', () => { AclEntry = models.AclEntry; User = models.User; AccessRole = models.AccessRole; - SystemGrant = models.SystemGrant; // Seed default roles using our methods await methods.seedDefaultRoles(); @@ -543,7 +533,7 @@ describe('File Routes - Agent Files Endpoint', () => { expect(processAgentFileUpload).not.toHaveBeenCalled(); }); - it('should allow file upload for user with MANAGE_AGENTS capability regardless of agent ownership', async () => { + it('should allow file upload for admin user regardless of agent ownership', async () => { // Create an agent owned by authorId await createAgent({ id: agentCustomId, @@ -553,14 +543,6 @@ describe('File Routes - Agent Files Endpoint', () => { author: authorId, }); - // Seed MANAGE_AGENTS capability for the ADMIN role - await SystemGrant.create({ - principalType: PrincipalType.ROLE, - principalId: SystemRoles.ADMIN, - capability: SystemCapabilities.MANAGE_AGENTS, - grantedAt: new Date(), - }); - // Create app with admin user (otherUserId as admin) const testApp = createAppWithUser(otherUserId, SystemRoles.ADMIN); diff --git a/api/server/routes/files/files.js b/api/server/routes/files/files.js index eb13ecdc31..5de2ddb379 100644 --- a/api/server/routes/files/files.js +++ b/api/server/routes/files/files.js @@ -1,17 +1,13 @@ const fs = require('fs').promises; const express = require('express'); const { EnvVar } = require('@librechat/agents'); -const { logger, SystemCapabilities } = require('@librechat/data-schemas'); -const { - refreshS3FileUrls, - resolveUploadErrorMessage, - verifyAgentUploadPermission, -} = require('@librechat/api'); +const { logger } = require('@librechat/data-schemas'); const { Time, isUUID, CacheKeys, FileSources, + SystemRoles, ResourceType, EModelEndpoint, PermissionBits, @@ -27,27 +23,29 @@ const { const { fileAccess } = require('~/server/middleware/accessResources/fileAccess'); const { getStrategyFunctions } = require('~/server/services/Files/strategies'); const { getOpenAIClient } = require('~/server/controllers/assistants/helpers'); -const { hasCapability } = require('~/server/middleware/roles/capabilities'); const { checkPermission } = require('~/server/services/PermissionService'); const { loadAuthValues } = require('~/server/services/Tools/credentials'); +const { refreshS3FileUrls } = require('~/server/services/Files/S3/crud'); const { hasAccessToFilesViaAgent } = require('~/server/services/Files'); +const { getFiles, batchUpdateFiles } = require('~/models'); const { cleanFileName } = require('~/server/utils/files'); +const { getAssistant } = require('~/models/Assistant'); +const { getAgent } = require('~/models/Agent'); const { getLogStores } = require('~/cache'); const { Readable } = require('stream'); -const db = require('~/models'); const router = express.Router(); router.get('/', async (req, res) => { try { const appConfig = req.config; - const files = await db.getFiles({ user: req.user.id }); + const files = await getFiles({ user: req.user.id }); if (appConfig.fileStrategy === FileSources.s3) { try { const cache = getLogStores(CacheKeys.S3_EXPIRY_INTERVAL); const alreadyChecked = await cache.get(req.user.id); if (!alreadyChecked) { - await refreshS3FileUrls(files, db.batchUpdateFiles); + await refreshS3FileUrls(files, batchUpdateFiles); await cache.set(req.user.id, true, Time.THIRTY_MINUTES); } } catch (error) { @@ -76,7 +74,7 @@ router.get('/agent/:agent_id', async (req, res) => { return res.status(400).json({ error: 'Agent ID is required' }); } - const agent = await db.getAgent({ id: agent_id }); + const agent = await getAgent({ id: agent_id }); if (!agent) { return res.status(200).json([]); } @@ -108,7 +106,7 @@ router.get('/agent/:agent_id', async (req, res) => { return res.status(200).json([]); } - const files = await db.getFiles({ file_id: { $in: agentFileIds } }, null, { text: 0 }); + const files = await getFiles({ file_id: { $in: agentFileIds } }, null, { text: 0 }); res.status(200).json(files); } catch (error) { @@ -153,7 +151,7 @@ router.delete('/', async (req, res) => { } const fileIds = files.map((file) => file.file_id); - const dbFiles = await db.getFiles({ file_id: { $in: fileIds } }); + const dbFiles = await getFiles({ file_id: { $in: fileIds } }); const ownedFiles = []; const nonOwnedFiles = []; @@ -211,7 +209,7 @@ router.delete('/', async (req, res) => { /* Handle agent unlinking even if no valid files to delete */ if (req.body.agent_id && req.body.tool_resource && dbFiles.length === 0) { - const agent = await db.getAgent({ + const agent = await getAgent({ id: req.body.agent_id, }); @@ -225,7 +223,7 @@ router.delete('/', async (req, res) => { /* Handle assistant unlinking even if no valid files to delete */ if (req.body.assistant_id && req.body.tool_resource && dbFiles.length === 0) { - const assistant = await db.getAssistant({ + const assistant = await getAssistant({ id: req.body.assistant_id, }); @@ -383,31 +381,67 @@ router.post('/', async (req, res) => { return await processFileUpload({ req, res, metadata }); } - let skipUploadAuth = false; - try { - skipUploadAuth = await hasCapability(req.user, SystemCapabilities.MANAGE_AGENTS); - } catch (err) { - logger.warn(`[/files] capability check failed, denying bypass: ${err.message}`); - } + /** + * Check agent permissions for permanent agent file uploads (not message attachments). + * Message attachments (message_file=true) are temporary files for a single conversation + * and should be allowed for users who can chat with the agent. + * Permanent file uploads to tool_resources require EDIT permission. + */ + const isMessageAttachment = metadata.message_file === true || metadata.message_file === 'true'; + if (metadata.agent_id && metadata.tool_resource && !isMessageAttachment) { + const userId = req.user.id; - if (!skipUploadAuth) { - const denied = await verifyAgentUploadPermission({ - req, - res, - metadata, - getAgent: db.getAgent, - checkPermission, - }); - if (denied) { - return; + /** Admin users bypass permission checks */ + if (req.user.role !== SystemRoles.ADMIN) { + const agent = await getAgent({ id: metadata.agent_id }); + + if (!agent) { + return res.status(404).json({ + error: 'Not Found', + message: 'Agent not found', + }); + } + + /** Check if user is the author or has edit permission */ + if (agent.author.toString() !== userId) { + const hasEditPermission = await checkPermission({ + userId, + role: req.user.role, + resourceType: ResourceType.AGENT, + resourceId: agent._id, + requiredPermission: PermissionBits.EDIT, + }); + + if (!hasEditPermission) { + logger.warn( + `[/files] User ${userId} denied upload to agent ${metadata.agent_id} (insufficient permissions)`, + ); + return res.status(403).json({ + error: 'Forbidden', + message: 'Insufficient permissions to upload files to this agent', + }); + } + } } } return await processAgentFileUpload({ req, res, metadata }); } catch (error) { - const message = resolveUploadErrorMessage(error); + let message = 'Error processing file'; logger.error('[/files] Error processing file:', error); + if (error.message?.includes('file_ids')) { + message += ': ' + error.message; + } + + if ( + error.message?.includes('Invalid file format') || + error.message?.includes('No OCR result') || + error.message?.includes('exceeds token limit') + ) { + message = error.message; + } + try { await fs.unlink(req.file.path); cleanup = false; diff --git a/api/server/routes/files/files.test.js b/api/server/routes/files/files.test.js index 37cbf68b93..1d548b44be 100644 --- a/api/server/routes/files/files.test.js +++ b/api/server/routes/files/files.test.js @@ -10,7 +10,8 @@ const { AccessRoleIds, PrincipalType, } = require('librechat-data-provider'); -const { createAgent, createFile } = require('~/models'); +const { createAgent } = require('~/models/Agent'); +const { createFile } = require('~/models'); // Only mock the external dependencies that we don't want to test jest.mock('~/server/services/Files/process', () => ({ @@ -32,16 +33,7 @@ jest.mock('~/server/services/Tools/credentials', () => ({ loadAuthValues: jest.fn(), })); -jest.mock('sharp', () => - jest.fn(() => ({ - metadata: jest.fn().mockResolvedValue({}), - toFormat: jest.fn().mockReturnThis(), - toBuffer: jest.fn().mockResolvedValue(Buffer.alloc(0)), - })), -); - -jest.mock('@librechat/api', () => ({ - ...jest.requireActual('@librechat/api'), +jest.mock('~/server/services/Files/S3/crud', () => ({ refreshS3FileUrls: jest.fn(), })); diff --git a/api/server/routes/files/images.agents.test.js b/api/server/routes/files/images.agents.test.js deleted file mode 100644 index f855a436d4..0000000000 --- a/api/server/routes/files/images.agents.test.js +++ /dev/null @@ -1,376 +0,0 @@ -const express = require('express'); -const request = require('supertest'); -const mongoose = require('mongoose'); -const { v4: uuidv4 } = require('uuid'); -const { createMethods } = require('@librechat/data-schemas'); -const { MongoMemoryServer } = require('mongodb-memory-server'); -const { - SystemRoles, - AccessRoleIds, - ResourceType, - PrincipalType, -} = require('librechat-data-provider'); -const { createAgent } = require('~/models'); - -jest.mock('~/server/services/Files/process', () => ({ - processAgentFileUpload: jest.fn().mockImplementation(async ({ res }) => { - return res.status(200).json({ message: 'Agent file uploaded', file_id: 'test-file-id' }); - }), - processImageFile: jest.fn().mockImplementation(async ({ res }) => { - return res.status(200).json({ message: 'Image processed' }); - }), - filterFile: jest.fn(), -})); - -jest.mock('fs', () => { - const actualFs = jest.requireActual('fs'); - return { - ...actualFs, - promises: { - ...actualFs.promises, - unlink: jest.fn().mockResolvedValue(undefined), - }, - }; -}); - -const fs = require('fs'); -const { processAgentFileUpload } = require('~/server/services/Files/process'); - -const router = require('~/server/routes/files/images'); - -describe('POST /images - Agent Upload Permission Check (Integration)', () => { - let mongoServer; - let authorId; - let otherUserId; - let agentCustomId; - let User; - let Agent; - let AclEntry; - let methods; - let modelsToCleanup = []; - - beforeAll(async () => { - mongoServer = await MongoMemoryServer.create(); - const mongoUri = mongoServer.getUri(); - await mongoose.connect(mongoUri); - - const { createModels } = require('@librechat/data-schemas'); - const models = createModels(mongoose); - modelsToCleanup = Object.keys(models); - Object.assign(mongoose.models, models); - methods = createMethods(mongoose); - - User = models.User; - Agent = models.Agent; - AclEntry = models.AclEntry; - - await methods.seedDefaultRoles(); - }); - - afterAll(async () => { - const collections = mongoose.connection.collections; - for (const key in collections) { - await collections[key].deleteMany({}); - } - for (const modelName of modelsToCleanup) { - if (mongoose.models[modelName]) { - delete mongoose.models[modelName]; - } - } - await mongoose.disconnect(); - await mongoServer.stop(); - }); - - beforeEach(async () => { - await Agent.deleteMany({}); - await User.deleteMany({}); - await AclEntry.deleteMany({}); - - authorId = new mongoose.Types.ObjectId(); - otherUserId = new mongoose.Types.ObjectId(); - agentCustomId = `agent_${uuidv4().replace(/-/g, '').substring(0, 21)}`; - - await User.create({ _id: authorId, username: 'author', email: 'author@test.com' }); - await User.create({ _id: otherUserId, username: 'other', email: 'other@test.com' }); - - jest.clearAllMocks(); - }); - - const createAppWithUser = (userId, userRole = SystemRoles.USER) => { - const app = express(); - app.use(express.json()); - app.use((req, _res, next) => { - if (req.method === 'POST') { - req.file = { - originalname: 'test.png', - mimetype: 'image/png', - size: 100, - path: '/tmp/t.png', - filename: 'test.png', - }; - req.file_id = uuidv4(); - } - next(); - }); - app.use((req, _res, next) => { - req.user = { id: userId.toString(), role: userRole }; - req.app = { locals: {} }; - req.config = { fileStrategy: 'local', paths: { imageOutput: '/tmp/images' } }; - next(); - }); - app.use('/images', router); - return app; - }; - - it('should return 403 when user has no permission on agent', async () => { - await createAgent({ - id: agentCustomId, - name: 'Test Agent', - provider: 'openai', - model: 'gpt-4', - author: authorId, - }); - - const app = createAppWithUser(otherUserId); - const response = await request(app).post('/images').send({ - endpoint: 'agents', - agent_id: agentCustomId, - tool_resource: 'context', - file_id: uuidv4(), - }); - - expect(response.status).toBe(403); - expect(response.body.error).toBe('Forbidden'); - expect(processAgentFileUpload).not.toHaveBeenCalled(); - expect(fs.promises.unlink).toHaveBeenCalledWith('/tmp/t.png'); - }); - - it('should allow upload for agent owner', async () => { - await createAgent({ - id: agentCustomId, - name: 'Test Agent', - provider: 'openai', - model: 'gpt-4', - author: authorId, - }); - - const app = createAppWithUser(authorId); - const response = await request(app).post('/images').send({ - endpoint: 'agents', - agent_id: agentCustomId, - tool_resource: 'context', - file_id: uuidv4(), - }); - - expect(response.status).toBe(200); - expect(processAgentFileUpload).toHaveBeenCalled(); - }); - - it('should allow upload for admin regardless of ownership', async () => { - await createAgent({ - id: agentCustomId, - name: 'Test Agent', - provider: 'openai', - model: 'gpt-4', - author: authorId, - }); - - const app = createAppWithUser(otherUserId, SystemRoles.ADMIN); - const response = await request(app).post('/images').send({ - endpoint: 'agents', - agent_id: agentCustomId, - tool_resource: 'context', - file_id: uuidv4(), - }); - - expect(response.status).toBe(200); - expect(processAgentFileUpload).toHaveBeenCalled(); - }); - - it('should allow upload for user with EDIT permission', async () => { - const agent = await createAgent({ - id: agentCustomId, - name: 'Test Agent', - provider: 'openai', - model: 'gpt-4', - author: authorId, - }); - - const { grantPermission } = require('~/server/services/PermissionService'); - await grantPermission({ - principalType: PrincipalType.USER, - principalId: otherUserId, - resourceType: ResourceType.AGENT, - resourceId: agent._id, - accessRoleId: AccessRoleIds.AGENT_EDITOR, - grantedBy: authorId, - }); - - const app = createAppWithUser(otherUserId); - const response = await request(app).post('/images').send({ - endpoint: 'agents', - agent_id: agentCustomId, - tool_resource: 'context', - file_id: uuidv4(), - }); - - expect(response.status).toBe(200); - expect(processAgentFileUpload).toHaveBeenCalled(); - }); - - it('should deny upload for user with only VIEW permission', async () => { - const agent = await createAgent({ - id: agentCustomId, - name: 'Test Agent', - provider: 'openai', - model: 'gpt-4', - author: authorId, - }); - - const { grantPermission } = require('~/server/services/PermissionService'); - await grantPermission({ - principalType: PrincipalType.USER, - principalId: otherUserId, - resourceType: ResourceType.AGENT, - resourceId: agent._id, - accessRoleId: AccessRoleIds.AGENT_VIEWER, - grantedBy: authorId, - }); - - const app = createAppWithUser(otherUserId); - const response = await request(app).post('/images').send({ - endpoint: 'agents', - agent_id: agentCustomId, - tool_resource: 'context', - file_id: uuidv4(), - }); - - expect(response.status).toBe(403); - expect(response.body.error).toBe('Forbidden'); - expect(processAgentFileUpload).not.toHaveBeenCalled(); - expect(fs.promises.unlink).toHaveBeenCalledWith('/tmp/t.png'); - }); - - it('should skip permission check for regular image uploads without agent_id/tool_resource', async () => { - const app = createAppWithUser(otherUserId); - const response = await request(app).post('/images').send({ - endpoint: 'agents', - file_id: uuidv4(), - }); - - expect(response.status).toBe(200); - }); - - it('should return 404 for non-existent agent', async () => { - const app = createAppWithUser(otherUserId); - const response = await request(app).post('/images').send({ - endpoint: 'agents', - agent_id: 'agent_nonexistent123456789', - tool_resource: 'context', - file_id: uuidv4(), - }); - - expect(response.status).toBe(404); - expect(response.body.error).toBe('Not Found'); - expect(processAgentFileUpload).not.toHaveBeenCalled(); - expect(fs.promises.unlink).toHaveBeenCalledWith('/tmp/t.png'); - }); - - it('should allow message_file attachment (boolean true) without EDIT permission', async () => { - const agent = await createAgent({ - id: agentCustomId, - name: 'Test Agent', - provider: 'openai', - model: 'gpt-4', - author: authorId, - }); - - const { grantPermission } = require('~/server/services/PermissionService'); - await grantPermission({ - principalType: PrincipalType.USER, - principalId: otherUserId, - resourceType: ResourceType.AGENT, - resourceId: agent._id, - accessRoleId: AccessRoleIds.AGENT_VIEWER, - grantedBy: authorId, - }); - - const app = createAppWithUser(otherUserId); - const response = await request(app).post('/images').send({ - endpoint: 'agents', - agent_id: agentCustomId, - tool_resource: 'context', - message_file: true, - file_id: uuidv4(), - }); - - expect(response.status).toBe(200); - expect(processAgentFileUpload).toHaveBeenCalled(); - }); - - it('should allow message_file attachment (string "true") without EDIT permission', async () => { - const agent = await createAgent({ - id: agentCustomId, - name: 'Test Agent', - provider: 'openai', - model: 'gpt-4', - author: authorId, - }); - - const { grantPermission } = require('~/server/services/PermissionService'); - await grantPermission({ - principalType: PrincipalType.USER, - principalId: otherUserId, - resourceType: ResourceType.AGENT, - resourceId: agent._id, - accessRoleId: AccessRoleIds.AGENT_VIEWER, - grantedBy: authorId, - }); - - const app = createAppWithUser(otherUserId); - const response = await request(app).post('/images').send({ - endpoint: 'agents', - agent_id: agentCustomId, - tool_resource: 'context', - message_file: 'true', - file_id: uuidv4(), - }); - - expect(response.status).toBe(200); - expect(processAgentFileUpload).toHaveBeenCalled(); - }); - - it('should deny upload when message_file is false (not a message attachment)', async () => { - const agent = await createAgent({ - id: agentCustomId, - name: 'Test Agent', - provider: 'openai', - model: 'gpt-4', - author: authorId, - }); - - const { grantPermission } = require('~/server/services/PermissionService'); - await grantPermission({ - principalType: PrincipalType.USER, - principalId: otherUserId, - resourceType: ResourceType.AGENT, - resourceId: agent._id, - accessRoleId: AccessRoleIds.AGENT_VIEWER, - grantedBy: authorId, - }); - - const app = createAppWithUser(otherUserId); - const response = await request(app).post('/images').send({ - endpoint: 'agents', - agent_id: agentCustomId, - tool_resource: 'context', - message_file: false, - file_id: uuidv4(), - }); - - expect(response.status).toBe(403); - expect(response.body.error).toBe('Forbidden'); - expect(processAgentFileUpload).not.toHaveBeenCalled(); - expect(fs.promises.unlink).toHaveBeenCalledWith('/tmp/t.png'); - }); -}); diff --git a/api/server/routes/files/images.js b/api/server/routes/files/images.js index 353557dc4f..8072612a69 100644 --- a/api/server/routes/files/images.js +++ b/api/server/routes/files/images.js @@ -2,15 +2,12 @@ const path = require('path'); const fs = require('fs').promises; const express = require('express'); const { logger } = require('@librechat/data-schemas'); -const { verifyAgentUploadPermission, resolveUploadErrorMessage } = require('@librechat/api'); const { isAssistantsEndpoint } = require('librechat-data-provider'); const { processAgentFileUpload, processImageFile, filterFile, } = require('~/server/services/Files/process'); -const { checkPermission } = require('~/server/services/PermissionService'); -const db = require('~/models'); const router = express.Router(); @@ -25,16 +22,6 @@ router.post('/', async (req, res) => { metadata.file_id = req.file_id; if (!isAssistantsEndpoint(metadata.endpoint) && metadata.tool_resource != null) { - const denied = await verifyAgentUploadPermission({ - req, - res, - metadata, - getAgent: db.getAgent, - checkPermission, - }); - if (denied) { - return; - } return await processAgentFileUpload({ req, res, metadata }); } @@ -43,7 +30,15 @@ router.post('/', async (req, res) => { // TODO: delete remote file if it exists logger.error('[/files/images] Error processing file:', error); - const message = resolveUploadErrorMessage(error); + let message = 'Error processing file'; + + if ( + error.message?.includes('Invalid file format') || + error.message?.includes('No OCR result') || + error.message?.includes('exceeds token limit') + ) { + message = error.message; + } try { const filepath = path.join( diff --git a/api/server/routes/index.js b/api/server/routes/index.js index 1feaf63fdb..6a48919db3 100644 --- a/api/server/routes/index.js +++ b/api/server/routes/index.js @@ -2,11 +2,6 @@ const accessPermissions = require('./accessPermissions'); const assistants = require('./assistants'); const categories = require('./categories'); const adminAuth = require('./admin/auth'); -const adminConfig = require('./admin/config'); -const adminGrants = require('./admin/grants'); -const adminGroups = require('./admin/groups'); -const adminRoles = require('./admin/roles'); -const adminUsers = require('./admin/users'); const endpoints = require('./endpoints'); const staticRoute = require('./static'); const messages = require('./messages'); @@ -36,11 +31,6 @@ module.exports = { mcp, auth, adminAuth, - adminConfig, - adminGrants, - adminGroups, - adminRoles, - adminUsers, keys, apiKeys, user, diff --git a/api/server/routes/mcp.js b/api/server/routes/mcp.js index c6496ad4b4..2db8c2c462 100644 --- a/api/server/routes/mcp.js +++ b/api/server/routes/mcp.js @@ -1,5 +1,5 @@ const { Router } = require('express'); -const { logger, getTenantId } = require('@librechat/data-schemas'); +const { logger } = require('@librechat/data-schemas'); const { CacheKeys, Constants, @@ -13,7 +13,6 @@ const { MCPOAuthHandler, MCPTokenStorage, setOAuthSession, - PENDING_STALE_MS, getUserMCPAuthMap, validateOAuthCsrf, OAUTH_CSRF_COOKIE, @@ -36,34 +35,20 @@ const { getFlowStateManager, getMCPManager, } = require('~/config'); -const { - getServerConnectionStatus, - resolveConfigServers, - getMCPSetupData, -} = require('~/server/services/MCP'); +const { getMCPSetupData, getServerConnectionStatus } = require('~/server/services/MCP'); const { requireJwtAuth, canAccessMCPServerResource } = require('~/server/middleware'); +const { findToken, updateToken, createToken, deleteTokens } = require('~/models'); const { getUserPluginAuthValue } = require('~/server/services/PluginService'); const { updateMCPServerTools } = require('~/server/services/Config/mcp'); const { reinitMCPServer } = require('~/server/services/Tools/mcp'); +const { findPluginAuthsByKeys } = require('~/models'); +const { getRoleByName } = require('~/models/Role'); const { getLogStores } = require('~/cache'); -const db = require('~/models'); const router = Router(); const OAUTH_CSRF_COOKIE_PATH = '/api/mcp'; -const checkMCPUsePermissions = generateCheckAccess({ - permissionType: PermissionTypes.MCP_SERVERS, - permissions: [Permissions.USE], - getRoleByName: db.getRoleByName, -}); - -const checkMCPCreate = generateCheckAccess({ - permissionType: PermissionTypes.MCP_SERVERS, - permissions: [Permissions.USE, Permissions.CREATE], - getRoleByName: db.getRoleByName, -}); - /** * Get all MCP tools available to the user * Returns only MCP tools, completely decoupled from regular LibreChat tools @@ -105,13 +90,8 @@ router.get('/:serverName/oauth/initiate', requireJwtAuth, setOAuthSession, async return res.status(400).json({ error: 'Invalid flow state' }); } - const configServers = await resolveConfigServers(req); - const oauthHeaders = await getOAuthHeaders(serverName, userId, configServers); - const { - authorizationUrl, - flowId: oauthFlowId, - flowMetadata, - } = await MCPOAuthHandler.initiateOAuthFlow( + const oauthHeaders = await getOAuthHeaders(serverName, userId); + const { authorizationUrl, flowId: oauthFlowId } = await MCPOAuthHandler.initiateOAuthFlow( serverName, serverUrl, userId, @@ -121,7 +101,6 @@ router.get('/:serverName/oauth/initiate', requireJwtAuth, setOAuthSession, async logger.debug('[MCP OAuth] OAuth flow initiated', { oauthFlowId, authorizationUrl }); - await MCPOAuthHandler.storeStateMapping(flowMetadata.state, oauthFlowId, flowManager); setOAuthCsrfCookie(res, oauthFlowId, OAUTH_CSRF_COOKIE_PATH); res.redirect(authorizationUrl); } catch (error) { @@ -164,53 +143,31 @@ router.get('/:serverName/oauth/callback', async (req, res) => { return res.redirect(`${basePath}/oauth/error?error=missing_state`); } - const flowsCache = getLogStores(CacheKeys.FLOWS); - const flowManager = getFlowStateManager(flowsCache); - - const flowId = await MCPOAuthHandler.resolveStateToFlowId(state, flowManager); - if (!flowId) { - logger.error('[MCP OAuth] Could not resolve state to flow ID', { state }); - return res.redirect(`${basePath}/oauth/error?error=invalid_state`); - } - logger.debug('[MCP OAuth] Resolved flow ID from state', { flowId }); + const flowId = state; + logger.debug('[MCP OAuth] Using flow ID from state', { flowId }); const flowParts = flowId.split(':'); if (flowParts.length < 2 || !flowParts[0] || !flowParts[1]) { - logger.error('[MCP OAuth] Invalid flow ID format', { flowId }); + logger.error('[MCP OAuth] Invalid flow ID format in state', { flowId }); return res.redirect(`${basePath}/oauth/error?error=invalid_state`); } const [flowUserId] = flowParts; - - const hasCsrf = validateOAuthCsrf(req, res, flowId, OAUTH_CSRF_COOKIE_PATH); - const hasSession = !hasCsrf && validateOAuthSession(req, flowUserId); - let hasActiveFlow = false; - if (!hasCsrf && !hasSession) { - const pendingFlow = await flowManager.getFlowState(flowId, 'mcp_oauth'); - const pendingAge = pendingFlow?.createdAt ? Date.now() - pendingFlow.createdAt : Infinity; - hasActiveFlow = pendingFlow?.status === 'PENDING' && pendingAge < PENDING_STALE_MS; - if (hasActiveFlow) { - logger.debug( - '[MCP OAuth] CSRF/session cookies absent, validating via active PENDING flow', - { - flowId, - }, - ); - } - } - - if (!hasCsrf && !hasSession && !hasActiveFlow) { - logger.error( - '[MCP OAuth] CSRF validation failed: no valid CSRF cookie, session cookie, or active flow', - { - flowId, - hasCsrfCookie: !!req.cookies?.[OAUTH_CSRF_COOKIE], - hasSessionCookie: !!req.cookies?.[OAUTH_SESSION_COOKIE], - }, - ); + if ( + !validateOAuthCsrf(req, res, flowId, OAUTH_CSRF_COOKIE_PATH) && + !validateOAuthSession(req, flowUserId) + ) { + logger.error('[MCP OAuth] CSRF validation failed: no valid CSRF or session cookie', { + flowId, + hasCsrfCookie: !!req.cookies?.[OAUTH_CSRF_COOKIE], + hasSessionCookie: !!req.cookies?.[OAUTH_SESSION_COOKIE], + }); return res.redirect(`${basePath}/oauth/error?error=csrf_validation_failed`); } + const flowsCache = getLogStores(CacheKeys.FLOWS); + const flowManager = getFlowStateManager(flowsCache); + logger.debug('[MCP OAuth] Getting flow state for flowId: ' + flowId); const flowState = await MCPOAuthHandler.getFlowState(flowId, flowManager); @@ -238,14 +195,7 @@ router.get('/:serverName/oauth/callback', async (req, res) => { } logger.debug('[MCP OAuth] Completing OAuth flow'); - if (!flowState.oauthHeaders) { - logger.warn( - '[MCP OAuth] oauthHeaders absent from flow state — config-source server oauth_headers will be empty', - { serverName, flowId }, - ); - } - const oauthHeaders = - flowState.oauthHeaders ?? (await getOAuthHeaders(serverName, flowState.userId)); + const oauthHeaders = await getOAuthHeaders(serverName, flowState.userId); const tokens = await MCPOAuthHandler.completeOAuthFlow(flowId, code, flowManager, oauthHeaders); logger.info('[MCP OAuth] OAuth flow completed, tokens received in callback route'); @@ -256,9 +206,9 @@ router.get('/:serverName/oauth/callback', async (req, res) => { userId: flowState.userId, serverName, tokens, - createToken: db.createToken, - updateToken: db.updateToken, - findToken: db.findToken, + createToken, + updateToken, + findToken, clientInfo: flowState.clientInfo, metadata: flowState.metadata, }); @@ -296,10 +246,10 @@ router.get('/:serverName/oauth/callback', async (req, res) => { serverName, flowManager, tokenMethods: { - findToken: db.findToken, - updateToken: db.updateToken, - createToken: db.createToken, - deleteTokens: db.deleteTokens, + findToken, + updateToken, + createToken, + deleteTokens, }, }); @@ -331,13 +281,7 @@ router.get('/:serverName/oauth/callback', async (req, res) => { const toolFlowId = flowState.metadata?.toolFlowId; if (toolFlowId) { logger.debug('[MCP OAuth] Completing tool flow', { toolFlowId }); - const completed = await flowManager.completeFlow(toolFlowId, 'mcp_oauth', tokens); - if (!completed) { - logger.warn( - '[MCP OAuth] Tool flow state not found during completion — waiter will time out', - { toolFlowId }, - ); - } + await flowManager.completeFlow(toolFlowId, 'mcp_oauth', tokens); } /** Redirect to success page with flowId and serverName */ @@ -492,82 +436,69 @@ router.post('/oauth/cancel/:serverName', requireJwtAuth, async (req, res) => { * Reinitialize MCP server * This endpoint allows reinitializing a specific MCP server */ -router.post( - '/:serverName/reinitialize', - requireJwtAuth, - checkMCPUsePermissions, - setOAuthSession, - async (req, res) => { - try { - const { serverName } = req.params; - const user = createSafeUser(req.user); +router.post('/:serverName/reinitialize', requireJwtAuth, setOAuthSession, async (req, res) => { + try { + const { serverName } = req.params; + const user = createSafeUser(req.user); - if (!user.id) { - return res.status(401).json({ error: 'User not authenticated' }); - } - - logger.info(`[MCP Reinitialize] Reinitializing server: ${serverName}`); - - const mcpManager = getMCPManager(); - const configServers = await resolveConfigServers(req); - const serverConfig = await getMCPServersRegistry().getServerConfig( - serverName, - user.id, - configServers, - ); - if (!serverConfig) { - return res.status(404).json({ - error: `MCP server '${serverName}' not found in configuration`, - }); - } - - await mcpManager.disconnectUserConnection(user.id, serverName); - logger.info( - `[MCP Reinitialize] Disconnected existing user connection for server: ${serverName}`, - ); - - /** @type {Record> | undefined} */ - let userMCPAuthMap; - if (serverConfig.customUserVars && typeof serverConfig.customUserVars === 'object') { - userMCPAuthMap = await getUserMCPAuthMap({ - userId: user.id, - servers: [serverName], - findPluginAuthsByKeys: db.findPluginAuthsByKeys, - }); - } - - const result = await reinitMCPServer({ - user, - serverName, - serverConfig, - configServers, - userMCPAuthMap, - }); - - if (!result) { - return res.status(500).json({ error: 'Failed to reinitialize MCP server for user' }); - } - - const { success, message, oauthRequired, oauthUrl } = result; - - if (oauthRequired) { - const flowId = MCPOAuthHandler.generateFlowId(user.id, serverName); - setOAuthCsrfCookie(res, flowId, OAUTH_CSRF_COOKIE_PATH); - } - - res.json({ - success, - message, - oauthUrl, - serverName, - oauthRequired, - }); - } catch (error) { - logger.error('[MCP Reinitialize] Unexpected error', error); - res.status(500).json({ error: 'Internal server error' }); + if (!user.id) { + return res.status(401).json({ error: 'User not authenticated' }); } - }, -); + + logger.info(`[MCP Reinitialize] Reinitializing server: ${serverName}`); + + const mcpManager = getMCPManager(); + const serverConfig = await getMCPServersRegistry().getServerConfig(serverName, user.id); + if (!serverConfig) { + return res.status(404).json({ + error: `MCP server '${serverName}' not found in configuration`, + }); + } + + await mcpManager.disconnectUserConnection(user.id, serverName); + logger.info( + `[MCP Reinitialize] Disconnected existing user connection for server: ${serverName}`, + ); + + /** @type {Record> | undefined} */ + let userMCPAuthMap; + if (serverConfig.customUserVars && typeof serverConfig.customUserVars === 'object') { + userMCPAuthMap = await getUserMCPAuthMap({ + userId: user.id, + servers: [serverName], + findPluginAuthsByKeys, + }); + } + + const result = await reinitMCPServer({ + user, + serverName, + userMCPAuthMap, + }); + + if (!result) { + return res.status(500).json({ error: 'Failed to reinitialize MCP server for user' }); + } + + const { success, message, oauthRequired, oauthUrl } = result; + + if (oauthRequired) { + const flowId = MCPOAuthHandler.generateFlowId(user.id, serverName); + setOAuthCsrfCookie(res, flowId, OAUTH_CSRF_COOKIE_PATH); + } + + res.json({ + success, + message, + oauthUrl, + serverName, + oauthRequired, + }); + } catch (error) { + logger.error('[MCP Reinitialize] Unexpected error', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); /** * Get connection status for all MCP servers @@ -583,7 +514,6 @@ router.get('/connection/status', requireJwtAuth, async (req, res) => { const { mcpConfig, appConnections, userConnections, oauthServers } = await getMCPSetupData( user.id, - { role: user.role, tenantId: getTenantId() }, ); const connectionStatus = {}; @@ -613,6 +543,9 @@ router.get('/connection/status', requireJwtAuth, async (req, res) => { connectionStatus, }); } catch (error) { + if (error.message === 'MCP config not found') { + return res.status(404).json({ error: error.message }); + } logger.error('[MCP Connection Status] Failed to get connection status', error); res.status(500).json({ error: 'Failed to get connection status' }); } @@ -633,7 +566,6 @@ router.get('/connection/status/:serverName', requireJwtAuth, async (req, res) => const { mcpConfig, appConnections, userConnections, oauthServers } = await getMCPSetupData( user.id, - { role: user.role, tenantId: getTenantId() }, ); if (!mcpConfig[serverName]) { @@ -658,6 +590,9 @@ router.get('/connection/status/:serverName', requireJwtAuth, async (req, res) => requiresOAuth: serverStatus.requiresOAuth, }); } catch (error) { + if (error.message === 'MCP config not found') { + return res.status(404).json({ error: error.message }); + } logger.error( `[MCP Per-Server Status] Failed to get connection status for ${req.params.serverName}`, error, @@ -670,7 +605,7 @@ router.get('/connection/status/:serverName', requireJwtAuth, async (req, res) => * Check which authentication values exist for a specific MCP server * This endpoint returns only boolean flags indicating if values are set, not the actual values */ -router.get('/:serverName/auth-values', requireJwtAuth, checkMCPUsePermissions, async (req, res) => { +router.get('/:serverName/auth-values', requireJwtAuth, async (req, res) => { try { const { serverName } = req.params; const user = req.user; @@ -679,12 +614,7 @@ router.get('/:serverName/auth-values', requireJwtAuth, checkMCPUsePermissions, a return res.status(401).json({ error: 'User not authenticated' }); } - const configServers = await resolveConfigServers(req); - const serverConfig = await getMCPServersRegistry().getServerConfig( - serverName, - user.id, - configServers, - ); + const serverConfig = await getMCPServersRegistry().getServerConfig(serverName, user.id); if (!serverConfig) { return res.status(404).json({ error: `MCP server '${serverName}' not found in configuration`, @@ -723,12 +653,8 @@ router.get('/:serverName/auth-values', requireJwtAuth, checkMCPUsePermissions, a } }); -async function getOAuthHeaders(serverName, userId, configServers) { - const serverConfig = await getMCPServersRegistry().getServerConfig( - serverName, - userId, - configServers, - ); +async function getOAuthHeaders(serverName, userId) { + const serverConfig = await getMCPServersRegistry().getServerConfig(serverName, userId); return serverConfig?.oauth_headers ?? {}; } @@ -736,6 +662,19 @@ async function getOAuthHeaders(serverName, userId, configServers) { MCP Server CRUD Routes (User-Managed MCP Servers) */ +// Permission checkers for MCP server management +const checkMCPUsePermissions = generateCheckAccess({ + permissionType: PermissionTypes.MCP_SERVERS, + permissions: [Permissions.USE], + getRoleByName, +}); + +const checkMCPCreate = generateCheckAccess({ + permissionType: PermissionTypes.MCP_SERVERS, + permissions: [Permissions.USE, Permissions.CREATE], + getRoleByName, +}); + /** * Get list of accessible MCP servers * @route GET /api/mcp/servers diff --git a/api/server/routes/memories.js b/api/server/routes/memories.js index e71e94f457..58955d8ec4 100644 --- a/api/server/routes/memories.js +++ b/api/server/routes/memories.js @@ -4,12 +4,12 @@ const { PermissionTypes, Permissions } = require('librechat-data-provider'); const { getAllUserMemories, toggleUserMemories, - getRoleByName, createMemory, deleteMemory, setMemory, } = require('~/models'); const { requireJwtAuth, configMiddleware } = require('~/server/middleware'); +const { getRoleByName } = require('~/models/Role'); const router = express.Router(); diff --git a/api/server/routes/messages.js b/api/server/routes/messages.js index 21b2b23fea..c208e9c406 100644 --- a/api/server/routes/messages.js +++ b/api/server/routes/messages.js @@ -3,9 +3,18 @@ const { v4: uuidv4 } = require('uuid'); const { logger } = require('@librechat/data-schemas'); const { ContentTypes } = require('librechat-data-provider'); const { unescapeLaTeX, countTokens } = require('@librechat/api'); +const { + saveConvo, + getMessage, + saveMessage, + getMessages, + updateMessage, + deleteMessages, +} = require('~/models'); const { findAllArtifacts, replaceArtifactContent } = require('~/server/services/Artifacts/update'); const { requireJwtAuth, validateMessageReq } = require('~/server/middleware'); -const db = require('~/models'); +const { getConvosQueried } = require('~/models/Conversation'); +const { Message } = require('~/db/models'); const router = express.Router(); router.use(requireJwtAuth); @@ -31,19 +40,34 @@ router.get('/', async (req, res) => { const sortOrder = sortDirection === 'asc' ? 1 : -1; if (conversationId && messageId) { - const messages = await db.getMessages({ conversationId, messageId, user }); - response = { messages: messages?.length ? [messages[0]] : [], nextCursor: null }; + const message = await Message.findOne({ + conversationId, + messageId, + user: user, + }).lean(); + response = { messages: message ? [message] : [], nextCursor: null }; } else if (conversationId) { - response = await db.getMessagesByCursor( - { conversationId, user }, - { sortField, sortOrder, limit: pageSize, cursor }, - ); + const filter = { conversationId, user: user }; + if (cursor) { + filter[sortField] = sortOrder === 1 ? { $gt: cursor } : { $lt: cursor }; + } + const messages = await Message.find(filter) + .sort({ [sortField]: sortOrder }) + .limit(pageSize + 1) + .lean(); + let nextCursor = null; + if (messages.length > pageSize) { + messages.pop(); // Remove extra item used to detect next page + // Create cursor from the last RETURNED item (not the popped one) + nextCursor = messages[messages.length - 1][sortField]; + } + response = { messages, nextCursor }; } else if (search) { - const searchResults = await db.searchMessages(search, { filter: `user = "${user}"` }, true); + const searchResults = await Message.meiliSearch(search, { filter: `user = "${user}"` }, true); const messages = searchResults.hits || []; - const result = await db.getConvosQueried(req.user.id, messages, cursor); + const result = await getConvosQueried(req.user.id, messages, cursor); const messageIds = []; const cleanedMessages = []; @@ -55,7 +79,7 @@ router.get('/', async (req, res) => { } } - const dbMessages = await db.getMessages({ + const dbMessages = await getMessages({ user, messageId: { $in: messageIds }, }); @@ -112,7 +136,7 @@ router.post('/branch', async (req, res) => { return res.status(400).json({ error: 'messageId and agentId are required' }); } - const sourceMessage = await db.getMessage({ user: userId, messageId }); + const sourceMessage = await getMessage({ user: userId, messageId }); if (!sourceMessage) { return res.status(404).json({ error: 'Source message not found' }); } @@ -163,15 +187,9 @@ router.post('/branch', async (req, res) => { user: userId, }; - const savedMessage = await db.saveMessage( - { - userId: req?.user?.id, - isTemporary: req?.body?.isTemporary, - interfaceConfig: req?.config?.interfaceConfig, - }, - newMessage, - { context: 'POST /api/messages/branch' }, - ); + const savedMessage = await saveMessage(req, newMessage, { + context: 'POST /api/messages/branch', + }); if (!savedMessage) { return res.status(500).json({ error: 'Failed to save branch message' }); @@ -193,7 +211,7 @@ router.post('/artifact/:messageId', async (req, res) => { return res.status(400).json({ error: 'Invalid request parameters' }); } - const message = await db.getMessage({ user: req.user.id, messageId }); + const message = await getMessage({ user: req.user.id, messageId }); if (!message) { return res.status(404).json({ error: 'Message not found' }); } @@ -238,12 +256,8 @@ router.post('/artifact/:messageId', async (req, res) => { return res.status(400).json({ error: 'Original content not found in target artifact' }); } - const savedMessage = await db.saveMessage( - { - userId: req?.user?.id, - isTemporary: req?.body?.isTemporary, - interfaceConfig: req?.config?.interfaceConfig, - }, + const savedMessage = await saveMessage( + req, { messageId, conversationId: message.conversationId, @@ -269,7 +283,7 @@ router.post('/artifact/:messageId', async (req, res) => { router.get('/:conversationId', validateMessageReq, async (req, res) => { try { const { conversationId } = req.params; - const messages = await db.getMessages({ conversationId }, '-_id -__v -user'); + const messages = await getMessages({ conversationId }, '-_id -__v -user'); res.status(200).json(messages); } catch (error) { logger.error('Error fetching messages:', error); @@ -280,20 +294,15 @@ router.get('/:conversationId', validateMessageReq, async (req, res) => { router.post('/:conversationId', validateMessageReq, async (req, res) => { try { const message = req.body; - const reqCtx = { - userId: req?.user?.id, - isTemporary: req?.body?.isTemporary, - interfaceConfig: req?.config?.interfaceConfig, - }; - const savedMessage = await db.saveMessage( - reqCtx, + const savedMessage = await saveMessage( + req, { ...message, user: req.user.id }, { context: 'POST /api/messages/:conversationId' }, ); if (!savedMessage) { return res.status(400).json({ error: 'Message not saved' }); } - await db.saveConvo(reqCtx, savedMessage, { context: 'POST /api/messages/:conversationId' }); + await saveConvo(req, savedMessage, { context: 'POST /api/messages/:conversationId' }); res.status(201).json(savedMessage); } catch (error) { logger.error('Error saving message:', error); @@ -304,7 +313,7 @@ router.post('/:conversationId', validateMessageReq, async (req, res) => { router.get('/:conversationId/:messageId', validateMessageReq, async (req, res) => { try { const { conversationId, messageId } = req.params; - const message = await db.getMessages({ conversationId, messageId }, '-_id -__v -user'); + const message = await getMessages({ conversationId, messageId }, '-_id -__v -user'); if (!message) { return res.status(404).json({ error: 'Message not found' }); } @@ -322,7 +331,7 @@ router.put('/:conversationId/:messageId', validateMessageReq, async (req, res) = if (index === undefined) { const tokenCount = await countTokens(text, model); - const result = await db.updateMessage(req?.user?.id, { messageId, text, tokenCount }); + const result = await updateMessage(req, { messageId, text, tokenCount }); return res.status(200).json(result); } @@ -330,9 +339,7 @@ router.put('/:conversationId/:messageId', validateMessageReq, async (req, res) = return res.status(400).json({ error: 'Invalid index' }); } - const message = ( - await db.getMessages({ conversationId, messageId }, 'content tokenCount') - )?.[0]; + const message = (await getMessages({ conversationId, messageId }, 'content tokenCount'))?.[0]; if (!message) { return res.status(404).json({ error: 'Message not found' }); } @@ -362,11 +369,7 @@ router.put('/:conversationId/:messageId', validateMessageReq, async (req, res) = tokenCount = Math.max(0, tokenCount - oldTokenCount) + newTokenCount; } - const result = await db.updateMessage(req?.user?.id, { - messageId, - content: updatedContent, - tokenCount, - }); + const result = await updateMessage(req, { messageId, content: updatedContent, tokenCount }); return res.status(200).json(result); } catch (error) { logger.error('Error updating message:', error); @@ -379,8 +382,8 @@ router.put('/:conversationId/:messageId/feedback', validateMessageReq, async (re const { conversationId, messageId } = req.params; const { feedback } = req.body; - const updatedMessage = await db.updateMessage( - req?.user?.id, + const updatedMessage = await updateMessage( + req, { messageId, feedback: feedback || null, @@ -401,8 +404,8 @@ router.put('/:conversationId/:messageId/feedback', validateMessageReq, async (re router.delete('/:conversationId/:messageId', validateMessageReq, async (req, res) => { try { - const { conversationId, messageId } = req.params; - await db.deleteMessages({ messageId, conversationId, user: req.user.id }); + const { messageId } = req.params; + await deleteMessages({ messageId }); res.status(204).send(); } catch (error) { logger.error('Error deleting message:', error); diff --git a/api/server/routes/oauth.js b/api/server/routes/oauth.js index 5302158031..f4bb5b6026 100644 --- a/api/server/routes/oauth.js +++ b/api/server/routes/oauth.js @@ -7,13 +7,12 @@ const { ErrorTypes } = require('librechat-data-provider'); const { createSetBalanceConfig } = require('@librechat/api'); const { checkDomainAllowed, loginLimiter, logHeaders } = require('~/server/middleware'); const { createOAuthHandler } = require('~/server/controllers/auth/oauth'); -const { findBalanceByUser, upsertBalanceFields } = require('~/models'); const { getAppConfig } = require('~/server/services/Config'); +const { Balance } = require('~/db/models'); const setBalanceConfig = createSetBalanceConfig({ getAppConfig, - findBalanceByUser, - upsertBalanceFields, + Balance, }); const router = express.Router(); diff --git a/api/server/routes/prompts.js b/api/server/routes/prompts.js index 5fcf51ba73..037bf04813 100644 --- a/api/server/routes/prompts.js +++ b/api/server/routes/prompts.js @@ -1,6 +1,5 @@ const express = require('express'); -const { ObjectId } = require('mongodb'); -const { logger, isValidObjectIdString } = require('@librechat/data-schemas'); +const { logger } = require('@librechat/data-schemas'); const { generateCheckAccess, markPublicPromptGroups, @@ -12,32 +11,28 @@ const { } = require('@librechat/api'); const { Permissions, + SystemRoles, ResourceType, AccessRoleIds, PrincipalType, PermissionBits, PermissionTypes, } = require('librechat-data-provider'); -const { SystemCapabilities } = require('@librechat/data-schemas'); const { getListPromptGroupsByAccess, - getOwnedPromptGroupIds, - incrementPromptGroupUsage, makePromptProduction, updatePromptGroup, deletePromptGroup, createPromptGroup, getPromptGroup, - getRoleByName, deletePrompt, getPrompts, savePrompt, getPrompt, -} = require('~/models'); +} = require('~/models/Prompt'); const { canAccessPromptGroupResource, canAccessPromptViaGroup, - promptUsageLimiter, requireJwtAuth, } = require('~/server/middleware'); const { @@ -46,7 +41,7 @@ const { findAccessibleResources, grantPermission, } = require('~/server/services/PermissionService'); -const { hasCapability } = require('~/server/middleware/roles/capabilities'); +const { getRoleByName } = require('~/models/Role'); const router = express.Router(); @@ -61,15 +56,18 @@ const checkPromptCreate = generateCheckAccess({ getRoleByName, }); -router.use(requireJwtAuth); -router.use(checkPromptAccess); - const checkGlobalPromptShare = generateCheckAccess({ permissionType: PermissionTypes.PROMPTS, permissions: [Permissions.USE, Permissions.CREATE], + bodyProps: { + [Permissions.SHARE]: ['projectIds', 'removeProjectIds'], + }, getRoleByName, }); +router.use(requireJwtAuth); +router.use(checkPromptAccess); + /** * Route to get single prompt group by its ID * GET /groups/:groupId @@ -104,10 +102,11 @@ router.get( router.get('/all', async (req, res) => { try { const userId = req.user.id; - const { name, category } = req.query; + const { name, category, ...otherFilters } = req.query; const { filter, searchShared, searchSharedOnly } = buildPromptGroupFilter({ name, category, + ...otherFilters, }); let accessibleIds = await findAccessibleResources({ @@ -117,20 +116,16 @@ router.get('/all', async (req, res) => { requiredPermissions: PermissionBits.VIEW, }); - const [publiclyAccessibleIds, ownedPromptGroupIds] = await Promise.all([ - findPubliclyAccessibleResources({ - resourceType: ResourceType.PROMPTGROUP, - requiredPermissions: PermissionBits.VIEW, - }), - getOwnedPromptGroupIds(userId), - ]); + const publiclyAccessibleIds = await findPubliclyAccessibleResources({ + resourceType: ResourceType.PROMPTGROUP, + requiredPermissions: PermissionBits.VIEW, + }); const filteredAccessibleIds = await filterAccessibleIdsBySharedLogic({ accessibleIds, searchShared, searchSharedOnly, publicPromptGroupIds: publiclyAccessibleIds, - ownedPromptGroupIds, }); const result = await getListPromptGroupsByAccess({ @@ -162,11 +157,12 @@ router.get('/all', async (req, res) => { router.get('/groups', async (req, res) => { try { const userId = req.user.id; - const { pageSize, limit, cursor, name, category } = req.query; + const { pageSize, limit, cursor, name, category, ...otherFilters } = req.query; const { filter, searchShared, searchSharedOnly } = buildPromptGroupFilter({ name, category, + ...otherFilters, }); let actualLimit = limit; @@ -190,20 +186,16 @@ router.get('/groups', async (req, res) => { requiredPermissions: PermissionBits.VIEW, }); - const [publiclyAccessibleIds, ownedPromptGroupIds] = await Promise.all([ - findPubliclyAccessibleResources({ - resourceType: ResourceType.PROMPTGROUP, - requiredPermissions: PermissionBits.VIEW, - }), - getOwnedPromptGroupIds(userId), - ]); + const publiclyAccessibleIds = await findPubliclyAccessibleResources({ + resourceType: ResourceType.PROMPTGROUP, + requiredPermissions: PermissionBits.VIEW, + }); const filteredAccessibleIds = await filterAccessibleIdsBySharedLogic({ accessibleIds, searchShared, searchSharedOnly, publicPromptGroupIds: publiclyAccessibleIds, - ownedPromptGroupIds, }); // Cursor-based pagination only @@ -307,16 +299,6 @@ const addPromptToGroup = async (req, res) => { return res.status(400).send({ error: 'Prompt is required' }); } - if (typeof prompt.prompt !== 'string' || !prompt.prompt.trim()) { - return res - .status(400) - .send({ error: 'Prompt text is required and must be a non-empty string' }); - } - - if (prompt.type !== 'text' && prompt.type !== 'chat') { - return res.status(400).send({ error: 'Prompt type must be "text" or "chat"' }); - } - // Ensure the prompt is associated with the correct group prompt.groupId = groupId; @@ -347,37 +329,6 @@ router.post( addPromptToGroup, ); -/** - * Records a prompt group usage (increments numberOfGenerations) - * POST /groups/:groupId/use - */ -router.post( - '/groups/:groupId/use', - promptUsageLimiter, - canAccessPromptGroupResource({ - requiredPermission: PermissionBits.VIEW, - }), - async (req, res) => { - try { - const { groupId } = req.params; - if (!isValidObjectIdString(groupId)) { - return res.status(400).send({ error: 'Invalid groupId' }); - } - const result = await incrementPromptGroupUsage(groupId); - res.status(200).send(result); - } catch (error) { - logger.error('[recordPromptUsage]', error); - if (error.message === 'Invalid groupId') { - return res.status(400).send({ error: 'Invalid groupId' }); - } - if (error.message === 'Prompt group not found') { - return res.status(404).send({ error: 'Prompt group not found' }); - } - res.status(500).send({ error: 'Error recording prompt usage' }); - } - }, -); - /** * Updates a prompt group * @param {object} req @@ -389,8 +340,11 @@ router.post( const patchPromptGroup = async (req, res) => { try { const { groupId } = req.params; - // Don't pass author - permissions are now checked by middleware - const filter = { _id: groupId }; + const author = req.user.id; + const filter = { _id: groupId, author }; + if (req.user.role === SystemRoles.ADMIN) { + delete filter.author; + } const validationResult = safeValidatePromptGroupUpdate(req.body); if (!validationResult.success) { @@ -456,10 +410,6 @@ router.get('/', async (req, res) => { // If requesting prompts for a specific group, check permissions if (groupId) { - if (!isValidObjectIdString(groupId)) { - return res.status(400).send({ error: 'Invalid groupId' }); - } - const permissions = await getEffectivePermissions({ userId: req.user.id, role: req.user.role, @@ -474,20 +424,13 @@ router.get('/', async (req, res) => { } // If user has access, fetch all prompts in the group (not just their own) - const prompts = await getPrompts({ groupId: new ObjectId(groupId) }); + const prompts = await getPrompts({ groupId }); return res.status(200).send(prompts); } // If no groupId, return user's own prompts const query = { author }; - let canReadPrompts = false; - try { - canReadPrompts = await hasCapability(req.user, SystemCapabilities.READ_PROMPTS); - } catch (err) { - logger.warn(`[GET /prompts] capability check failed, denying bypass: ${err.message}`); - } - if (canReadPrompts) { - logger.debug(`[GET /prompts] READ_PROMPTS bypass for user ${req.user.id}`); + if (req.user.role === SystemRoles.ADMIN) { delete query.author; } const prompts = await getPrompts(query); @@ -511,10 +454,8 @@ const deletePromptController = async (req, res) => { try { const { promptId } = req.params; const { groupId } = req.query; - if (!groupId || !isValidObjectIdString(groupId)) { - return res.status(400).send({ error: 'Invalid or missing groupId' }); - } - const query = { promptId, groupId }; + const author = req.user.id; + const query = { promptId, groupId, author, role: req.user.role }; const result = await deletePrompt(query); res.status(200).send(result); } catch (error) { @@ -532,8 +473,8 @@ const deletePromptController = async (req, res) => { const deletePromptGroupController = async (req, res) => { try { const { groupId: _id } = req.params; - // Don't pass author or role - permissions are checked by ACL middleware - const message = await deletePromptGroup({ _id }); + // Don't pass author - permissions are now checked by middleware + const message = await deletePromptGroup({ _id, role: req.user.role }); res.send(message); } catch (error) { logger.error('Error deleting prompt group', error); diff --git a/api/server/routes/prompts.test.js b/api/server/routes/prompts.test.js index c979023ffc..caeb90ddfb 100644 --- a/api/server/routes/prompts.test.js +++ b/api/server/routes/prompts.test.js @@ -10,33 +10,18 @@ const { PrincipalType, PermissionBits, } = require('librechat-data-provider'); -const { SystemCapabilities } = require('@librechat/data-schemas'); // Mock modules before importing jest.mock('~/server/services/Config', () => ({ getCachedTools: jest.fn().mockResolvedValue({}), })); -jest.mock('~/models', () => { - const mongoose = require('mongoose'); - const { createMethods } = require('@librechat/data-schemas'); - const methods = createMethods(mongoose, { - removeAllPermissions: async ({ resourceType, resourceId }) => { - const AclEntry = mongoose.models.AclEntry; - if (AclEntry) { - await AclEntry.deleteMany({ resourceType, resourceId }); - } - }, - }); - return { - ...methods, - getRoleByName: jest.fn(), - }; -}); +jest.mock('~/models/Role', () => ({ + getRoleByName: jest.fn(), +})); jest.mock('~/server/middleware', () => ({ requireJwtAuth: (req, res, next) => next(), - promptUsageLimiter: (req, res, next) => next(), canAccessPromptViaGroup: jest.requireActual('~/server/middleware').canAccessPromptViaGroup, canAccessPromptGroupResource: jest.requireActual('~/server/middleware').canAccessPromptGroupResource, @@ -45,7 +30,7 @@ jest.mock('~/server/middleware', () => ({ let app; let mongoServer; let promptRoutes; -let Prompt, PromptGroup, AclEntry, AccessRole, User, SystemGrant; +let Prompt, PromptGroup, AclEntry, AccessRole, User; let testUsers, testRoles; let grantPermission; let currentTestUser; // Track current user for middleware @@ -67,7 +52,6 @@ beforeAll(async () => { AclEntry = dbModels.AclEntry; AccessRole = dbModels.AccessRole; User = dbModels.User; - SystemGrant = dbModels.SystemGrant; // Import permission service const permissionService = require('~/server/services/PermissionService'); @@ -168,24 +152,8 @@ async function setupTestData() { }), }; - // Seed capabilities for the ADMIN role - await SystemGrant.create([ - { - principalType: PrincipalType.ROLE, - principalId: SystemRoles.ADMIN, - capability: SystemCapabilities.MANAGE_PROMPTS, - grantedAt: new Date(), - }, - { - principalType: PrincipalType.ROLE, - principalId: SystemRoles.ADMIN, - capability: SystemCapabilities.READ_PROMPTS, - grantedAt: new Date(), - }, - ]); - // Mock getRoleByName - const { getRoleByName } = require('~/models'); + const { getRoleByName } = require('~/models/Role'); getRoleByName.mockImplementation((roleName) => { switch (roleName) { case SystemRoles.USER: diff --git a/api/server/routes/roles.js b/api/server/routes/roles.js index 25ee47854d..12e18c7624 100644 --- a/api/server/routes/roles.js +++ b/api/server/routes/roles.js @@ -1,5 +1,4 @@ const express = require('express'); -const { logger, SystemCapabilities } = require('@librechat/data-schemas'); const { SystemRoles, roleDefaults, @@ -12,13 +11,11 @@ const { peoplePickerPermissionsSchema, remoteAgentsPermissionsSchema, } = require('librechat-data-provider'); -const { hasCapability, requireCapability } = require('~/server/middleware/roles/capabilities'); -const { updateRoleByName, getRoleByName } = require('~/models'); -const { requireJwtAuth } = require('~/server/middleware'); +const { checkAdmin, requireJwtAuth } = require('~/server/middleware'); +const { updateRoleByName, getRoleByName } = require('~/models/Role'); const router = express.Router(); router.use(requireJwtAuth); -const manageRoles = requireCapability(SystemCapabilities.MANAGE_ROLES); /** * Permission configuration mapping @@ -114,17 +111,14 @@ router.get('/:roleName', async (req, res) => { // TODO: TEMP, use a better parsing for roleName const roleName = _r.toUpperCase(); - try { - let hasReadRoles = false; - try { - hasReadRoles = await hasCapability(req.user, SystemCapabilities.READ_ROLES); - } catch (err) { - logger.warn(`[GET /roles/:roleName] capability check failed: ${err.message}`); - } - if (!hasReadRoles && (roleName === SystemRoles.ADMIN || !roleDefaults[roleName])) { - return res.status(403).send({ message: 'Unauthorized' }); - } + if ( + (req.user.role !== SystemRoles.ADMIN && roleName === SystemRoles.ADMIN) || + (req.user.role !== SystemRoles.ADMIN && !roleDefaults[roleName]) + ) { + return res.status(403).send({ message: 'Unauthorized' }); + } + try { const role = await getRoleByName(roleName, '-_id -__v'); if (!role) { return res.status(404).send({ message: 'Role not found' }); @@ -132,8 +126,7 @@ router.get('/:roleName', async (req, res) => { res.status(200).send(role); } catch (error) { - logger.error('[GET /roles/:roleName] Error:', error); - return res.status(500).send({ message: 'Failed to retrieve role' }); + return res.status(500).send({ message: 'Failed to retrieve role', error: error.message }); } }); @@ -141,42 +134,42 @@ router.get('/:roleName', async (req, res) => { * PUT /api/roles/:roleName/prompts * Update prompt permissions for a specific role */ -router.put('/:roleName/prompts', manageRoles, createPermissionUpdateHandler('prompts')); +router.put('/:roleName/prompts', checkAdmin, createPermissionUpdateHandler('prompts')); /** * PUT /api/roles/:roleName/agents * Update agent permissions for a specific role */ -router.put('/:roleName/agents', manageRoles, createPermissionUpdateHandler('agents')); +router.put('/:roleName/agents', checkAdmin, createPermissionUpdateHandler('agents')); /** * PUT /api/roles/:roleName/memories * Update memory permissions for a specific role */ -router.put('/:roleName/memories', manageRoles, createPermissionUpdateHandler('memories')); +router.put('/:roleName/memories', checkAdmin, createPermissionUpdateHandler('memories')); /** * PUT /api/roles/:roleName/people-picker * Update people picker permissions for a specific role */ -router.put('/:roleName/people-picker', manageRoles, createPermissionUpdateHandler('people-picker')); +router.put('/:roleName/people-picker', checkAdmin, createPermissionUpdateHandler('people-picker')); /** * PUT /api/roles/:roleName/mcp-servers * Update MCP servers permissions for a specific role */ -router.put('/:roleName/mcp-servers', manageRoles, createPermissionUpdateHandler('mcp-servers')); +router.put('/:roleName/mcp-servers', checkAdmin, createPermissionUpdateHandler('mcp-servers')); /** * PUT /api/roles/:roleName/marketplace * Update marketplace permissions for a specific role */ -router.put('/:roleName/marketplace', manageRoles, createPermissionUpdateHandler('marketplace')); +router.put('/:roleName/marketplace', checkAdmin, createPermissionUpdateHandler('marketplace')); /** * PUT /api/roles/:roleName/remote-agents * Update remote agents (API) permissions for a specific role */ -router.put('/:roleName/remote-agents', manageRoles, createPermissionUpdateHandler('remote-agents')); +router.put('/:roleName/remote-agents', checkAdmin, createPermissionUpdateHandler('remote-agents')); module.exports = router; diff --git a/api/server/routes/share.js b/api/server/routes/share.js index 296644afde..6400b8b637 100644 --- a/api/server/routes/share.js +++ b/api/server/routes/share.js @@ -19,7 +19,9 @@ const allowSharedLinks = process.env.ALLOW_SHARED_LINKS === undefined || isEnabled(process.env.ALLOW_SHARED_LINKS); if (allowSharedLinks) { - const allowSharedLinksPublic = isEnabled(process.env.ALLOW_SHARED_LINKS_PUBLIC); + const allowSharedLinksPublic = + process.env.ALLOW_SHARED_LINKS_PUBLIC === undefined || + isEnabled(process.env.ALLOW_SHARED_LINKS_PUBLIC); router.get( '/:shareId', allowSharedLinksPublic ? (req, res, next) => next() : requireJwtAuth, diff --git a/api/server/routes/tags.js b/api/server/routes/tags.js index a1fa1f77bb..0a4ee5084c 100644 --- a/api/server/routes/tags.js +++ b/api/server/routes/tags.js @@ -8,9 +8,9 @@ const { createConversationTag, deleteConversationTag, getConversationTags, - getRoleByName, -} = require('~/models'); +} = require('~/models/ConversationTag'); const { requireJwtAuth } = require('~/server/middleware'); +const { getRoleByName } = require('~/models/Role'); const router = express.Router(); diff --git a/api/server/services/ActionService.js b/api/server/services/ActionService.js index c8ed7bebc4..5e96726a46 100644 --- a/api/server/services/ActionService.js +++ b/api/server/services/ActionService.js @@ -20,20 +20,14 @@ const { isImageVisionTool, actionDomainSeparator, } = require('librechat-data-provider'); -const { - findToken, - updateToken, - createToken, - getActions, - deleteActions, - deleteAssistant, -} = require('~/models'); +const { findToken, updateToken, createToken } = require('~/models'); +const { getActions, deleteActions } = require('~/models/Action'); +const { deleteAssistant } = require('~/models/Assistant'); const { getFlowStateManager } = require('~/config'); const { getLogStores } = require('~/cache'); const JWT_SECRET = process.env.JWT_SECRET; const toolNameRegex = /^[a-zA-Z0-9_-]+$/; -const protocolRegex = /^https?:\/\//; const replaceSeparatorRegex = new RegExp(actionDomainSeparator, 'g'); /** @@ -54,11 +48,7 @@ const validateAndUpdateTool = async ({ req, tool, assistant_id }) => { actions = await getActions({ assistant_id, user: req.user.id }, true); const matchingActions = actions.filter((action) => { const metadata = action.metadata; - if (!metadata) { - return false; - } - const strippedMetaDomain = stripProtocol(metadata.domain); - return strippedMetaDomain === domain || metadata.domain === domain; + return metadata && metadata.domain === domain; }); const action = matchingActions[0]; if (!action) { @@ -76,36 +66,10 @@ const validateAndUpdateTool = async ({ req, tool, assistant_id }) => { return tool; }; -/** @param {string} domain */ -function stripProtocol(domain) { - const stripped = domain.replace(protocolRegex, ''); - const pathIdx = stripped.indexOf('/'); - return pathIdx === -1 ? stripped : stripped.substring(0, pathIdx); -} - -/** - * Encodes a domain using the legacy scheme (full URL including protocol). - * Used for backward-compatible matching against agents saved before the collision fix. - * @param {string} domain - * @returns {string} - */ -function legacyDomainEncode(domain) { - if (!domain) { - return ''; - } - if (domain.length <= Constants.ENCODED_DOMAIN_LENGTH) { - return domain.replace(/\./g, actionDomainSeparator); - } - const modifiedDomain = Buffer.from(domain).toString('base64'); - return modifiedDomain.substring(0, Constants.ENCODED_DOMAIN_LENGTH); -} - /** * Encodes or decodes a domain name to/from base64, or replacing periods with a custom separator. * * Necessary due to `[a-zA-Z0-9_-]*` Regex Validation, limited to a 64-character maximum. - * Strips protocol prefix before encoding to prevent base64 collisions - * (all `https://` URLs share the same 10-char base64 prefix). * * @param {string} domain - The domain name to encode/decode. * @param {boolean} inverse - False to decode from base64, true to encode to base64. @@ -115,27 +79,23 @@ async function domainParser(domain, inverse = false) { if (!domain) { return; } - const domainsCache = getLogStores(CacheKeys.ENCODED_DOMAINS); + const cachedDomain = await domainsCache.get(domain); + if (inverse && cachedDomain) { + return domain; + } + + if (inverse && domain.length <= Constants.ENCODED_DOMAIN_LENGTH) { + return domain.replace(/\./g, actionDomainSeparator); + } if (inverse) { - const hostname = stripProtocol(domain); - const cachedDomain = await domainsCache.get(hostname); - if (cachedDomain) { - return hostname; - } - - if (hostname.length <= Constants.ENCODED_DOMAIN_LENGTH) { - return hostname.replace(/\./g, actionDomainSeparator); - } - - const modifiedDomain = Buffer.from(hostname).toString('base64'); + const modifiedDomain = Buffer.from(domain).toString('base64'); const key = modifiedDomain.substring(0, Constants.ENCODED_DOMAIN_LENGTH); await domainsCache.set(key, modifiedDomain); return key; } - const cachedDomain = await domainsCache.get(domain); if (!cachedDomain) { return domain.replace(replaceSeparatorRegex, '.'); } @@ -496,7 +456,6 @@ const deleteAssistantActions = async ({ req, assistant_id }) => { module.exports = { deleteAssistantActions, validateAndUpdateTool, - legacyDomainEncode, createActionTool, encryptMetadata, decryptMetadata, diff --git a/api/server/services/ActionService.spec.js b/api/server/services/ActionService.spec.js index 52419975f7..c60aef7ad1 100644 --- a/api/server/services/ActionService.spec.js +++ b/api/server/services/ActionService.spec.js @@ -1,539 +1,175 @@ -const { Constants, actionDelimiter, actionDomainSeparator } = require('librechat-data-provider'); -const { domainParser, legacyDomainEncode, validateAndUpdateTool } = require('./ActionService'); +const { Constants, actionDomainSeparator } = require('librechat-data-provider'); +const { domainParser } = require('./ActionService'); jest.mock('keyv'); -jest.mock('~/models', () => ({ - getActions: jest.fn(), - deleteActions: jest.fn(), -})); - -const { getActions } = require('~/models'); - -let mockDomainCache = {}; +const globalCache = {}; jest.mock('~/cache/getLogStores', () => { - return jest.fn().mockImplementation(() => ({ - get: async (key) => mockDomainCache[key] ?? null, - set: async (key, value) => { - mockDomainCache[key] = value; - return true; - }, - })); -}); + return jest.fn().mockImplementation(() => { + const EventEmitter = require('events'); + const { CacheKeys } = require('librechat-data-provider'); -beforeEach(() => { - mockDomainCache = {}; - getActions.mockReset(); -}); + class KeyvMongo extends EventEmitter { + constructor(url = 'mongodb://127.0.0.1:27017', options) { + super(); + this.ttlSupport = false; + url = url ?? {}; + if (typeof url === 'string') { + url = { url }; + } + if (url.uri) { + url = { url: url.uri, ...url }; + } + this.opts = { + url, + collection: 'keyv', + ...url, + ...options, + }; + } -const SEP = actionDomainSeparator; -const DELIM = actionDelimiter; -const MAX = Constants.ENCODED_DOMAIN_LENGTH; -const domainSepRegex = new RegExp(SEP, 'g'); + get = async (key) => { + return new Promise((resolve) => { + resolve(globalCache[key] || null); + }); + }; + + set = async (key, value) => { + return new Promise((resolve) => { + globalCache[key] = value; + resolve(true); + }); + }; + } + + return new KeyvMongo('', { + namespace: CacheKeys.ENCODED_DOMAINS, + ttl: 0, + }); + }); +}); describe('domainParser', () => { - describe('nullish input', () => { - it.each([null, undefined, ''])('returns undefined for %j', async (input) => { - expect(await domainParser(input, true)).toBeUndefined(); - expect(await domainParser(input, false)).toBeUndefined(); - }); + const TLD = '.com'; + + // Non-azure request + it('does not return domain as is if not azure', async () => { + const domain = `example.com${actionDomainSeparator}test${actionDomainSeparator}`; + const result1 = await domainParser(domain, false); + const result2 = await domainParser(domain, true); + expect(result1).not.toEqual(domain); + expect(result2).not.toEqual(domain); }); - describe('short-path encoding (hostname ≤ threshold)', () => { - it.each([ - ['examp.com', `examp${SEP}com`], - ['swapi.tech', `swapi${SEP}tech`], - ['a.b', `a${SEP}b`], - ])('replaces dots in %s → %s', async (domain, expected) => { - expect(await domainParser(domain, true)).toBe(expected); - }); - - it('handles domain exactly at threshold length', async () => { - const domain = 'a'.repeat(MAX - 4) + '.com'; - expect(domain).toHaveLength(MAX); - const result = await domainParser(domain, true); - expect(result).toBe(domain.replace(/\./g, SEP)); - }); + // Test for Empty or Null Inputs + it('returns undefined for null domain input', async () => { + const result = await domainParser(null, true); + expect(result).toBeUndefined(); }); - describe('base64-path encoding (hostname > threshold)', () => { - it('produces a key of exactly ENCODED_DOMAIN_LENGTH chars', async () => { - const result = await domainParser('api.example.com', true); - expect(result).toHaveLength(MAX); - }); - - it('encodes hostname, not full URL', async () => { - const hostname = 'api.example.com'; - const expectedKey = Buffer.from(hostname).toString('base64').substring(0, MAX); - expect(await domainParser(hostname, true)).toBe(expectedKey); - }); - - it('populates decode cache for round-trip', async () => { - const hostname = 'longdomainname.com'; - const key = await domainParser(hostname, true); - - expect(mockDomainCache[key]).toBe(Buffer.from(hostname).toString('base64')); - expect(await domainParser(key, false)).toBe(hostname); - }); + it('returns undefined for empty domain input', async () => { + const result = await domainParser('', true); + expect(result).toBeUndefined(); }); - describe('protocol stripping', () => { - it('https:// URL and bare hostname produce identical encoding', async () => { - const encoded = await domainParser('https://swapi.tech', true); - expect(encoded).toBe(await domainParser('swapi.tech', true)); - expect(encoded).toBe(`swapi${SEP}tech`); - }); + // Verify Correct Caching Behavior + it('caches encoded domain correctly', async () => { + const domain = 'longdomainname.com'; + const encodedDomain = Buffer.from(domain) + .toString('base64') + .substring(0, Constants.ENCODED_DOMAIN_LENGTH); - it('http:// URL and bare hostname produce identical encoding', async () => { - const encoded = await domainParser('http://api.example.com', true); - expect(encoded).toBe(await domainParser('api.example.com', true)); - }); + await domainParser(domain, true); - it('different https:// domains produce unique keys', async () => { - const keys = await Promise.all([ - domainParser('https://api.example.com', true), - domainParser('https://api.weather.com', true), - domainParser('https://data.github.com', true), - ]); - const unique = new Set(keys); - expect(unique.size).toBe(keys.length); - }); - - it('long hostname after stripping still uses base64 path', async () => { - const result = await domainParser('https://api.example.com', true); - expect(result).toHaveLength(MAX); - expect(result).not.toContain(SEP); - }); - - it('short hostname after stripping uses dot-replacement path', async () => { - const result = await domainParser('https://a.b.c', true); - expect(result).toBe(`a${SEP}b${SEP}c`); - }); - - it('strips path and query from full URL before encoding', async () => { - const result = await domainParser('https://api.example.com/v1/endpoint?foo=bar', true); - expect(result).toBe(await domainParser('api.example.com', true)); - }); + const cachedValue = await globalCache[encodedDomain]; + expect(cachedValue).toEqual(Buffer.from(domain).toString('base64')); }); - describe('unicode domains', () => { - it('encodes unicode hostname via base64 path', async () => { - const domain = 'täst.example.com'; - const result = await domainParser(domain, true); - expect(result).toHaveLength(MAX); - expect(result).toBe(Buffer.from(domain).toString('base64').substring(0, MAX)); - }); - - it('round-trips unicode hostname through encode then decode', async () => { - const domain = 'täst.example.com'; - const key = await domainParser(domain, true); - expect(await domainParser(key, false)).toBe(domain); - }); - - it('strips protocol before encoding unicode hostname', async () => { - const withProto = 'https://täst.example.com'; - const bare = 'täst.example.com'; - expect(await domainParser(withProto, true)).toBe(await domainParser(bare, true)); - }); + // Test for Edge Cases Around Length Threshold + it('encodes domain exactly at threshold without modification', async () => { + const domain = 'a'.repeat(Constants.ENCODED_DOMAIN_LENGTH - TLD.length) + TLD; + const expected = domain.replace(/\./g, actionDomainSeparator); + const result = await domainParser(domain, true); + expect(result).toEqual(expected); }); - describe('decode path', () => { - it('short-path encoded domain decodes via separator replacement', async () => { - expect(await domainParser(`examp${SEP}com`, false)).toBe('examp.com'); - }); + it('encodes domain just below threshold without modification', async () => { + const domain = 'a'.repeat(Constants.ENCODED_DOMAIN_LENGTH - 1 - TLD.length) + TLD; + const expected = domain.replace(/\./g, actionDomainSeparator); + const result = await domainParser(domain, true); + expect(result).toEqual(expected); + }); - it('base64-path encoded domain decodes via cache lookup', async () => { - const hostname = 'api.example.com'; - const key = await domainParser(hostname, true); - expect(await domainParser(key, false)).toBe(hostname); - }); + // Test for Unicode Domain Names + it('handles unicode characters in domain names correctly when encoding', async () => { + const unicodeDomain = 'täst.example.com'; + const encodedDomain = Buffer.from(unicodeDomain) + .toString('base64') + .substring(0, Constants.ENCODED_DOMAIN_LENGTH); + const result = await domainParser(unicodeDomain, true); + expect(result).toEqual(encodedDomain); + }); - it('returns input unchanged for unknown non-separator strings', async () => { - expect(await domainParser('not_base64_encoded', false)).toBe('not_base64_encoded'); - }); + it('decodes unicode domain names correctly', async () => { + const unicodeDomain = 'täst.example.com'; + const encodedDomain = Buffer.from(unicodeDomain).toString('base64'); + globalCache[encodedDomain.substring(0, Constants.ENCODED_DOMAIN_LENGTH)] = encodedDomain; // Simulate caching - it('returns a string without throwing for corrupt cache entries', async () => { - mockDomainCache['corrupt_key'] = '!!!'; - const result = await domainParser('corrupt_key', false); - expect(typeof result).toBe('string'); - }); - }); -}); - -describe('legacyDomainEncode', () => { - it.each(['', null, undefined])('returns empty string for %j', (input) => { - expect(legacyDomainEncode(input)).toBe(''); - }); - - it('is synchronous (returns a string, not a Promise)', () => { - const result = legacyDomainEncode('examp.com'); - expect(result).toBe(`examp${SEP}com`); - expect(result).not.toBeInstanceOf(Promise); - }); - - it('uses dot-replacement for short domains', () => { - expect(legacyDomainEncode('examp.com')).toBe(`examp${SEP}com`); - }); - - it('uses base64 prefix of full input for long domains', () => { - const domain = 'https://swapi.tech'; - const expected = Buffer.from(domain).toString('base64').substring(0, MAX); - expect(legacyDomainEncode(domain)).toBe(expected); - }); - - it('all https:// URLs collide to the same key', () => { - const results = [ - legacyDomainEncode('https://api.example.com'), - legacyDomainEncode('https://api.weather.com'), - legacyDomainEncode('https://totally.different.host'), - ]; - expect(new Set(results).size).toBe(1); - }); - - it('matches what old domainParser would have produced', () => { - const domain = 'https://api.example.com'; - const legacy = legacyDomainEncode(domain); - expect(legacy).toBe(Buffer.from(domain).toString('base64').substring(0, MAX)); - }); - - it('produces same result as new domainParser for short bare hostnames', async () => { - const domain = 'swapi.tech'; - expect(legacyDomainEncode(domain)).toBe(await domainParser(domain, true)); - }); -}); - -describe('validateAndUpdateTool', () => { - const mockReq = { user: { id: 'user123' } }; - - it('returns tool unchanged when name passes tool-name regex', async () => { - const tool = { function: { name: 'getPeople_action_swapi---tech' } }; - const result = await validateAndUpdateTool({ - req: mockReq, - tool, - assistant_id: 'asst_1', - }); - expect(result).toEqual(tool); - expect(getActions).not.toHaveBeenCalled(); - }); - - it('matches action when metadata.domain has https:// prefix and tool domain is bare hostname', async () => { - getActions.mockResolvedValue([{ metadata: { domain: 'https://api.example.com' } }]); - - const tool = { function: { name: `getPeople${DELIM}api.example.com` } }; - const result = await validateAndUpdateTool({ - req: mockReq, - tool, - assistant_id: 'asst_1', - }); - - expect(result).not.toBeNull(); - expect(result.function.name).toMatch(/^getPeople_action_/); - expect(result.function.name).not.toContain('.'); - }); - - it('matches action when metadata.domain has no protocol', async () => { - getActions.mockResolvedValue([{ metadata: { domain: 'api.example.com' } }]); - - const tool = { function: { name: `getPeople${DELIM}api.example.com` } }; - const result = await validateAndUpdateTool({ - req: mockReq, - tool, - assistant_id: 'asst_1', - }); - - expect(result).not.toBeNull(); - expect(result.function.name).toMatch(/^getPeople_action_/); - }); - - it('returns null when no action matches the domain', async () => { - getActions.mockResolvedValue([{ metadata: { domain: 'https://other.domain.com' } }]); - - const tool = { function: { name: `getPeople${DELIM}api.example.com` } }; - const result = await validateAndUpdateTool({ - req: mockReq, - tool, - assistant_id: 'asst_1', - }); - - expect(result).toBeNull(); - }); - - it('returns null when action has no metadata', async () => { - getActions.mockResolvedValue([{ metadata: null }]); - - const tool = { function: { name: `getPeople${DELIM}api.example.com` } }; - const result = await validateAndUpdateTool({ - req: mockReq, - tool, - assistant_id: 'asst_1', - }); - - expect(result).toBeNull(); - }); -}); - -describe('backward-compatible tool name matching', () => { - function normalizeToolName(name) { - return name.replace(domainSepRegex, '_'); - } - - function buildToolName(functionName, encodedDomain) { - return `${functionName}${DELIM}${encodedDomain}`; - } - - describe('definition-phase matching', () => { - it('new encoding matches agent tools stored with new encoding', async () => { - const metadataDomain = 'https://swapi.tech'; - const encoded = await domainParser(metadataDomain, true); - const normalized = normalizeToolName(encoded); - - const storedTool = buildToolName('getPeople', encoded); - const defToolName = `getPeople${DELIM}${normalized}`; - - expect(normalizeToolName(storedTool)).toBe(defToolName); - }); - - it('legacy encoding matches agent tools stored with legacy encoding', async () => { - const metadataDomain = 'https://swapi.tech'; - const legacy = legacyDomainEncode(metadataDomain); - const legacyNormalized = normalizeToolName(legacy); - - const storedTool = buildToolName('getPeople', legacy); - const legacyDefName = `getPeople${DELIM}${legacyNormalized}`; - - expect(normalizeToolName(storedTool)).toBe(legacyDefName); - }); - - it('new definition matches old stored tools via legacy fallback', async () => { - const metadataDomain = 'https://swapi.tech'; - const newDomain = await domainParser(metadataDomain, true); - const legacyDomain = legacyDomainEncode(metadataDomain); - const newNorm = normalizeToolName(newDomain); - const legacyNorm = normalizeToolName(legacyDomain); - - const oldStoredTool = buildToolName('getPeople', legacyDomain); - const newToolName = `getPeople${DELIM}${newNorm}`; - const legacyToolName = `getPeople${DELIM}${legacyNorm}`; - - const storedNormalized = normalizeToolName(oldStoredTool); - const hasMatch = storedNormalized === newToolName || storedNormalized === legacyToolName; - expect(hasMatch).toBe(true); - }); - - it('pre-normalized Set eliminates per-tool normalization', async () => { - const metadataDomain = 'https://api.example.com'; - const domain = await domainParser(metadataDomain, true); - const legacyDomain = legacyDomainEncode(metadataDomain); - const normalizedDomain = normalizeToolName(domain); - const legacyNormalized = normalizeToolName(legacyDomain); - - const storedTools = [ - buildToolName('getWeather', legacyDomain), - buildToolName('getForecast', domain), - ]; - - const preNormalized = new Set(storedTools.map((t) => normalizeToolName(t))); - - const toolName = `getWeather${DELIM}${normalizedDomain}`; - const legacyToolName = `getWeather${DELIM}${legacyNormalized}`; - expect(preNormalized.has(toolName) || preNormalized.has(legacyToolName)).toBe(true); - }); - }); - - describe('execution-phase tool lookup', () => { - it('model-called tool name resolves via normalizedToDomain map (new encoding)', async () => { - const metadataDomain = 'https://api.example.com'; - const domain = await domainParser(metadataDomain, true); - const normalized = normalizeToolName(domain); - - const normalizedToDomain = new Map(); - normalizedToDomain.set(normalized, domain); - - const modelToolName = `getWeather${DELIM}${normalized}`; - - let matched = ''; - for (const [norm, canonical] of normalizedToDomain.entries()) { - if (modelToolName.includes(norm)) { - matched = canonical; - break; - } - } - - expect(matched).toBe(domain); - - const functionName = modelToolName.replace(`${DELIM}${normalizeToolName(matched)}`, ''); - expect(functionName).toBe('getWeather'); - }); - - it('model-called tool name resolves via legacy entry in normalizedToDomain map', async () => { - const metadataDomain = 'https://api.example.com'; - const domain = await domainParser(metadataDomain, true); - const legacyDomain = legacyDomainEncode(metadataDomain); - const legacyNorm = normalizeToolName(legacyDomain); - - const normalizedToDomain = new Map(); - normalizedToDomain.set(normalizeToolName(domain), domain); - normalizedToDomain.set(legacyNorm, domain); - - const legacyModelToolName = `getWeather${DELIM}${legacyNorm}`; - - let matched = ''; - for (const [norm, canonical] of normalizedToDomain.entries()) { - if (legacyModelToolName.includes(norm)) { - matched = canonical; - break; - } - } - - expect(matched).toBe(domain); - }); - - it('legacy guard skips duplicate map entry for short bare hostnames', async () => { - const domain = 'swapi.tech'; - const newEncoding = await domainParser(domain, true); - const legacyEncoding = legacyDomainEncode(domain); - - expect(newEncoding).toBe(legacyEncoding); - - const normalizedToDomain = new Map(); - normalizedToDomain.set(newEncoding, newEncoding); - if (legacyEncoding !== newEncoding) { - normalizedToDomain.set(legacyEncoding, newEncoding); - } - expect(normalizedToDomain.size).toBe(1); - }); - }); - - describe('processRequiredActions matching (assistants path)', () => { - it('legacy tool from OpenAI matches via normalizedToDomain with both encodings', async () => { - const metadataDomain = 'https://swapi.tech'; - const domain = await domainParser(metadataDomain, true); - const legacyDomain = legacyDomainEncode(metadataDomain); - - const normalizedToDomain = new Map(); - normalizedToDomain.set(domain, domain); - if (legacyDomain !== domain) { - normalizedToDomain.set(legacyDomain, domain); - } - - const legacyToolName = buildToolName('getPeople', legacyDomain); - - let currentDomain = ''; - let matchedKey = ''; - for (const [key, canonical] of normalizedToDomain.entries()) { - if (legacyToolName.includes(key)) { - currentDomain = canonical; - matchedKey = key; - break; - } - } - - expect(currentDomain).toBe(domain); - expect(matchedKey).toBe(legacyDomain); - - const functionName = legacyToolName.replace(`${DELIM}${matchedKey}`, ''); - expect(functionName).toBe('getPeople'); - }); - - it('new tool name matches via the canonical domain key', async () => { - const metadataDomain = 'https://swapi.tech'; - const domain = await domainParser(metadataDomain, true); - const legacyDomain = legacyDomainEncode(metadataDomain); - - const normalizedToDomain = new Map(); - normalizedToDomain.set(domain, domain); - if (legacyDomain !== domain) { - normalizedToDomain.set(legacyDomain, domain); - } - - const newToolName = buildToolName('getPeople', domain); - - let currentDomain = ''; - let matchedKey = ''; - for (const [key, canonical] of normalizedToDomain.entries()) { - if (newToolName.includes(key)) { - currentDomain = canonical; - matchedKey = key; - break; - } - } - - expect(currentDomain).toBe(domain); - expect(matchedKey).toBe(domain); - - const functionName = newToolName.replace(`${DELIM}${matchedKey}`, ''); - expect(functionName).toBe('getPeople'); - }); - }); - - describe('save-route cleanup', () => { - it('tool filter removes tools matching new encoding', async () => { - const metadataDomain = 'https://swapi.tech'; - const domain = await domainParser(metadataDomain, true); - const legacyDomain = legacyDomainEncode(metadataDomain); - - const tools = [ - buildToolName('getPeople', domain), - buildToolName('unrelated', 'other---domain'), - ]; - - const filtered = tools.filter((t) => !t.includes(domain) && !t.includes(legacyDomain)); - - expect(filtered).toEqual([buildToolName('unrelated', 'other---domain')]); - }); - - it('tool filter removes tools matching legacy encoding', async () => { - const metadataDomain = 'https://swapi.tech'; - const domain = await domainParser(metadataDomain, true); - const legacyDomain = legacyDomainEncode(metadataDomain); - - const tools = [ - buildToolName('getPeople', legacyDomain), - buildToolName('unrelated', 'other---domain'), - ]; - - const filtered = tools.filter((t) => !t.includes(domain) && !t.includes(legacyDomain)); - - expect(filtered).toEqual([buildToolName('unrelated', 'other---domain')]); - }); - }); - - describe('delete-route domain extraction', () => { - it('domain extracted from actions array is usable as-is for tool filtering', async () => { - const metadataDomain = 'https://api.example.com'; - const domain = await domainParser(metadataDomain, true); - const actionId = 'abc123'; - const actionEntry = `${domain}${DELIM}${actionId}`; - - const [storedDomain] = actionEntry.split(DELIM); - expect(storedDomain).toBe(domain); - - const tools = [buildToolName('getWeather', domain), buildToolName('getPeople', 'other')]; - - const filtered = tools.filter((t) => !t.includes(storedDomain)); - expect(filtered).toEqual([buildToolName('getPeople', 'other')]); - }); - }); - - describe('multi-action agents (collision scenario)', () => { - it('two https:// actions now produce distinct tool names', async () => { - const domain1 = await domainParser('https://api.weather.com', true); - const domain2 = await domainParser('https://api.spacex.com', true); - - const tool1 = buildToolName('getData', domain1); - const tool2 = buildToolName('getData', domain2); - - expect(tool1).not.toBe(tool2); - }); - - it('two https:// actions used to collide in legacy encoding', () => { - const legacy1 = legacyDomainEncode('https://api.weather.com'); - const legacy2 = legacyDomainEncode('https://api.spacex.com'); - - const tool1 = buildToolName('getData', legacy1); - const tool2 = buildToolName('getData', legacy2); - - expect(tool1).toBe(tool2); - }); + const result = await domainParser( + encodedDomain.substring(0, Constants.ENCODED_DOMAIN_LENGTH), + false, + ); + expect(result).toEqual(unicodeDomain); + }); + + // Core Functionality Tests + it('returns domain with replaced separators if no cached domain exists', async () => { + const domain = 'example.com'; + const withSeparator = domain.replace(/\./g, actionDomainSeparator); + const result = await domainParser(withSeparator, false); + expect(result).toEqual(domain); + }); + + it('returns domain with replaced separators when inverse is false and under encoding length', async () => { + const domain = 'examp.com'; + const withSeparator = domain.replace(/\./g, actionDomainSeparator); + const result = await domainParser(withSeparator, false); + expect(result).toEqual(domain); + }); + + it('replaces periods with actionDomainSeparator when inverse is true and under encoding length', async () => { + const domain = 'examp.com'; + const expected = domain.replace(/\./g, actionDomainSeparator); + const result = await domainParser(domain, true); + expect(result).toEqual(expected); + }); + + it('encodes domain when length is above threshold and inverse is true', async () => { + const domain = 'a'.repeat(Constants.ENCODED_DOMAIN_LENGTH + 1).concat('.com'); + const result = await domainParser(domain, true); + expect(result).not.toEqual(domain); + expect(result.length).toBeLessThanOrEqual(Constants.ENCODED_DOMAIN_LENGTH); + }); + + it('returns encoded value if no encoded value is cached, and inverse is false', async () => { + const originalDomain = 'example.com'; + const encodedDomain = Buffer.from( + originalDomain.replace(/\./g, actionDomainSeparator), + ).toString('base64'); + const result = await domainParser(encodedDomain, false); + expect(result).toEqual(encodedDomain); + }); + + it('decodes encoded value if cached and encoded value is provided, and inverse is false', async () => { + const originalDomain = 'example.com'; + const encodedDomain = await domainParser(originalDomain, true); + const result = await domainParser(encodedDomain, false); + expect(result).toEqual(originalDomain); + }); + + it('handles invalid base64 encoded values gracefully', async () => { + const invalidBase64Domain = 'not_base64_encoded'; + const result = await domainParser(invalidBase64Domain, false); + expect(result).toEqual(invalidBase64Domain); }); }); diff --git a/api/server/services/AuthService.js b/api/server/services/AuthService.js index 816a0eac5b..ef50a365b9 100644 --- a/api/server/services/AuthService.js +++ b/api/server/services/AuthService.js @@ -13,7 +13,6 @@ const { checkEmailConfig, isEmailDomainAllowed, shouldUseSecureCookie, - resolveAppConfigForUser, } = require('@librechat/api'); const { findUser, @@ -190,7 +189,7 @@ const registerUser = async (user, additionalData = {}) => { let newUserId; try { - const appConfig = await getAppConfig({ baseOnly: true }); + const appConfig = await getAppConfig(); if (!isEmailDomainAllowed(email, appConfig?.registration?.allowedDomains)) { const errorMessage = 'The email address provided cannot be used. Please use a different email address.'; @@ -256,52 +255,19 @@ const registerUser = async (user, additionalData = {}) => { }; /** - * Request password reset. - * - * Uses a two-phase domain check: fast-fail with the memory-cached base config - * (zero DB queries) to block globally denied domains before user lookup, then - * re-check with tenant-scoped config after user lookup so tenant-specific - * restrictions are enforced. - * - * Phase 1 (base check) returns an Error (HTTP 400) — this intentionally reveals - * that the domain is globally blocked, but fires before any DB lookup so it - * cannot confirm user existence. Phase 2 (tenant check) returns the generic - * success message (HTTP 200) to prevent user-enumeration via status codes. - * + * Request password reset * @param {ServerRequest} req */ const requestPasswordReset = async (req) => { const { email } = req.body; - - const baseConfig = await getAppConfig({ baseOnly: true }); - if (!isEmailDomainAllowed(email, baseConfig?.registration?.allowedDomains)) { - logger.warn( - `[requestPasswordReset] Blocked - email domain not allowed [Email: ${email}] [IP: ${req.ip}]`, - ); + const appConfig = await getAppConfig(); + if (!isEmailDomainAllowed(email, appConfig?.registration?.allowedDomains)) { const error = new Error(ErrorTypes.AUTH_FAILED); error.code = ErrorTypes.AUTH_FAILED; error.message = 'Email domain not allowed'; return error; } - - const user = await findUser({ email }, 'email _id role tenantId'); - let appConfig = baseConfig; - if (user?.tenantId) { - try { - appConfig = await resolveAppConfigForUser(getAppConfig, user); - } catch (err) { - logger.error('[requestPasswordReset] Failed to resolve tenant config, using base:', err); - } - } - - if (!isEmailDomainAllowed(email, appConfig?.registration?.allowedDomains)) { - logger.warn( - `[requestPasswordReset] Tenant config blocked domain [Email: ${email}] [IP: ${req.ip}]`, - ); - return { - message: 'If an account with that email exists, a password reset link has been sent to it.', - }; - } + const user = await findUser({ email }, 'email _id'); const emailEnabled = checkEmailConfig(); logger.warn(`[requestPasswordReset] [Password reset request initiated] [Email: ${email}]`); diff --git a/api/server/services/AuthService.spec.js b/api/server/services/AuthService.spec.js index c8abafdbe5..da78f8d775 100644 --- a/api/server/services/AuthService.spec.js +++ b/api/server/services/AuthService.spec.js @@ -14,7 +14,6 @@ jest.mock('@librechat/api', () => ({ isEmailDomainAllowed: jest.fn(), math: jest.fn((val, fallback) => (val ? Number(val) : fallback)), shouldUseSecureCookie: jest.fn(() => false), - resolveAppConfigForUser: jest.fn(async (_getAppConfig, _user) => ({})), })); jest.mock('~/models', () => ({ findUser: jest.fn(), @@ -36,14 +35,8 @@ jest.mock('~/strategies/validators', () => ({ registerSchema: { parse: jest.fn() jest.mock('~/server/services/Config', () => ({ getAppConfig: jest.fn() })); jest.mock('~/server/utils', () => ({ sendEmail: jest.fn() })); -const { - shouldUseSecureCookie, - isEmailDomainAllowed, - resolveAppConfigForUser, -} = require('@librechat/api'); -const { findUser } = require('~/models'); -const { getAppConfig } = require('~/server/services/Config'); -const { setOpenIDAuthTokens, requestPasswordReset } = require('./AuthService'); +const { shouldUseSecureCookie } = require('@librechat/api'); +const { setOpenIDAuthTokens } = require('./AuthService'); /** Helper to build a mock Express response */ function mockResponse() { @@ -274,68 +267,3 @@ describe('setOpenIDAuthTokens', () => { }); }); }); - -describe('requestPasswordReset', () => { - beforeEach(() => { - jest.clearAllMocks(); - isEmailDomainAllowed.mockReturnValue(true); - getAppConfig.mockResolvedValue({ - registration: { allowedDomains: ['example.com'] }, - }); - resolveAppConfigForUser.mockResolvedValue({ - registration: { allowedDomains: ['example.com'] }, - }); - }); - - it('should fast-fail with base config before DB lookup for blocked domains', async () => { - isEmailDomainAllowed.mockReturnValue(false); - - const req = { body: { email: 'blocked@evil.com' }, ip: '127.0.0.1' }; - const result = await requestPasswordReset(req); - - expect(getAppConfig).toHaveBeenCalledWith({ baseOnly: true }); - expect(findUser).not.toHaveBeenCalled(); - expect(result).toBeInstanceOf(Error); - }); - - it('should call resolveAppConfigForUser for tenant user', async () => { - const user = { - _id: 'user-tenant', - email: 'user@example.com', - tenantId: 'tenant-x', - role: 'USER', - }; - findUser.mockResolvedValue(user); - - const req = { body: { email: 'user@example.com' }, ip: '127.0.0.1' }; - await requestPasswordReset(req); - - expect(resolveAppConfigForUser).toHaveBeenCalledWith(getAppConfig, user); - }); - - it('should reuse baseConfig for non-tenant user without calling resolveAppConfigForUser', async () => { - findUser.mockResolvedValue({ _id: 'user-no-tenant', email: 'user@example.com' }); - - const req = { body: { email: 'user@example.com' }, ip: '127.0.0.1' }; - await requestPasswordReset(req); - - expect(resolveAppConfigForUser).not.toHaveBeenCalled(); - }); - - it('should return generic response when tenant config blocks the domain (non-enumerable)', async () => { - const user = { - _id: 'user-tenant', - email: 'user@example.com', - tenantId: 'tenant-x', - role: 'USER', - }; - findUser.mockResolvedValue(user); - isEmailDomainAllowed.mockReturnValueOnce(true).mockReturnValueOnce(false); - - const req = { body: { email: 'user@example.com' }, ip: '127.0.0.1' }; - const result = await requestPasswordReset(req); - - expect(result).not.toBeInstanceOf(Error); - expect(result.message).toContain('If an account with that email exists'); - }); -}); diff --git a/api/server/services/Config/__tests__/invalidateConfigCaches.spec.js b/api/server/services/Config/__tests__/invalidateConfigCaches.spec.js deleted file mode 100644 index ddc97042b9..0000000000 --- a/api/server/services/Config/__tests__/invalidateConfigCaches.spec.js +++ /dev/null @@ -1,122 +0,0 @@ -// ── Mocks ────────────────────────────────────────────────────────────── - -const mockClearAppConfigCache = jest.fn().mockResolvedValue(undefined); -const mockClearOverrideCache = jest.fn().mockResolvedValue(undefined); - -jest.mock('~/cache/getLogStores', () => { - return jest.fn(() => ({})); -}); - -jest.mock('~/server/services/start/tools', () => ({ - loadAndFormatTools: jest.fn(() => ({})), -})); - -jest.mock('../loadCustomConfig', () => jest.fn().mockResolvedValue({})); - -jest.mock('@librechat/data-schemas', () => { - const actual = jest.requireActual('@librechat/data-schemas'); - return { ...actual, AppService: jest.fn(() => ({ availableTools: {} })) }; -}); - -jest.mock('~/models', () => ({ - getApplicableConfigs: jest.fn().mockResolvedValue([]), - getUserPrincipals: jest.fn().mockResolvedValue([]), -})); - -const mockInvalidateCachedTools = jest.fn().mockResolvedValue(undefined); -jest.mock('../getCachedTools', () => ({ - setCachedTools: jest.fn().mockResolvedValue(undefined), - invalidateCachedTools: mockInvalidateCachedTools, -})); - -const mockClearMcpConfigCache = jest.fn().mockResolvedValue(undefined); -jest.mock('@librechat/api', () => ({ - createAppConfigService: jest.fn(() => ({ - getAppConfig: jest.fn().mockResolvedValue({ availableTools: {} }), - clearAppConfigCache: mockClearAppConfigCache, - clearOverrideCache: mockClearOverrideCache, - })), - clearMcpConfigCache: mockClearMcpConfigCache, -})); - -// ── Tests ────────────────────────────────────────────────────────────── - -const { invalidateConfigCaches } = require('../app'); - -describe('invalidateConfigCaches', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('clears all caches', async () => { - await invalidateConfigCaches(); - - expect(mockClearAppConfigCache).toHaveBeenCalledTimes(1); - expect(mockClearOverrideCache).toHaveBeenCalledTimes(1); - expect(mockInvalidateCachedTools).toHaveBeenCalledWith({ invalidateGlobal: true }); - expect(mockClearMcpConfigCache).toHaveBeenCalledTimes(1); - }); - - it('passes tenantId through to clearOverrideCache', async () => { - await invalidateConfigCaches('tenant-a'); - - expect(mockClearOverrideCache).toHaveBeenCalledWith('tenant-a'); - expect(mockClearAppConfigCache).toHaveBeenCalledTimes(1); - expect(mockInvalidateCachedTools).toHaveBeenCalledWith({ invalidateGlobal: true }); - }); - - it('all operations run in parallel (not sequentially)', async () => { - const order = []; - - mockClearAppConfigCache.mockImplementation( - () => - new Promise((r) => - setTimeout(() => { - order.push('base'); - r(); - }, 10), - ), - ); - mockClearOverrideCache.mockImplementation( - () => - new Promise((r) => - setTimeout(() => { - order.push('override'); - r(); - }, 10), - ), - ); - mockInvalidateCachedTools.mockImplementation( - () => - new Promise((r) => - setTimeout(() => { - order.push('tools'); - r(); - }, 10), - ), - ); - mockClearMcpConfigCache.mockImplementation( - () => - new Promise((r) => - setTimeout(() => { - order.push('mcp'); - r(); - }, 10), - ), - ); - - await invalidateConfigCaches(); - - expect(order).toHaveLength(4); - expect(new Set(order)).toEqual(new Set(['base', 'override', 'tools', 'mcp'])); - }); - - it('resolves even when clearAppConfigCache throws (partial failure)', async () => { - mockClearAppConfigCache.mockRejectedValueOnce(new Error('cache connection lost')); - - await expect(invalidateConfigCaches()).resolves.not.toThrow(); - - expect(mockClearOverrideCache).toHaveBeenCalledTimes(1); - expect(mockInvalidateCachedTools).toHaveBeenCalledWith({ invalidateGlobal: true }); - }); -}); diff --git a/api/server/services/Config/app.js b/api/server/services/Config/app.js index 7aa913e636..75a5cbe56d 100644 --- a/api/server/services/Config/app.js +++ b/api/server/services/Config/app.js @@ -1,12 +1,12 @@ const { CacheKeys } = require('librechat-data-provider'); -const { AppService, logger } = require('@librechat/data-schemas'); -const { createAppConfigService, clearMcpConfigCache } = require('@librechat/api'); -const { setCachedTools, invalidateCachedTools } = require('./getCachedTools'); +const { logger, AppService } = require('@librechat/data-schemas'); const { loadAndFormatTools } = require('~/server/services/start/tools'); const loadCustomConfig = require('./loadCustomConfig'); +const { setCachedTools } = require('./getCachedTools'); const getLogStores = require('~/cache/getLogStores'); const paths = require('~/config/paths'); -const db = require('~/models'); + +const BASE_CONFIG_KEY = '_BASE_'; const loadBaseConfig = async () => { /** @type {TCustomConfig} */ @@ -20,43 +20,65 @@ const loadBaseConfig = async () => { return AppService({ config, paths, systemTools }); }; -const { getAppConfig, clearAppConfigCache, clearOverrideCache } = createAppConfigService({ - loadBaseConfig, - setCachedTools, - getCache: getLogStores, - cacheKeys: CacheKeys, - getApplicableConfigs: db.getApplicableConfigs, - getUserPrincipals: db.getUserPrincipals, -}); - /** - * Invalidate all config-related caches after an admin config mutation. - * Clears the base config, per-principal override caches, tool caches, - * and the MCP config-source server cache. - * @param {string} [tenantId] - Optional tenant ID to scope override cache clearing. + * Get the app configuration based on user context + * @param {Object} [options] + * @param {string} [options.role] - User role for role-based config + * @param {boolean} [options.refresh] - Force refresh the cache + * @returns {Promise} */ -async function invalidateConfigCaches(tenantId) { - const results = await Promise.allSettled([ - clearAppConfigCache(), - clearOverrideCache(tenantId), - invalidateCachedTools({ invalidateGlobal: true }), - clearMcpConfigCache(), - ]); - const labels = [ - 'clearAppConfigCache', - 'clearOverrideCache', - 'invalidateCachedTools', - 'clearMcpConfigCache', - ]; - for (let i = 0; i < results.length; i++) { - if (results[i].status === 'rejected') { - logger.error(`[invalidateConfigCaches] ${labels[i]} failed:`, results[i].reason); +async function getAppConfig(options = {}) { + const { role, refresh } = options; + + const cache = getLogStores(CacheKeys.APP_CONFIG); + const cacheKey = role ? role : BASE_CONFIG_KEY; + + if (!refresh) { + const cached = await cache.get(cacheKey); + if (cached) { + return cached; } } + + let baseConfig = await cache.get(BASE_CONFIG_KEY); + if (!baseConfig) { + logger.info('[getAppConfig] App configuration not initialized. Initializing AppService...'); + baseConfig = await loadBaseConfig(); + + if (!baseConfig) { + throw new Error('Failed to initialize app configuration through AppService.'); + } + + if (baseConfig.availableTools) { + await setCachedTools(baseConfig.availableTools); + } + + await cache.set(BASE_CONFIG_KEY, baseConfig); + } + + // For now, return the base config + // In the future, this is where we'll apply role-based modifications + if (role) { + // TODO: Apply role-based config modifications + // const roleConfig = await applyRoleBasedConfig(baseConfig, role); + // await cache.set(cacheKey, roleConfig); + // return roleConfig; + } + + return baseConfig; +} + +/** + * Clear the app configuration cache + * @returns {Promise} + */ +async function clearAppConfigCache() { + const cache = getLogStores(CacheKeys.CONFIG_STORE); + const cacheKey = CacheKeys.APP_CONFIG; + return await cache.delete(cacheKey); } module.exports = { getAppConfig, clearAppConfigCache, - invalidateConfigCaches, }; diff --git a/api/server/services/Config/getEndpointsConfig.js b/api/server/services/Config/getEndpointsConfig.js index d09b45626c..bb22584851 100644 --- a/api/server/services/Config/getEndpointsConfig.js +++ b/api/server/services/Config/getEndpointsConfig.js @@ -1,10 +1,133 @@ -const { createEndpointsConfigService } = require('@librechat/api'); +const { loadCustomEndpointsConfig } = require('@librechat/api'); +const { + CacheKeys, + EModelEndpoint, + isAgentsEndpoint, + orderEndpointsConfig, + defaultAgentCapabilities, +} = require('librechat-data-provider'); const loadDefaultEndpointsConfig = require('./loadDefaultEConfig'); +const getLogStores = require('~/cache/getLogStores'); const { getAppConfig } = require('./app'); -const { getEndpointsConfig, checkCapability } = createEndpointsConfigService({ - getAppConfig, - loadDefaultEndpointsConfig, -}); +/** + * + * @param {ServerRequest} req + * @returns {Promise} + */ +async function getEndpointsConfig(req) { + const cache = getLogStores(CacheKeys.CONFIG_STORE); + const cachedEndpointsConfig = await cache.get(CacheKeys.ENDPOINT_CONFIG); + if (cachedEndpointsConfig) { + if (cachedEndpointsConfig.gptPlugins) { + await cache.delete(CacheKeys.ENDPOINT_CONFIG); + } else { + return cachedEndpointsConfig; + } + } + + const appConfig = req.config ?? (await getAppConfig({ role: req.user?.role })); + const defaultEndpointsConfig = await loadDefaultEndpointsConfig(appConfig); + const customEndpointsConfig = loadCustomEndpointsConfig(appConfig?.endpoints?.custom); + + /** @type {TEndpointsConfig} */ + const mergedConfig = { + ...defaultEndpointsConfig, + ...customEndpointsConfig, + }; + + if (appConfig.endpoints?.[EModelEndpoint.azureOpenAI]) { + /** @type {Omit} */ + mergedConfig[EModelEndpoint.azureOpenAI] = { + userProvide: false, + }; + } + + // Enable Anthropic endpoint when Vertex AI is configured in YAML + if (appConfig.endpoints?.[EModelEndpoint.anthropic]?.vertexConfig?.enabled) { + /** @type {Omit} */ + mergedConfig[EModelEndpoint.anthropic] = { + userProvide: false, + }; + } + + if (appConfig.endpoints?.[EModelEndpoint.azureOpenAI]?.assistants) { + /** @type {Omit} */ + mergedConfig[EModelEndpoint.azureAssistants] = { + userProvide: false, + }; + } + + if ( + mergedConfig[EModelEndpoint.assistants] && + appConfig?.endpoints?.[EModelEndpoint.assistants] + ) { + const { disableBuilder, retrievalModels, capabilities, version, ..._rest } = + appConfig.endpoints[EModelEndpoint.assistants]; + + mergedConfig[EModelEndpoint.assistants] = { + ...mergedConfig[EModelEndpoint.assistants], + version, + retrievalModels, + disableBuilder, + capabilities, + }; + } + if (mergedConfig[EModelEndpoint.agents] && appConfig?.endpoints?.[EModelEndpoint.agents]) { + const { disableBuilder, capabilities, allowedProviders, ..._rest } = + appConfig.endpoints[EModelEndpoint.agents]; + + mergedConfig[EModelEndpoint.agents] = { + ...mergedConfig[EModelEndpoint.agents], + allowedProviders, + disableBuilder, + capabilities, + }; + } + + if ( + mergedConfig[EModelEndpoint.azureAssistants] && + appConfig?.endpoints?.[EModelEndpoint.azureAssistants] + ) { + const { disableBuilder, retrievalModels, capabilities, version, ..._rest } = + appConfig.endpoints[EModelEndpoint.azureAssistants]; + + mergedConfig[EModelEndpoint.azureAssistants] = { + ...mergedConfig[EModelEndpoint.azureAssistants], + version, + retrievalModels, + disableBuilder, + capabilities, + }; + } + + if (mergedConfig[EModelEndpoint.bedrock] && appConfig?.endpoints?.[EModelEndpoint.bedrock]) { + const { availableRegions } = appConfig.endpoints[EModelEndpoint.bedrock]; + mergedConfig[EModelEndpoint.bedrock] = { + ...mergedConfig[EModelEndpoint.bedrock], + availableRegions, + }; + } + + const endpointsConfig = orderEndpointsConfig(mergedConfig); + + await cache.set(CacheKeys.ENDPOINT_CONFIG, endpointsConfig); + return endpointsConfig; +} + +/** + * @param {ServerRequest} req + * @param {import('librechat-data-provider').AgentCapabilities} capability + * @returns {Promise} + */ +const checkCapability = async (req, capability) => { + const isAgents = isAgentsEndpoint(req.body?.endpointType || req.body?.endpoint); + const endpointsConfig = await getEndpointsConfig(req); + const capabilities = + isAgents || endpointsConfig?.[EModelEndpoint.agents]?.capabilities != null + ? (endpointsConfig?.[EModelEndpoint.agents]?.capabilities ?? []) + : defaultAgentCapabilities; + return capabilities.includes(capability); +}; module.exports = { getEndpointsConfig, checkCapability }; diff --git a/api/server/services/Config/loadConfigModels.js b/api/server/services/Config/loadConfigModels.js index 93212cd030..2bc83ecc3a 100644 --- a/api/server/services/Config/loadConfigModels.js +++ b/api/server/services/Config/loadConfigModels.js @@ -1,11 +1,117 @@ -const { createLoadConfigModels, fetchModels } = require('@librechat/api'); +const { isUserProvided, fetchModels } = require('@librechat/api'); +const { + EModelEndpoint, + extractEnvVariable, + normalizeEndpointName, +} = require('librechat-data-provider'); const { getAppConfig } = require('./app'); -const db = require('~/models'); -const loadConfigModels = createLoadConfigModels({ - getAppConfig, - getUserKeyValues: db.getUserKeyValues, - fetchModels, -}); +/** + * Load config endpoints from the cached configuration object + * @function loadConfigModels + * @param {ServerRequest} req - The Express request object. + */ +async function loadConfigModels(req) { + const appConfig = await getAppConfig({ role: req.user?.role }); + if (!appConfig) { + return {}; + } + const modelsConfig = {}; + const azureConfig = appConfig.endpoints?.[EModelEndpoint.azureOpenAI]; + const { modelNames } = azureConfig ?? {}; + + if (modelNames && azureConfig) { + modelsConfig[EModelEndpoint.azureOpenAI] = modelNames; + } + + if (azureConfig?.assistants && azureConfig.assistantModels) { + modelsConfig[EModelEndpoint.azureAssistants] = azureConfig.assistantModels; + } + + const bedrockConfig = appConfig.endpoints?.[EModelEndpoint.bedrock]; + if (bedrockConfig?.models && Array.isArray(bedrockConfig.models)) { + modelsConfig[EModelEndpoint.bedrock] = bedrockConfig.models; + } + + if (!Array.isArray(appConfig.endpoints?.[EModelEndpoint.custom])) { + return modelsConfig; + } + + const customEndpoints = appConfig.endpoints[EModelEndpoint.custom].filter( + (endpoint) => + endpoint.baseURL && + endpoint.apiKey && + endpoint.name && + endpoint.models && + (endpoint.models.fetch || endpoint.models.default), + ); + + /** + * @type {Record>} + * Map for promises keyed by unique combination of baseURL and apiKey */ + const fetchPromisesMap = {}; + /** + * @type {Record} + * Map to associate unique keys with endpoint names; note: one key may can correspond to multiple endpoints */ + const uniqueKeyToEndpointsMap = {}; + /** + * @type {Record>} + * Map to associate endpoint names to their configurations */ + const endpointsMap = {}; + + for (let i = 0; i < customEndpoints.length; i++) { + const endpoint = customEndpoints[i]; + const { models, name: configName, baseURL, apiKey, headers: endpointHeaders } = endpoint; + const name = normalizeEndpointName(configName); + endpointsMap[name] = endpoint; + + const API_KEY = extractEnvVariable(apiKey); + const BASE_URL = extractEnvVariable(baseURL); + + const uniqueKey = `${BASE_URL}__${API_KEY}`; + + modelsConfig[name] = []; + + if (models.fetch && !isUserProvided(API_KEY) && !isUserProvided(BASE_URL)) { + fetchPromisesMap[uniqueKey] = + fetchPromisesMap[uniqueKey] || + fetchModels({ + name, + apiKey: API_KEY, + baseURL: BASE_URL, + user: req.user.id, + userObject: req.user, + headers: endpointHeaders, + direct: endpoint.directEndpoint, + userIdQuery: models.userIdQuery, + }); + uniqueKeyToEndpointsMap[uniqueKey] = uniqueKeyToEndpointsMap[uniqueKey] || []; + uniqueKeyToEndpointsMap[uniqueKey].push(name); + continue; + } + + if (Array.isArray(models.default)) { + modelsConfig[name] = models.default.map((model) => + typeof model === 'string' ? model : model.name, + ); + } + } + + const fetchedData = await Promise.all(Object.values(fetchPromisesMap)); + const uniqueKeys = Object.keys(fetchPromisesMap); + + for (let i = 0; i < fetchedData.length; i++) { + const currentKey = uniqueKeys[i]; + const modelData = fetchedData[i]; + const associatedNames = uniqueKeyToEndpointsMap[currentKey]; + + for (const name of associatedNames) { + const endpoint = endpointsMap[name]; + modelsConfig[name] = !modelData?.length ? (endpoint.models.default ?? []) : modelData; + } + } + + return modelsConfig; +} module.exports = loadConfigModels; diff --git a/api/server/services/Config/loadConfigModels.spec.js b/api/server/services/Config/loadConfigModels.spec.js index d3ec0309ae..6ffb8ba522 100644 --- a/api/server/services/Config/loadConfigModels.spec.js +++ b/api/server/services/Config/loadConfigModels.spec.js @@ -7,13 +7,6 @@ jest.mock('@librechat/api', () => ({ fetchModels: jest.fn(), })); jest.mock('./app'); -jest.mock('@librechat/data-schemas', () => ({ - ...jest.requireActual('@librechat/data-schemas'), - logger: { debug: jest.fn(), error: jest.fn(), warn: jest.fn() }, -})); -jest.mock('~/models', () => ({ - getUserKeyValues: jest.fn(), -})); const exampleConfig = { endpoints: { @@ -75,11 +68,11 @@ describe('loadConfigModels', () => { const originalEnv = process.env; beforeEach(() => { - jest.clearAllMocks(); - fetchModels.mockReset(); - require('~/models').getUserKeyValues.mockReset(); + jest.resetAllMocks(); + jest.resetModules(); process.env = { ...originalEnv }; + // Default mock for getAppConfig getAppConfig.mockResolvedValue({}); }); @@ -344,168 +337,6 @@ describe('loadConfigModels', () => { expect(result.FalsyFetchModel).toEqual(['defaultModel1', 'defaultModel2']); }); - describe('user-provided API key model fetching', () => { - it('fetches models using user-provided API key when key is stored', async () => { - const { getUserKeyValues } = require('~/models'); - getUserKeyValues.mockResolvedValueOnce({ - apiKey: 'sk-user-key', - baseURL: 'https://api.x.com/v1', - }); - getAppConfig.mockResolvedValue({ - endpoints: { - custom: [ - { - name: 'UserEndpoint', - apiKey: 'user_provided', - baseURL: 'user_provided', - models: { fetch: true, default: ['fallback-model'] }, - }, - ], - }, - }); - fetchModels.mockResolvedValue(['fetched-model-a', 'fetched-model-b']); - - const result = await loadConfigModels(mockRequest); - - expect(getUserKeyValues).toHaveBeenCalledWith({ userId: 'testUserId', name: 'UserEndpoint' }); - expect(fetchModels).toHaveBeenCalledWith( - expect.objectContaining({ - apiKey: 'sk-user-key', - baseURL: 'https://api.x.com/v1', - skipCache: true, - }), - ); - expect(result.UserEndpoint).toEqual(['fetched-model-a', 'fetched-model-b']); - }); - - it('falls back to defaults when getUserKeyValues returns no apiKey', async () => { - const { getUserKeyValues } = require('~/models'); - getUserKeyValues.mockResolvedValueOnce({ baseURL: 'https://api.x.com/v1' }); - getAppConfig.mockResolvedValue({ - endpoints: { - custom: [ - { - name: 'NoKeyEndpoint', - apiKey: 'user_provided', - baseURL: 'https://api.x.com/v1', - models: { fetch: true, default: ['default-model'] }, - }, - ], - }, - }); - - const result = await loadConfigModels(mockRequest); - - expect(fetchModels).not.toHaveBeenCalled(); - expect(result.NoKeyEndpoint).toEqual(['default-model']); - }); - - it('falls back to defaults and logs warn when getUserKeyValues throws infra error', async () => { - const { getUserKeyValues } = require('~/models'); - const { logger } = require('@librechat/data-schemas'); - getUserKeyValues.mockRejectedValueOnce(new Error('DB connection timeout')); - getAppConfig.mockResolvedValue({ - endpoints: { - custom: [ - { - name: 'ErrorEndpoint', - apiKey: 'user_provided', - baseURL: 'https://api.example.com/v1', - models: { fetch: true, default: ['fallback'] }, - }, - ], - }, - }); - - const result = await loadConfigModels(mockRequest); - - expect(fetchModels).not.toHaveBeenCalled(); - expect(result.ErrorEndpoint).toEqual(['fallback']); - expect(logger.warn).toHaveBeenCalledWith( - expect.stringContaining( - 'Failed to retrieve user key for "ErrorEndpoint": DB connection timeout', - ), - ); - expect(logger.debug).not.toHaveBeenCalledWith(expect.stringContaining('No user key stored')); - }); - - it('logs debug (not warn) for NO_USER_KEY errors', async () => { - const { getUserKeyValues } = require('~/models'); - const { logger } = require('@librechat/data-schemas'); - getUserKeyValues.mockRejectedValueOnce(new Error(JSON.stringify({ type: 'no_user_key' }))); - getAppConfig.mockResolvedValue({ - endpoints: { - custom: [ - { - name: 'MissingKeyEndpoint', - apiKey: 'user_provided', - baseURL: 'https://api.example.com/v1', - models: { fetch: true, default: ['default-model'] }, - }, - ], - }, - }); - - const result = await loadConfigModels(mockRequest); - - expect(result.MissingKeyEndpoint).toEqual(['default-model']); - expect(logger.debug).toHaveBeenCalledWith(expect.stringContaining('No user key stored')); - expect(logger.warn).not.toHaveBeenCalledWith( - expect.stringContaining('Failed to retrieve user key'), - ); - }); - - it('skips user key lookup when req.user.id is undefined', async () => { - const { getUserKeyValues } = require('~/models'); - getAppConfig.mockResolvedValue({ - endpoints: { - custom: [ - { - name: 'NoUserEndpoint', - apiKey: 'user_provided', - baseURL: 'https://api.x.com/v1', - models: { fetch: true, default: ['anon-model'] }, - }, - ], - }, - }); - - const result = await loadConfigModels({ user: {} }); - - expect(getUserKeyValues).not.toHaveBeenCalled(); - expect(result.NoUserEndpoint).toEqual(['anon-model']); - }); - - it('uses stored baseURL only when baseURL is user_provided', async () => { - const { getUserKeyValues } = require('~/models'); - getUserKeyValues.mockResolvedValueOnce({ apiKey: 'sk-key' }); - getAppConfig.mockResolvedValue({ - endpoints: { - custom: [ - { - name: 'KeyOnly', - apiKey: 'user_provided', - baseURL: 'https://fixed-base.com/v1', - models: { fetch: true, default: ['default'] }, - }, - ], - }, - }); - fetchModels.mockResolvedValue(['model-from-fixed-base']); - - const result = await loadConfigModels(mockRequest); - - expect(fetchModels).toHaveBeenCalledWith( - expect.objectContaining({ - apiKey: 'sk-key', - baseURL: 'https://fixed-base.com/v1', - skipCache: true, - }), - ); - expect(result.KeyOnly).toEqual(['model-from-fixed-base']); - }); - }); - it('normalizes Ollama endpoint name to lowercase', async () => { const testCases = [ { diff --git a/api/server/services/Config/loadDefaultModels.js b/api/server/services/Config/loadDefaultModels.js index 85f2c42a33..31aa831a70 100644 --- a/api/server/services/Config/loadDefaultModels.js +++ b/api/server/services/Config/loadDefaultModels.js @@ -16,8 +16,7 @@ const { getAppConfig } = require('./app'); */ async function loadDefaultModels(req) { try { - const appConfig = - req.config ?? (await getAppConfig({ role: req.user?.role, tenantId: req.user?.tenantId })); + const appConfig = req.config ?? (await getAppConfig({ role: req.user?.role })); const vertexConfig = appConfig?.endpoints?.[EModelEndpoint.anthropic]?.vertexConfig; const [openAI, anthropic, azureOpenAI, assistants, azureAssistants, google, bedrock] = diff --git a/api/server/services/Config/mcp.js b/api/server/services/Config/mcp.js index fa37e223f5..cc4e98b59e 100644 --- a/api/server/services/Config/mcp.js +++ b/api/server/services/Config/mcp.js @@ -1,10 +1,97 @@ -const { createMCPToolCacheService } = require('@librechat/api'); +const { logger } = require('@librechat/data-schemas'); +const { CacheKeys, Constants } = require('librechat-data-provider'); const { getCachedTools, setCachedTools } = require('./getCachedTools'); +const { getLogStores } = require('~/cache'); -const { mergeAppTools, cacheMCPServerTools, updateMCPServerTools } = createMCPToolCacheService({ - getCachedTools, - setCachedTools, -}); +/** + * Updates MCP tools in the cache for a specific server + * @param {Object} params - Parameters for updating MCP tools + * @param {string} params.userId - User ID for user-specific caching + * @param {string} params.serverName - MCP server name + * @param {Array} params.tools - Array of tool objects from MCP server + * @returns {Promise} + */ +async function updateMCPServerTools({ userId, serverName, tools }) { + try { + const serverTools = {}; + const mcpDelimiter = Constants.mcp_delimiter; + + if (tools == null || tools.length === 0) { + logger.debug(`[MCP Cache] No tools to update for server ${serverName} (user: ${userId})`); + return serverTools; + } + + for (const tool of tools) { + const name = `${tool.name}${mcpDelimiter}${serverName}`; + serverTools[name] = { + type: 'function', + ['function']: { + name, + description: tool.description, + parameters: tool.inputSchema, + }, + }; + } + + await setCachedTools(serverTools, { userId, serverName }); + + const cache = getLogStores(CacheKeys.TOOL_CACHE); + await cache.delete(CacheKeys.TOOLS); + logger.debug( + `[MCP Cache] Updated ${tools.length} tools for server ${serverName} (user: ${userId})`, + ); + return serverTools; + } catch (error) { + logger.error(`[MCP Cache] Failed to update tools for ${serverName} (user: ${userId}):`, error); + throw error; + } +} + +/** + * Merges app-level tools with global tools + * @param {import('@librechat/api').LCAvailableTools} appTools + * @returns {Promise} + */ +async function mergeAppTools(appTools) { + try { + const count = Object.keys(appTools).length; + if (!count) { + return; + } + const cachedTools = await getCachedTools(); + const mergedTools = { ...cachedTools, ...appTools }; + await setCachedTools(mergedTools); + const cache = getLogStores(CacheKeys.TOOL_CACHE); + await cache.delete(CacheKeys.TOOLS); + logger.debug(`Merged ${count} app-level tools`); + } catch (error) { + logger.error('Failed to merge app-level tools:', error); + throw error; + } +} + +/** + * Caches MCP server tools (no longer merges with global) + * @param {object} params + * @param {string} params.userId - User ID for user-specific caching + * @param {string} params.serverName + * @param {import('@librechat/api').LCAvailableTools} params.serverTools + * @returns {Promise} + */ +async function cacheMCPServerTools({ userId, serverName, serverTools }) { + try { + const count = Object.keys(serverTools).length; + if (!count) { + return; + } + // Only cache server-specific tools, no merging with global + await setCachedTools(serverTools, { userId, serverName }); + logger.debug(`Cached ${count} MCP server tools for ${serverName} (user: ${userId})`); + } catch (error) { + logger.error(`Failed to cache MCP server tools for ${serverName} (user: ${userId}):`, error); + throw error; + } +} module.exports = { mergeAppTools, diff --git a/api/server/services/Endpoints/agents/addedConvo.js b/api/server/services/Endpoints/agents/addedConvo.js index 7561053f8f..7e9385267a 100644 --- a/api/server/services/Endpoints/agents/addedConvo.js +++ b/api/server/services/Endpoints/agents/addedConvo.js @@ -1,16 +1,12 @@ const { logger } = require('@librechat/data-schemas'); -const { - ADDED_AGENT_ID, - initializeAgent, - validateAgentModel, - loadAddedAgent: loadAddedAgentFn, -} = require('@librechat/api'); -const { filterFilesByAgentAccess } = require('~/server/services/Files/permissions'); -const { getMCPServerTools } = require('~/server/services/Config'); +const { initializeAgent, validateAgentModel } = require('@librechat/api'); +const { loadAddedAgent, setGetAgent, ADDED_AGENT_ID } = require('~/models/loadAddedAgent'); +const { getConvoFiles } = require('~/models/Conversation'); +const { getAgent } = require('~/models/Agent'); const db = require('~/models'); -const loadAddedAgent = (params) => - loadAddedAgentFn(params, { getAgent: db.getAgent, getMCPServerTools }); +// Initialize the getAgent dependency +setGetAgent(getAgent); /** * Process addedConvo for parallel agent execution. @@ -59,16 +55,16 @@ const processAddedConvo = async ({ userMCPAuthMap, }) => { const addedConvo = endpointOption.addedConvo; + logger.debug('[processAddedConvo] Called with addedConvo:', { + hasAddedConvo: addedConvo != null, + addedConvoEndpoint: addedConvo?.endpoint, + addedConvoModel: addedConvo?.model, + addedConvoAgentId: addedConvo?.agent_id, + }); if (addedConvo == null) { return { userMCPAuthMap }; } - logger.debug('[processAddedConvo] Processing added conversation', { - model: addedConvo.model, - agentId: addedConvo.agent_id, - endpoint: addedConvo.endpoint, - }); - try { const addedAgent = await loadAddedAgent({ req, conversation: addedConvo, primaryAgent }); if (!addedAgent) { @@ -103,16 +99,15 @@ const processAddedConvo = async ({ allowedProviders, }, { + getConvoFiles, getFiles: db.getFiles, getUserKey: db.getUserKey, getMessages: db.getMessages, - getConvoFiles: db.getConvoFiles, updateFilesUsage: db.updateFilesUsage, getUserCodeFiles: db.getUserCodeFiles, getUserKeyValues: db.getUserKeyValues, getToolFilesByIds: db.getToolFilesByIds, getCodeGeneratedFiles: db.getCodeGeneratedFiles, - filterFilesByAgentAccess, }, ); diff --git a/api/server/services/Endpoints/agents/build.js b/api/server/services/Endpoints/agents/build.js index 19ae3ab7e8..a95640e528 100644 --- a/api/server/services/Endpoints/agents/build.js +++ b/api/server/services/Endpoints/agents/build.js @@ -1,10 +1,6 @@ const { logger } = require('@librechat/data-schemas'); -const { loadAgent: loadAgentFn } = require('@librechat/api'); const { isAgentsEndpoint, removeNullishValues, Constants } = require('librechat-data-provider'); -const { getMCPServerTools } = require('~/server/services/Config'); -const db = require('~/models'); - -const loadAgent = (params) => loadAgentFn(params, { getAgent: db.getAgent, getMCPServerTools }); +const { loadAgent } = require('~/models/Agent'); const buildOptions = (req, endpoint, parsedBody, endpointType) => { const { spec, iconURL, agent_id, ...model_parameters } = parsedBody; diff --git a/api/server/services/Endpoints/agents/initialize.js b/api/server/services/Endpoints/agents/initialize.js index 69767e191c..0888f23cd5 100644 --- a/api/server/services/Endpoints/agents/initialize.js +++ b/api/server/services/Endpoints/agents/initialize.js @@ -10,8 +10,6 @@ const { createSequentialChainEdges, } = require('@librechat/api'); const { - ResourceType, - PermissionBits, EModelEndpoint, isAgentsEndpoint, getResponseSender, @@ -22,11 +20,11 @@ const { getDefaultHandlers, } = require('~/server/controllers/agents/callbacks'); const { loadAgentTools, loadToolsForExecution } = require('~/server/services/ToolService'); -const { filterFilesByAgentAccess } = require('~/server/services/Files/permissions'); const { getModelsConfig } = require('~/server/controllers/ModelController'); -const { checkPermission } = require('~/server/services/PermissionService'); const AgentClient = require('~/server/controllers/agents/client'); +const { getConvoFiles } = require('~/models/Conversation'); const { processAddedConvo } = require('./addedConvo'); +const { getAgent } = require('~/models/Agent'); const { logViolation } = require('~/cache'); const db = require('~/models'); @@ -82,14 +80,6 @@ function createToolLoader(signal, streamId = null, definitionsOnly = false) { }; } -/** - * Initializes the AgentClient for a given request/response cycle. - * @param {Object} params - * @param {Express.Request} params.req - * @param {Express.Response} params.res - * @param {AbortSignal} params.signal - * @param {Object} params.endpointOption - */ const initializeClient = async ({ req, res, signal, endpointOption }) => { if (!endpointOption) { throw new Error('Endpoint option not provided'); @@ -135,7 +125,6 @@ const initializeClient = async ({ req, res, signal, endpointOption }) => { toolRegistry: ctx.toolRegistry, userMCPAuthMap: ctx.userMCPAuthMap, tool_resources: ctx.tool_resources, - actionsEnabled: ctx.actionsEnabled, }); logger.debug(`[ON_TOOL_EXECUTE] loaded ${result.loadedTools?.length ?? 0} tools`); @@ -144,13 +133,9 @@ const initializeClient = async ({ req, res, signal, endpointOption }) => { toolEndCallback, }; - const summarizationOptions = - appConfig?.summarization?.enabled === false ? { enabled: false } : { enabled: true }; - const eventHandlers = getDefaultHandlers({ res, toolExecuteOptions, - summarizationOptions, aggregateContent, toolEndCallback, collectedUsage, @@ -206,28 +191,32 @@ const initializeClient = async ({ req, res, signal, endpointOption }) => { isInitialAgent: true, }, { + getConvoFiles, getFiles: db.getFiles, getUserKey: db.getUserKey, getMessages: db.getMessages, - getConvoFiles: db.getConvoFiles, updateFilesUsage: db.updateFilesUsage, getUserKeyValues: db.getUserKeyValues, getUserCodeFiles: db.getUserCodeFiles, getToolFilesByIds: db.getToolFilesByIds, getCodeGeneratedFiles: db.getCodeGeneratedFiles, - filterFilesByAgentAccess, }, ); logger.debug( - `[initializeClient] Storing tool context for ${primaryConfig.id}: ${primaryConfig.toolDefinitions?.length ?? 0} tools, registry size: ${primaryConfig.toolRegistry?.size ?? '0'}`, + `[initializeClient] Tool definitions for primary agent: ${primaryConfig.toolDefinitions?.length ?? 0}`, + ); + + /** Store primary agent's tool context for ON_TOOL_EXECUTE callback */ + logger.debug(`[initializeClient] Storing tool context for agentId: ${primaryConfig.id}`); + logger.debug( + `[initializeClient] toolRegistry size: ${primaryConfig.toolRegistry?.size ?? 'undefined'}`, ); agentToolContexts.set(primaryConfig.id, { agent: primaryAgent, toolRegistry: primaryConfig.toolRegistry, userMCPAuthMap: primaryConfig.userMCPAuthMap, tool_resources: primaryConfig.tool_resources, - actionsEnabled: primaryConfig.actionsEnabled, }); const agent_ids = primaryConfig.agent_ids; @@ -237,7 +226,7 @@ const initializeClient = async ({ req, res, signal, endpointOption }) => { const skippedAgentIds = new Set(); async function processAgent(agentId) { - const agent = await db.getAgent({ id: agentId }); + const agent = await getAgent({ id: agentId }); if (!agent) { logger.warn( `[processAgent] Handoff agent ${agentId} not found, skipping (orphaned reference)`, @@ -246,22 +235,6 @@ const initializeClient = async ({ req, res, signal, endpointOption }) => { return null; } - const hasAccess = await checkPermission({ - userId: req.user.id, - role: req.user.role, - resourceType: ResourceType.AGENT, - resourceId: agent._id, - requiredPermission: PermissionBits.VIEW, - }); - - if (!hasAccess) { - logger.warn( - `[processAgent] User ${req.user.id} lacks VIEW access to handoff agent ${agentId}, skipping`, - ); - skippedAgentIds.add(agentId); - return null; - } - const validationResult = await validateAgentModel({ req, res, @@ -287,16 +260,15 @@ const initializeClient = async ({ req, res, signal, endpointOption }) => { allowedProviders, }, { + getConvoFiles, getFiles: db.getFiles, getUserKey: db.getUserKey, getMessages: db.getMessages, - getConvoFiles: db.getConvoFiles, updateFilesUsage: db.updateFilesUsage, getUserKeyValues: db.getUserKeyValues, getUserCodeFiles: db.getUserCodeFiles, getToolFilesByIds: db.getToolFilesByIds, getCodeGeneratedFiles: db.getCodeGeneratedFiles, - filterFilesByAgentAccess, }, ); @@ -312,7 +284,6 @@ const initializeClient = async ({ req, res, signal, endpointOption }) => { toolRegistry: config.toolRegistry, userMCPAuthMap: config.userMCPAuthMap, tool_resources: config.tool_resources, - actionsEnabled: config.actionsEnabled, }); agentConfigs.set(agentId, config); @@ -341,7 +312,6 @@ const initializeClient = async ({ req, res, signal, endpointOption }) => { } } catch (err) { logger.error(`[initializeClient] Error processing agent ${agentId}:`, err); - skippedAgentIds.add(agentId); } } @@ -351,12 +321,7 @@ const initializeClient = async ({ req, res, signal, endpointOption }) => { if (checkAgentInit(agentId)) { continue; } - try { - await processAgent(agentId); - } catch (err) { - logger.error(`[initializeClient] Error processing chain agent ${agentId}:`, err); - skippedAgentIds.add(agentId); - } + await processAgent(agentId); } const chain = await createSequentialChainEdges([primaryConfig.id].concat(agent_ids), '{convo}'); collectEdges(chain); @@ -386,19 +351,6 @@ const initializeClient = async ({ req, res, signal, endpointOption }) => { userMCPAuthMap = updatedMCPAuthMap; } - for (const [agentId, config] of agentConfigs) { - if (agentToolContexts.has(agentId)) { - continue; - } - agentToolContexts.set(agentId, { - agent: config, - toolRegistry: config.toolRegistry, - userMCPAuthMap: config.userMCPAuthMap, - tool_resources: config.tool_resources, - actionsEnabled: config.actionsEnabled, - }); - } - // Ensure edges is an array when we have multiple agents (multi-agent mode) // MultiAgentGraph.categorizeEdges requires edges to be iterable if (agentConfigs.size > 0 && !edges) { diff --git a/api/server/services/Endpoints/agents/initialize.spec.js b/api/server/services/Endpoints/agents/initialize.spec.js deleted file mode 100644 index 8027744965..0000000000 --- a/api/server/services/Endpoints/agents/initialize.spec.js +++ /dev/null @@ -1,201 +0,0 @@ -const mongoose = require('mongoose'); -const { - ResourceType, - PermissionBits, - PrincipalType, - PrincipalModel, -} = require('librechat-data-provider'); -const { MongoMemoryServer } = require('mongodb-memory-server'); - -const mockInitializeAgent = jest.fn(); -const mockValidateAgentModel = jest.fn(); - -jest.mock('@librechat/agents', () => ({ - ...jest.requireActual('@librechat/agents'), - createContentAggregator: jest.fn(() => ({ - contentParts: [], - aggregateContent: jest.fn(), - })), -})); - -jest.mock('@librechat/api', () => ({ - ...jest.requireActual('@librechat/api'), - initializeAgent: (...args) => mockInitializeAgent(...args), - validateAgentModel: (...args) => mockValidateAgentModel(...args), - GenerationJobManager: { setCollectedUsage: jest.fn() }, - getCustomEndpointConfig: jest.fn(), - createSequentialChainEdges: jest.fn(), -})); - -jest.mock('~/server/controllers/agents/callbacks', () => ({ - createToolEndCallback: jest.fn(() => jest.fn()), - getDefaultHandlers: jest.fn(() => ({})), -})); - -jest.mock('~/server/services/ToolService', () => ({ - loadAgentTools: jest.fn(), - loadToolsForExecution: jest.fn(), -})); - -jest.mock('~/server/controllers/ModelController', () => ({ - getModelsConfig: jest.fn().mockResolvedValue({}), -})); - -let agentClientArgs; -jest.mock('~/server/controllers/agents/client', () => { - return jest.fn().mockImplementation((args) => { - agentClientArgs = args; - return {}; - }); -}); - -jest.mock('./addedConvo', () => ({ - processAddedConvo: jest.fn().mockResolvedValue({ userMCPAuthMap: undefined }), -})); - -jest.mock('~/cache', () => ({ - logViolation: jest.fn(), -})); - -const { initializeClient } = require('./initialize'); -const { User, AclEntry } = require('~/db/models'); -const { createAgent } = require('~/models'); - -const PRIMARY_ID = 'agent_primary'; -const TARGET_ID = 'agent_target'; -const AUTHORIZED_ID = 'agent_authorized'; - -describe('initializeClient — processAgent ACL gate', () => { - let mongoServer; - let testUser; - - beforeAll(async () => { - mongoServer = await MongoMemoryServer.create(); - await mongoose.connect(mongoServer.getUri()); - }); - - afterAll(async () => { - await mongoose.disconnect(); - await mongoServer.stop(); - }); - - beforeEach(async () => { - await mongoose.connection.dropDatabase(); - jest.clearAllMocks(); - agentClientArgs = undefined; - - testUser = await User.create({ - email: 'test@example.com', - name: 'Test User', - username: 'testuser', - role: 'USER', - }); - - mockValidateAgentModel.mockResolvedValue({ isValid: true }); - }); - - const makeReq = () => ({ - user: { id: testUser._id.toString(), role: 'USER' }, - body: { conversationId: 'conv_1', files: [] }, - config: { endpoints: {} }, - _resumableStreamId: null, - }); - - const makeEndpointOption = () => ({ - agent: Promise.resolve({ - id: PRIMARY_ID, - name: 'Primary', - provider: 'openai', - model: 'gpt-4', - tools: [], - }), - model_parameters: { model: 'gpt-4' }, - endpoint: 'agents', - }); - - const makePrimaryConfig = (edges) => ({ - id: PRIMARY_ID, - endpoint: 'agents', - edges, - toolDefinitions: [], - toolRegistry: new Map(), - userMCPAuthMap: null, - tool_resources: {}, - resendFiles: true, - maxContextTokens: 4096, - }); - - it('should skip handoff agent and filter its edge when user lacks VIEW access', async () => { - await createAgent({ - id: TARGET_ID, - name: 'Target Agent', - provider: 'openai', - model: 'gpt-4', - author: new mongoose.Types.ObjectId(), - tools: [], - }); - - const edges = [{ from: PRIMARY_ID, to: TARGET_ID, edgeType: 'handoff' }]; - mockInitializeAgent.mockResolvedValue(makePrimaryConfig(edges)); - - await initializeClient({ - req: makeReq(), - res: {}, - signal: new AbortController().signal, - endpointOption: makeEndpointOption(), - }); - - expect(mockInitializeAgent).toHaveBeenCalledTimes(1); - expect(agentClientArgs.agent.edges).toEqual([]); - }); - - it('should initialize handoff agent and keep its edge when user has VIEW access', async () => { - const authorizedAgent = await createAgent({ - id: AUTHORIZED_ID, - name: 'Authorized Agent', - provider: 'openai', - model: 'gpt-4', - author: new mongoose.Types.ObjectId(), - tools: [], - }); - - await AclEntry.create({ - principalType: PrincipalType.USER, - principalId: testUser._id, - principalModel: PrincipalModel.USER, - resourceType: ResourceType.AGENT, - resourceId: authorizedAgent._id, - permBits: PermissionBits.VIEW, - grantedBy: testUser._id, - }); - - const edges = [{ from: PRIMARY_ID, to: AUTHORIZED_ID, edgeType: 'handoff' }]; - const handoffConfig = { - id: AUTHORIZED_ID, - edges: [], - toolDefinitions: [], - toolRegistry: new Map(), - userMCPAuthMap: null, - tool_resources: {}, - }; - - let callCount = 0; - mockInitializeAgent.mockImplementation(() => { - callCount++; - return callCount === 1 - ? Promise.resolve(makePrimaryConfig(edges)) - : Promise.resolve(handoffConfig); - }); - - await initializeClient({ - req: makeReq(), - res: {}, - signal: new AbortController().signal, - endpointOption: makeEndpointOption(), - }); - - expect(mockInitializeAgent).toHaveBeenCalledTimes(2); - expect(agentClientArgs.agent.edges).toHaveLength(1); - expect(agentClientArgs.agent.edges[0].to).toBe(AUTHORIZED_ID); - }); -}); diff --git a/api/server/services/Endpoints/agents/title.js b/api/server/services/Endpoints/agents/title.js index b7e1a54e06..e31cdeea11 100644 --- a/api/server/services/Endpoints/agents/title.js +++ b/api/server/services/Endpoints/agents/title.js @@ -66,11 +66,7 @@ const addTitle = async (req, { text, response, client }) => { await titleCache.set(key, title, 120000); await saveConvo( - { - userId: req?.user?.id, - isTemporary: req?.body?.isTemporary, - interfaceConfig: req?.config?.interfaceConfig, - }, + req, { conversationId: response.conversationId, title, diff --git a/api/server/services/Endpoints/assistants/build.js b/api/server/services/Endpoints/assistants/build.js index 85f7090211..00a2abf606 100644 --- a/api/server/services/Endpoints/assistants/build.js +++ b/api/server/services/Endpoints/assistants/build.js @@ -1,6 +1,6 @@ const { removeNullishValues } = require('librechat-data-provider'); const generateArtifactsPrompt = require('~/app/clients/prompts/artifacts'); -const { getAssistant } = require('~/models'); +const { getAssistant } = require('~/models/Assistant'); const buildOptions = async (endpoint, parsedBody) => { const { promptPrefix, assistant_id, iconURL, greeting, spec, artifacts, ...modelOptions } = diff --git a/api/server/services/Endpoints/assistants/title.js b/api/server/services/Endpoints/assistants/title.js index b31289eb60..1fae68cf54 100644 --- a/api/server/services/Endpoints/assistants/title.js +++ b/api/server/services/Endpoints/assistants/title.js @@ -1,9 +1,9 @@ const { isEnabled, sanitizeTitle } = require('@librechat/api'); const { logger } = require('@librechat/data-schemas'); const { CacheKeys } = require('librechat-data-provider'); +const { saveConvo } = require('~/models/Conversation'); const getLogStores = require('~/cache/getLogStores'); const initializeClient = require('./initalize'); -const { saveConvo } = require('~/models'); /** * Generates a conversation title using OpenAI SDK @@ -63,13 +63,8 @@ const addTitle = async (req, { text, responseText, conversationId }) => { const title = await generateTitle({ openai, text, responseText }); await titleCache.set(key, title, 120000); - const reqCtx = { - userId: req?.user?.id, - isTemporary: req?.body?.isTemporary, - interfaceConfig: req?.config?.interfaceConfig, - }; await saveConvo( - reqCtx, + req, { conversationId, title, @@ -81,11 +76,7 @@ const addTitle = async (req, { text, responseText, conversationId }) => { const fallbackTitle = text.length > 40 ? text.substring(0, 37) + '...' : text; await titleCache.set(key, fallbackTitle, 120000); await saveConvo( - { - userId: req?.user?.id, - isTemporary: req?.body?.isTemporary, - interfaceConfig: req?.config?.interfaceConfig, - }, + req, { conversationId, title: fallbackTitle, diff --git a/api/server/services/Endpoints/azureAssistants/build.js b/api/server/services/Endpoints/azureAssistants/build.js index 315447ed7f..53b1dbeb68 100644 --- a/api/server/services/Endpoints/azureAssistants/build.js +++ b/api/server/services/Endpoints/azureAssistants/build.js @@ -1,6 +1,6 @@ const { removeNullishValues } = require('librechat-data-provider'); const generateArtifactsPrompt = require('~/app/clients/prompts/artifacts'); -const { getAssistant } = require('~/models'); +const { getAssistant } = require('~/models/Assistant'); const buildOptions = async (endpoint, parsedBody) => { const { promptPrefix, assistant_id, iconURL, greeting, spec, artifacts, ...modelOptions } = diff --git a/api/server/services/Endpoints/index.js b/api/server/services/Endpoints/index.js new file mode 100644 index 0000000000..3cabfe1c58 --- /dev/null +++ b/api/server/services/Endpoints/index.js @@ -0,0 +1,77 @@ +const { Providers } = require('@librechat/agents'); +const { EModelEndpoint } = require('librechat-data-provider'); +const { getCustomEndpointConfig } = require('@librechat/api'); +const initAnthropic = require('~/server/services/Endpoints/anthropic/initialize'); +const getBedrockOptions = require('~/server/services/Endpoints/bedrock/options'); +const initOpenAI = require('~/server/services/Endpoints/openAI/initialize'); +const initCustom = require('~/server/services/Endpoints/custom/initialize'); +const initGoogle = require('~/server/services/Endpoints/google/initialize'); + +/** Check if the provider is a known custom provider + * @param {string | undefined} [provider] - The provider string + * @returns {boolean} - True if the provider is a known custom provider, false otherwise + */ +function isKnownCustomProvider(provider) { + return [Providers.XAI, Providers.DEEPSEEK, Providers.OPENROUTER, Providers.MOONSHOT].includes( + provider?.toLowerCase() || '', + ); +} + +const providerConfigMap = { + [Providers.XAI]: initCustom, + [Providers.DEEPSEEK]: initCustom, + [Providers.MOONSHOT]: initCustom, + [Providers.OPENROUTER]: initCustom, + [EModelEndpoint.openAI]: initOpenAI, + [EModelEndpoint.google]: initGoogle, + [EModelEndpoint.azureOpenAI]: initOpenAI, + [EModelEndpoint.anthropic]: initAnthropic, + [EModelEndpoint.bedrock]: getBedrockOptions, +}; + +/** + * Get the provider configuration and override endpoint based on the provider string + * @param {Object} params + * @param {string} params.provider - The provider string + * @param {AppConfig} params.appConfig - The application configuration + * @returns {{ + * getOptions: (typeof providerConfigMap)[keyof typeof providerConfigMap], + * overrideProvider: string, + * customEndpointConfig?: TEndpoint + * }} + */ +function getProviderConfig({ provider, appConfig }) { + let getOptions = providerConfigMap[provider]; + let overrideProvider = provider; + /** @type {TEndpoint | undefined} */ + let customEndpointConfig; + + if (!getOptions && providerConfigMap[provider.toLowerCase()] != null) { + overrideProvider = provider.toLowerCase(); + getOptions = providerConfigMap[overrideProvider]; + } else if (!getOptions) { + customEndpointConfig = getCustomEndpointConfig({ endpoint: provider, appConfig }); + if (!customEndpointConfig) { + throw new Error(`Provider ${provider} not supported`); + } + getOptions = initCustom; + overrideProvider = Providers.OPENAI; + } + + if (isKnownCustomProvider(overrideProvider) && !customEndpointConfig) { + customEndpointConfig = getCustomEndpointConfig({ endpoint: provider, appConfig }); + if (!customEndpointConfig) { + throw new Error(`Provider ${provider} not supported`); + } + } + + return { + getOptions, + overrideProvider, + customEndpointConfig, + }; +} + +module.exports = { + getProviderConfig, +}; diff --git a/api/server/services/Files/Audio/STTService.js b/api/server/services/Files/Audio/STTService.js index c9a35c35ea..4ba62a7eeb 100644 --- a/api/server/services/Files/Audio/STTService.js +++ b/api/server/services/Files/Audio/STTService.js @@ -142,7 +142,6 @@ class STTService { req.config ?? (await getAppConfig({ role: req?.user?.role, - tenantId: req?.user?.tenantId, })); const sttSchema = appConfig?.speech?.stt; if (!sttSchema) { diff --git a/api/server/services/Files/Audio/TTSService.js b/api/server/services/Files/Audio/TTSService.js index 1125dd74ed..2c932968c6 100644 --- a/api/server/services/Files/Audio/TTSService.js +++ b/api/server/services/Files/Audio/TTSService.js @@ -297,7 +297,6 @@ class TTSService { req.config ?? (await getAppConfig({ role: req.user?.role, - tenantId: req.user?.tenantId, })); try { res.setHeader('Content-Type', 'audio/mpeg'); @@ -366,7 +365,6 @@ class TTSService { req.config ?? (await getAppConfig({ role: req.user?.role, - tenantId: req.user?.tenantId, })); const provider = this.getProvider(appConfig); const ttsSchema = appConfig?.speech?.tts?.[provider]; diff --git a/api/server/services/Files/Audio/getCustomConfigSpeech.js b/api/server/services/Files/Audio/getCustomConfigSpeech.js index b438771ec1..d0d0b51ac2 100644 --- a/api/server/services/Files/Audio/getCustomConfigSpeech.js +++ b/api/server/services/Files/Audio/getCustomConfigSpeech.js @@ -17,7 +17,6 @@ async function getCustomConfigSpeech(req, res) { try { const appConfig = await getAppConfig({ role: req.user?.role, - tenantId: req.user?.tenantId, }); if (!appConfig) { diff --git a/api/server/services/Files/Audio/getVoices.js b/api/server/services/Files/Audio/getVoices.js index 22bd7cea6e..f2f8e100c3 100644 --- a/api/server/services/Files/Audio/getVoices.js +++ b/api/server/services/Files/Audio/getVoices.js @@ -18,7 +18,6 @@ async function getVoices(req, res) { req.config ?? (await getAppConfig({ role: req.user?.role, - tenantId: req.user?.tenantId, })); const ttsSchema = appConfig?.speech?.tts; diff --git a/api/server/services/Files/Audio/streamAudio.js b/api/server/services/Files/Audio/streamAudio.js index 7120399b5e..a1d7c7a649 100644 --- a/api/server/services/Files/Audio/streamAudio.js +++ b/api/server/services/Files/Audio/streamAudio.js @@ -1,4 +1,3 @@ -const { scopedCacheKey } = require('@librechat/data-schemas'); const { Time, CacheKeys, @@ -6,8 +5,8 @@ const { parseTextParts, findLastSeparatorIndex, } = require('librechat-data-provider'); +const { getMessage } = require('~/models/Message'); const { getLogStores } = require('~/cache'); -const { getMessage } = require('~/models'); /** * @param {string[]} voiceIds - Array of voice IDs @@ -68,8 +67,6 @@ function createChunkProcessor(user, messageId) { } const messageCache = getLogStores(CacheKeys.MESSAGES); - // Captured at creation time — must be called within an active request ALS scope - const cacheKey = scopedCacheKey(messageId); /** * @returns {Promise<{ text: string, isFinished: boolean }[] | string>} @@ -84,7 +81,7 @@ function createChunkProcessor(user, messageId) { } /** @type { string | { text: string; complete: boolean } } */ - let message = await messageCache.get(cacheKey); + let message = await messageCache.get(messageId); if (!message) { message = await getMessage({ user, messageId }); } @@ -95,7 +92,7 @@ function createChunkProcessor(user, messageId) { } else { const text = message.content?.length > 0 ? parseTextParts(message.content) : message.text; messageCache.set( - cacheKey, + messageId, { text, complete: true, diff --git a/api/server/services/Files/Audio/streamAudio.spec.js b/api/server/services/Files/Audio/streamAudio.spec.js index 977d8730aa..e76c0849c7 100644 --- a/api/server/services/Files/Audio/streamAudio.spec.js +++ b/api/server/services/Files/Audio/streamAudio.spec.js @@ -3,7 +3,7 @@ const { createChunkProcessor, splitTextIntoChunks } = require('./streamAudio'); jest.mock('keyv'); const globalCache = {}; -jest.mock('~/models', () => { +jest.mock('~/models/Message', () => { return { getMessage: jest.fn().mockImplementation((messageId) => { return globalCache[messageId] || null; diff --git a/api/server/services/Files/Citations/index.js b/api/server/services/Files/Citations/index.js index a1d9322467..7cb2ee6de0 100644 --- a/api/server/services/Files/Citations/index.js +++ b/api/server/services/Files/Citations/index.js @@ -8,7 +8,8 @@ const { EModelEndpoint, PermissionTypes, } = require('librechat-data-provider'); -const { getRoleByName, getFiles } = require('~/models'); +const { getRoleByName } = require('~/models/Role'); +const { Files } = require('~/models'); /** * Process file search results from tool calls @@ -47,10 +48,7 @@ async function processFileCitations({ user, appConfig, toolArtifact, toolCallId, logger.error( `[processFileCitations] Permission check failed for FILE_CITATIONS: ${error.message}`, ); - logger.warn( - '[processFileCitations] Returning null citations due to permission check error — citations will not be shown for this message', - ); - return null; + logger.debug(`[processFileCitations] Proceeding with citations due to permission error`); } } @@ -129,7 +127,7 @@ async function enhanceSourcesWithMetadata(sources, appConfig) { let fileMetadataMap = {}; try { - const files = await getFiles({ file_id: { $in: fileIds } }); + const files = await Files.find({ file_id: { $in: fileIds } }); fileMetadataMap = files.reduce((map, file) => { map[file.file_id] = file; return map; @@ -148,8 +146,6 @@ async function enhanceSourcesWithMetadata(sources, appConfig) { metadata: { ...source.metadata, storageType: configuredStorageType, - fileType: fileRecord.type || undefined, - fileBytes: fileRecord.bytes || undefined, }, }; }); diff --git a/api/server/services/Files/Code/__tests__/process-traversal.spec.js b/api/server/services/Files/Code/__tests__/process-traversal.spec.js deleted file mode 100644 index 0b8548445d..0000000000 --- a/api/server/services/Files/Code/__tests__/process-traversal.spec.js +++ /dev/null @@ -1,130 +0,0 @@ -jest.mock('uuid', () => ({ v4: jest.fn(() => 'mock-uuid') })); - -jest.mock('@librechat/data-schemas', () => ({ - logger: { warn: jest.fn(), debug: jest.fn(), error: jest.fn() }, -})); - -jest.mock('@librechat/agents', () => ({ - getCodeBaseURL: jest.fn(() => 'http://localhost:8000'), -})); - -const mockSanitizeFilename = jest.fn(); - -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'), - mergeFileConfig: jest.fn(() => ({ serverFileSizeLimit: 100 * 1024 * 1024 })), - getEndpointFileConfig: jest.fn(() => ({ - fileSizeLimit: 100 * 1024 * 1024, - supportedMimeTypes: ['*/*'], - })), - fileConfig: { checkType: jest.fn(() => true) }, -})); - -jest.mock('~/models', () => ({ - createFile: jest.fn().mockResolvedValue({}), - getFiles: jest.fn().mockResolvedValue([]), - updateFile: jest.fn(), - claimCodeFile: jest.fn().mockResolvedValue({ file_id: 'mock-uuid', usage: 0 }), -})); - -const mockSaveBuffer = jest.fn().mockResolvedValue('/uploads/user123/mock-uuid__output.csv'); - -jest.mock('~/server/services/Files/strategies', () => ({ - getStrategyFunctions: jest.fn(() => ({ - saveBuffer: mockSaveBuffer, - })), -})); - -jest.mock('~/server/services/Files/permissions', () => ({ - filterFilesByAgentAccess: jest.fn().mockResolvedValue([]), -})); - -jest.mock('~/server/services/Files/images/convert', () => ({ - convertImage: jest.fn(), -})); - -jest.mock('~/server/utils', () => ({ - determineFileType: jest.fn().mockResolvedValue({ mime: 'text/csv' }), -})); - -const { createFile } = require('~/models'); -const { processCodeOutput } = require('../process'); - -const baseParams = { - req: { - user: { id: 'user123' }, - config: { - fileStrategy: 'local', - imageOutputType: 'webp', - fileConfig: {}, - }, - }, - id: 'code-file-id', - apiKey: 'test-key', - toolCallId: 'tool-1', - conversationId: 'conv-1', - messageId: 'msg-1', - session_id: 'session-1', -}; - -describe('processCodeOutput path traversal protection', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - test('sanitizeFilename is called with the raw artifact name', async () => { - mockSanitizeFilename.mockReturnValueOnce('output.csv'); - await processCodeOutput({ ...baseParams, name: 'output.csv' }); - expect(mockSanitizeFilename).toHaveBeenCalledWith('output.csv'); - }); - - test('sanitized name is used in saveBuffer fileName', async () => { - mockSanitizeFilename.mockReturnValueOnce('sanitized-name.txt'); - await processCodeOutput({ ...baseParams, name: '../../../tmp/poc.txt' }); - - expect(mockSanitizeFilename).toHaveBeenCalledWith('../../../tmp/poc.txt'); - const call = mockSaveBuffer.mock.calls[0][0]; - expect(call.fileName).toBe('mock-uuid__sanitized-name.txt'); - }); - - test('sanitized name is stored as filename in the file record', async () => { - mockSanitizeFilename.mockReturnValueOnce('safe-output.csv'); - await processCodeOutput({ ...baseParams, name: 'unsafe/../../output.csv' }); - - const fileArg = createFile.mock.calls[0][0]; - expect(fileArg.filename).toBe('safe-output.csv'); - }); - - test('sanitized name is used for image file records', async () => { - const { convertImage } = require('~/server/services/Files/images/convert'); - convertImage.mockResolvedValueOnce({ - filepath: '/images/user123/mock-uuid.webp', - bytes: 100, - }); - - mockSanitizeFilename.mockReturnValueOnce('safe-chart.png'); - await processCodeOutput({ ...baseParams, name: '../../../chart.png' }); - - expect(mockSanitizeFilename).toHaveBeenCalledWith('../../../chart.png'); - const fileArg = createFile.mock.calls[0][0]; - expect(fileArg.filename).toBe('safe-chart.png'); - }); -}); diff --git a/api/server/services/Files/Code/crud.js b/api/server/services/Files/Code/crud.js index 945aec787b..4781219fcf 100644 --- a/api/server/services/Files/Code/crud.js +++ b/api/server/services/Files/Code/crud.js @@ -1,11 +1,6 @@ const FormData = require('form-data'); const { getCodeBaseURL } = require('@librechat/agents'); -const { - logAxiosError, - createAxiosInstance, - codeServerHttpAgent, - codeServerHttpsAgent, -} = require('@librechat/api'); +const { createAxiosInstance, logAxiosError } = require('@librechat/api'); const axios = createAxiosInstance(); @@ -30,8 +25,6 @@ async function getCodeOutputDownloadStream(fileIdentifier, apiKey) { 'User-Agent': 'LibreChat/1.0', 'X-API-Key': apiKey, }, - httpAgent: codeServerHttpAgent, - httpsAgent: codeServerHttpsAgent, timeout: 15000, }; @@ -76,9 +69,6 @@ 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 deleted file mode 100644 index 261f0f052b..0000000000 --- a/api/server/services/Files/Code/crud.spec.js +++ /dev/null @@ -1,149 +0,0 @@ -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 7cdebeb202..3f0bfcfc87 100644 --- a/api/server/services/Files/Code/process.js +++ b/api/server/services/Files/Code/process.js @@ -1,15 +1,9 @@ const path = require('path'); const { v4 } = require('uuid'); +const axios = require('axios'); const { logger } = require('@librechat/data-schemas'); const { getCodeBaseURL } = require('@librechat/agents'); -const { - getBasePath, - logAxiosError, - sanitizeFilename, - createAxiosInstance, - codeServerHttpAgent, - codeServerHttpsAgent, -} = require('@librechat/api'); +const { logAxiosError, getBasePath } = require('@librechat/api'); const { Tools, megabyte, @@ -29,8 +23,6 @@ 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. @@ -110,8 +102,6 @@ const processCodeOutput = async ({ 'User-Agent': 'LibreChat/1.0', 'X-API-Key': apiKey, }, - httpAgent: codeServerHttpAgent, - httpsAgent: codeServerHttpsAgent, timeout: 15000, }); @@ -156,13 +146,6 @@ const processCodeOutput = async ({ ); } - const safeName = sanitizeFilename(name); - if (safeName !== name) { - logger.warn( - `[processCodeOutput] Filename sanitized: "${name}" -> "${safeName}" | conv=${conversationId}`, - ); - } - if (isImage) { const usage = isUpdate ? (claimed.usage ?? 0) + 1 : 1; const _file = await convertImage(req, buffer, 'high', `${file_id}${fileExt}`); @@ -173,7 +156,7 @@ const processCodeOutput = async ({ file_id, messageId, usage, - filename: safeName, + filename: name, conversationId, user: req.user.id, type: `image/${appConfig.imageOutputType}`, @@ -217,7 +200,7 @@ const processCodeOutput = async ({ ); } - const fileName = `${file_id}__${safeName}`; + const fileName = `${file_id}__${name}`; const filepath = await saveBuffer({ userId: req.user.id, buffer, @@ -230,7 +213,7 @@ const processCodeOutput = async ({ filepath, messageId, object: 'file', - filename: safeName, + filename: name, type: mimeType, conversationId, user: req.user.id, @@ -246,11 +229,6 @@ const processCodeOutput = async ({ await createFile(file, true); return Object.assign(file, { messageId, toolCallId }); } catch (error) { - if (error?.message === 'Path traversal detected in filename') { - logger.warn( - `[processCodeOutput] Path traversal blocked for file "${name}" | conv=${conversationId}`, - ); - } logAxiosError({ message: 'Error downloading/processing code environment file', error, @@ -310,8 +288,6 @@ async function getSessionInfo(fileIdentifier, apiKey) { 'User-Agent': 'LibreChat/1.0', 'X-API-Key': apiKey, }, - httpAgent: codeServerHttpAgent, - httpsAgent: codeServerHttpsAgent, timeout: 5000, }); @@ -460,6 +436,5 @@ 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 a805ee2bcc..f01a623f90 100644 --- a/api/server/services/Files/Code/process.spec.js +++ b/api/server/services/Files/Code/process.spec.js @@ -36,24 +36,11 @@ jest.mock('uuid', () => ({ v4: jest.fn(() => 'mock-uuid-1234'), })); -// 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 axios +jest.mock('axios'); +const axios = require('axios'); +// Mock logger jest.mock('@librechat/data-schemas', () => ({ logger: { warn: jest.fn(), @@ -62,10 +49,17 @@ 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(() => ''), +})); + // Mock models const mockClaimCodeFile = jest.fn(); jest.mock('~/models', () => ({ @@ -95,16 +89,14 @@ 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'); -const { processCodeOutput, getSessionInfo } = require('./process'); +// Import after mocks +const { processCodeOutput } = require('./process'); describe('Code Process', () => { const mockReq = { @@ -152,7 +144,7 @@ describe('Code Process', () => { }); const smallBuffer = Buffer.alloc(100); - mockAxios.mockResolvedValue({ data: smallBuffer }); + axios.mockResolvedValue({ data: smallBuffer }); const result = await processCodeOutput(baseParams); @@ -175,7 +167,7 @@ describe('Code Process', () => { }); const smallBuffer = Buffer.alloc(100); - mockAxios.mockResolvedValue({ data: smallBuffer }); + axios.mockResolvedValue({ data: smallBuffer }); const result = await processCodeOutput(baseParams); @@ -189,7 +181,7 @@ describe('Code Process', () => { it('should process image files using convertImage', async () => { const imageParams = { ...baseParams, name: 'chart.png' }; const imageBuffer = Buffer.alloc(500); - mockAxios.mockResolvedValue({ data: imageBuffer }); + axios.mockResolvedValue({ data: imageBuffer }); const convertedFile = { filepath: '/uploads/converted-image.webp', @@ -219,7 +211,7 @@ describe('Code Process', () => { }); const imageBuffer = Buffer.alloc(500); - mockAxios.mockResolvedValue({ data: imageBuffer }); + axios.mockResolvedValue({ data: imageBuffer }); convertImage.mockResolvedValue({ filepath: '/images/user-123/existing-img-id.webp' }); const result = await processCodeOutput(imageParams); @@ -242,7 +234,7 @@ describe('Code Process', () => { describe('non-image file processing', () => { it('should process non-image files using saveBuffer', async () => { const smallBuffer = Buffer.alloc(100); - mockAxios.mockResolvedValue({ data: smallBuffer }); + axios.mockResolvedValue({ data: smallBuffer }); const mockSaveBuffer = jest.fn().mockResolvedValue('/uploads/saved-file.txt'); getStrategyFunctions.mockReturnValue({ saveBuffer: mockSaveBuffer }); @@ -263,7 +255,7 @@ describe('Code Process', () => { it('should detect MIME type from buffer', async () => { const smallBuffer = Buffer.alloc(100); - mockAxios.mockResolvedValue({ data: smallBuffer }); + axios.mockResolvedValue({ data: smallBuffer }); determineFileType.mockResolvedValue({ mime: 'application/pdf' }); const result = await processCodeOutput({ ...baseParams, name: 'document.pdf' }); @@ -274,7 +266,7 @@ describe('Code Process', () => { it('should fallback to application/octet-stream for unknown types', async () => { const smallBuffer = Buffer.alloc(100); - mockAxios.mockResolvedValue({ data: smallBuffer }); + axios.mockResolvedValue({ data: smallBuffer }); determineFileType.mockResolvedValue(null); const result = await processCodeOutput({ ...baseParams, name: 'unknown.xyz' }); @@ -289,7 +281,7 @@ describe('Code Process', () => { fileSizeLimitConfig.value = 1000; // 1KB limit const largeBuffer = Buffer.alloc(5000); // 5KB - exceeds 1KB limit - mockAxios.mockResolvedValue({ data: largeBuffer }); + axios.mockResolvedValue({ data: largeBuffer }); const result = await processCodeOutput(baseParams); @@ -307,7 +299,7 @@ describe('Code Process', () => { describe('fallback behavior', () => { it('should fallback to download URL when saveBuffer is not available', async () => { const smallBuffer = Buffer.alloc(100); - mockAxios.mockResolvedValue({ data: smallBuffer }); + axios.mockResolvedValue({ data: smallBuffer }); getStrategyFunctions.mockReturnValue({ saveBuffer: null }); const result = await processCodeOutput(baseParams); @@ -320,7 +312,7 @@ describe('Code Process', () => { }); it('should fallback to download URL on axios error', async () => { - mockAxios.mockRejectedValue(new Error('Network error')); + axios.mockRejectedValue(new Error('Network error')); const result = await processCodeOutput(baseParams); @@ -334,7 +326,7 @@ describe('Code Process', () => { describe('usage counter increment', () => { it('should set usage to 1 for new files', async () => { const smallBuffer = Buffer.alloc(100); - mockAxios.mockResolvedValue({ data: smallBuffer }); + axios.mockResolvedValue({ data: smallBuffer }); const result = await processCodeOutput(baseParams); @@ -348,7 +340,7 @@ describe('Code Process', () => { createdAt: '2024-01-01', }); const smallBuffer = Buffer.alloc(100); - mockAxios.mockResolvedValue({ data: smallBuffer }); + axios.mockResolvedValue({ data: smallBuffer }); const result = await processCodeOutput(baseParams); @@ -361,7 +353,7 @@ describe('Code Process', () => { createdAt: '2024-01-01', }); const smallBuffer = Buffer.alloc(100); - mockAxios.mockResolvedValue({ data: smallBuffer }); + axios.mockResolvedValue({ data: smallBuffer }); const result = await processCodeOutput(baseParams); @@ -372,7 +364,7 @@ describe('Code Process', () => { describe('metadata and file properties', () => { it('should include fileIdentifier in metadata', async () => { const smallBuffer = Buffer.alloc(100); - mockAxios.mockResolvedValue({ data: smallBuffer }); + axios.mockResolvedValue({ data: smallBuffer }); const result = await processCodeOutput(baseParams); @@ -383,7 +375,7 @@ describe('Code Process', () => { it('should set correct context for code-generated files', async () => { const smallBuffer = Buffer.alloc(100); - mockAxios.mockResolvedValue({ data: smallBuffer }); + axios.mockResolvedValue({ data: smallBuffer }); const result = await processCodeOutput(baseParams); @@ -392,7 +384,7 @@ describe('Code Process', () => { it('should include toolCallId and messageId in result', async () => { const smallBuffer = Buffer.alloc(100); - mockAxios.mockResolvedValue({ data: smallBuffer }); + axios.mockResolvedValue({ data: smallBuffer }); const result = await processCodeOutput(baseParams); @@ -402,7 +394,7 @@ describe('Code Process', () => { it('should call createFile with upsert enabled', async () => { const smallBuffer = Buffer.alloc(100); - mockAxios.mockResolvedValue({ data: smallBuffer }); + axios.mockResolvedValue({ data: smallBuffer }); await processCodeOutput(baseParams); @@ -415,36 +407,5 @@ 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/api/server/services/Files/Local/__tests__/crud-traversal.spec.js b/api/server/services/Files/Local/__tests__/crud-traversal.spec.js deleted file mode 100644 index 57ba221d68..0000000000 --- a/api/server/services/Files/Local/__tests__/crud-traversal.spec.js +++ /dev/null @@ -1,69 +0,0 @@ -jest.mock('@librechat/api', () => ({ deleteRagFile: jest.fn() })); -jest.mock('@librechat/data-schemas', () => ({ - logger: { warn: jest.fn(), error: jest.fn() }, -})); - -const mockTmpBase = require('fs').mkdtempSync( - require('path').join(require('os').tmpdir(), 'crud-traversal-'), -); - -jest.mock('~/config/paths', () => { - const path = require('path'); - return { - publicPath: path.join(mockTmpBase, 'public'), - uploads: path.join(mockTmpBase, 'uploads'), - }; -}); - -const fs = require('fs'); -const path = require('path'); -const { saveLocalBuffer } = require('../crud'); - -describe('saveLocalBuffer path containment', () => { - beforeAll(() => { - fs.mkdirSync(path.join(mockTmpBase, 'public', 'images'), { recursive: true }); - fs.mkdirSync(path.join(mockTmpBase, 'uploads'), { recursive: true }); - }); - - afterAll(() => { - fs.rmSync(mockTmpBase, { recursive: true, force: true }); - }); - - test('rejects filenames with path traversal sequences', async () => { - await expect( - saveLocalBuffer({ - userId: 'user1', - buffer: Buffer.from('malicious'), - fileName: '../../../etc/passwd', - basePath: 'uploads', - }), - ).rejects.toThrow('Path traversal detected in filename'); - }); - - test('rejects prefix-collision traversal (startsWith bypass)', async () => { - fs.mkdirSync(path.join(mockTmpBase, 'uploads', 'user10'), { recursive: true }); - await expect( - saveLocalBuffer({ - userId: 'user1', - buffer: Buffer.from('malicious'), - fileName: '../user10/evil', - basePath: 'uploads', - }), - ).rejects.toThrow('Path traversal detected in filename'); - }); - - test('allows normal filenames', async () => { - const result = await saveLocalBuffer({ - userId: 'user1', - buffer: Buffer.from('safe content'), - fileName: 'file-id__output.csv', - basePath: 'uploads', - }); - - expect(result).toBe('/uploads/user1/file-id__output.csv'); - - const filePath = path.join(mockTmpBase, 'uploads', 'user1', 'file-id__output.csv'); - expect(fs.existsSync(filePath)).toBe(true); - fs.unlinkSync(filePath); - }); -}); diff --git a/api/server/services/Files/Local/crud.js b/api/server/services/Files/Local/crud.js index c86774d472..1f38a01f83 100644 --- a/api/server/services/Files/Local/crud.js +++ b/api/server/services/Files/Local/crud.js @@ -78,13 +78,7 @@ async function saveLocalBuffer({ userId, buffer, fileName, basePath = 'images' } fs.mkdirSync(directoryPath, { recursive: true }); } - const resolvedDir = path.resolve(directoryPath); - const resolvedPath = path.resolve(resolvedDir, fileName); - const rel = path.relative(resolvedDir, resolvedPath); - if (rel.startsWith('..') || path.isAbsolute(rel) || rel.includes(`..${path.sep}`)) { - throw new Error('Path traversal detected in filename'); - } - fs.writeFileSync(resolvedPath, buffer); + fs.writeFileSync(path.join(directoryPath, fileName), buffer); const filePath = path.posix.join('/', basePath, userId, fileName); @@ -171,8 +165,9 @@ async function getLocalFileURL({ fileName, basePath = 'images' }) { } /** - * Validates that a filepath is strictly contained within a subdirectory under a base path, - * using path.relative to prevent prefix-collision bypasses. + * Validates if a given filepath is within a specified subdirectory under a base path. This function constructs + * the expected base path using the base, subfolder, and user id from the request, and then checks if the + * provided filepath starts with this constructed base path. * * @param {ServerRequest} req - The request object from Express. It should contain a `user` property with an `id`. * @param {string} base - The base directory path. @@ -185,8 +180,7 @@ async function getLocalFileURL({ fileName, basePath = 'images' }) { const isValidPath = (req, base, subfolder, filepath) => { const normalizedBase = path.resolve(base, subfolder, req.user.id); const normalizedFilepath = path.resolve(filepath); - const rel = path.relative(normalizedBase, normalizedFilepath); - return !rel.startsWith('..') && !path.isAbsolute(rel) && !rel.includes(`..${path.sep}`); + return normalizedFilepath.startsWith(normalizedBase); }; /** diff --git a/api/server/services/Files/S3/crud.js b/api/server/services/Files/S3/crud.js new file mode 100644 index 0000000000..0721e33b29 --- /dev/null +++ b/api/server/services/Files/S3/crud.js @@ -0,0 +1,485 @@ +const fs = require('fs'); +const fetch = require('node-fetch'); +const { logger } = require('@librechat/data-schemas'); +const { FileSources } = require('librechat-data-provider'); +const { getSignedUrl } = require('@aws-sdk/s3-request-presigner'); +const { initializeS3, deleteRagFile } = require('@librechat/api'); +const { + PutObjectCommand, + GetObjectCommand, + HeadObjectCommand, + DeleteObjectCommand, +} = require('@aws-sdk/client-s3'); + +const bucketName = process.env.AWS_BUCKET_NAME; +const defaultBasePath = 'images'; + +let s3UrlExpirySeconds = 2 * 60; // 2 minutes +let s3RefreshExpiryMs = null; + +if (process.env.S3_URL_EXPIRY_SECONDS !== undefined) { + const parsed = parseInt(process.env.S3_URL_EXPIRY_SECONDS, 10); + + if (!isNaN(parsed) && parsed > 0) { + s3UrlExpirySeconds = Math.min(parsed, 7 * 24 * 60 * 60); + } else { + logger.warn( + `[S3] Invalid S3_URL_EXPIRY_SECONDS value: "${process.env.S3_URL_EXPIRY_SECONDS}". Using 2-minute expiry.`, + ); + } +} + +if (process.env.S3_REFRESH_EXPIRY_MS !== null && process.env.S3_REFRESH_EXPIRY_MS) { + const parsed = parseInt(process.env.S3_REFRESH_EXPIRY_MS, 10); + + if (!isNaN(parsed) && parsed > 0) { + s3RefreshExpiryMs = parsed; + logger.info(`[S3] Using custom refresh expiry time: ${s3RefreshExpiryMs}ms`); + } else { + logger.warn( + `[S3] Invalid S3_REFRESH_EXPIRY_MS value: "${process.env.S3_REFRESH_EXPIRY_MS}". Using default refresh logic.`, + ); + } +} + +/** + * Constructs the S3 key based on the base path, user ID, and file name. + */ +const getS3Key = (basePath, userId, fileName) => `${basePath}/${userId}/${fileName}`; + +/** + * Uploads a buffer to S3 and returns a signed URL. + * + * @param {Object} params + * @param {string} params.userId - The user's unique identifier. + * @param {Buffer} params.buffer - The buffer containing file data. + * @param {string} params.fileName - The file name to use in S3. + * @param {string} [params.basePath='images'] - The base path in the bucket. + * @returns {Promise} Signed URL of the uploaded file. + */ +async function saveBufferToS3({ userId, buffer, fileName, basePath = defaultBasePath }) { + const key = getS3Key(basePath, userId, fileName); + const params = { Bucket: bucketName, Key: key, Body: buffer }; + + try { + const s3 = initializeS3(); + await s3.send(new PutObjectCommand(params)); + return await getS3URL({ userId, fileName, basePath }); + } catch (error) { + logger.error('[saveBufferToS3] Error uploading buffer to S3:', error.message); + throw error; + } +} + +/** + * Retrieves a URL for a file stored in S3. + * Returns a signed URL with expiration time or a proxy URL based on config + * + * @param {Object} params + * @param {string} params.userId - The user's unique identifier. + * @param {string} params.fileName - The file name in S3. + * @param {string} [params.basePath='images'] - The base path in the bucket. + * @param {string} [params.customFilename] - Custom filename for Content-Disposition header (overrides extracted filename). + * @param {string} [params.contentType] - Custom content type for the response. + * @returns {Promise} A URL to access the S3 object + */ +async function getS3URL({ + userId, + fileName, + basePath = defaultBasePath, + customFilename = null, + contentType = null, +}) { + const key = getS3Key(basePath, userId, fileName); + const params = { Bucket: bucketName, Key: key }; + + // Add response headers if specified + if (customFilename) { + params.ResponseContentDisposition = `attachment; filename="${customFilename}"`; + } + + if (contentType) { + params.ResponseContentType = contentType; + } + + try { + const s3 = initializeS3(); + return await getSignedUrl(s3, new GetObjectCommand(params), { expiresIn: s3UrlExpirySeconds }); + } catch (error) { + logger.error('[getS3URL] Error getting signed URL from S3:', error.message); + throw error; + } +} + +/** + * Saves a file from a given URL to S3. + * + * @param {Object} params + * @param {string} params.userId - The user's unique identifier. + * @param {string} params.URL - The source URL of the file. + * @param {string} params.fileName - The file name to use in S3. + * @param {string} [params.basePath='images'] - The base path in the bucket. + * @returns {Promise} Signed URL of the uploaded file. + */ +async function saveURLToS3({ userId, URL, fileName, basePath = defaultBasePath }) { + try { + const response = await fetch(URL); + const buffer = await response.buffer(); + // Optionally you can call getBufferMetadata(buffer) if needed. + return await saveBufferToS3({ userId, buffer, fileName, basePath }); + } catch (error) { + logger.error('[saveURLToS3] Error uploading file from URL to S3:', error.message); + throw error; + } +} + +/** + * Deletes a file from S3. + * + * @param {Object} params + * @param {ServerRequest} params.req + * @param {MongoFile} params.file - The file object to delete. + * @returns {Promise} + */ +async function deleteFileFromS3(req, file) { + await deleteRagFile({ userId: req.user.id, file }); + + const key = extractKeyFromS3Url(file.filepath); + const params = { Bucket: bucketName, Key: key }; + if (!key.includes(req.user.id)) { + const message = `[deleteFileFromS3] User ID mismatch: ${req.user.id} vs ${key}`; + logger.error(message); + throw new Error(message); + } + + try { + const s3 = initializeS3(); + + try { + const headCommand = new HeadObjectCommand(params); + await s3.send(headCommand); + logger.debug('[deleteFileFromS3] File exists, proceeding with deletion'); + } catch (headErr) { + if (headErr.name === 'NotFound') { + logger.warn(`[deleteFileFromS3] File does not exist: ${key}`); + return; + } + } + + const deleteResult = await s3.send(new DeleteObjectCommand(params)); + logger.debug('[deleteFileFromS3] Delete command response:', JSON.stringify(deleteResult)); + try { + await s3.send(new HeadObjectCommand(params)); + logger.error('[deleteFileFromS3] File still exists after deletion!'); + } catch (verifyErr) { + if (verifyErr.name === 'NotFound') { + logger.debug(`[deleteFileFromS3] Verified file is deleted: ${key}`); + } else { + logger.error('[deleteFileFromS3] Error verifying deletion:', verifyErr); + } + } + + logger.debug('[deleteFileFromS3] S3 File deletion completed'); + } catch (error) { + logger.error(`[deleteFileFromS3] Error deleting file from S3: ${error.message}`); + logger.error(error.stack); + + // If the file is not found, we can safely return. + if (error.code === 'NoSuchKey') { + return; + } + throw error; + } +} + +/** + * Uploads a local file to S3 by streaming it directly without loading into memory. + * + * @param {Object} params + * @param {import('express').Request} params.req - The Express request (must include user). + * @param {Express.Multer.File} params.file - The file object from Multer. + * @param {string} params.file_id - Unique file identifier. + * @param {string} [params.basePath='images'] - The base path in the bucket. + * @returns {Promise<{ filepath: string, bytes: number }>} + */ +async function uploadFileToS3({ req, file, file_id, basePath = defaultBasePath }) { + try { + const inputFilePath = file.path; + const userId = req.user.id; + const fileName = `${file_id}__${file.originalname}`; + const key = getS3Key(basePath, userId, fileName); + + const stats = await fs.promises.stat(inputFilePath); + const bytes = stats.size; + const fileStream = fs.createReadStream(inputFilePath); + + const s3 = initializeS3(); + const uploadParams = { + Bucket: bucketName, + Key: key, + Body: fileStream, + }; + + await s3.send(new PutObjectCommand(uploadParams)); + const fileURL = await getS3URL({ userId, fileName, basePath }); + return { filepath: fileURL, bytes }; + } catch (error) { + logger.error('[uploadFileToS3] Error streaming file to S3:', error); + try { + if (file && file.path) { + await fs.promises.unlink(file.path); + } + } catch (unlinkError) { + logger.error( + '[uploadFileToS3] Error deleting temporary file, likely already deleted:', + unlinkError.message, + ); + } + throw error; + } +} + +/** + * Extracts the S3 key from a URL or returns the key if already properly formatted + * + * @param {string} fileUrlOrKey - The file URL or key + * @returns {string} The S3 key + */ +function extractKeyFromS3Url(fileUrlOrKey) { + if (!fileUrlOrKey) { + throw new Error('Invalid input: URL or key is empty'); + } + + try { + const url = new URL(fileUrlOrKey); + return url.pathname.substring(1); + } catch (error) { + const parts = fileUrlOrKey.split('/'); + + if (parts.length >= 3 && !fileUrlOrKey.startsWith('http') && !fileUrlOrKey.startsWith('/')) { + return fileUrlOrKey; + } + + return fileUrlOrKey.startsWith('/') ? fileUrlOrKey.substring(1) : fileUrlOrKey; + } +} + +/** + * Retrieves a readable stream for a file stored in S3. + * + * @param {ServerRequest} req - Server request object. + * @param {string} filePath - The S3 key of the file. + * @returns {Promise} + */ +async function getS3FileStream(_req, filePath) { + try { + const Key = extractKeyFromS3Url(filePath); + const params = { Bucket: bucketName, Key }; + const s3 = initializeS3(); + const data = await s3.send(new GetObjectCommand(params)); + return data.Body; // Returns a Node.js ReadableStream. + } catch (error) { + logger.error('[getS3FileStream] Error retrieving S3 file stream:', error); + throw error; + } +} + +/** + * Determines if a signed S3 URL is close to expiration + * + * @param {string} signedUrl - The signed S3 URL + * @param {number} bufferSeconds - Buffer time in seconds + * @returns {boolean} True if the URL needs refreshing + */ +function needsRefresh(signedUrl, bufferSeconds) { + try { + // Parse the URL + const url = new URL(signedUrl); + + // Check if it has the signature parameters that indicate it's a signed URL + // X-Amz-Signature is the most reliable indicator for AWS signed URLs + if (!url.searchParams.has('X-Amz-Signature')) { + // Not a signed URL, so no expiration to check (or it's already a proxy URL) + return false; + } + + // Extract the expiration time from the URL + const expiresParam = url.searchParams.get('X-Amz-Expires'); + const dateParam = url.searchParams.get('X-Amz-Date'); + + if (!expiresParam || !dateParam) { + // Missing expiration information, assume it needs refresh to be safe + return true; + } + + // Parse the AWS date format (YYYYMMDDTHHMMSSZ) + const year = dateParam.substring(0, 4); + const month = dateParam.substring(4, 6); + const day = dateParam.substring(6, 8); + const hour = dateParam.substring(9, 11); + const minute = dateParam.substring(11, 13); + const second = dateParam.substring(13, 15); + + const dateObj = new Date(`${year}-${month}-${day}T${hour}:${minute}:${second}Z`); + const expiresAtDate = new Date(dateObj.getTime() + parseInt(expiresParam) * 1000); + + // Check if it's close to expiration + const now = new Date(); + + // If S3_REFRESH_EXPIRY_MS is set, use it to determine if URL is expired + if (s3RefreshExpiryMs !== null) { + const urlCreationTime = dateObj.getTime(); + const urlAge = now.getTime() - urlCreationTime; + return urlAge >= s3RefreshExpiryMs; + } + + // Otherwise use the default buffer-based logic + const bufferTime = new Date(now.getTime() + bufferSeconds * 1000); + return expiresAtDate <= bufferTime; + } catch (error) { + logger.error('Error checking URL expiration:', error); + // If we can't determine, assume it needs refresh to be safe + return true; + } +} + +/** + * Generates a new URL for an expired S3 URL + * @param {string} currentURL - The current file URL + * @returns {Promise} + */ +async function getNewS3URL(currentURL) { + try { + const s3Key = extractKeyFromS3Url(currentURL); + if (!s3Key) { + return; + } + const keyParts = s3Key.split('/'); + if (keyParts.length < 3) { + return; + } + + const basePath = keyParts[0]; + const userId = keyParts[1]; + const fileName = keyParts.slice(2).join('/'); + + return await getS3URL({ + userId, + fileName, + basePath, + }); + } catch (error) { + logger.error('Error getting new S3 URL:', error); + } +} + +/** + * Refreshes S3 URLs for an array of files if they're expired or close to expiring + * + * @param {MongoFile[]} files - Array of file documents + * @param {(files: MongoFile[]) => Promise} batchUpdateFiles - Function to update files in the database + * @param {number} [bufferSeconds=3600] - Buffer time in seconds to check for expiration + * @returns {Promise} The files with refreshed URLs if needed + */ +async function refreshS3FileUrls(files, batchUpdateFiles, bufferSeconds = 3600) { + if (!files || !Array.isArray(files) || files.length === 0) { + return files; + } + + const filesToUpdate = []; + + for (let i = 0; i < files.length; i++) { + const file = files[i]; + if (!file?.file_id) { + continue; + } + if (file.source !== FileSources.s3) { + continue; + } + if (!file.filepath) { + continue; + } + if (!needsRefresh(file.filepath, bufferSeconds)) { + continue; + } + try { + const newURL = await getNewS3URL(file.filepath); + if (!newURL) { + continue; + } + filesToUpdate.push({ + file_id: file.file_id, + filepath: newURL, + }); + files[i].filepath = newURL; + } catch (error) { + logger.error(`Error refreshing S3 URL for file ${file.file_id}:`, error); + } + } + + if (filesToUpdate.length > 0) { + await batchUpdateFiles(filesToUpdate); + } + + return files; +} + +/** + * Refreshes a single S3 URL if it's expired or close to expiring + * + * @param {{ filepath: string, source: string }} fileObj - Simple file object containing filepath and source + * @param {number} [bufferSeconds=3600] - Buffer time in seconds to check for expiration + * @returns {Promise} The refreshed URL or the original URL if no refresh needed + */ +async function refreshS3Url(fileObj, bufferSeconds = 3600) { + if (!fileObj || fileObj.source !== FileSources.s3 || !fileObj.filepath) { + return fileObj?.filepath || ''; + } + + if (!needsRefresh(fileObj.filepath, bufferSeconds)) { + return fileObj.filepath; + } + + try { + const s3Key = extractKeyFromS3Url(fileObj.filepath); + if (!s3Key) { + logger.warn(`Unable to extract S3 key from URL: ${fileObj.filepath}`); + return fileObj.filepath; + } + + const keyParts = s3Key.split('/'); + if (keyParts.length < 3) { + logger.warn(`Invalid S3 key format: ${s3Key}`); + return fileObj.filepath; + } + + const basePath = keyParts[0]; + const userId = keyParts[1]; + const fileName = keyParts.slice(2).join('/'); + + const newUrl = await getS3URL({ + userId, + fileName, + basePath, + }); + + logger.debug(`Refreshed S3 URL for key: ${s3Key}`); + return newUrl; + } catch (error) { + logger.error(`Error refreshing S3 URL: ${error.message}`); + return fileObj.filepath; + } +} + +module.exports = { + saveBufferToS3, + saveURLToS3, + getS3URL, + deleteFileFromS3, + uploadFileToS3, + getS3FileStream, + refreshS3FileUrls, + refreshS3Url, + needsRefresh, + getNewS3URL, +}; diff --git a/api/server/services/Files/S3/images.js b/api/server/services/Files/S3/images.js new file mode 100644 index 0000000000..9bdae940c3 --- /dev/null +++ b/api/server/services/Files/S3/images.js @@ -0,0 +1,129 @@ +const fs = require('fs'); +const path = require('path'); +const sharp = require('sharp'); +const { logger } = require('@librechat/data-schemas'); +const { resizeImageBuffer } = require('../images/resize'); +const { updateUser, updateFile } = require('~/models'); +const { saveBufferToS3 } = require('./crud'); + +const defaultBasePath = 'images'; + +/** + * Resizes, converts, and uploads an image file to S3. + * + * @param {Object} params + * @param {import('express').Request} params.req - Express request (expects `user` and `appConfig.imageOutputType`). + * @param {Express.Multer.File} params.file - File object from Multer. + * @param {string} params.file_id - Unique file identifier. + * @param {any} params.endpoint - Endpoint identifier used in image processing. + * @param {string} [params.resolution='high'] - Desired image resolution. + * @param {string} [params.basePath='images'] - Base path in the bucket. + * @returns {Promise<{ filepath: string, bytes: number, width: number, height: number }>} + */ +async function uploadImageToS3({ + req, + file, + file_id, + endpoint, + resolution = 'high', + basePath = defaultBasePath, +}) { + try { + const appConfig = req.config; + const inputFilePath = file.path; + const inputBuffer = await fs.promises.readFile(inputFilePath); + const { + buffer: resizedBuffer, + width, + height, + } = await resizeImageBuffer(inputBuffer, resolution, endpoint); + const extension = path.extname(inputFilePath); + const userId = req.user.id; + + let processedBuffer; + let fileName = `${file_id}__${path.basename(inputFilePath)}`; + const targetExtension = `.${appConfig.imageOutputType}`; + + if (extension.toLowerCase() === targetExtension) { + processedBuffer = resizedBuffer; + } else { + processedBuffer = await sharp(resizedBuffer).toFormat(appConfig.imageOutputType).toBuffer(); + fileName = fileName.replace(new RegExp(path.extname(fileName) + '$'), targetExtension); + if (!path.extname(fileName)) { + fileName += targetExtension; + } + } + + const downloadURL = await saveBufferToS3({ + userId, + buffer: processedBuffer, + fileName, + basePath, + }); + await fs.promises.unlink(inputFilePath); + const bytes = Buffer.byteLength(processedBuffer); + return { filepath: downloadURL, bytes, width, height }; + } catch (error) { + logger.error('[uploadImageToS3] Error uploading image to S3:', error.message); + throw error; + } +} + +/** + * Updates a file record and returns its signed URL. + * + * @param {import('express').Request} req - Express request. + * @param {Object} file - File metadata. + * @returns {Promise<[Promise, string]>} + */ +async function prepareImageURLS3(req, file) { + try { + const updatePromise = updateFile({ file_id: file.file_id }); + return Promise.all([updatePromise, file.filepath]); + } catch (error) { + logger.error('[prepareImageURLS3] Error preparing image URL:', error.message); + throw error; + } +} + +/** + * Processes a user's avatar image by uploading it to S3 and updating the user's avatar URL if required. + * + * @param {Object} params + * @param {Buffer} params.buffer - Avatar image buffer. + * @param {string} params.userId - User's unique identifier. + * @param {string} params.manual - 'true' or 'false' flag for manual update. + * @param {string} [params.agentId] - Optional agent ID if this is an agent avatar. + * @param {string} [params.basePath='images'] - Base path in the bucket. + * @returns {Promise} Signed URL of the uploaded avatar. + */ +async function processS3Avatar({ buffer, userId, manual, agentId, basePath = defaultBasePath }) { + try { + const metadata = await sharp(buffer).metadata(); + const extension = metadata.format === 'gif' ? 'gif' : 'png'; + const timestamp = new Date().getTime(); + + /** Unique filename with timestamp and optional agent ID */ + const fileName = agentId + ? `agent-${agentId}-avatar-${timestamp}.${extension}` + : `avatar-${timestamp}.${extension}`; + + const downloadURL = await saveBufferToS3({ userId, buffer, fileName, basePath }); + + // Only update user record if this is a user avatar (manual === 'true') + if (manual === 'true' && !agentId) { + await updateUser(userId, { avatar: downloadURL }); + } + + return downloadURL; + } catch (error) { + logger.error('[processS3Avatar] Error processing S3 avatar:', error.message); + throw error; + } +} + +module.exports = { + uploadImageToS3, + prepareImageURLS3, + processS3Avatar, +}; diff --git a/api/server/services/Files/S3/index.js b/api/server/services/Files/S3/index.js new file mode 100644 index 0000000000..21e2f2ba7d --- /dev/null +++ b/api/server/services/Files/S3/index.js @@ -0,0 +1,7 @@ +const crud = require('./crud'); +const images = require('./images'); + +module.exports = { + ...crud, + ...images, +}; diff --git a/api/server/services/Files/permissions.js b/api/server/services/Files/permissions.js index ffa8e74799..d909afe25a 100644 --- a/api/server/services/Files/permissions.js +++ b/api/server/services/Files/permissions.js @@ -1,29 +1,10 @@ const { logger } = require('@librechat/data-schemas'); -const { PermissionBits, ResourceType, isEphemeralAgentId } = require('librechat-data-provider'); +const { PermissionBits, ResourceType } = require('librechat-data-provider'); const { checkPermission } = require('~/server/services/PermissionService'); -const { getAgent } = require('~/models'); +const { getAgent } = require('~/models/Agent'); /** - * @param {Object} agent - The agent document (lean) - * @returns {Set} All file IDs attached across all resource types - */ -function getAttachedFileIds(agent) { - const attachedFileIds = new Set(); - if (agent.tool_resources) { - for (const resource of Object.values(agent.tool_resources)) { - if (resource?.file_ids && Array.isArray(resource.file_ids)) { - for (const fileId of resource.file_ids) { - attachedFileIds.add(fileId); - } - } - } - } - return attachedFileIds; -} - -/** - * Checks if a user has access to multiple files through a shared agent (batch operation). - * Access is always scoped to files actually attached to the agent's tool_resources. + * Checks if a user has access to multiple files through a shared agent (batch operation) * @param {Object} params - Parameters object * @param {string} params.userId - The user ID to check access for * @param {string} [params.role] - Optional user role to avoid DB query @@ -35,6 +16,7 @@ function getAttachedFileIds(agent) { const hasAccessToFilesViaAgent = async ({ userId, role, fileIds, agentId, isDelete }) => { const accessMap = new Map(); + // Initialize all files as no access fileIds.forEach((fileId) => accessMap.set(fileId, false)); try { @@ -44,17 +26,13 @@ const hasAccessToFilesViaAgent = async ({ userId, role, fileIds, agentId, isDele return accessMap; } - const attachedFileIds = getAttachedFileIds(agent); - + // Check if user is the author - if so, grant access to all files if (agent.author.toString() === userId.toString()) { - fileIds.forEach((fileId) => { - if (attachedFileIds.has(fileId)) { - accessMap.set(fileId, true); - } - }); + fileIds.forEach((fileId) => accessMap.set(fileId, true)); return accessMap; } + // Check if user has at least VIEW permission on the agent const hasViewPermission = await checkPermission({ userId, role, @@ -68,6 +46,7 @@ const hasAccessToFilesViaAgent = async ({ userId, role, fileIds, agentId, isDele } if (isDelete) { + // Check if user has EDIT permission (which would indicate collaborative access) const hasEditPermission = await checkPermission({ userId, role, @@ -76,11 +55,23 @@ const hasAccessToFilesViaAgent = async ({ userId, role, fileIds, agentId, isDele requiredPermission: PermissionBits.EDIT, }); + // If user only has VIEW permission, they can't access files + // Only users with EDIT permission or higher can access agent files if (!hasEditPermission) { return accessMap; } } + const attachedFileIds = new Set(); + if (agent.tool_resources) { + for (const [_resourceType, resource] of Object.entries(agent.tool_resources)) { + if (resource?.file_ids && Array.isArray(resource.file_ids)) { + resource.file_ids.forEach((fileId) => attachedFileIds.add(fileId)); + } + } + } + + // Grant access only to files that are attached to this agent fileIds.forEach((fileId) => { if (attachedFileIds.has(fileId)) { accessMap.set(fileId, true); @@ -104,7 +95,7 @@ const hasAccessToFilesViaAgent = async ({ userId, role, fileIds, agentId, isDele * @returns {Promise>} Filtered array of accessible files */ const filterFilesByAgentAccess = async ({ files, userId, role, agentId }) => { - if (!userId || !agentId || !files || files.length === 0 || isEphemeralAgentId(agentId)) { + if (!userId || !agentId || !files || files.length === 0) { return files; } diff --git a/api/server/services/Files/permissions.spec.js b/api/server/services/Files/permissions.spec.js deleted file mode 100644 index c926e83464..0000000000 --- a/api/server/services/Files/permissions.spec.js +++ /dev/null @@ -1,409 +0,0 @@ -jest.mock('@librechat/data-schemas', () => ({ - logger: { error: jest.fn() }, -})); - -jest.mock('~/server/services/PermissionService', () => ({ - checkPermission: jest.fn(), -})); - -jest.mock('~/models', () => ({ - getAgent: jest.fn(), -})); - -const { logger } = require('@librechat/data-schemas'); -const { Constants, PermissionBits, ResourceType } = require('librechat-data-provider'); -const { checkPermission } = require('~/server/services/PermissionService'); -const { getAgent } = require('~/models'); -const { filterFilesByAgentAccess, hasAccessToFilesViaAgent } = require('./permissions'); - -const AUTHOR_ID = 'author-user-id'; -const USER_ID = 'viewer-user-id'; -const AGENT_ID = 'agent_test-abc123'; -const AGENT_MONGO_ID = 'mongo-agent-id'; - -function makeFile(file_id, user) { - return { file_id, user, filename: `${file_id}.txt` }; -} - -function makeAgent(overrides = {}) { - return { - _id: AGENT_MONGO_ID, - id: AGENT_ID, - author: AUTHOR_ID, - tool_resources: { - file_search: { file_ids: ['attached-1', 'attached-2'] }, - execute_code: { file_ids: ['attached-3'] }, - }, - ...overrides, - }; -} - -beforeEach(() => { - jest.clearAllMocks(); -}); - -describe('filterFilesByAgentAccess', () => { - describe('early returns (no DB calls)', () => { - it('should return files unfiltered for ephemeral agentId', async () => { - const files = [makeFile('f1', 'other-user')]; - const result = await filterFilesByAgentAccess({ - files, - userId: USER_ID, - agentId: Constants.EPHEMERAL_AGENT_ID, - }); - - expect(result).toBe(files); - expect(getAgent).not.toHaveBeenCalled(); - }); - - it('should return files unfiltered for non-agent_ prefixed agentId', async () => { - const files = [makeFile('f1', 'other-user')]; - const result = await filterFilesByAgentAccess({ - files, - userId: USER_ID, - agentId: 'custom-memory-id', - }); - - expect(result).toBe(files); - expect(getAgent).not.toHaveBeenCalled(); - }); - - it('should return files when userId is missing', async () => { - const files = [makeFile('f1', 'someone')]; - const result = await filterFilesByAgentAccess({ - files, - userId: undefined, - agentId: AGENT_ID, - }); - - expect(result).toBe(files); - expect(getAgent).not.toHaveBeenCalled(); - }); - - it('should return files when agentId is missing', async () => { - const files = [makeFile('f1', 'someone')]; - const result = await filterFilesByAgentAccess({ - files, - userId: USER_ID, - agentId: undefined, - }); - - expect(result).toBe(files); - expect(getAgent).not.toHaveBeenCalled(); - }); - - it('should return empty array when files is empty', async () => { - const result = await filterFilesByAgentAccess({ - files: [], - userId: USER_ID, - agentId: AGENT_ID, - }); - - expect(result).toEqual([]); - expect(getAgent).not.toHaveBeenCalled(); - }); - - it('should return undefined when files is nullish', async () => { - const result = await filterFilesByAgentAccess({ - files: null, - userId: USER_ID, - agentId: AGENT_ID, - }); - - expect(result).toBeNull(); - expect(getAgent).not.toHaveBeenCalled(); - }); - }); - - describe('all files owned by userId', () => { - it('should return all files without calling getAgent', async () => { - const files = [makeFile('f1', USER_ID), makeFile('f2', USER_ID)]; - const result = await filterFilesByAgentAccess({ - files, - userId: USER_ID, - agentId: AGENT_ID, - }); - - expect(result).toEqual(files); - expect(getAgent).not.toHaveBeenCalled(); - }); - }); - - describe('mixed owned and non-owned files', () => { - const ownedFile = makeFile('owned-1', USER_ID); - const sharedFile = makeFile('attached-1', AUTHOR_ID); - const unattachedFile = makeFile('not-attached', AUTHOR_ID); - - it('should return owned + accessible non-owned files when user has VIEW', async () => { - getAgent.mockResolvedValue(makeAgent()); - checkPermission.mockResolvedValue(true); - - const result = await filterFilesByAgentAccess({ - files: [ownedFile, sharedFile, unattachedFile], - userId: USER_ID, - role: 'USER', - agentId: AGENT_ID, - }); - - expect(result).toHaveLength(2); - expect(result.map((f) => f.file_id)).toContain('owned-1'); - expect(result.map((f) => f.file_id)).toContain('attached-1'); - expect(result.map((f) => f.file_id)).not.toContain('not-attached'); - }); - - it('should return only owned files when user lacks VIEW permission', async () => { - getAgent.mockResolvedValue(makeAgent()); - checkPermission.mockResolvedValue(false); - - const result = await filterFilesByAgentAccess({ - files: [ownedFile, sharedFile], - userId: USER_ID, - role: 'USER', - agentId: AGENT_ID, - }); - - expect(result).toEqual([ownedFile]); - }); - - it('should return only owned files when agent is not found', async () => { - getAgent.mockResolvedValue(null); - - const result = await filterFilesByAgentAccess({ - files: [ownedFile, sharedFile], - userId: USER_ID, - agentId: AGENT_ID, - }); - - expect(result).toEqual([ownedFile]); - }); - - it('should return only owned files on DB error (fail-closed)', async () => { - getAgent.mockRejectedValue(new Error('DB connection lost')); - - const result = await filterFilesByAgentAccess({ - files: [ownedFile, sharedFile], - userId: USER_ID, - agentId: AGENT_ID, - }); - - expect(result).toEqual([ownedFile]); - expect(logger.error).toHaveBeenCalled(); - }); - }); - - describe('file with no user field', () => { - it('should treat file as non-owned and run through access check', async () => { - const noUserFile = makeFile('attached-1', undefined); - getAgent.mockResolvedValue(makeAgent()); - checkPermission.mockResolvedValue(true); - - const result = await filterFilesByAgentAccess({ - files: [noUserFile], - userId: USER_ID, - role: 'USER', - agentId: AGENT_ID, - }); - - expect(getAgent).toHaveBeenCalled(); - expect(result).toEqual([noUserFile]); - }); - - it('should exclude file with no user field when not attached to agent', async () => { - const noUserFile = makeFile('not-attached', null); - getAgent.mockResolvedValue(makeAgent()); - checkPermission.mockResolvedValue(true); - - const result = await filterFilesByAgentAccess({ - files: [noUserFile], - userId: USER_ID, - role: 'USER', - agentId: AGENT_ID, - }); - - expect(result).toEqual([]); - }); - }); - - describe('no owned files (all non-owned)', () => { - const file1 = makeFile('attached-1', AUTHOR_ID); - const file2 = makeFile('not-attached', AUTHOR_ID); - - it('should return only attached files when user has VIEW', async () => { - getAgent.mockResolvedValue(makeAgent()); - checkPermission.mockResolvedValue(true); - - const result = await filterFilesByAgentAccess({ - files: [file1, file2], - userId: USER_ID, - role: 'USER', - agentId: AGENT_ID, - }); - - expect(result).toEqual([file1]); - }); - - it('should return empty array when no VIEW permission', async () => { - getAgent.mockResolvedValue(makeAgent()); - checkPermission.mockResolvedValue(false); - - const result = await filterFilesByAgentAccess({ - files: [file1, file2], - userId: USER_ID, - agentId: AGENT_ID, - }); - - expect(result).toEqual([]); - }); - - it('should return empty array when agent not found', async () => { - getAgent.mockResolvedValue(null); - - const result = await filterFilesByAgentAccess({ - files: [file1], - userId: USER_ID, - agentId: AGENT_ID, - }); - - expect(result).toEqual([]); - }); - }); -}); - -describe('hasAccessToFilesViaAgent', () => { - describe('agent not found', () => { - it('should return all-false map', async () => { - getAgent.mockResolvedValue(null); - - const result = await hasAccessToFilesViaAgent({ - userId: USER_ID, - fileIds: ['f1', 'f2'], - agentId: AGENT_ID, - }); - - expect(result.get('f1')).toBe(false); - expect(result.get('f2')).toBe(false); - }); - }); - - describe('author path', () => { - it('should grant access to attached files for the agent author', async () => { - getAgent.mockResolvedValue(makeAgent()); - - const result = await hasAccessToFilesViaAgent({ - userId: AUTHOR_ID, - fileIds: ['attached-1', 'not-attached'], - agentId: AGENT_ID, - }); - - expect(result.get('attached-1')).toBe(true); - expect(result.get('not-attached')).toBe(false); - expect(checkPermission).not.toHaveBeenCalled(); - }); - }); - - describe('VIEW permission path', () => { - it('should grant access to attached files for viewer with VIEW permission', async () => { - getAgent.mockResolvedValue(makeAgent()); - checkPermission.mockResolvedValue(true); - - const result = await hasAccessToFilesViaAgent({ - userId: USER_ID, - role: 'USER', - fileIds: ['attached-1', 'attached-3', 'not-attached'], - agentId: AGENT_ID, - }); - - expect(result.get('attached-1')).toBe(true); - expect(result.get('attached-3')).toBe(true); - expect(result.get('not-attached')).toBe(false); - - expect(checkPermission).toHaveBeenCalledWith({ - userId: USER_ID, - role: 'USER', - resourceType: ResourceType.AGENT, - resourceId: AGENT_MONGO_ID, - requiredPermission: PermissionBits.VIEW, - }); - }); - - it('should deny all when VIEW permission is missing', async () => { - getAgent.mockResolvedValue(makeAgent()); - checkPermission.mockResolvedValue(false); - - const result = await hasAccessToFilesViaAgent({ - userId: USER_ID, - fileIds: ['attached-1'], - agentId: AGENT_ID, - }); - - expect(result.get('attached-1')).toBe(false); - }); - }); - - describe('delete path (EDIT permission required)', () => { - it('should grant access when both VIEW and EDIT pass', async () => { - getAgent.mockResolvedValue(makeAgent()); - checkPermission.mockResolvedValueOnce(true).mockResolvedValueOnce(true); - - const result = await hasAccessToFilesViaAgent({ - userId: USER_ID, - fileIds: ['attached-1'], - agentId: AGENT_ID, - isDelete: true, - }); - - expect(result.get('attached-1')).toBe(true); - expect(checkPermission).toHaveBeenCalledTimes(2); - expect(checkPermission).toHaveBeenLastCalledWith( - expect.objectContaining({ requiredPermission: PermissionBits.EDIT }), - ); - }); - - it('should deny all when VIEW passes but EDIT fails', async () => { - getAgent.mockResolvedValue(makeAgent()); - checkPermission.mockResolvedValueOnce(true).mockResolvedValueOnce(false); - - const result = await hasAccessToFilesViaAgent({ - userId: USER_ID, - fileIds: ['attached-1'], - agentId: AGENT_ID, - isDelete: true, - }); - - expect(result.get('attached-1')).toBe(false); - }); - }); - - describe('error handling', () => { - it('should return all-false map on DB error (fail-closed)', async () => { - getAgent.mockRejectedValue(new Error('connection refused')); - - const result = await hasAccessToFilesViaAgent({ - userId: USER_ID, - fileIds: ['f1', 'f2'], - agentId: AGENT_ID, - }); - - expect(result.get('f1')).toBe(false); - expect(result.get('f2')).toBe(false); - expect(logger.error).toHaveBeenCalledWith( - '[hasAccessToFilesViaAgent] Error checking file access:', - expect.any(Error), - ); - }); - }); - - describe('agent with no tool_resources', () => { - it('should deny all files even for the author', async () => { - getAgent.mockResolvedValue(makeAgent({ tool_resources: undefined })); - - const result = await hasAccessToFilesViaAgent({ - userId: AUTHOR_ID, - fileIds: ['f1'], - agentId: AGENT_ID, - }); - - expect(result.get('f1')).toBe(false); - }); - }); -}); diff --git a/api/server/services/Files/process.js b/api/server/services/Files/process.js index f7d7731975..30b47f2e52 100644 --- a/api/server/services/Files/process.js +++ b/api/server/services/Files/process.js @@ -16,7 +16,6 @@ const { removeNullishValues, isAssistantsEndpoint, getEndpointFileConfig, - documentParserMimeTypes, } = require('librechat-data-provider'); const { EnvVar } = require('@librechat/agents'); const { logger } = require('@librechat/data-schemas'); @@ -27,15 +26,16 @@ const { resizeImageBuffer, } = require('~/server/services/Files/images'); const { addResourceFileId, deleteResourceFileId } = require('~/server/controllers/assistants/v2'); +const { addAgentResourceFile, removeAgentResourceFiles } = require('~/models/Agent'); const { getOpenAIClient } = require('~/server/controllers/assistants/helpers'); const { loadAuthValues } = require('~/server/services/Tools/credentials'); +const { createFile, updateFileUsage, deleteFiles } = require('~/models'); const { getFileStrategy } = require('~/server/utils/getFileStrategy'); const { checkCapability } = require('~/server/services/Config'); const { LB_QueueAsyncCall } = require('~/server/utils/queue'); const { getStrategyFunctions } = require('./strategies'); const { determineFileType } = require('~/server/utils'); const { STTService } = require('./Audio/STTService'); -const db = require('~/models'); /** * Creates a modular file upload wrapper that ensures filename sanitization @@ -210,7 +210,7 @@ const processDeleteRequest = async ({ req, files }) => { if (agentFiles.length > 0) { promises.push( - db.removeAgentResourceFiles({ + removeAgentResourceFiles({ agent_id: req.body.agent_id, files: agentFiles, }), @@ -218,7 +218,7 @@ const processDeleteRequest = async ({ req, files }) => { } await Promise.allSettled(promises); - await db.deleteFiles(resolvedFileIds); + await deleteFiles(resolvedFileIds); }; /** @@ -250,7 +250,7 @@ const processFileURL = async ({ fileStrategy, userId, URL, fileName, basePath, c dimensions = {}, } = (await saveURL({ userId, URL, fileName, basePath })) || {}; const filepath = await getFileURL({ fileName: `${userId}/${fileName}`, basePath }); - return await db.createFile( + return await createFile( { user: userId, file_id: v4(), @@ -296,7 +296,7 @@ const processImageFile = async ({ req, res, metadata, returnFile = false }) => { endpoint, }); - const result = await db.createFile( + const result = await createFile( { user: req.user.id, file_id, @@ -348,7 +348,7 @@ const uploadImageBuffer = async ({ req, context, metadata = {}, resize = true }) } const fileName = `${file_id}-${filename}`; const filepath = await saveBuffer({ userId: req.user.id, fileName, buffer }); - return await db.createFile( + return await createFile( { user: req.user.id, file_id, @@ -434,7 +434,7 @@ const processFileUpload = async ({ req, res, metadata }) => { filepath = result.filepath; } - const result = await db.createFile( + const result = await createFile( { user: req.user.id, file_id: id ?? file_id, @@ -523,12 +523,6 @@ const processAgentFileUpload = async ({ req, res, metadata }) => { * @return {Promise} */ const createTextFile = async ({ text, bytes, filepath, type = 'text/plain' }) => { - const textBytes = Buffer.byteLength(text, 'utf8'); - if (textBytes > 15 * megabyte) { - throw new Error( - `Extracted text from "${file.originalname}" exceeds the 15MB storage limit (${Math.round(textBytes / megabyte)}MB). Try a shorter document.`, - ); - } const fileInfo = removeNullishValues({ text, bytes, @@ -544,14 +538,14 @@ const processAgentFileUpload = async ({ req, res, metadata }) => { }); if (!messageAttachment && tool_resource) { - await db.addAgentResourceFile({ + await addAgentResourceFile({ + req, file_id, agent_id, tool_resource, - updatingUserId: req?.user?.id, }); } - const result = await db.createFile(fileInfo, true); + const result = await createFile(fileInfo, true); return res .status(200) .json({ message: 'Agent file uploaded and processed successfully', ...result }); @@ -559,52 +553,29 @@ const processAgentFileUpload = async ({ req, res, metadata }) => { const fileConfig = mergeFileConfig(appConfig.fileConfig); - const shouldUseConfiguredOCR = + const shouldUseOCR = appConfig?.ocr != null && fileConfig.checkType(file.mimetype, fileConfig.ocr?.supportedMimeTypes || []); - const shouldUseDocumentParser = - !shouldUseConfiguredOCR && documentParserMimeTypes.some((regex) => regex.test(file.mimetype)); - - const shouldUseOCR = shouldUseConfiguredOCR || shouldUseDocumentParser; - - const resolveDocumentText = async () => { - if (shouldUseConfiguredOCR) { - try { - const ocrStrategy = appConfig?.ocr?.strategy ?? FileSources.document_parser; - const { handleFileUpload } = getStrategyFunctions(ocrStrategy); - return await handleFileUpload({ req, file, loadAuthValues }); - } catch (err) { - logger.error( - `[processAgentFileUpload] Configured OCR failed for "${file.originalname}", falling back to document_parser:`, - err, - ); - } - } + if (shouldUseOCR && !(await checkCapability(req, AgentCapabilities.ocr))) { + throw new Error('OCR capability is not enabled for Agents'); + } else if (shouldUseOCR) { try { - const { handleFileUpload } = getStrategyFunctions(FileSources.document_parser); - return await handleFileUpload({ req, file, loadAuthValues }); - } catch (err) { + const { handleFileUpload: uploadOCR } = getStrategyFunctions( + appConfig?.ocr?.strategy ?? FileSources.mistral_ocr, + ); + const { + text, + bytes, + filepath: ocrFileURL, + } = await uploadOCR({ req, file, loadAuthValues }); + return await createTextFile({ text, bytes, filepath: ocrFileURL }); + } catch (ocrError) { logger.error( - `[processAgentFileUpload] Document parser failed for "${file.originalname}":`, - err, + `[processAgentFileUpload] OCR processing failed for file "${file.originalname}", falling back to text extraction:`, + ocrError, ); } - }; - - if (shouldUseConfiguredOCR && !(await checkCapability(req, AgentCapabilities.ocr))) { - throw new Error('OCR capability is not enabled for Agents'); - } - - if (shouldUseOCR) { - const ocrResult = await resolveDocumentText(); - if (ocrResult) { - const { text, bytes, filepath: ocrFileURL } = ocrResult; - return await createTextFile({ text, bytes, filepath: ocrFileURL }); - } - throw new Error( - `Unable to extract text from "${file.originalname}". The document may be image-based and requires an OCR service to process.`, - ); } const shouldUseSTT = fileConfig.checkType( @@ -684,11 +655,11 @@ const processAgentFileUpload = async ({ req, res, metadata }) => { let filepath = _filepath; if (!messageAttachment && tool_resource) { - await db.addAgentResourceFile({ + await addAgentResourceFile({ + req, file_id, agent_id, tool_resource, - updatingUserId: req?.user?.id, }); } @@ -719,7 +690,7 @@ const processAgentFileUpload = async ({ req, res, metadata }) => { width, }); - const result = await db.createFile(fileInfo, true); + const result = await createFile(fileInfo, true); res.status(200).json({ message: 'Agent file uploaded and processed successfully', ...result }); }; @@ -765,10 +736,10 @@ const processOpenAIFile = async ({ }; if (saveFile) { - await db.createFile(file, true); + await createFile(file, true); } else if (updateUsage) { try { - await db.updateFileUsage({ file_id }); + await updateFileUsage({ file_id }); } catch (error) { logger.error('Error updating file usage', error); } @@ -806,7 +777,7 @@ const processOpenAIImageOutput = async ({ req, buffer, file_id, filename, fileEx file_id, filename, }; - db.createFile(file, true); + createFile(file, true); return file; }; @@ -950,7 +921,7 @@ async function saveBase64Image( fileName: filename, buffer: image.buffer, }); - return await db.createFile( + return await createFile( { type, source, diff --git a/api/server/services/Files/process.spec.js b/api/server/services/Files/process.spec.js deleted file mode 100644 index 39300161a8..0000000000 --- a/api/server/services/Files/process.spec.js +++ /dev/null @@ -1,344 +0,0 @@ -jest.mock('uuid', () => ({ v4: jest.fn(() => 'mock-uuid') })); - -jest.mock('@librechat/data-schemas', () => ({ - logger: { warn: jest.fn(), debug: jest.fn(), error: jest.fn() }, -})); - -jest.mock('@librechat/agents', () => ({ - EnvVar: { CODE_API_KEY: 'CODE_API_KEY' }, -})); - -jest.mock('@librechat/api', () => ({ - sanitizeFilename: jest.fn((n) => n), - parseText: jest.fn().mockResolvedValue({ text: '', bytes: 0 }), - processAudioFile: jest.fn(), -})); - -jest.mock('librechat-data-provider', () => ({ - ...jest.requireActual('librechat-data-provider'), - mergeFileConfig: jest.fn(), -})); - -jest.mock('~/server/services/Files/images', () => ({ - convertImage: jest.fn(), - resizeAndConvert: jest.fn(), - resizeImageBuffer: jest.fn(), -})); - -jest.mock('~/server/controllers/assistants/v2', () => ({ - addResourceFileId: jest.fn(), - deleteResourceFileId: jest.fn(), -})); - -jest.mock('~/server/controllers/assistants/helpers', () => ({ - getOpenAIClient: jest.fn(), -})); - -jest.mock('~/server/services/Tools/credentials', () => ({ - loadAuthValues: jest.fn(), -})); - -jest.mock('~/models', () => ({ - createFile: jest.fn().mockResolvedValue({ file_id: 'created-file-id' }), - updateFileUsage: jest.fn(), - deleteFiles: jest.fn(), - addAgentResourceFile: jest.fn().mockResolvedValue({}), - removeAgentResourceFiles: jest.fn(), -})); - -jest.mock('~/server/utils/getFileStrategy', () => ({ - getFileStrategy: jest.fn().mockReturnValue('local'), -})); - -jest.mock('~/server/services/Config', () => ({ - checkCapability: jest.fn().mockResolvedValue(true), -})); - -jest.mock('~/server/utils/queue', () => ({ - LB_QueueAsyncCall: jest.fn(), -})); - -jest.mock('~/server/services/Files/strategies', () => ({ - getStrategyFunctions: jest.fn(), -})); - -jest.mock('~/server/utils', () => ({ - determineFileType: jest.fn(), -})); - -jest.mock('~/server/services/Files/Audio/STTService', () => ({ - STTService: { getInstance: jest.fn() }, -})); - -const { EToolResources, FileSources, AgentCapabilities } = require('librechat-data-provider'); -const { mergeFileConfig } = require('librechat-data-provider'); -const { checkCapability } = require('~/server/services/Config'); -const { getStrategyFunctions } = require('~/server/services/Files/strategies'); -const { processAgentFileUpload } = require('./process'); - -const PDF_MIME = 'application/pdf'; -const DOCX_MIME = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'; -const XLSX_MIME = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; -const XLS_MIME = 'application/vnd.ms-excel'; -const ODS_MIME = 'application/vnd.oasis.opendocument.spreadsheet'; -const ODT_MIME = 'application/vnd.oasis.opendocument.text'; -const ODP_MIME = 'application/vnd.oasis.opendocument.presentation'; -const ODG_MIME = 'application/vnd.oasis.opendocument.graphics'; - -const makeReq = ({ mimetype = PDF_MIME, ocrConfig = null } = {}) => ({ - user: { id: 'user-123' }, - file: { - path: '/tmp/upload.bin', - originalname: 'upload.bin', - filename: 'upload-uuid.bin', - mimetype, - }, - body: { model: 'gpt-4o' }, - config: { - fileConfig: {}, - fileStrategy: 'local', - ocr: ocrConfig, - }, -}); - -const makeMetadata = () => ({ - agent_id: 'agent-abc', - tool_resource: EToolResources.context, - file_id: 'file-uuid-123', -}); - -const mockRes = { - status: jest.fn().mockReturnThis(), - json: jest.fn().mockReturnValue({}), -}; - -const makeFileConfig = ({ ocrSupportedMimeTypes = [] } = {}) => ({ - checkType: (mime, types) => (types ?? []).includes(mime), - ocr: { supportedMimeTypes: ocrSupportedMimeTypes }, - stt: { supportedMimeTypes: [] }, - text: { supportedMimeTypes: [] }, -}); - -describe('processAgentFileUpload', () => { - beforeEach(() => { - jest.clearAllMocks(); - mockRes.status.mockReturnThis(); - mockRes.json.mockReturnValue({}); - checkCapability.mockResolvedValue(true); - getStrategyFunctions.mockReturnValue({ - handleFileUpload: jest - .fn() - .mockResolvedValue({ text: 'extracted text', bytes: 42, filepath: 'doc://result' }), - }); - mergeFileConfig.mockReturnValue(makeFileConfig()); - }); - - describe('OCR strategy selection', () => { - test.each([ - ['PDF', PDF_MIME], - ['DOCX', DOCX_MIME], - ['XLSX', XLSX_MIME], - ['XLS', XLS_MIME], - ['ODS', ODS_MIME], - ['Excel variant (msexcel)', 'application/msexcel'], - ['Excel variant (x-msexcel)', 'application/x-msexcel'], - ])('uses document_parser automatically for %s when no OCR is configured', async (_, mime) => { - mergeFileConfig.mockReturnValue(makeFileConfig()); - const req = makeReq({ mimetype: mime, ocrConfig: null }); - - await processAgentFileUpload({ req, res: mockRes, metadata: makeMetadata() }); - - expect(getStrategyFunctions).toHaveBeenCalledWith(FileSources.document_parser); - }); - - test('does not check OCR capability when using automatic document_parser fallback', async () => { - const req = makeReq({ mimetype: PDF_MIME, ocrConfig: null }); - - await processAgentFileUpload({ req, res: mockRes, metadata: makeMetadata() }); - - expect(checkCapability).not.toHaveBeenCalledWith(expect.anything(), AgentCapabilities.ocr); - expect(getStrategyFunctions).toHaveBeenCalledWith(FileSources.document_parser); - }); - - test('uses the configured OCR strategy when OCR is set up for the file type', async () => { - mergeFileConfig.mockReturnValue(makeFileConfig({ ocrSupportedMimeTypes: [PDF_MIME] })); - const req = makeReq({ - mimetype: PDF_MIME, - ocrConfig: { strategy: FileSources.mistral_ocr }, - }); - - await processAgentFileUpload({ req, res: mockRes, metadata: makeMetadata() }); - - expect(checkCapability).toHaveBeenCalledWith(expect.anything(), AgentCapabilities.ocr); - expect(getStrategyFunctions).toHaveBeenCalledWith(FileSources.mistral_ocr); - }); - - test('uses document_parser as default when OCR is configured but no strategy is specified', async () => { - mergeFileConfig.mockReturnValue(makeFileConfig({ ocrSupportedMimeTypes: [PDF_MIME] })); - const req = makeReq({ - mimetype: PDF_MIME, - ocrConfig: { supportedMimeTypes: [PDF_MIME] }, - }); - - await processAgentFileUpload({ req, res: mockRes, metadata: makeMetadata() }); - - expect(checkCapability).toHaveBeenCalledWith(expect.anything(), AgentCapabilities.ocr); - expect(getStrategyFunctions).toHaveBeenCalledWith(FileSources.document_parser); - }); - - test('throws when configured OCR capability is not enabled for the agent', async () => { - mergeFileConfig.mockReturnValue(makeFileConfig({ ocrSupportedMimeTypes: [PDF_MIME] })); - checkCapability.mockResolvedValue(false); - const req = makeReq({ - mimetype: PDF_MIME, - ocrConfig: { strategy: FileSources.mistral_ocr }, - }); - - await expect( - processAgentFileUpload({ req, res: mockRes, metadata: makeMetadata() }), - ).rejects.toThrow('OCR capability is not enabled for Agents'); - }); - - test('uses document_parser (no capability check) when OCR capability returns false but no OCR config', async () => { - checkCapability.mockResolvedValue(false); - const req = makeReq({ mimetype: PDF_MIME, ocrConfig: null }); - - await processAgentFileUpload({ req, res: mockRes, metadata: makeMetadata() }); - - expect(checkCapability).not.toHaveBeenCalledWith(expect.anything(), AgentCapabilities.ocr); - expect(getStrategyFunctions).toHaveBeenCalledWith(FileSources.document_parser); - }); - - test('uses document_parser when OCR is configured but the file type is not in OCR supported types', async () => { - mergeFileConfig.mockReturnValue(makeFileConfig({ ocrSupportedMimeTypes: [PDF_MIME] })); - const req = makeReq({ - mimetype: DOCX_MIME, - ocrConfig: { strategy: FileSources.mistral_ocr }, - }); - - await processAgentFileUpload({ req, res: mockRes, metadata: makeMetadata() }); - - expect(checkCapability).not.toHaveBeenCalledWith(expect.anything(), AgentCapabilities.ocr); - expect(getStrategyFunctions).toHaveBeenCalledWith(FileSources.document_parser); - expect(getStrategyFunctions).not.toHaveBeenCalledWith(FileSources.mistral_ocr); - }); - - test('does not invoke any OCR strategy for unsupported MIME types without OCR config', async () => { - const req = makeReq({ mimetype: 'text/plain', ocrConfig: null }); - - await expect( - processAgentFileUpload({ req, res: mockRes, metadata: makeMetadata() }), - ).rejects.toThrow('File type text/plain is not supported for text parsing.'); - - expect(getStrategyFunctions).not.toHaveBeenCalled(); - }); - - test.each([ - ['ODT', ODT_MIME], - ['ODP', ODP_MIME], - ['ODG', ODG_MIME], - ])('routes %s through configured OCR when OCR supports the type', async (_, mime) => { - mergeFileConfig.mockReturnValue(makeFileConfig({ ocrSupportedMimeTypes: [mime] })); - const req = makeReq({ - mimetype: mime, - ocrConfig: { strategy: FileSources.mistral_ocr }, - }); - - await processAgentFileUpload({ req, res: mockRes, metadata: makeMetadata() }); - - expect(checkCapability).toHaveBeenCalledWith(expect.anything(), AgentCapabilities.ocr); - expect(getStrategyFunctions).toHaveBeenCalledWith(FileSources.mistral_ocr); - }); - - test('throws instead of falling back to parseText when document_parser fails for a document MIME type', async () => { - getStrategyFunctions.mockReturnValue({ - handleFileUpload: jest.fn().mockRejectedValue(new Error('No text found in document')), - }); - const req = makeReq({ mimetype: PDF_MIME, ocrConfig: null }); - const { parseText } = require('@librechat/api'); - - await expect( - processAgentFileUpload({ req, res: mockRes, metadata: makeMetadata() }), - ).rejects.toThrow(/image-based and requires an OCR service/); - - expect(parseText).not.toHaveBeenCalled(); - }); - - test('falls back to document_parser when configured OCR fails for a document MIME type', async () => { - mergeFileConfig.mockReturnValue(makeFileConfig({ ocrSupportedMimeTypes: [PDF_MIME] })); - const failingUpload = jest.fn().mockRejectedValue(new Error('OCR API returned 500')); - const fallbackUpload = jest - .fn() - .mockResolvedValue({ text: 'parsed text', bytes: 11, filepath: 'doc://result' }); - getStrategyFunctions - .mockReturnValueOnce({ handleFileUpload: failingUpload }) - .mockReturnValueOnce({ handleFileUpload: fallbackUpload }); - const req = makeReq({ - mimetype: PDF_MIME, - ocrConfig: { strategy: FileSources.mistral_ocr }, - }); - - await expect( - processAgentFileUpload({ req, res: mockRes, metadata: makeMetadata() }), - ).resolves.not.toThrow(); - - expect(getStrategyFunctions).toHaveBeenCalledWith(FileSources.mistral_ocr); - expect(getStrategyFunctions).toHaveBeenCalledWith(FileSources.document_parser); - }); - - test('throws when both configured OCR and document_parser fallback fail', async () => { - mergeFileConfig.mockReturnValue(makeFileConfig({ ocrSupportedMimeTypes: [PDF_MIME] })); - getStrategyFunctions.mockReturnValue({ - handleFileUpload: jest.fn().mockRejectedValue(new Error('failure')), - }); - const req = makeReq({ - mimetype: PDF_MIME, - ocrConfig: { strategy: FileSources.mistral_ocr }, - }); - const { parseText } = require('@librechat/api'); - - await expect( - processAgentFileUpload({ req, res: mockRes, metadata: makeMetadata() }), - ).rejects.toThrow(/image-based and requires an OCR service/); - - expect(parseText).not.toHaveBeenCalled(); - }); - }); - - describe('text size guard', () => { - test('throws before writing to MongoDB when extracted text exceeds 15MB', async () => { - const oversizedText = 'x'.repeat(15 * 1024 * 1024 + 1); - getStrategyFunctions.mockReturnValue({ - handleFileUpload: jest.fn().mockResolvedValue({ - text: oversizedText, - bytes: Buffer.byteLength(oversizedText, 'utf8'), - filepath: 'doc://result', - }), - }); - const req = makeReq({ mimetype: PDF_MIME, ocrConfig: null }); - const { createFile } = require('~/models'); - - await expect( - processAgentFileUpload({ req, res: mockRes, metadata: makeMetadata() }), - ).rejects.toThrow(/exceeds the 15MB storage limit/); - - expect(createFile).not.toHaveBeenCalled(); - }); - - test('succeeds when extracted text is within the 15MB limit', async () => { - const okText = 'x'.repeat(1024); - getStrategyFunctions.mockReturnValue({ - handleFileUpload: jest.fn().mockResolvedValue({ - text: okText, - bytes: Buffer.byteLength(okText, 'utf8'), - filepath: 'doc://result', - }), - }); - const req = makeReq({ mimetype: PDF_MIME, ocrConfig: null }); - - await expect( - processAgentFileUpload({ req, res: mockRes, metadata: makeMetadata() }), - ).resolves.not.toThrow(); - }); - }); -}); diff --git a/api/server/services/Files/strategies.js b/api/server/services/Files/strategies.js index 47b39cb87b..2ad526194b 100644 --- a/api/server/services/Files/strategies.js +++ b/api/server/services/Files/strategies.js @@ -1,13 +1,5 @@ const { FileSources } = require('librechat-data-provider'); const { - getS3URL, - saveURLToS3, - parseDocument, - uploadFileToS3, - S3ImageService, - saveBufferToS3, - getS3FileStream, - deleteFileFromS3, uploadMistralOCR, uploadAzureMistralOCR, uploadGoogleVertexMistralOCR, @@ -34,18 +26,17 @@ const { processLocalAvatar, getLocalFileStream, } = require('./Local'); -const { resizeImageBuffer } = require('./images/resize'); -const { updateUser, updateFile } = require('~/models'); - -const s3ImageService = new S3ImageService({ - resizeImageBuffer, - updateUser, - updateFile, -}); - -const uploadImageToS3 = (params) => s3ImageService.uploadImageToS3(params); -const prepareImageURLS3 = (_req, file) => s3ImageService.prepareImageURL(file); -const processS3Avatar = (params) => s3ImageService.processAvatar(params); +const { + getS3URL, + saveURLToS3, + saveBufferToS3, + getS3FileStream, + uploadImageToS3, + prepareImageURLS3, + deleteFileFromS3, + processS3Avatar, + uploadFileToS3, +} = require('./S3'); const { saveBufferToAzure, saveURLToAzure, @@ -255,26 +246,6 @@ const vertexMistralOCRStrategy = () => ({ handleFileUpload: uploadGoogleVertexMistralOCR, }); -const documentParserStrategy = () => ({ - /** @type {typeof saveFileFromURL | null} */ - saveURL: null, - /** @type {typeof getLocalFileURL | null} */ - getFileURL: null, - /** @type {typeof saveLocalBuffer | null} */ - saveBuffer: null, - /** @type {typeof processLocalAvatar | null} */ - processAvatar: null, - /** @type {typeof uploadLocalImage | null} */ - handleImageUpload: null, - /** @type {typeof prepareImagesLocal | null} */ - prepareImagePayload: null, - /** @type {typeof deleteLocalFile | null} */ - deleteFile: null, - /** @type {typeof getLocalFileStream | null} */ - getDownloadStream: null, - handleFileUpload: parseDocument, -}); - // Strategy Selector const getStrategyFunctions = (fileSource) => { if (fileSource === FileSources.firebase) { @@ -299,8 +270,6 @@ const getStrategyFunctions = (fileSource) => { return azureMistralOCRStrategy(); } else if (fileSource === FileSources.vertexai_mistral_ocr) { return vertexMistralOCRStrategy(); - } else if (fileSource === FileSources.document_parser) { - return documentParserStrategy(); } else if (fileSource === FileSources.text) { return localStrategy(); // Text files use local strategy } else { diff --git a/api/server/services/GraphTokenService.js b/api/server/services/GraphTokenService.js index 843adbe5a2..d5cd6a94f2 100644 --- a/api/server/services/GraphTokenService.js +++ b/api/server/services/GraphTokenService.js @@ -7,7 +7,7 @@ const getLogStores = require('~/cache/getLogStores'); /** * Get Microsoft Graph API token using existing token exchange mechanism * @param {Object} user - User object with OpenID information - * @param {string} accessToken - Federated access token used as OBO assertion + * @param {string} accessToken - Current access token from Authorization header * @param {string} scopes - Graph API scopes for the token * @param {boolean} fromCache - Whether to try getting token from cache first * @returns {Promise} Graph API token response with access_token and expires_in diff --git a/api/server/services/MCP.js b/api/server/services/MCP.js index dbb44740a9..ad1f9f5cc3 100644 --- a/api/server/services/MCP.js +++ b/api/server/services/MCP.js @@ -1,5 +1,5 @@ const { tool } = require('@langchain/core/tools'); -const { logger, getTenantId } = require('@librechat/data-schemas'); +const { logger } = require('@librechat/data-schemas'); const { Providers, StepTypes, @@ -14,9 +14,14 @@ const { normalizeJsonSchema, GenerationJobManager, resolveJsonSchemaRefs, - buildOAuthToolCallName, } = require('@librechat/api'); -const { Time, CacheKeys, Constants, isAssistantsEndpoint } = require('librechat-data-provider'); +const { + Time, + CacheKeys, + Constants, + ContentTypes, + isAssistantsEndpoint, +} = require('librechat-data-provider'); const { getOAuthReconnectionManager, getMCPServersRegistry, @@ -29,102 +34,6 @@ const { reinitMCPServer } = require('./Tools/mcp'); const { getAppConfig } = require('./Config'); const { getLogStores } = require('~/cache'); -const MAX_CACHE_SIZE = 1000; -const lastReconnectAttempts = new Map(); -const RECONNECT_THROTTLE_MS = 10_000; - -const missingToolCache = new Map(); -const MISSING_TOOL_TTL_MS = 10_000; - -function evictStale(map, ttl) { - if (map.size <= MAX_CACHE_SIZE) { - return; - } - const now = Date.now(); - for (const [key, timestamp] of map) { - if (now - timestamp >= ttl) { - map.delete(key); - } - if (map.size <= MAX_CACHE_SIZE) { - return; - } - } -} - -const unavailableMsg = - "This tool's MCP server is temporarily unavailable. Please try again shortly."; - -/** - * Resolves config-source MCP servers from admin Config overrides for the current - * request context. Returns the parsed configs keyed by server name. - * @param {import('express').Request} req - Express request with user context - * @returns {Promise>} - */ -async function resolveConfigServers(req) { - try { - const registry = getMCPServersRegistry(); - const user = req?.user; - const appConfig = await getAppConfig({ - role: user?.role, - tenantId: getTenantId(), - userId: user?.id, - }); - return await registry.ensureConfigServers(appConfig?.mcpConfig || {}); - } catch (error) { - logger.warn( - '[resolveConfigServers] Failed to resolve config servers, degrading to empty:', - error, - ); - return {}; - } -} - -/** - * Resolves config-source servers and merges all server configs (YAML + config + user DB) - * for the given user context. Shared helper for controllers needing the full merged config. - * @param {string} userId - * @param {{ id?: string, role?: string }} [user] - * @returns {Promise>} - */ -async function resolveAllMcpConfigs(userId, user) { - const registry = getMCPServersRegistry(); - const appConfig = await getAppConfig({ role: user?.role, tenantId: getTenantId(), userId }); - let configServers = {}; - try { - configServers = await registry.ensureConfigServers(appConfig?.mcpConfig || {}); - } catch (error) { - logger.warn( - '[resolveAllMcpConfigs] Config server resolution failed, continuing without:', - error, - ); - } - return await registry.getAllServerConfigs(userId, configServers); -} - -/** - * @param {string} toolName - * @param {string} serverName - */ -function createUnavailableToolStub(toolName, serverName) { - const normalizedToolKey = `${toolName}${Constants.mcp_delimiter}${normalizeServerName(serverName)}`; - const _call = async () => [unavailableMsg, null]; - const toolInstance = tool(_call, { - schema: { - type: 'object', - properties: { - input: { type: 'string', description: 'Input for the tool' }, - }, - required: [], - }, - name: normalizedToolKey, - description: unavailableMsg, - responseFormat: AgentConstants.CONTENT_AND_ARTIFACT, - }); - toolInstance.mcp = true; - toolInstance.mcpRawServerName = serverName; - return toolInstance; -} - function isEmptyObjectSchema(jsonSchema) { return ( jsonSchema != null && @@ -296,31 +205,19 @@ async function reconnectServer({ index, signal, serverName, - configServers, userMCPAuthMap, streamId = null, }) { logger.debug( `[MCP][reconnectServer] serverName: ${serverName}, user: ${user?.id}, hasUserMCPAuthMap: ${!!userMCPAuthMap}`, ); - - const throttleKey = `${user.id}:${serverName}`; - const now = Date.now(); - const lastAttempt = lastReconnectAttempts.get(throttleKey) ?? 0; - if (now - lastAttempt < RECONNECT_THROTTLE_MS) { - logger.debug(`[MCP][reconnectServer] Throttled reconnect for ${serverName}`); - return null; - } - lastReconnectAttempts.set(throttleKey, now); - evictStale(lastReconnectAttempts, RECONNECT_THROTTLE_MS); - const runId = Constants.USE_PRELIM_RESPONSE_MESSAGE_ID; const flowId = `${user.id}:${serverName}:${Date.now()}`; const flowManager = getFlowStateManager(getLogStores(CacheKeys.FLOWS)); const stepId = 'step_oauth_login_' + serverName; const toolCall = { id: flowId, - name: buildOAuthToolCallName(serverName), + name: serverName, type: 'tool_call_chunk', }; @@ -365,13 +262,12 @@ async function reconnectServer({ user, signal, serverName, - configServers, oauthStart, flowManager, userMCPAuthMap, forceNew: true, returnOnOAuth: false, - connectionTimeout: Time.THIRTY_SECONDS, + connectionTimeout: Time.TWO_MINUTES, }); } finally { // Clean up abort handler to prevent memory leaks @@ -408,14 +304,15 @@ async function createMCPTools({ config, provider, serverName, - configServers, userMCPAuthMap, streamId = null, }) { + // Early domain validation before reconnecting server (avoid wasted work on disallowed domains) + // Use getAppConfig() to support per-user/role domain restrictions const serverConfig = - config ?? (await getMCPServersRegistry().getServerConfig(serverName, user?.id, configServers)); + config ?? (await getMCPServersRegistry().getServerConfig(serverName, user?.id)); if (serverConfig?.url) { - const appConfig = await getAppConfig({ role: user?.role, tenantId: user?.tenantId }); + const appConfig = await getAppConfig({ role: user?.role }); const allowedDomains = appConfig?.mcpSettings?.allowedDomains; const isDomainAllowed = await isMCPDomainAllowed(serverConfig, allowedDomains); if (!isDomainAllowed) { @@ -430,17 +327,12 @@ async function createMCPTools({ index, signal, serverName, - configServers, userMCPAuthMap, streamId, }); - if (result === null) { - logger.debug(`[MCP][${serverName}] Reconnect throttled, skipping tool creation.`); - return []; - } if (!result || !result.tools) { logger.warn(`[MCP][${serverName}] Failed to reinitialize MCP server.`); - return []; + return; } const serverTools = []; @@ -450,7 +342,6 @@ async function createMCPTools({ user, provider, userMCPAuthMap, - configServers, streamId, availableTools: result.availableTools, toolKey: `${tool.name}${Constants.mcp_delimiter}${serverName}`, @@ -490,15 +381,16 @@ async function createMCPTool({ userMCPAuthMap, availableTools, config, - configServers, streamId = null, }) { const [toolName, serverName] = toolKey.split(Constants.mcp_delimiter); + // Runtime domain validation: check if the server's domain is still allowed + // Use getAppConfig() to support per-user/role domain restrictions const serverConfig = - config ?? (await getMCPServersRegistry().getServerConfig(serverName, user?.id, configServers)); + config ?? (await getMCPServersRegistry().getServerConfig(serverName, user?.id)); if (serverConfig?.url) { - const appConfig = await getAppConfig({ role: user?.role, tenantId: user?.tenantId }); + const appConfig = await getAppConfig({ role: user?.role }); const allowedDomains = appConfig?.mcpSettings?.allowedDomains; const isDomainAllowed = await isMCPDomainAllowed(serverConfig, allowedDomains); if (!isDomainAllowed) { @@ -510,14 +402,6 @@ async function createMCPTool({ /** @type {LCTool | undefined} */ let toolDefinition = availableTools?.[toolKey]?.function; if (!toolDefinition) { - const cachedAt = missingToolCache.get(toolKey); - if (cachedAt && Date.now() - cachedAt < MISSING_TOOL_TTL_MS) { - logger.debug( - `[MCP][${serverName}][${toolName}] Tool in negative cache, returning unavailable stub.`, - ); - return createUnavailableToolStub(toolName, serverName); - } - logger.warn( `[MCP][${serverName}][${toolName}] Requested tool not found in available tools, re-initializing MCP server.`, ); @@ -527,23 +411,15 @@ async function createMCPTool({ index, signal, serverName, - configServers, userMCPAuthMap, streamId, }); toolDefinition = result?.availableTools?.[toolKey]?.function; - - if (!toolDefinition) { - missingToolCache.set(toolKey, Date.now()); - evictStale(missingToolCache, MISSING_TOOL_TTL_MS); - } } if (!toolDefinition) { - logger.warn( - `[MCP][${serverName}][${toolName}] Tool definition not found, returning unavailable stub.`, - ); - return createUnavailableToolStub(toolName, serverName); + logger.warn(`[MCP][${serverName}][${toolName}] Tool definition not found, cannot create tool.`); + return; } return createToolInstance({ @@ -551,7 +427,6 @@ async function createMCPTool({ provider, toolName, serverName, - serverConfig, toolDefinition, streamId, }); @@ -561,14 +436,13 @@ function createToolInstance({ res, toolName, serverName, - serverConfig: capturedServerConfig, toolDefinition, - provider: capturedProvider, + provider: _provider, streamId = null, }) { /** @type {LCTool} */ const { description, parameters } = toolDefinition; - const isGoogle = capturedProvider === Providers.VERTEXAI || capturedProvider === Providers.GOOGLE; + const isGoogle = _provider === Providers.VERTEXAI || _provider === Providers.GOOGLE; let schema = parameters ? normalizeJsonSchema(resolveJsonSchemaRefs(parameters)) : null; @@ -597,7 +471,7 @@ function createToolInstance({ const flowManager = getFlowStateManager(flowsCache); derivedSignal = config?.signal ? AbortSignal.any([config.signal]) : undefined; const mcpManager = getMCPManager(userId); - const provider = (config?.metadata?.provider || capturedProvider)?.toLowerCase(); + const provider = (config?.metadata?.provider || _provider)?.toLowerCase(); const { args: _args, stepId, ...toolCall } = config.toolCall ?? {}; const flowId = `${serverName}:oauth_login:${config.metadata.thread_id}:${config.metadata.run_id}`; @@ -629,7 +503,6 @@ function createToolInstance({ const result = await mcpManager.callTool({ serverName, - serverConfig: capturedServerConfig, toolName, provider, toolArguments, @@ -653,6 +526,9 @@ function createToolInstance({ if (isAssistantsEndpoint(provider) && Array.isArray(result)) { return result[0]; } + if (isGoogle && Array.isArray(result[0]) && result[0][0]?.type === ContentTypes.TEXT) { + return [result[0][0].text, result[1]]; + } return result; } catch (error) { logger.error( @@ -697,36 +573,30 @@ function createToolInstance({ } /** - * Get MCP setup data including config, connections, and OAuth servers. - * Resolves config-source servers from admin Config overrides when tenant context is available. + * Get MCP setup data including config, connections, and OAuth servers * @param {string} userId - The user ID - * @param {{ role?: string, tenantId?: string }} [options] - Optional role/tenant context * @returns {Object} Object containing mcpConfig, appConnections, userConnections, and oauthServers */ -async function getMCPSetupData(userId, options = {}) { - const registry = getMCPServersRegistry(); - const { role, tenantId } = options; +async function getMCPSetupData(userId) { + const mcpConfig = await getMCPServersRegistry().getAllServerConfigs(userId); + + if (!mcpConfig) { + throw new Error('MCP config not found'); + } - const appConfig = await getAppConfig({ role, tenantId, userId }); - const configServers = await registry.ensureConfigServers(appConfig?.mcpConfig || {}); - const mcpConfig = await registry.getAllServerConfigs(userId, configServers); const mcpManager = getMCPManager(userId); /** @type {Map} */ let appConnections = new Map(); try { - // Use getLoaded() instead of getAll() to avoid forcing connection creation. + // Use getLoaded() instead of getAll() to avoid forcing connection creation // getAll() creates connections for all servers, which is problematic for servers - // that require user context (e.g., those with {{LIBRECHAT_USER_ID}} placeholders). + // that require user context (e.g., those with {{LIBRECHAT_USER_ID}} placeholders) appConnections = (await mcpManager.appConnections?.getLoaded()) || new Map(); } catch (error) { logger.error(`[MCP][User: ${userId}] Error getting app connections:`, error); } const userConnections = mcpManager.getUserConnections(userId) || new Map(); - const oauthServers = new Set( - Object.entries(mcpConfig) - .filter(([, config]) => config.requiresOAuth) - .map(([name]) => name), - ); + const oauthServers = await getMCPServersRegistry().getOAuthServers(userId); return { mcpConfig, @@ -848,9 +718,6 @@ module.exports = { createMCPTool, createMCPTools, getMCPSetupData, - resolveConfigServers, - resolveAllMcpConfigs, checkOAuthFlowStatus, getServerConnectionStatus, - createUnavailableToolStub, }; diff --git a/api/server/services/MCP.spec.js b/api/server/services/MCP.spec.js index c9925827f8..b2caebc91e 100644 --- a/api/server/services/MCP.spec.js +++ b/api/server/services/MCP.spec.js @@ -14,7 +14,6 @@ const mockRegistryInstance = { getOAuthServers: jest.fn(() => Promise.resolve(new Set())), getAllServerConfigs: jest.fn(() => Promise.resolve({})), getServerConfig: jest.fn(() => Promise.resolve(null)), - ensureConfigServers: jest.fn(() => Promise.resolve({})), }; // Create isMCPDomainAllowed mock that can be configured per-test @@ -46,7 +45,6 @@ const { getMCPSetupData, checkOAuthFlowStatus, getServerConnectionStatus, - createUnavailableToolStub, } = require('./MCP'); jest.mock('./Config', () => ({ @@ -114,43 +112,38 @@ describe('tests for the new helper functions used by the MCP connection status e }); it('should successfully return MCP setup data', async () => { - const mockConfigWithOAuth = { - server1: { type: 'stdio' }, - server2: { type: 'http', requiresOAuth: true }, - }; - mockRegistryInstance.getAllServerConfigs.mockResolvedValue(mockConfigWithOAuth); + mockRegistryInstance.getAllServerConfigs.mockResolvedValue(mockConfig); const mockAppConnections = new Map([['server1', { status: 'connected' }]]); const mockUserConnections = new Map([['server2', { status: 'disconnected' }]]); + const mockOAuthServers = new Set(['server2']); const mockMCPManager = { appConnections: { getLoaded: jest.fn(() => Promise.resolve(mockAppConnections)) }, getUserConnections: jest.fn(() => mockUserConnections), }; mockGetMCPManager.mockReturnValue(mockMCPManager); + mockRegistryInstance.getOAuthServers.mockResolvedValue(mockOAuthServers); const result = await getMCPSetupData(mockUserId); - expect(mockRegistryInstance.ensureConfigServers).toHaveBeenCalled(); - expect(mockRegistryInstance.getAllServerConfigs).toHaveBeenCalledWith( - mockUserId, - expect.any(Object), - ); + expect(mockRegistryInstance.getAllServerConfigs).toHaveBeenCalledWith(mockUserId); expect(mockGetMCPManager).toHaveBeenCalledWith(mockUserId); expect(mockMCPManager.appConnections.getLoaded).toHaveBeenCalled(); expect(mockMCPManager.getUserConnections).toHaveBeenCalledWith(mockUserId); + expect(mockRegistryInstance.getOAuthServers).toHaveBeenCalledWith(mockUserId); - expect(result.mcpConfig).toEqual(mockConfigWithOAuth); - expect(result.appConnections).toEqual(mockAppConnections); - expect(result.userConnections).toEqual(mockUserConnections); - expect(result.oauthServers).toEqual(new Set(['server2'])); + expect(result).toEqual({ + mcpConfig: mockConfig, + appConnections: mockAppConnections, + userConnections: mockUserConnections, + oauthServers: mockOAuthServers, + }); }); - it('should return empty data when no servers are configured', async () => { - mockRegistryInstance.getAllServerConfigs.mockResolvedValue({}); - const result = await getMCPSetupData(mockUserId); - expect(result.mcpConfig).toEqual({}); - expect(result.oauthServers).toEqual(new Set()); + it('should throw error when MCP config not found', async () => { + mockRegistryInstance.getAllServerConfigs.mockResolvedValue(null); + await expect(getMCPSetupData(mockUserId)).rejects.toThrow('MCP config not found'); }); it('should handle null values from MCP manager gracefully', async () => { @@ -1105,188 +1098,6 @@ describe('User parameter passing tests', () => { }); }); - describe('createUnavailableToolStub', () => { - it('should return a tool whose _call returns a valid CONTENT_AND_ARTIFACT two-tuple', async () => { - const stub = createUnavailableToolStub('myTool', 'myServer'); - // invoke() goes through langchain's base tool, which checks responseFormat. - // CONTENT_AND_ARTIFACT requires [content, artifact] — a bare string would throw: - // "Tool response format is "content_and_artifact" but the output was not a two-tuple" - const result = await stub.invoke({}); - // If we reach here without throwing, the two-tuple format is correct. - // invoke() returns the content portion of [content, artifact] as a string. - expect(result).toContain('temporarily unavailable'); - }); - }); - - describe('negative tool cache and throttle interaction', () => { - it('should cache tool as missing even when throttled (cross-user dedup)', async () => { - const mockUser = { id: 'throttle-test-user' }; - const mockRes = { write: jest.fn(), flush: jest.fn() }; - - // First call: reconnect succeeds but tool not found - mockReinitMCPServer.mockResolvedValueOnce({ - availableTools: {}, - }); - - await createMCPTool({ - res: mockRes, - user: mockUser, - toolKey: `missing-tool${D}cache-dedup-server`, - provider: 'openai', - userMCPAuthMap: {}, - availableTools: undefined, - }); - - // Second call within 10s for DIFFERENT tool on same server: - // reconnect is throttled (returns null), tool is still cached as missing. - // This is intentional: the cache acts as cross-user dedup since the - // throttle is per-user-per-server and can't prevent N different users - // from each triggering their own reconnect. - const result2 = await createMCPTool({ - res: mockRes, - user: mockUser, - toolKey: `other-tool${D}cache-dedup-server`, - provider: 'openai', - userMCPAuthMap: {}, - availableTools: undefined, - }); - - expect(result2).toBeDefined(); - expect(result2.name).toContain('other-tool'); - expect(mockReinitMCPServer).toHaveBeenCalledTimes(1); - }); - - it('should prevent user B from triggering reconnect when user A already cached the tool', async () => { - const userA = { id: 'cache-user-A' }; - const userB = { id: 'cache-user-B' }; - const mockRes = { write: jest.fn(), flush: jest.fn() }; - - // User A: real reconnect, tool not found → cached - mockReinitMCPServer.mockResolvedValueOnce({ - availableTools: {}, - }); - - await createMCPTool({ - res: mockRes, - user: userA, - toolKey: `shared-tool${D}cross-user-server`, - provider: 'openai', - userMCPAuthMap: {}, - availableTools: undefined, - }); - - expect(mockReinitMCPServer).toHaveBeenCalledTimes(1); - - // User B requests the SAME tool within 10s. - // The negative cache is keyed by toolKey (no user prefix), so user B - // gets a cache hit and no reconnect fires. This is the cross-user - // storm protection: without this, user B's unthrottled first request - // would trigger a second reconnect to the same server. - const result = await createMCPTool({ - res: mockRes, - user: userB, - toolKey: `shared-tool${D}cross-user-server`, - provider: 'openai', - userMCPAuthMap: {}, - availableTools: undefined, - }); - - expect(result).toBeDefined(); - expect(result.name).toContain('shared-tool'); - // reinitMCPServer still called only once — user B hit the cache - expect(mockReinitMCPServer).toHaveBeenCalledTimes(1); - }); - - it('should prevent user B from triggering reconnect for throttle-cached tools', async () => { - const userA = { id: 'storm-user-A' }; - const userB = { id: 'storm-user-B' }; - const mockRes = { write: jest.fn(), flush: jest.fn() }; - - // User A: real reconnect for tool-1, tool not found → cached - mockReinitMCPServer.mockResolvedValueOnce({ - availableTools: {}, - }); - - await createMCPTool({ - res: mockRes, - user: userA, - toolKey: `tool-1${D}storm-server`, - provider: 'openai', - userMCPAuthMap: {}, - availableTools: undefined, - }); - - // User A: tool-2 on same server within 10s → throttled → cached from throttle - await createMCPTool({ - res: mockRes, - user: userA, - toolKey: `tool-2${D}storm-server`, - provider: 'openai', - userMCPAuthMap: {}, - availableTools: undefined, - }); - - expect(mockReinitMCPServer).toHaveBeenCalledTimes(1); - - // User B requests tool-2 — gets cache hit from the throttle-cached entry. - // Without this caching, user B would trigger a real reconnect since - // user B has their own throttle key and hasn't reconnected yet. - const result = await createMCPTool({ - res: mockRes, - user: userB, - toolKey: `tool-2${D}storm-server`, - provider: 'openai', - userMCPAuthMap: {}, - availableTools: undefined, - }); - - expect(result).toBeDefined(); - expect(result.name).toContain('tool-2'); - // Still only 1 real reconnect — user B was protected by the cache - expect(mockReinitMCPServer).toHaveBeenCalledTimes(1); - }); - }); - - describe('createMCPTools throttle handling', () => { - it('should return empty array with debug log when reconnect is throttled', async () => { - const mockUser = { id: 'throttle-tools-user' }; - const mockRes = { write: jest.fn(), flush: jest.fn() }; - - // First call: real reconnect - mockReinitMCPServer.mockResolvedValueOnce({ - tools: [{ name: 'tool1' }], - availableTools: { - [`tool1${D}throttle-tools-server`]: { - function: { description: 'Tool 1', parameters: {} }, - }, - }, - }); - - await createMCPTools({ - res: mockRes, - user: mockUser, - serverName: 'throttle-tools-server', - provider: 'openai', - userMCPAuthMap: {}, - }); - - // Second call within 10s — throttled - const result = await createMCPTools({ - res: mockRes, - user: mockUser, - serverName: 'throttle-tools-server', - provider: 'openai', - userMCPAuthMap: {}, - }); - - expect(result).toEqual([]); - // reinitMCPServer called only once — second was throttled - expect(mockReinitMCPServer).toHaveBeenCalledTimes(1); - // Should log at debug level (not warn) for throttled case - expect(logger.debug).toHaveBeenCalledWith(expect.stringContaining('Reconnect throttled')); - }); - }); - describe('User parameter integrity', () => { it('should preserve user object properties through the call chain', async () => { const complexUser = { diff --git a/api/server/services/PermissionService.js b/api/server/services/PermissionService.js index fc67b0bc49..a843f48f6f 100644 --- a/api/server/services/PermissionService.js +++ b/api/server/services/PermissionService.js @@ -9,7 +9,22 @@ const { getGroupMembers, getGroupOwners, } = require('~/server/services/GraphApiService'); -const db = require('~/models'); +const { + findAccessibleResources: findAccessibleResourcesACL, + getEffectivePermissions: getEffectivePermissionsACL, + getEffectivePermissionsForResources: getEffectivePermissionsForResourcesACL, + grantPermission: grantPermissionACL, + findEntriesByPrincipalsAndResource, + findGroupByExternalId, + findRoleByIdentifier, + getUserPrincipals, + hasPermission, + createGroup, + createUser, + updateUser, + findUser, +} = require('~/models'); +const { AclEntry, AccessRole, Group } = require('~/db/models'); /** @type {boolean|null} */ let transactionSupportCache = null; @@ -81,7 +96,7 @@ const grantPermission = async ({ validateResourceType(resourceType); // Get the role to determine permission bits - const role = await db.findRoleByIdentifier(accessRoleId); + const role = await findRoleByIdentifier(accessRoleId); if (!role) { throw new Error(`Role ${accessRoleId} not found`); } @@ -92,7 +107,7 @@ const grantPermission = async ({ `Role ${accessRoleId} is for ${role.resourceType} resources, not ${resourceType}`, ); } - return await db.grantPermission( + return await grantPermissionACL( principalType, principalId, resourceType, @@ -126,13 +141,13 @@ const checkPermission = async ({ userId, role, resourceType, resourceId, require validateResourceType(resourceType); - const principals = await db.getUserPrincipals({ userId, role }); + const principals = await getUserPrincipals({ userId, role }); if (principals.length === 0) { return false; } - return await db.hasPermission(principals, resourceType, resourceId, requiredPermission); + return await hasPermission(principals, resourceType, resourceId, requiredPermission); } catch (error) { logger.error(`[PermissionService.checkPermission] Error: ${error.message}`); if (error.message.includes('requiredPermission must be')) { @@ -155,13 +170,13 @@ const getEffectivePermissions = async ({ userId, role, resourceType, resourceId try { validateResourceType(resourceType); - const principals = await db.getUserPrincipals({ userId, role }); + const principals = await getUserPrincipals({ userId, role }); if (principals.length === 0) { return 0; } - return await db.getEffectivePermissions(principals, resourceType, resourceId); + return await getEffectivePermissionsACL(principals, resourceType, resourceId); } catch (error) { logger.error(`[PermissionService.getEffectivePermissions] Error: ${error.message}`); return 0; @@ -191,10 +206,10 @@ const getResourcePermissionsMap = async ({ userId, role, resourceType, resourceI try { // Get user principals (user + groups + public) - const principals = await db.getUserPrincipals({ userId, role }); + const principals = await getUserPrincipals({ userId, role }); // Use batch method from aclEntry - const permissionsMap = await db.getEffectivePermissionsForResources( + const permissionsMap = await getEffectivePermissionsForResourcesACL( principals, resourceType, resourceIds, @@ -229,12 +244,12 @@ const findAccessibleResources = async ({ userId, role, resourceType, requiredPer validateResourceType(resourceType); // Get all principals for the user (user + groups + public) - const principalsList = await db.getUserPrincipals({ userId, role }); + const principalsList = await getUserPrincipals({ userId, role }); if (principalsList.length === 0) { return []; } - return await db.findAccessibleResources(principalsList, resourceType, requiredPermissions); + return await findAccessibleResourcesACL(principalsList, resourceType, requiredPermissions); } catch (error) { logger.error(`[PermissionService.findAccessibleResources] Error: ${error.message}`); // Re-throw validation errors @@ -260,9 +275,17 @@ const findPubliclyAccessibleResources = async ({ resourceType, requiredPermissio validateResourceType(resourceType); - return await db.findPublicResourceIds(resourceType, requiredPermissions); + // Find all public ACL entries where the public principal has at least the required permission bits + const entries = await AclEntry.find({ + principalType: PrincipalType.PUBLIC, + resourceType, + permBits: { $bitsAllSet: requiredPermissions }, + }).distinct('resourceId'); + + return entries; } catch (error) { logger.error(`[PermissionService.findPubliclyAccessibleResources] Error: ${error.message}`); + // Re-throw validation errors if (error.message.includes('requiredPermissions must be')) { throw error; } @@ -279,7 +302,7 @@ const findPubliclyAccessibleResources = async ({ resourceType, requiredPermissio const getAvailableRoles = async ({ resourceType }) => { validateResourceType(resourceType); - return await db.findRolesByResourceType(resourceType); + return await AccessRole.find({ resourceType }).lean(); }; /** @@ -308,15 +331,15 @@ const ensurePrincipalExists = async function (principal) { throw new Error('Entra ID user principals must have email and idOnTheSource'); } - let existingUser = await db.findUser({ idOnTheSource: principal.idOnTheSource }); + let existingUser = await findUser({ idOnTheSource: principal.idOnTheSource }); if (!existingUser) { - existingUser = await db.findUser({ email: principal.email }); + existingUser = await findUser({ email: principal.email }); } if (existingUser) { if (!existingUser.idOnTheSource && principal.idOnTheSource) { - await db.updateUser(existingUser._id, { + await updateUser(existingUser._id, { idOnTheSource: principal.idOnTheSource, provider: 'openid', }); @@ -332,7 +355,7 @@ const ensurePrincipalExists = async function (principal) { idOnTheSource: principal.idOnTheSource, }; - const userId = await db.createUser(userData, true, true); + const userId = await createUser(userData, true, true); return userId.toString(); } @@ -397,10 +420,10 @@ const ensureGroupPrincipalExists = async function (principal, authContext = null } } - let existingGroup = await db.findGroupByExternalId(principal.idOnTheSource, 'entra'); + let existingGroup = await findGroupByExternalId(principal.idOnTheSource, 'entra'); if (!existingGroup && principal.email) { - existingGroup = await db.findGroupByQuery({ email: principal.email.toLowerCase() }); + existingGroup = await Group.findOne({ email: principal.email.toLowerCase() }).lean(); } if (existingGroup) { @@ -429,7 +452,7 @@ const ensureGroupPrincipalExists = async function (principal, authContext = null } if (needsUpdate) { - await db.updateGroupById(existingGroup._id, updateData); + await Group.findByIdAndUpdate(existingGroup._id, { $set: updateData }, { new: true }); } return existingGroup._id.toString(); @@ -450,7 +473,7 @@ const ensureGroupPrincipalExists = async function (principal, authContext = null groupData.description = principal.description; } - const newGroup = await db.createGroup(groupData); + const newGroup = await createGroup(groupData); return newGroup._id.toString(); } if (principal.id && authContext == null) { @@ -497,7 +520,7 @@ const syncUserEntraGroupMemberships = async (user, accessToken, session = null) const sessionOptions = session ? { session } : {}; - await db.bulkUpdateGroups( + await Group.updateMany( { idOnTheSource: { $in: allGroupIds }, source: 'entra', @@ -507,13 +530,13 @@ const syncUserEntraGroupMemberships = async (user, accessToken, session = null) sessionOptions, ); - await db.bulkUpdateGroups( + await Group.updateMany( { source: 'entra', memberIds: user.idOnTheSource, idOnTheSource: { $nin: allGroupIds }, }, - { $pullAll: { memberIds: [user.idOnTheSource] } }, + { $pull: { memberIds: user.idOnTheSource } }, sessionOptions, ); } catch (error) { @@ -540,7 +563,7 @@ const hasPublicPermission = async ({ resourceType, resourceId, requiredPermissio // Use public principal to check permissions const publicPrincipal = [{ principalType: PrincipalType.PUBLIC }]; - const entries = await db.findEntriesByPrincipalsAndResource( + const entries = await findEntriesByPrincipalsAndResource( publicPrincipal, resourceType, resourceId, @@ -605,7 +628,7 @@ const bulkUpdateResourcePermissions = async ({ const sessionOptions = localSession ? { session: localSession } : {}; - const roles = await db.findRolesByResourceType(resourceType); + const roles = await AccessRole.find({ resourceType }).lean(); const rolesMap = new Map(); roles.forEach((role) => { rolesMap.set(role.accessRoleId, role); @@ -709,7 +732,7 @@ const bulkUpdateResourcePermissions = async ({ } if (bulkWrites.length > 0) { - await db.bulkWriteAclEntries(bulkWrites, sessionOptions); + await AclEntry.bulkWrite(bulkWrites, sessionOptions); } const deleteQueries = []; @@ -750,7 +773,12 @@ const bulkUpdateResourcePermissions = async ({ } if (deleteQueries.length > 0) { - await db.deleteAclEntries({ $or: deleteQueries }, sessionOptions); + await AclEntry.deleteMany( + { + $or: deleteQueries, + }, + sessionOptions, + ); } if (shouldEndSession && supportsTransactions) { @@ -760,15 +788,7 @@ const bulkUpdateResourcePermissions = async ({ return results; } catch (error) { if (shouldEndSession && supportsTransactions) { - try { - await localSession.abortTransaction(); - } catch (transactionError) { - /** best-effort abort; may fail if commit already succeeded */ - logger.error( - `[PermissionService.bulkUpdateResourcePermissions] Error aborting transaction:`, - transactionError, - ); - } + await localSession.abortTransaction(); } logger.error(`[PermissionService.bulkUpdateResourcePermissions] Error: ${error.message}`); throw error; @@ -794,7 +814,7 @@ const removeAllPermissions = async ({ resourceType, resourceId }) => { throw new Error(`Invalid resource ID: ${resourceId}`); } - const result = await db.deleteAclEntries({ + const result = await AclEntry.deleteMany({ resourceType, resourceId, }); diff --git a/api/server/services/PermissionService.spec.js b/api/server/services/PermissionService.spec.js index 477b0702b9..b41780f345 100644 --- a/api/server/services/PermissionService.spec.js +++ b/api/server/services/PermissionService.spec.js @@ -9,7 +9,6 @@ const { } = require('librechat-data-provider'); const { bulkUpdateResourcePermissions, - syncUserEntraGroupMemberships, getEffectivePermissions, findAccessibleResources, getAvailableRoles, @@ -27,11 +26,7 @@ jest.mock('@librechat/data-schemas', () => ({ // Mock GraphApiService to prevent config loading issues jest.mock('~/server/services/GraphApiService', () => ({ - entraIdPrincipalFeatureEnabled: jest.fn().mockReturnValue(false), - getUserOwnedEntraGroups: jest.fn().mockResolvedValue([]), - getUserEntraGroups: jest.fn().mockResolvedValue([]), getGroupMembers: jest.fn().mockResolvedValue([]), - getGroupOwners: jest.fn().mockResolvedValue([]), })); // Mock the logger @@ -1938,134 +1933,3 @@ describe('PermissionService', () => { }); }); }); - -describe('syncUserEntraGroupMemberships - $pullAll on Group.memberIds', () => { - const { - entraIdPrincipalFeatureEnabled, - getUserEntraGroups, - } = require('~/server/services/GraphApiService'); - const { Group } = require('~/db/models'); - - const userEntraId = 'entra-user-001'; - const user = { - openidId: 'openid-sub-001', - idOnTheSource: userEntraId, - provider: 'openid', - }; - - beforeEach(async () => { - await Group.deleteMany({}); - entraIdPrincipalFeatureEnabled.mockReturnValue(true); - }); - - afterEach(() => { - entraIdPrincipalFeatureEnabled.mockReturnValue(false); - getUserEntraGroups.mockResolvedValue([]); - }); - - it('should add user to matching Entra groups and remove from non-matching ones', async () => { - await Group.create([ - { name: 'Group A', source: 'entra', idOnTheSource: 'entra-group-a', memberIds: [] }, - { - name: 'Group B', - source: 'entra', - idOnTheSource: 'entra-group-b', - memberIds: [userEntraId], - }, - { - name: 'Group C', - source: 'entra', - idOnTheSource: 'entra-group-c', - memberIds: [userEntraId], - }, - ]); - - getUserEntraGroups.mockResolvedValue(['entra-group-a', 'entra-group-c']); - - await syncUserEntraGroupMemberships(user, 'fake-access-token'); - - const groups = await Group.find({ source: 'entra' }).sort({ name: 1 }).lean(); - expect(groups[0].memberIds).toContain(userEntraId); - expect(groups[1].memberIds).not.toContain(userEntraId); - expect(groups[2].memberIds).toContain(userEntraId); - }); - - it('should not modify groups when API returns empty list (early return)', async () => { - await Group.create([ - { - name: 'Group X', - source: 'entra', - idOnTheSource: 'entra-x', - memberIds: [userEntraId, 'other-user'], - }, - { name: 'Group Y', source: 'entra', idOnTheSource: 'entra-y', memberIds: [userEntraId] }, - ]); - - getUserEntraGroups.mockResolvedValue([]); - - await syncUserEntraGroupMemberships(user, 'fake-token'); - - const groups = await Group.find({ source: 'entra' }).sort({ name: 1 }).lean(); - expect(groups[0].memberIds).toContain(userEntraId); - expect(groups[0].memberIds).toContain('other-user'); - expect(groups[1].memberIds).toContain(userEntraId); - }); - - it('should remove user from groups not in the API response via $pullAll', async () => { - await Group.create([ - { name: 'Keep', source: 'entra', idOnTheSource: 'entra-keep', memberIds: [userEntraId] }, - { - name: 'Remove', - source: 'entra', - idOnTheSource: 'entra-remove', - memberIds: [userEntraId, 'other-user'], - }, - ]); - - getUserEntraGroups.mockResolvedValue(['entra-keep']); - - await syncUserEntraGroupMemberships(user, 'fake-token'); - - const keep = await Group.findOne({ idOnTheSource: 'entra-keep' }).lean(); - const remove = await Group.findOne({ idOnTheSource: 'entra-remove' }).lean(); - expect(keep.memberIds).toContain(userEntraId); - expect(remove.memberIds).not.toContain(userEntraId); - expect(remove.memberIds).toContain('other-user'); - }); - - it('should not modify local groups', async () => { - await Group.create([ - { name: 'Local Group', source: 'local', memberIds: [userEntraId] }, - { - name: 'Entra Group', - source: 'entra', - idOnTheSource: 'entra-only', - memberIds: [userEntraId], - }, - ]); - - getUserEntraGroups.mockResolvedValue([]); - - await syncUserEntraGroupMemberships(user, 'fake-token'); - - const localGroup = await Group.findOne({ source: 'local' }).lean(); - expect(localGroup.memberIds).toContain(userEntraId); - }); - - it('should early-return when feature is disabled', async () => { - entraIdPrincipalFeatureEnabled.mockReturnValue(false); - - await Group.create({ - name: 'Should Not Touch', - source: 'entra', - idOnTheSource: 'entra-safe', - memberIds: [userEntraId], - }); - - getUserEntraGroups.mockResolvedValue([]); - await syncUserEntraGroupMemberships(user, 'fake-token'); - - const group = await Group.findOne({ idOnTheSource: 'entra-safe' }).lean(); - expect(group.memberIds).toContain(userEntraId); - }); -}); diff --git a/api/server/services/Threads/manage.js b/api/server/services/Threads/manage.js index 27520f38a5..627dba1a35 100644 --- a/api/server/services/Threads/manage.js +++ b/api/server/services/Threads/manage.js @@ -1,15 +1,16 @@ const path = require('path'); const { v4 } = require('uuid'); -const { countTokens } = require('@librechat/api'); -const { escapeRegExp } = require('@librechat/data-schemas'); +const { countTokens, escapeRegExp } = require('@librechat/api'); const { Constants, ContentTypes, AnnotationTypes, defaultOrderQuery, } = require('librechat-data-provider'); -const { recordMessage, getMessages, spendTokens, saveConvo } = require('~/models'); const { retrieveAndProcessFile } = require('~/server/services/Files/process'); +const { recordMessage, getMessages } = require('~/models/Message'); +const { spendTokens } = require('~/models/spendTokens'); +const { saveConvo } = require('~/models/Conversation'); /** * Initializes a new thread or adds messages to an existing thread. @@ -61,6 +62,24 @@ async function initThread({ openai, body, thread_id: _thread_id }) { async function saveUserMessage(req, params) { const tokenCount = await countTokens(params.text); + // todo: do this on the frontend + // const { file_ids = [] } = params; + // let content; + // if (file_ids.length) { + // content = [ + // { + // value: params.text, + // }, + // ...( + // file_ids + // .filter(f => f) + // .map((file_id) => ({ + // file_id, + // })) + // ), + // ]; + // } + const userMessage = { user: params.user, endpoint: params.endpoint, @@ -91,15 +110,9 @@ async function saveUserMessage(req, params) { } const message = await recordMessage(userMessage); - await saveConvo( - { - userId: req?.user?.id, - isTemporary: req?.body?.isTemporary, - interfaceConfig: req?.config?.interfaceConfig, - }, - convo, - { context: 'api/server/services/Threads/manage.js #saveUserMessage' }, - ); + await saveConvo(req, convo, { + context: 'api/server/services/Threads/manage.js #saveUserMessage', + }); return message; } @@ -148,11 +161,7 @@ async function saveAssistantMessage(req, params) { }); await saveConvo( - { - userId: req?.user?.id, - isTemporary: req?.body?.isTemporary, - interfaceConfig: req?.config?.interfaceConfig, - }, + req, { endpoint: params.endpoint, conversationId: params.conversationId, @@ -344,11 +353,7 @@ async function syncMessages({ await Promise.all(recordPromises); await saveConvo( - { - userId: openai.req?.user?.id, - isTemporary: openai.req?.body?.isTemporary, - interfaceConfig: openai.req?.config?.interfaceConfig, - }, + openai.req, { conversationId, file_ids: attached_file_ids, diff --git a/api/server/services/ToolService.js b/api/server/services/ToolService.js index b4d948eda4..eedb95bd4d 100644 --- a/api/server/services/ToolService.js +++ b/api/server/services/ToolService.js @@ -12,6 +12,7 @@ const { const { sendEvent, getToolkitKey, + hasCustomUserVars, getUserMCPAuthMap, loadToolDefinitions, GenerationJobManager, @@ -19,7 +20,6 @@ const { buildWebSearchContext, buildImageToolContext, buildToolClassification, - buildOAuthToolCallName, } = require('@librechat/api'); const { Time, @@ -31,7 +31,6 @@ const { imageGenTools, EModelEndpoint, EToolResources, - isActionTool, actionDelimiter, ImageVisionTool, openapiToFunction, @@ -44,7 +43,6 @@ const { } = require('librechat-data-provider'); const { createActionTool, - legacyDomainEncode, decryptMetadata, loadActionSets, domainParser, @@ -61,35 +59,12 @@ const { manifestToolMap, toolkits } = require('~/app/clients/tools/manifest'); const { createOnSearchResults } = require('~/server/services/Tools/search'); const { loadAuthValues } = require('~/server/services/Tools/credentials'); const { reinitMCPServer } = require('~/server/services/Tools/mcp'); -const { resolveConfigServers } = require('~/server/services/MCP'); const { recordUsage } = require('~/server/services/Threads'); const { loadTools } = require('~/app/clients/tools/util'); const { redactMessage } = require('~/config/parsers'); const { findPluginAuthsByKeys } = require('~/models'); const { getFlowStateManager } = require('~/config'); const { getLogStores } = require('~/cache'); - -const domainSeparatorRegex = new RegExp(actionDomainSeparator, 'g'); - -/** - * Resolves the set of enabled agent capabilities from endpoints config, - * falling back to app-level or default capabilities for ephemeral agents. - * @param {ServerRequest} req - * @param {Object} appConfig - * @param {string} agentId - * @returns {Promise>} - */ -async function resolveAgentCapabilities(req, appConfig, agentId) { - const endpointsConfig = await getEndpointsConfig(req); - let capabilities = new Set(endpointsConfig?.[EModelEndpoint.agents]?.capabilities ?? []); - if (capabilities.size === 0 && isEphemeralAgentId(agentId)) { - capabilities = new Set( - appConfig.endpoints?.[EModelEndpoint.agents]?.capabilities ?? defaultAgentCapabilities, - ); - } - return capabilities; -} - /** * Processes the required actions by calling the appropriate tools and returning the outputs. * @param {OpenAIClient} client - OpenAI or StreamRunManager Client. @@ -178,7 +153,8 @@ async function processRequiredActions(client, requiredActions) { const promises = []; - let actionSetsData = null; + /** @type {Action[]} */ + let actionSets = []; let isActionTool = false; const ActionToolMap = {}; const ActionBuildersMap = {}; @@ -264,9 +240,9 @@ async function processRequiredActions(client, requiredActions) { if (!tool) { // throw new Error(`Tool ${currentAction.tool} not found.`); - if (!actionSetsData) { - /** @type {Action[]} */ - const actionSets = + // Load all action sets once if not already loaded + if (!actionSets.length) { + actionSets = (await loadActionSets({ assistant_id: client.req.body.assistant_id, })) ?? []; @@ -274,16 +250,11 @@ async function processRequiredActions(client, requiredActions) { // Process all action sets once // Map domains to their processed action sets const processedDomains = new Map(); - const domainLookupMap = new Map(); + const domainMap = new Map(); for (const action of actionSets) { const domain = await domainParser(action.metadata.domain, true); - domainLookupMap.set(domain, domain); - - const legacyDomain = legacyDomainEncode(action.metadata.domain); - if (legacyDomain !== domain) { - domainLookupMap.set(legacyDomain, domain); - } + domainMap.set(domain, action); const isDomainAllowed = await isActionDomainAllowed( action.metadata.domain, @@ -338,26 +309,27 @@ async function processRequiredActions(client, requiredActions) { ActionBuildersMap[action.metadata.domain] = requestBuilders; } - actionSetsData = { domainLookupMap, processedDomains }; + // Update actionSets reference to use the domain map + actionSets = { domainMap, processedDomains }; } + // Find the matching domain for this tool let currentDomain = ''; - let matchedKey = ''; - for (const [key, canonical] of actionSetsData.domainLookupMap.entries()) { - if (currentAction.tool.includes(key)) { - currentDomain = canonical; - matchedKey = key; + for (const domain of actionSets.domainMap.keys()) { + if (currentAction.tool.includes(domain)) { + currentDomain = domain; break; } } - if (!currentDomain || !actionSetsData.processedDomains.has(currentDomain)) { + if (!currentDomain || !actionSets.processedDomains.has(currentDomain)) { + // TODO: try `function` if no action set is found + // throw new Error(`Tool ${currentAction.tool} not found.`); continue; } - const { action, requestBuilders, encrypted } = - actionSetsData.processedDomains.get(currentDomain); - const functionName = currentAction.tool.replace(`${actionDelimiter}${matchedKey}`, ''); + const { action, requestBuilders, encrypted } = actionSets.processedDomains.get(currentDomain); + const functionName = currentAction.tool.replace(`${actionDelimiter}${currentDomain}`, ''); const requestBuilder = requestBuilders[functionName]; if (!requestBuilder) { @@ -474,11 +446,17 @@ async function loadToolDefinitionsWrapper({ req, res, agent, streamId = null, to } const appConfig = req.config; - const enabledCapabilities = await resolveAgentCapabilities(req, appConfig, agent.id); + const endpointsConfig = await getEndpointsConfig(req); + let enabledCapabilities = new Set(endpointsConfig?.[EModelEndpoint.agents]?.capabilities ?? []); + + if (enabledCapabilities.size === 0 && isEphemeralAgentId(agent.id)) { + enabledCapabilities = new Set( + appConfig.endpoints?.[EModelEndpoint.agents]?.capabilities ?? defaultAgentCapabilities, + ); + } const checkCapability = (capability) => enabledCapabilities.has(capability); const areToolsEnabled = checkCapability(AgentCapabilities.tools); - const actionsEnabled = checkCapability(AgentCapabilities.actions); const deferredToolsEnabled = checkCapability(AgentCapabilities.deferred_tools); const filteredTools = agent.tools?.filter((tool) => { @@ -491,10 +469,7 @@ async function loadToolDefinitionsWrapper({ req, res, agent, streamId = null, to if (tool === Tools.web_search) { return checkCapability(AgentCapabilities.web_search); } - if (isActionTool(tool)) { - return actionsEnabled; - } - if (!areToolsEnabled) { + if (!areToolsEnabled && !tool.includes(actionDelimiter)) { return false; } return true; @@ -506,7 +481,7 @@ async function loadToolDefinitionsWrapper({ req, res, agent, streamId = null, to /** @type {Record>} */ let userMCPAuthMap; - if (agent.tools?.some((t) => t.includes(Constants.mcp_delimiter))) { + if (hasCustomUserVars(req.config)) { userMCPAuthMap = await getUserMCPAuthMap({ tools: agent.tools, userId: req.user.id, @@ -516,7 +491,6 @@ async function loadToolDefinitionsWrapper({ req, res, agent, streamId = null, to const flowsCache = getLogStores(CacheKeys.FLOWS); const flowManager = getFlowStateManager(flowsCache); - const configServers = await resolveConfigServers(req); const pendingOAuthServers = new Set(); const createOAuthEmitter = (serverName) => { @@ -525,7 +499,7 @@ async function loadToolDefinitionsWrapper({ req, res, agent, streamId = null, to const stepId = 'step_oauth_login_' + serverName; const toolCall = { id: flowId, - name: buildOAuthToolCallName(serverName), + name: serverName, type: 'tool_call_chunk', }; @@ -582,7 +556,6 @@ async function loadToolDefinitionsWrapper({ req, res, agent, streamId = null, to oauthStart, flowManager, serverName, - configServers, userMCPAuthMap, }); @@ -597,17 +570,12 @@ async function loadToolDefinitionsWrapper({ req, res, agent, streamId = null, to const definitions = []; const allowedDomains = appConfig?.actions?.allowedDomains; - const normalizedToolNames = new Set( - actionToolNames.map((n) => n.replace(domainSeparatorRegex, '_')), - ); + const domainSeparatorRegex = new RegExp(actionDomainSeparator, 'g'); for (const action of actionSets) { const domain = await domainParser(action.metadata.domain, true); const normalizedDomain = domain.replace(domainSeparatorRegex, '_'); - const legacyDomain = legacyDomainEncode(action.metadata.domain); - const legacyNormalized = legacyDomain.replace(domainSeparatorRegex, '_'); - const isDomainAllowed = await isActionDomainAllowed(action.metadata.domain, allowedDomains); if (!isDomainAllowed) { logger.warn( @@ -627,8 +595,7 @@ async function loadToolDefinitionsWrapper({ req, res, agent, streamId = null, to for (const sig of functionSignatures) { const toolName = `${sig.name}${actionDelimiter}${normalizedDomain}`; - const legacyToolName = `${sig.name}${actionDelimiter}${legacyNormalized}`; - if (!normalizedToolNames.has(toolName) && !normalizedToolNames.has(legacyToolName)) { + if (!actionToolNames.some((name) => name.replace(domainSeparatorRegex, '_') === toolName)) { continue; } @@ -670,7 +637,6 @@ async function loadToolDefinitionsWrapper({ req, res, agent, streamId = null, to const result = await reinitMCPServer({ user: req.user, serverName, - configServers, userMCPAuthMap, flowManager, returnOnOAuth: false, @@ -800,7 +766,6 @@ async function loadToolDefinitionsWrapper({ req, res, agent, streamId = null, to toolContextMap, toolDefinitions, hasDeferredTools, - actionsEnabled, }; } @@ -844,7 +809,14 @@ async function loadAgentTools({ } const appConfig = req.config; - const enabledCapabilities = await resolveAgentCapabilities(req, appConfig, agent.id); + const endpointsConfig = await getEndpointsConfig(req); + let enabledCapabilities = new Set(endpointsConfig?.[EModelEndpoint.agents]?.capabilities ?? []); + /** Edge case: use defined/fallback capabilities when the "agents" endpoint is not enabled */ + if (enabledCapabilities.size === 0 && isEphemeralAgentId(agent.id)) { + enabledCapabilities = new Set( + appConfig.endpoints?.[EModelEndpoint.agents]?.capabilities ?? defaultAgentCapabilities, + ); + } const checkCapability = (capability) => { const enabled = enabledCapabilities.has(capability); if (!enabled) { @@ -861,7 +833,6 @@ async function loadAgentTools({ return enabled; }; const areToolsEnabled = checkCapability(AgentCapabilities.tools); - const actionsEnabled = checkCapability(AgentCapabilities.actions); let includesWebSearch = false; const _agentTools = agent.tools?.filter((tool) => { @@ -872,9 +843,7 @@ async function loadAgentTools({ } else if (tool === Tools.web_search) { includesWebSearch = checkCapability(AgentCapabilities.web_search); return includesWebSearch; - } else if (isActionTool(tool)) { - return actionsEnabled; - } else if (!areToolsEnabled) { + } else if (!areToolsEnabled && !tool.includes(actionDelimiter)) { return false; } return true; @@ -891,7 +860,8 @@ async function loadAgentTools({ /** @type {Record>} */ let userMCPAuthMap; - if (agent.tools?.some((t) => t.includes(Constants.mcp_delimiter))) { + //TODO pass config from registry + if (hasCustomUserVars(req.config)) { userMCPAuthMap = await getUserMCPAuthMap({ tools: agent.tools, userId: req.user.id, @@ -979,15 +949,13 @@ async function loadAgentTools({ agentTools.push(...additionalTools); - const hasActionTools = _agentTools.some((t) => isActionTool(t)); - if (!hasActionTools) { + if (!checkCapability(AgentCapabilities.actions)) { return { toolRegistry, userMCPAuthMap, toolContextMap, toolDefinitions, hasDeferredTools, - actionsEnabled, tools: agentTools, }; } @@ -1003,22 +971,19 @@ async function loadAgentTools({ toolContextMap, toolDefinitions, hasDeferredTools, - actionsEnabled, tools: agentTools, }; } + // Process each action set once (validate spec, decrypt metadata) const processedActionSets = new Map(); - const domainLookupMap = new Map(); + const domainMap = new Map(); for (const action of actionSets) { const domain = await domainParser(action.metadata.domain, true); - domainLookupMap.set(domain, domain); + domainMap.set(domain, action); - const legacyDomain = legacyDomainEncode(action.metadata.domain); - if (legacyDomain !== domain) { - domainLookupMap.set(legacyDomain, domain); - } + // Check if domain is allowed (do this once per action set) const isDomainAllowed = await isActionDomainAllowed( action.metadata.domain, appConfig?.actions?.allowedDomains, @@ -1080,12 +1045,11 @@ async function loadAgentTools({ continue; } + // Find the matching domain for this tool let currentDomain = ''; - let matchedKey = ''; - for (const [key, canonical] of domainLookupMap.entries()) { - if (toolName.includes(key)) { - currentDomain = canonical; - matchedKey = key; + for (const domain of domainMap.keys()) { + if (toolName.includes(domain)) { + currentDomain = domain; break; } } @@ -1096,7 +1060,7 @@ async function loadAgentTools({ const { action, encrypted, zodSchemas, requestBuilders, functionSignatures } = processedActionSets.get(currentDomain); - const functionName = toolName.replace(`${actionDelimiter}${matchedKey}`, ''); + const functionName = toolName.replace(`${actionDelimiter}${currentDomain}`, ''); const functionSig = functionSignatures.find((sig) => sig.name === functionName); const requestBuilder = requestBuilders[functionName]; const zodSchema = zodSchemas[functionName]; @@ -1139,7 +1103,6 @@ async function loadAgentTools({ userMCPAuthMap, toolDefinitions, hasDeferredTools, - actionsEnabled, tools: agentTools, }; } @@ -1157,11 +1120,9 @@ async function loadAgentTools({ * @param {AbortSignal} [params.signal] - Abort signal * @param {Object} params.agent - The agent object * @param {string[]} params.toolNames - Names of tools to load - * @param {Map} [params.toolRegistry] - Tool registry * @param {Record>} [params.userMCPAuthMap] - User MCP auth map * @param {Object} [params.tool_resources] - Tool resources * @param {string|null} [params.streamId] - Stream ID for web search callbacks - * @param {boolean} [params.actionsEnabled] - Whether the actions capability is enabled * @returns {Promise<{ loadedTools: Array, configurable: Object }>} */ async function loadToolsForExecution({ @@ -1174,17 +1135,11 @@ async function loadToolsForExecution({ userMCPAuthMap, tool_resources, streamId = null, - actionsEnabled, }) { const appConfig = req.config; const allLoadedTools = []; const configurable = { userMCPAuthMap }; - if (actionsEnabled === undefined) { - const enabledCapabilities = await resolveAgentCapabilities(req, appConfig, agent?.id); - actionsEnabled = enabledCapabilities.has(AgentCapabilities.actions); - } - const isToolSearch = toolNames.includes(AgentConstants.TOOL_SEARCH); const isPTC = toolNames.includes(AgentConstants.PROGRAMMATIC_TOOL_CALLING); @@ -1238,12 +1193,10 @@ async function loadToolsForExecution({ ? [...new Set([...requestedNonSpecialToolNames, ...ptcOrchestratedToolNames])] : requestedNonSpecialToolNames; - const actionToolNames = []; - const regularToolNames = []; - for (const name of allToolNamesToLoad) { - (isActionTool(name) ? actionToolNames : regularToolNames).push(name); - } + const actionToolNames = allToolNamesToLoad.filter((name) => name.includes(actionDelimiter)); + const regularToolNames = allToolNamesToLoad.filter((name) => !name.includes(actionDelimiter)); + /** @type {Record} */ if (regularToolNames.length > 0) { const includesWebSearch = regularToolNames.includes(Tools.web_search); const webSearchCallbacks = includesWebSearch ? createOnSearchResults(res, streamId) : undefined; @@ -1274,7 +1227,7 @@ async function loadToolsForExecution({ } } - if (actionToolNames.length > 0 && agent && actionsEnabled) { + if (actionToolNames.length > 0 && agent) { const actionTools = await loadActionToolsForExecution({ req, res, @@ -1284,11 +1237,6 @@ async function loadToolsForExecution({ actionToolNames, }); allLoadedTools.push(...actionTools); - } else if (actionToolNames.length > 0 && agent && !actionsEnabled) { - logger.warn( - `[loadToolsForExecution] Capability "${AgentCapabilities.actions}" disabled. ` + - `Skipping action tool execution. User: ${req.user.id} | Agent: ${agent.id} | Tools: ${actionToolNames.join(', ')}`, - ); } if (isPTC && allLoadedTools.length > 0) { @@ -1334,20 +1282,12 @@ async function loadActionToolsForExecution({ } const processedActionSets = new Map(); - /** Maps both new and legacy normalized domains to their canonical (new) domain key */ - const normalizedToDomain = new Map(); + const domainMap = new Map(); const allowedDomains = appConfig?.actions?.allowedDomains; for (const action of actionSets) { const domain = await domainParser(action.metadata.domain, true); - const normalizedDomain = domain.replace(domainSeparatorRegex, '_'); - normalizedToDomain.set(normalizedDomain, domain); - - const legacyDomain = legacyDomainEncode(action.metadata.domain); - const legacyNormalized = legacyDomain.replace(domainSeparatorRegex, '_'); - if (legacyNormalized !== normalizedDomain) { - normalizedToDomain.set(legacyNormalized, domain); - } + domainMap.set(domain, action); const isDomainAllowed = await isActionDomainAllowed(action.metadata.domain, allowedDomains); if (!isDomainAllowed) { @@ -1396,15 +1336,16 @@ async function loadActionToolsForExecution({ functionSignatures, zodSchemas, encrypted, - legacyNormalized, }); } + const domainSeparatorRegex = new RegExp(actionDomainSeparator, 'g'); for (const toolName of actionToolNames) { let currentDomain = ''; - for (const [normalizedDomain, canonicalDomain] of normalizedToDomain.entries()) { + for (const domain of domainMap.keys()) { + const normalizedDomain = domain.replace(domainSeparatorRegex, '_'); if (toolName.includes(normalizedDomain)) { - currentDomain = canonicalDomain; + currentDomain = domain; break; } } @@ -1413,7 +1354,7 @@ async function loadActionToolsForExecution({ continue; } - const { action, encrypted, zodSchemas, requestBuilders, functionSignatures, legacyNormalized } = + const { action, encrypted, zodSchemas, requestBuilders, functionSignatures } = processedActionSets.get(currentDomain); const normalizedDomain = currentDomain.replace(domainSeparatorRegex, '_'); const functionName = toolName.replace(`${actionDelimiter}${normalizedDomain}`, ''); @@ -1422,25 +1363,6 @@ async function loadActionToolsForExecution({ const zodSchema = zodSchemas[functionName]; if (!requestBuilder) { - const legacyFnName = toolName.replace(`${actionDelimiter}${legacyNormalized}`, ''); - if (legacyFnName !== toolName && requestBuilders[legacyFnName]) { - const legacyTool = await createActionTool({ - userId: req.user.id, - res, - action, - streamId, - encrypted, - requestBuilder: requestBuilders[legacyFnName], - zodSchema: zodSchemas[legacyFnName], - name: toolName, - description: - functionSignatures.find((sig) => sig.name === legacyFnName)?.description ?? '', - useSSRFProtection: !Array.isArray(allowedDomains) || allowedDomains.length === 0, - }); - if (legacyTool) { - loadedActionTools.push(legacyTool); - } - } continue; } @@ -1475,5 +1397,4 @@ module.exports = { loadAgentTools, loadToolsForExecution, processRequiredActions, - resolveAgentCapabilities, }; diff --git a/api/server/services/Tools/mcp.js b/api/server/services/Tools/mcp.js index f1ebcf9796..10f2d71a18 100644 --- a/api/server/services/Tools/mcp.js +++ b/api/server/services/Tools/mcp.js @@ -1,8 +1,8 @@ const { logger } = require('@librechat/data-schemas'); const { CacheKeys, Constants } = require('librechat-data-provider'); -const { getMCPManager, getMCPServersRegistry, getFlowStateManager } = require('~/config'); const { findToken, createToken, updateToken, deleteTokens } = require('~/models'); const { updateMCPServerTools } = require('~/server/services/Config'); +const { getMCPManager, getFlowStateManager } = require('~/config'); const { getLogStores } = require('~/cache'); /** @@ -25,13 +25,11 @@ async function reinitMCPServer({ signal, forceNew, serverName, - configServers, userMCPAuthMap, connectionTimeout, returnOnOAuth = true, oauthStart: _oauthStart, flowManager: _flowManager, - serverConfig: providedConfig, }) { /** @type {MCPConnection | null} */ let connection = null; @@ -43,48 +41,6 @@ async function reinitMCPServer({ let oauthUrl = null; try { - const registry = getMCPServersRegistry(); - const serverConfig = - providedConfig ?? (await registry.getServerConfig(serverName, user?.id, configServers)); - if (serverConfig?.inspectionFailed) { - if (serverConfig.source === 'config') { - logger.info( - `[MCP Reinitialize] Config-source server ${serverName} has inspectionFailed — retry handled by config cache`, - ); - return { - availableTools: null, - success: false, - message: `MCP server '${serverName}' is still unreachable`, - oauthRequired: false, - serverName, - oauthUrl: null, - tools: null, - }; - } - logger.info( - `[MCP Reinitialize] Server ${serverName} had failed inspection, attempting reinspection`, - ); - try { - const storageLocation = serverConfig.source === 'user' ? 'DB' : 'CACHE'; - await registry.reinspectServer(serverName, storageLocation, user?.id); - logger.info(`[MCP Reinitialize] Reinspection succeeded for server: ${serverName}`); - } catch (reinspectError) { - logger.error( - `[MCP Reinitialize] Reinspection failed for server ${serverName}:`, - reinspectError, - ); - return { - availableTools: null, - success: false, - message: `MCP server '${serverName}' is still unreachable`, - oauthRequired: false, - serverName, - oauthUrl: null, - tools: null, - }; - } - } - const customUserVars = userMCPAuthMap?.[`${Constants.mcp_prefix}${serverName}`]; const flowManager = _flowManager ?? getFlowStateManager(getLogStores(CacheKeys.FLOWS)); const mcpManager = getMCPManager(); @@ -110,7 +66,6 @@ async function reinitMCPServer({ returnOnOAuth, customUserVars, connectionTimeout, - serverConfig, }); logger.info(`[MCP Reinitialize] Successfully established connection for ${serverName}`); @@ -143,7 +98,6 @@ async function reinitMCPServer({ oauthStart, customUserVars, connectionTimeout, - configServers, }); if (discoveryResult.tools && discoveryResult.tools.length > 0) { diff --git a/api/server/services/__tests__/MCP.spec.js b/api/server/services/__tests__/MCP.spec.js deleted file mode 100644 index 39e99d54ac..0000000000 --- a/api/server/services/__tests__/MCP.spec.js +++ /dev/null @@ -1,131 +0,0 @@ -const mockRegistry = { - ensureConfigServers: jest.fn(), - getAllServerConfigs: jest.fn(), -}; - -jest.mock('~/config', () => ({ - getMCPServersRegistry: jest.fn(() => mockRegistry), - getMCPManager: jest.fn(), - getFlowStateManager: jest.fn(), - getOAuthReconnectionManager: jest.fn(), -})); - -jest.mock('@librechat/data-schemas', () => ({ - getTenantId: jest.fn(() => 'tenant-1'), - logger: { debug: jest.fn(), info: jest.fn(), warn: jest.fn(), error: jest.fn() }, -})); - -jest.mock('~/server/services/Config', () => ({ - getAppConfig: jest.fn(), - setCachedTools: jest.fn(), - getCachedTools: jest.fn(), - getMCPServerTools: jest.fn(), - loadCustomConfig: jest.fn(), -})); - -jest.mock('~/cache', () => ({ getLogStores: jest.fn() })); -jest.mock('~/models', () => ({ - findToken: jest.fn(), - createToken: jest.fn(), - updateToken: jest.fn(), -})); -jest.mock('~/server/services/GraphTokenService', () => ({ - getGraphApiToken: jest.fn(), -})); -jest.mock('~/server/services/Tools/mcp', () => ({ - reinitMCPServer: jest.fn(), -})); - -const { getAppConfig } = require('~/server/services/Config'); -const { resolveConfigServers, resolveAllMcpConfigs } = require('../MCP'); - -describe('resolveConfigServers', () => { - beforeEach(() => jest.clearAllMocks()); - - it('resolves config servers for the current request context', async () => { - getAppConfig.mockResolvedValue({ mcpConfig: { srv: { url: 'http://a' } } }); - mockRegistry.ensureConfigServers.mockResolvedValue({ srv: { name: 'srv' } }); - - const result = await resolveConfigServers({ user: { id: 'u1', role: 'admin' } }); - - expect(result).toEqual({ srv: { name: 'srv' } }); - expect(getAppConfig).toHaveBeenCalledWith( - expect.objectContaining({ role: 'admin', userId: 'u1' }), - ); - expect(mockRegistry.ensureConfigServers).toHaveBeenCalledWith({ srv: { url: 'http://a' } }); - }); - - it('returns {} when ensureConfigServers throws', async () => { - getAppConfig.mockResolvedValue({ mcpConfig: { srv: {} } }); - mockRegistry.ensureConfigServers.mockRejectedValue(new Error('inspect failed')); - - const result = await resolveConfigServers({ user: { id: 'u1' } }); - - expect(result).toEqual({}); - }); - - it('returns {} when getAppConfig throws', async () => { - getAppConfig.mockRejectedValue(new Error('db timeout')); - - const result = await resolveConfigServers({ user: { id: 'u1' } }); - - expect(result).toEqual({}); - }); - - it('passes empty mcpConfig when appConfig has none', async () => { - getAppConfig.mockResolvedValue({}); - mockRegistry.ensureConfigServers.mockResolvedValue({}); - - await resolveConfigServers({ user: { id: 'u1' } }); - - expect(mockRegistry.ensureConfigServers).toHaveBeenCalledWith({}); - }); -}); - -describe('resolveAllMcpConfigs', () => { - beforeEach(() => jest.clearAllMocks()); - - it('merges config servers with base servers', async () => { - getAppConfig.mockResolvedValue({ mcpConfig: { cfg_srv: {} } }); - mockRegistry.ensureConfigServers.mockResolvedValue({ cfg_srv: { name: 'cfg_srv' } }); - mockRegistry.getAllServerConfigs.mockResolvedValue({ - cfg_srv: { name: 'cfg_srv' }, - yaml_srv: { name: 'yaml_srv' }, - }); - - const result = await resolveAllMcpConfigs('u1', { id: 'u1', role: 'user' }); - - expect(result).toEqual({ - cfg_srv: { name: 'cfg_srv' }, - yaml_srv: { name: 'yaml_srv' }, - }); - expect(mockRegistry.getAllServerConfigs).toHaveBeenCalledWith('u1', { - cfg_srv: { name: 'cfg_srv' }, - }); - }); - - it('continues with empty configServers when ensureConfigServers fails', async () => { - getAppConfig.mockResolvedValue({ mcpConfig: { srv: {} } }); - mockRegistry.ensureConfigServers.mockRejectedValue(new Error('inspect failed')); - mockRegistry.getAllServerConfigs.mockResolvedValue({ yaml_srv: { name: 'yaml_srv' } }); - - const result = await resolveAllMcpConfigs('u1', { id: 'u1' }); - - expect(result).toEqual({ yaml_srv: { name: 'yaml_srv' } }); - expect(mockRegistry.getAllServerConfigs).toHaveBeenCalledWith('u1', {}); - }); - - it('propagates getAllServerConfigs failures', async () => { - getAppConfig.mockResolvedValue({ mcpConfig: {} }); - mockRegistry.ensureConfigServers.mockResolvedValue({}); - mockRegistry.getAllServerConfigs.mockRejectedValue(new Error('redis down')); - - await expect(resolveAllMcpConfigs('u1', { id: 'u1' })).rejects.toThrow('redis down'); - }); - - it('propagates getAppConfig failures', async () => { - getAppConfig.mockRejectedValue(new Error('mongo down')); - - await expect(resolveAllMcpConfigs('u1', { id: 'u1' })).rejects.toThrow('mongo down'); - }); -}); diff --git a/api/server/services/__tests__/ToolService.spec.js b/api/server/services/__tests__/ToolService.spec.js index 740bb06e5a..2f00bbc3d6 100644 --- a/api/server/services/__tests__/ToolService.spec.js +++ b/api/server/services/__tests__/ToolService.spec.js @@ -1,363 +1,15 @@ -const { - Tools, - Constants, - EModelEndpoint, - isActionTool, - actionDelimiter, - AgentCapabilities, - defaultAgentCapabilities, -} = require('librechat-data-provider'); - -const mockGetEndpointsConfig = jest.fn(); -const mockGetMCPServerTools = jest.fn(); -const mockGetCachedTools = jest.fn(); -jest.mock('~/server/services/Config', () => ({ - getEndpointsConfig: (...args) => mockGetEndpointsConfig(...args), - getMCPServerTools: (...args) => mockGetMCPServerTools(...args), - getCachedTools: (...args) => mockGetCachedTools(...args), -})); - -const mockLoadToolDefinitions = jest.fn(); -const mockGetUserMCPAuthMap = jest.fn(); -jest.mock('@librechat/api', () => ({ - ...jest.requireActual('@librechat/api'), - loadToolDefinitions: (...args) => mockLoadToolDefinitions(...args), - getUserMCPAuthMap: (...args) => mockGetUserMCPAuthMap(...args), -})); - -const mockLoadToolsUtil = jest.fn(); -jest.mock('~/app/clients/tools/util', () => ({ - loadTools: (...args) => mockLoadToolsUtil(...args), -})); - -const mockLoadActionSets = jest.fn(); -jest.mock('~/server/services/Tools/credentials', () => ({ - loadAuthValues: jest.fn().mockResolvedValue({}), -})); -jest.mock('~/server/services/Tools/search', () => ({ - createOnSearchResults: jest.fn(), -})); -jest.mock('~/server/services/Tools/mcp', () => ({ - reinitMCPServer: jest.fn(), -})); -jest.mock('~/server/services/Files/process', () => ({ - processFileURL: jest.fn(), - uploadImageBuffer: jest.fn(), -})); -jest.mock('~/app/clients/tools/util/fileSearch', () => ({ - primeFiles: jest.fn().mockResolvedValue({}), -})); -jest.mock('~/server/services/Files/Code/process', () => ({ - primeFiles: jest.fn().mockResolvedValue({}), -})); -jest.mock('../ActionService', () => ({ - loadActionSets: (...args) => mockLoadActionSets(...args), - decryptMetadata: jest.fn(), - createActionTool: jest.fn(), - domainParser: jest.fn(), -})); -jest.mock('~/server/services/Threads', () => ({ - recordUsage: jest.fn(), -})); -jest.mock('~/models', () => ({ - findPluginAuthsByKeys: jest.fn(), -})); -jest.mock('~/config', () => ({ - getFlowStateManager: jest.fn(() => ({})), -})); -jest.mock('~/server/services/MCP', () => ({ - resolveConfigServers: jest.fn().mockResolvedValue({}), -})); -jest.mock('~/cache', () => ({ - getLogStores: jest.fn(() => ({})), -})); - -const { - loadAgentTools, - loadToolsForExecution, - resolveAgentCapabilities, -} = require('../ToolService'); - -function createMockReq(capabilities) { - return { - user: { id: 'user_123' }, - config: { - endpoints: { - [EModelEndpoint.agents]: { - capabilities, - }, - }, - }, - }; -} - -function createEndpointsConfig(capabilities) { - return { - [EModelEndpoint.agents]: { capabilities }, - }; -} - -describe('ToolService - Action Capability Gating', () => { - beforeEach(() => { - jest.clearAllMocks(); - mockLoadToolDefinitions.mockResolvedValue({ - toolDefinitions: [], - toolRegistry: new Map(), - hasDeferredTools: false, - }); - mockLoadToolsUtil.mockResolvedValue({ loadedTools: [], toolContextMap: {} }); - mockLoadActionSets.mockResolvedValue([]); - }); - - describe('resolveAgentCapabilities', () => { - it('should return capabilities from endpoints config', async () => { - const capabilities = [AgentCapabilities.tools, AgentCapabilities.actions]; - const req = createMockReq(capabilities); - mockGetEndpointsConfig.mockResolvedValue(createEndpointsConfig(capabilities)); - - const result = await resolveAgentCapabilities(req, req.config, 'agent_123'); - - expect(result).toBeInstanceOf(Set); - expect(result.has(AgentCapabilities.tools)).toBe(true); - expect(result.has(AgentCapabilities.actions)).toBe(true); - expect(result.has(AgentCapabilities.web_search)).toBe(false); - }); - - it('should fall back to default capabilities for ephemeral agents with empty config', async () => { - const req = createMockReq(defaultAgentCapabilities); - mockGetEndpointsConfig.mockResolvedValue({}); - - const result = await resolveAgentCapabilities(req, req.config, Constants.EPHEMERAL_AGENT_ID); - - for (const cap of defaultAgentCapabilities) { - expect(result.has(cap)).toBe(true); - } - }); - - it('should return empty set when no capabilities and not ephemeral', async () => { - const req = createMockReq([]); - mockGetEndpointsConfig.mockResolvedValue({}); - - const result = await resolveAgentCapabilities(req, req.config, 'agent_123'); - - expect(result.size).toBe(0); - }); - }); - - describe('isActionTool — cross-delimiter collision guard', () => { - it('should identify real action tools', () => { - expect(isActionTool(`get_weather${actionDelimiter}api_example_com`)).toBe(true); - expect(isActionTool(`fetch_data${actionDelimiter}my---domain---com`)).toBe(true); - }); - - it('should identify action tools whose operationId contains _mcp_', () => { - expect(isActionTool(`sync_mcp_state${actionDelimiter}api---example---com`)).toBe(true); - expect(isActionTool(`get_mcp_config${actionDelimiter}internal---api---com`)).toBe(true); - }); - - it('should reject MCP tools whose name ends with _action', () => { - expect(isActionTool(`get_action${Constants.mcp_delimiter}myserver`)).toBe(false); - expect(isActionTool(`fetch_action${Constants.mcp_delimiter}server_name`)).toBe(false); - expect(isActionTool(`retrieve_action${Constants.mcp_delimiter}srv`)).toBe(false); - }); - - it('should reject MCP tools with _action_ in the middle of their name', () => { - expect(isActionTool(`get_action_data${Constants.mcp_delimiter}myserver`)).toBe(false); - expect(isActionTool(`create_action_item${Constants.mcp_delimiter}server`)).toBe(false); - }); - - it('should reject tools without the action delimiter', () => { - expect(isActionTool('calculator')).toBe(false); - expect(isActionTool(`web_search${Constants.mcp_delimiter}myserver`)).toBe(false); - }); - - it('known limitation: non-RFC domain with _mcp_ substring yields false negative', () => { - // RFC 952/1123 prohibit underscores in hostnames, so this is not expected in practice. - // Encoded domain `api_mcp_internal_com` places `_mcp_` after `_action_`, which - // the guard interprets as the MCP suffix. - const edgeCaseTool = `getData${actionDelimiter}api_mcp_internal_com`; - expect(isActionTool(edgeCaseTool)).toBe(false); - }); - }); - - describe('loadAgentTools (definitionsOnly=true) — action tool filtering', () => { - const actionToolName = `get_weather${actionDelimiter}api_example_com`; - const regularTool = 'calculator'; - - it('should exclude action tools from definitions when actions capability is disabled', async () => { - const capabilities = [AgentCapabilities.tools, AgentCapabilities.web_search]; - const req = createMockReq(capabilities); - mockGetEndpointsConfig.mockResolvedValue(createEndpointsConfig(capabilities)); - - await loadAgentTools({ - req, - res: {}, - agent: { id: 'agent_123', tools: [regularTool, actionToolName] }, - definitionsOnly: true, - }); - - expect(mockLoadToolDefinitions).toHaveBeenCalledTimes(1); - const [callArgs] = mockLoadToolDefinitions.mock.calls[0]; - expect(callArgs.tools).toContain(regularTool); - expect(callArgs.tools).not.toContain(actionToolName); - }); - - it('should include action tools in definitions when actions capability is enabled', async () => { - const capabilities = [AgentCapabilities.tools, AgentCapabilities.actions]; - const req = createMockReq(capabilities); - mockGetEndpointsConfig.mockResolvedValue(createEndpointsConfig(capabilities)); - - await loadAgentTools({ - req, - res: {}, - agent: { id: 'agent_123', tools: [regularTool, actionToolName] }, - definitionsOnly: true, - }); - - expect(mockLoadToolDefinitions).toHaveBeenCalledTimes(1); - const [callArgs] = mockLoadToolDefinitions.mock.calls[0]; - expect(callArgs.tools).toContain(regularTool); - expect(callArgs.tools).toContain(actionToolName); - }); - - it('should not filter MCP tools whose name contains _action (cross-delimiter collision)', async () => { - const mcpToolWithAction = `get_action${Constants.mcp_delimiter}myserver`; - const capabilities = [AgentCapabilities.tools]; - const req = createMockReq(capabilities); - mockGetEndpointsConfig.mockResolvedValue(createEndpointsConfig(capabilities)); - - await loadAgentTools({ - req, - res: {}, - agent: { id: 'agent_123', tools: [regularTool, mcpToolWithAction] }, - definitionsOnly: true, - }); - - expect(mockLoadToolDefinitions).toHaveBeenCalledTimes(1); - const [callArgs] = mockLoadToolDefinitions.mock.calls[0]; - expect(callArgs.tools).toContain(mcpToolWithAction); - expect(callArgs.tools).toContain(regularTool); - }); - - it('should return actionsEnabled in the result', async () => { - const capabilities = [AgentCapabilities.tools]; - const req = createMockReq(capabilities); - mockGetEndpointsConfig.mockResolvedValue(createEndpointsConfig(capabilities)); - - const result = await loadAgentTools({ - req, - res: {}, - agent: { id: 'agent_123', tools: [regularTool] }, - definitionsOnly: true, - }); - - expect(result.actionsEnabled).toBe(false); - }); - }); - - describe('loadAgentTools (definitionsOnly=false) — action tool filtering', () => { - const actionToolName = `get_weather${actionDelimiter}api_example_com`; - const regularTool = 'calculator'; - - it('should not load action sets when actions capability is disabled', async () => { - const capabilities = [AgentCapabilities.tools, AgentCapabilities.web_search]; - const req = createMockReq(capabilities); - mockGetEndpointsConfig.mockResolvedValue(createEndpointsConfig(capabilities)); - - await loadAgentTools({ - req, - res: {}, - agent: { id: 'agent_123', tools: [regularTool, actionToolName] }, - definitionsOnly: false, - }); - - expect(mockLoadActionSets).not.toHaveBeenCalled(); - }); - - it('should load action sets when actions capability is enabled and action tools present', async () => { - const capabilities = [AgentCapabilities.tools, AgentCapabilities.actions]; - const req = createMockReq(capabilities); - mockGetEndpointsConfig.mockResolvedValue(createEndpointsConfig(capabilities)); - - await loadAgentTools({ - req, - res: {}, - agent: { id: 'agent_123', tools: [regularTool, actionToolName] }, - definitionsOnly: false, - }); - - expect(mockLoadActionSets).toHaveBeenCalledWith({ agent_id: 'agent_123' }); - }); - }); - - describe('loadToolsForExecution — action tool gating', () => { - const actionToolName = `get_weather${actionDelimiter}api_example_com`; - const regularTool = Tools.web_search; - - it('should skip action tool loading when actionsEnabled=false', async () => { - const req = createMockReq([]); - req.config = {}; - - const result = await loadToolsForExecution({ - req, - res: {}, - agent: { id: 'agent_123' }, - toolNames: [regularTool, actionToolName], - actionsEnabled: false, - }); - - expect(mockLoadActionSets).not.toHaveBeenCalled(); - expect(result.loadedTools).toBeDefined(); - }); - - it('should load action tools when actionsEnabled=true', async () => { - const req = createMockReq([AgentCapabilities.actions]); - req.config = {}; - - await loadToolsForExecution({ - req, - res: {}, - agent: { id: 'agent_123' }, - toolNames: [actionToolName], - actionsEnabled: true, - }); - - expect(mockLoadActionSets).toHaveBeenCalledWith({ agent_id: 'agent_123' }); - }); - - it('should resolve actionsEnabled from capabilities when not explicitly provided', async () => { - const capabilities = [AgentCapabilities.tools]; - const req = createMockReq(capabilities); - mockGetEndpointsConfig.mockResolvedValue(createEndpointsConfig(capabilities)); - - await loadToolsForExecution({ - req, - res: {}, - agent: { id: 'agent_123' }, - toolNames: [actionToolName], - }); - - expect(mockGetEndpointsConfig).toHaveBeenCalled(); - expect(mockLoadActionSets).not.toHaveBeenCalled(); - }); - - it('should not call loadActionSets when there are no action tools', async () => { - const req = createMockReq([AgentCapabilities.actions]); - req.config = {}; - - await loadToolsForExecution({ - req, - res: {}, - agent: { id: 'agent_123' }, - toolNames: [regularTool], - actionsEnabled: true, - }); - - expect(mockLoadActionSets).not.toHaveBeenCalled(); - }); - }); +const { AgentCapabilities, defaultAgentCapabilities } = require('librechat-data-provider'); +/** + * Tests for ToolService capability checking logic. + * The actual loadAgentTools function has many dependencies, so we test + * the capability checking logic in isolation. + */ +describe('ToolService - Capability Checking', () => { describe('checkCapability logic', () => { + /** + * Simulates the checkCapability function from loadAgentTools + */ const createCheckCapability = (enabledCapabilities, logger = { warn: jest.fn() }) => { return (capability) => { const enabled = enabledCapabilities.has(capability); @@ -467,68 +119,26 @@ describe('ToolService - Action Capability Gating', () => { }); }); - describe('userMCPAuthMap gating', () => { - const shouldFetchMCPAuth = (tools) => - tools?.some((t) => t.includes(Constants.mcp_delimiter)) ?? false; - - it('should return true when agent has MCP tools', () => { - const tools = ['web_search', `search${Constants.mcp_delimiter}my-mcp-server`, 'calculator']; - expect(shouldFetchMCPAuth(tools)).toBe(true); - }); - - it('should return false when agent has no MCP tools', () => { - const tools = ['web_search', 'calculator', 'code_interpreter']; - expect(shouldFetchMCPAuth(tools)).toBe(false); - }); - - it('should return false when tools is empty', () => { - expect(shouldFetchMCPAuth([])).toBe(false); - }); - - it('should return false when tools is undefined', () => { - expect(shouldFetchMCPAuth(undefined)).toBe(false); - }); - - it('should return false when tools is null', () => { - expect(shouldFetchMCPAuth(null)).toBe(false); - }); - - it('should detect MCP tools with different server names', () => { - const tools = [ - `listFiles${Constants.mcp_delimiter}file-server`, - `query${Constants.mcp_delimiter}db-server`, - ]; - expect(shouldFetchMCPAuth(tools)).toBe(true); - }); - - it('should return true even when only one tool is MCP', () => { - const tools = [ - 'web_search', - 'calculator', - 'code_interpreter', - `echo${Constants.mcp_delimiter}test-server`, - ]; - expect(shouldFetchMCPAuth(tools)).toBe(true); - }); - }); - describe('deferredToolsEnabled integration', () => { it('should correctly determine deferredToolsEnabled from capabilities set', () => { const createCheckCapability = (enabledCapabilities) => { return (capability) => enabledCapabilities.has(capability); }; + // When deferred_tools is in capabilities const withDeferred = new Set([AgentCapabilities.deferred_tools, AgentCapabilities.tools]); const checkWithDeferred = createCheckCapability(withDeferred); expect(checkWithDeferred(AgentCapabilities.deferred_tools)).toBe(true); + // When deferred_tools is NOT in capabilities const withoutDeferred = new Set([AgentCapabilities.tools, AgentCapabilities.actions]); const checkWithoutDeferred = createCheckCapability(withoutDeferred); expect(checkWithoutDeferred(AgentCapabilities.deferred_tools)).toBe(false); }); it('should use defaultAgentCapabilities when no capabilities configured', () => { - const endpointsConfig = {}; + // Simulates the fallback behavior in loadAgentTools + const endpointsConfig = {}; // No capabilities configured const enabledCapabilities = new Set( endpointsConfig?.capabilities ?? defaultAgentCapabilities, ); diff --git a/api/server/services/cleanup.js b/api/server/services/cleanup.js index dc4f62c2ac..7d3dfdec12 100644 --- a/api/server/services/cleanup.js +++ b/api/server/services/cleanup.js @@ -1,5 +1,5 @@ const { logger } = require('@librechat/data-schemas'); -const { deleteNullOrEmptyConversations } = require('~/models'); +const { deleteNullOrEmptyConversations } = require('~/models/Conversation'); const cleanup = async () => { try { diff --git a/api/server/services/initializeMCPs.js b/api/server/services/initializeMCPs.js index 5728730131..c7f27acd0e 100644 --- a/api/server/services/initializeMCPs.js +++ b/api/server/services/initializeMCPs.js @@ -7,7 +7,7 @@ const { createMCPServersRegistry, createMCPManager } = require('~/config'); * Initialize MCP servers */ async function initializeMCPs() { - const appConfig = await getAppConfig({ baseOnly: true }); + const appConfig = await getAppConfig(); const mcpServers = appConfig.mcpConfig; try { diff --git a/api/server/services/start/migration.js b/api/server/services/start/migration.js index 70f8300e08..83b9c83e39 100644 --- a/api/server/services/start/migration.js +++ b/api/server/services/start/migration.js @@ -6,6 +6,8 @@ const { checkAgentPermissionsMigration, checkPromptPermissionsMigration, } = require('@librechat/api'); +const { getProjectByName } = require('~/models/Project'); +const { Agent, PromptGroup } = require('~/db/models'); const { findRoleByIdentifier } = require('~/models'); /** @@ -18,8 +20,9 @@ async function checkMigrations() { mongoose, methods: { findRoleByIdentifier, + getProjectByName, }, - AgentModel: mongoose.models.Agent, + AgentModel: Agent, }); logAgentMigrationWarning(agentMigrationResult); } catch (error) { @@ -30,8 +33,9 @@ async function checkMigrations() { mongoose, methods: { findRoleByIdentifier, + getProjectByName, }, - PromptGroupModel: mongoose.models.PromptGroup, + PromptGroupModel: PromptGroup, }); logPromptMigrationWarning(promptMigrationResult); } catch (error) { diff --git a/api/server/services/systemGrant.spec.js b/api/server/services/systemGrant.spec.js deleted file mode 100644 index 4e10ee5641..0000000000 --- a/api/server/services/systemGrant.spec.js +++ /dev/null @@ -1,407 +0,0 @@ -const mongoose = require('mongoose'); -const { createModels, createMethods } = require('@librechat/data-schemas'); -const { MongoMemoryServer } = require('mongodb-memory-server'); -const { SystemRoles, PrincipalType } = require('librechat-data-provider'); -const { SystemCapabilities } = require('@librechat/data-schemas'); - -jest.mock('@librechat/data-schemas', () => ({ - ...jest.requireActual('@librechat/data-schemas'), - getTransactionSupport: jest.fn().mockResolvedValue(false), - createModels: jest.requireActual('@librechat/data-schemas').createModels, - createMethods: jest.requireActual('@librechat/data-schemas').createMethods, -})); - -jest.mock('~/server/services/GraphApiService', () => ({ - entraIdPrincipalFeatureEnabled: jest.fn().mockReturnValue(false), - getUserOwnedEntraGroups: jest.fn().mockResolvedValue([]), - getUserEntraGroups: jest.fn().mockResolvedValue([]), - getGroupMembers: jest.fn().mockResolvedValue([]), - getGroupOwners: jest.fn().mockResolvedValue([]), -})); - -jest.mock('~/config', () => ({ - logger: { error: jest.fn() }, -})); - -let mongoServer; -let methods; -let SystemGrant; - -beforeAll(async () => { - mongoServer = await MongoMemoryServer.create(); - await mongoose.connect(mongoServer.getUri()); - - createModels(mongoose); - const dbModels = require('~/db/models'); - Object.assign(mongoose.models, dbModels); - SystemGrant = dbModels.SystemGrant; - - methods = createMethods(mongoose, { - matchModelName: () => null, - findMatchingPattern: () => null, - getCache: () => ({ - get: async () => null, - set: async () => {}, - }), - }); -}); - -afterAll(async () => { - await mongoose.disconnect(); - await mongoServer.stop(); -}); - -beforeEach(async () => { - await SystemGrant.deleteMany({}); -}); - -describe('SystemGrant methods', () => { - describe('seedSystemGrants', () => { - it('seeds all capabilities for the ADMIN role', async () => { - await methods.seedSystemGrants(); - - const grants = await SystemGrant.find({ - principalType: PrincipalType.ROLE, - principalId: SystemRoles.ADMIN, - }).lean(); - - const expectedCount = Object.values(SystemCapabilities).length; - expect(grants).toHaveLength(expectedCount); - - const capabilities = grants.map((g) => g.capability).sort(); - const expected = Object.values(SystemCapabilities).sort(); - expect(capabilities).toEqual(expected); - }); - - it('is idempotent — calling twice does not duplicate grants', async () => { - await methods.seedSystemGrants(); - await methods.seedSystemGrants(); - - const count = await SystemGrant.countDocuments({ - principalType: PrincipalType.ROLE, - principalId: SystemRoles.ADMIN, - }); - - expect(count).toBe(Object.values(SystemCapabilities).length); - }); - - it('seeds grants with no tenantId', async () => { - await methods.seedSystemGrants(); - - const withTenant = await SystemGrant.countDocuments({ - principalType: PrincipalType.ROLE, - principalId: SystemRoles.ADMIN, - tenantId: { $exists: true }, - }); - - expect(withTenant).toBe(0); - }); - }); - - describe('grantCapability / revokeCapability', () => { - it('grants a capability to a user', async () => { - const userId = new mongoose.Types.ObjectId(); - - await methods.grantCapability({ - principalType: PrincipalType.USER, - principalId: userId, - capability: SystemCapabilities.READ_USERS, - }); - - const grant = await SystemGrant.findOne({ - principalType: PrincipalType.USER, - principalId: userId, - capability: SystemCapabilities.READ_USERS, - }).lean(); - - expect(grant).toBeTruthy(); - expect(grant.grantedAt).toBeInstanceOf(Date); - }); - - it('upsert does not create duplicates', async () => { - const userId = new mongoose.Types.ObjectId(); - - await methods.grantCapability({ - principalType: PrincipalType.USER, - principalId: userId, - capability: SystemCapabilities.READ_USERS, - }); - - await methods.grantCapability({ - principalType: PrincipalType.USER, - principalId: userId, - capability: SystemCapabilities.READ_USERS, - }); - - const count = await SystemGrant.countDocuments({ - principalType: PrincipalType.USER, - principalId: userId, - capability: SystemCapabilities.READ_USERS, - }); - - expect(count).toBe(1); - }); - - it('revokes a capability', async () => { - const userId = new mongoose.Types.ObjectId(); - - await methods.grantCapability({ - principalType: PrincipalType.USER, - principalId: userId, - capability: SystemCapabilities.READ_USERS, - }); - - await methods.revokeCapability({ - principalType: PrincipalType.USER, - principalId: userId, - capability: SystemCapabilities.READ_USERS, - }); - - const grant = await SystemGrant.findOne({ - principalType: PrincipalType.USER, - principalId: userId, - capability: SystemCapabilities.READ_USERS, - }).lean(); - - expect(grant).toBeNull(); - }); - }); - - describe('hasCapabilityForPrincipals', () => { - it('returns true when role principal has the capability', async () => { - await methods.seedSystemGrants(); - - const principals = [ - { principalType: PrincipalType.USER, principalId: new mongoose.Types.ObjectId() }, - { principalType: PrincipalType.ROLE, principalId: SystemRoles.ADMIN }, - { principalType: PrincipalType.PUBLIC }, - ]; - - const result = await methods.hasCapabilityForPrincipals({ - principals, - capability: SystemCapabilities.ACCESS_ADMIN, - }); - - expect(result).toBe(true); - }); - - it('returns false when no principal has the capability', async () => { - const principals = [ - { principalType: PrincipalType.USER, principalId: new mongoose.Types.ObjectId() }, - { principalType: PrincipalType.ROLE, principalId: SystemRoles.USER }, - { principalType: PrincipalType.PUBLIC }, - ]; - - const result = await methods.hasCapabilityForPrincipals({ - principals, - capability: SystemCapabilities.ACCESS_ADMIN, - }); - - expect(result).toBe(false); - }); - - it('returns false for an empty principals list', async () => { - const result = await methods.hasCapabilityForPrincipals({ - principals: [], - capability: SystemCapabilities.ACCESS_ADMIN, - }); - - expect(result).toBe(false); - }); - - it('ignores PUBLIC principals', async () => { - const result = await methods.hasCapabilityForPrincipals({ - principals: [{ principalType: PrincipalType.PUBLIC }], - capability: SystemCapabilities.ACCESS_ADMIN, - }); - - expect(result).toBe(false); - }); - - it('matches user-level grants', async () => { - const userId = new mongoose.Types.ObjectId(); - - await methods.grantCapability({ - principalType: PrincipalType.USER, - principalId: userId, - capability: SystemCapabilities.READ_CONFIGS, - }); - - const principals = [ - { principalType: PrincipalType.USER, principalId: userId }, - { principalType: PrincipalType.ROLE, principalId: SystemRoles.USER }, - { principalType: PrincipalType.PUBLIC }, - ]; - - const result = await methods.hasCapabilityForPrincipals({ - principals, - capability: SystemCapabilities.READ_CONFIGS, - }); - - expect(result).toBe(true); - }); - - it('matches group-level grants', async () => { - const groupId = new mongoose.Types.ObjectId(); - - await methods.grantCapability({ - principalType: PrincipalType.GROUP, - principalId: groupId, - capability: SystemCapabilities.READ_USAGE, - }); - - const principals = [ - { principalType: PrincipalType.USER, principalId: new mongoose.Types.ObjectId() }, - { principalType: PrincipalType.GROUP, principalId: groupId }, - { principalType: PrincipalType.PUBLIC }, - ]; - - const result = await methods.hasCapabilityForPrincipals({ - principals, - capability: SystemCapabilities.READ_USAGE, - }); - - expect(result).toBe(true); - }); - }); - - describe('getCapabilitiesForPrincipal', () => { - it('lists all capabilities for a principal', async () => { - await methods.seedSystemGrants(); - - const grants = await methods.getCapabilitiesForPrincipal({ - principalType: PrincipalType.ROLE, - principalId: SystemRoles.ADMIN, - }); - - expect(grants).toHaveLength(Object.values(SystemCapabilities).length); - }); - - it('returns empty array for a principal with no grants', async () => { - const grants = await methods.getCapabilitiesForPrincipal({ - principalType: PrincipalType.ROLE, - principalId: SystemRoles.USER, - }); - - expect(grants).toHaveLength(0); - }); - }); - - describe('principalId normalization', () => { - it('grant with string userId is found by hasCapabilityForPrincipals with ObjectId', async () => { - const userId = new mongoose.Types.ObjectId(); - - await methods.grantCapability({ - principalType: PrincipalType.USER, - principalId: userId.toString(), // string input - capability: SystemCapabilities.READ_USAGE, - }); - - const result = await methods.hasCapabilityForPrincipals({ - principals: [{ principalType: PrincipalType.USER, principalId: userId }], // ObjectId input - capability: SystemCapabilities.READ_USAGE, - }); - - expect(result).toBe(true); - }); - - it('revoke with string userId removes the grant stored as ObjectId', async () => { - const userId = new mongoose.Types.ObjectId(); - - await methods.grantCapability({ - principalType: PrincipalType.USER, - principalId: userId.toString(), - capability: SystemCapabilities.READ_USAGE, - }); - - await methods.revokeCapability({ - principalType: PrincipalType.USER, - principalId: userId.toString(), // string revoke - capability: SystemCapabilities.READ_USAGE, - }); - - const result = await methods.hasCapabilityForPrincipals({ - principals: [{ principalType: PrincipalType.USER, principalId: userId }], - capability: SystemCapabilities.READ_USAGE, - }); - - expect(result).toBe(false); - }); - - it('getCapabilitiesForPrincipal with string userId returns grants stored as ObjectId', async () => { - const userId = new mongoose.Types.ObjectId(); - - await methods.grantCapability({ - principalType: PrincipalType.USER, - principalId: userId.toString(), - capability: SystemCapabilities.READ_USAGE, - }); - - const grants = await methods.getCapabilitiesForPrincipal({ - principalType: PrincipalType.USER, - principalId: userId.toString(), // string lookup - }); - - expect(grants).toHaveLength(1); - expect(grants[0].capability).toBe(SystemCapabilities.READ_USAGE); - }); - }); - - describe('tenant scoping', () => { - it('tenant-scoped grant does not match platform-level query', async () => { - const userId = new mongoose.Types.ObjectId(); - - await methods.grantCapability({ - principalType: PrincipalType.USER, - principalId: userId, - capability: SystemCapabilities.READ_CONFIGS, - tenantId: 'tenant-1', - }); - - const result = await methods.hasCapabilityForPrincipals({ - principals: [{ principalType: PrincipalType.USER, principalId: userId }], - capability: SystemCapabilities.READ_CONFIGS, - }); - - expect(result).toBe(false); - }); - - it('tenant-scoped grant matches same-tenant query', async () => { - const userId = new mongoose.Types.ObjectId(); - - await methods.grantCapability({ - principalType: PrincipalType.USER, - principalId: userId, - capability: SystemCapabilities.READ_CONFIGS, - tenantId: 'tenant-1', - }); - - const result = await methods.hasCapabilityForPrincipals({ - principals: [{ principalType: PrincipalType.USER, principalId: userId }], - capability: SystemCapabilities.READ_CONFIGS, - tenantId: 'tenant-1', - }); - - expect(result).toBe(true); - }); - - it('tenant-scoped grant does not match different tenant', async () => { - const userId = new mongoose.Types.ObjectId(); - - await methods.grantCapability({ - principalType: PrincipalType.USER, - principalId: userId, - capability: SystemCapabilities.READ_CONFIGS, - tenantId: 'tenant-1', - }); - - const result = await methods.hasCapabilityForPrincipals({ - principals: [{ principalType: PrincipalType.USER, principalId: userId }], - capability: SystemCapabilities.READ_CONFIGS, - tenantId: 'tenant-2', - }); - - expect(result).toBe(false); - }); - }); -}); diff --git a/api/server/services/twoFactorService.js b/api/server/services/twoFactorService.js index 313c557133..cce24e2322 100644 --- a/api/server/services/twoFactorService.js +++ b/api/server/services/twoFactorService.js @@ -153,11 +153,9 @@ const generateBackupCodes = async (count = 10) => { * @param {Object} params * @param {Object} params.user * @param {string} params.backupCode - * @param {boolean} [params.persist=true] - Whether to persist the used-mark to the database. - * Pass `false` when the caller will immediately overwrite `backupCodes` (e.g. re-enrollment). * @returns {Promise} */ -const verifyBackupCode = async ({ user, backupCode, persist = true }) => { +const verifyBackupCode = async ({ user, backupCode }) => { if (!backupCode || !user || !Array.isArray(user.backupCodes)) { return false; } @@ -167,50 +165,17 @@ const verifyBackupCode = async ({ user, backupCode, persist = true }) => { (codeObj) => codeObj.codeHash === hashedInput && !codeObj.used, ); - if (!matchingCode) { - return false; - } - - if (persist) { + if (matchingCode) { const updatedBackupCodes = user.backupCodes.map((codeObj) => codeObj.codeHash === hashedInput && !codeObj.used ? { ...codeObj, used: true, usedAt: new Date() } : codeObj, ); + // Update the user record with the marked backup code. await updateUser(user._id, { backupCodes: updatedBackupCodes }); + return true; } - return true; -}; - -/** - * Verifies a user's identity via TOTP token or backup code. - * @param {Object} params - * @param {Object} params.user - The user document (must include totpSecret and backupCodes). - * @param {string} [params.token] - A 6-digit TOTP token. - * @param {string} [params.backupCode] - An 8-character backup code. - * @param {boolean} [params.persistBackupUse=true] - Whether to mark the backup code as used in the DB. - * @returns {Promise<{ verified: boolean, status?: number, message?: string }>} - */ -const verifyOTPOrBackupCode = async ({ user, token, backupCode, persistBackupUse = true }) => { - if (!token && !backupCode) { - return { verified: false, status: 400 }; - } - - if (token) { - const secret = await getTOTPSecret(user.totpSecret); - if (!secret) { - return { verified: false, status: 400, message: '2FA secret is missing or corrupted' }; - } - const ok = await verifyTOTP(secret, token); - return ok - ? { verified: true } - : { verified: false, status: 401, message: 'Invalid token or backup code' }; - } - - const ok = await verifyBackupCode({ user, backupCode, persist: persistBackupUse }); - return ok - ? { verified: true } - : { verified: false, status: 401, message: 'Invalid token or backup code' }; + return false; }; /** @@ -248,12 +213,11 @@ const generate2FATempToken = (userId) => { }; module.exports = { - verifyOTPOrBackupCode, - generate2FATempToken, - generateBackupCodes, generateTOTPSecret, - verifyBackupCode, - getTOTPSecret, generateTOTP, verifyTOTP, + generateBackupCodes, + verifyBackupCode, + getTOTPSecret, + generate2FATempToken, }; diff --git a/api/server/socialLogins.js b/api/server/socialLogins.js index dfb03b4d37..a84c33bd52 100644 --- a/api/server/socialLogins.js +++ b/api/server/socialLogins.js @@ -6,16 +6,11 @@ const { logger, DEFAULT_SESSION_EXPIRY } = require('@librechat/data-schemas'); const { openIdJwtLogin, facebookLogin, - facebookAdminLogin, discordLogin, - discordAdminLogin, setupOpenId, googleLogin, - googleAdminLogin, githubLogin, - githubAdminLogin, appleLogin, - appleAdminLogin, setupSaml, } = require('~/strategies'); const { getLogStores } = require('~/cache'); @@ -63,23 +58,18 @@ const configureSocialLogins = async (app) => { if (process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET) { passport.use(googleLogin()); - passport.use('googleAdmin', googleAdminLogin()); } if (process.env.FACEBOOK_CLIENT_ID && process.env.FACEBOOK_CLIENT_SECRET) { passport.use(facebookLogin()); - passport.use('facebookAdmin', facebookAdminLogin()); } if (process.env.GITHUB_CLIENT_ID && process.env.GITHUB_CLIENT_SECRET) { passport.use(githubLogin()); - passport.use('githubAdmin', githubAdminLogin()); } if (process.env.DISCORD_CLIENT_ID && process.env.DISCORD_CLIENT_SECRET) { passport.use(discordLogin()); - passport.use('discordAdmin', discordAdminLogin()); } if (process.env.APPLE_CLIENT_ID && process.env.APPLE_PRIVATE_KEY_PATH) { passport.use(appleLogin()); - passport.use('appleAdmin', appleAdminLogin()); } if ( process.env.OPENID_CLIENT_ID && diff --git a/api/server/utils/__tests__/sendEmail.spec.js b/api/server/utils/__tests__/sendEmail.spec.js deleted file mode 100644 index 5c79094c53..0000000000 --- a/api/server/utils/__tests__/sendEmail.spec.js +++ /dev/null @@ -1,143 +0,0 @@ -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/import/fork.js b/api/server/utils/import/fork.js index 5df4d27af2..c4ce8cb5d4 100644 --- a/api/server/utils/import/fork.js +++ b/api/server/utils/import/fork.js @@ -3,7 +3,8 @@ const { logger } = require('@librechat/data-schemas'); const { EModelEndpoint, Constants, ForkOptions } = require('librechat-data-provider'); const { createImportBatchBuilder } = require('./importBatchBuilder'); const BaseClient = require('~/app/clients/BaseClient'); -const { getConvo, getMessages } = require('~/models'); +const { getConvo } = require('~/models/Conversation'); +const { getMessages } = require('~/models/Message'); /** * Helper function to clone messages with proper parent-child relationships and timestamps @@ -357,15 +358,16 @@ function splitAtTargetLevel(messages, targetMessageId) { * @param {object} params - The parameters for duplicating the conversation. * @param {string} params.userId - The ID of the user duplicating the conversation. * @param {string} params.conversationId - The ID of the conversation to duplicate. - * @param {string} [params.title] - Optional title override for the duplicate. * @returns {Promise<{ conversation: TConversation, messages: TMessage[] }>} The duplicated conversation and messages. */ -async function duplicateConversation({ userId, conversationId, title }) { +async function duplicateConversation({ userId, conversationId }) { + // Get original conversation const originalConvo = await getConvo(userId, conversationId); if (!originalConvo) { throw new Error('Conversation not found'); } + // Get original messages const originalMessages = await getMessages({ user: userId, conversationId, @@ -381,11 +383,14 @@ async function duplicateConversation({ userId, conversationId, title }) { cloneMessagesWithTimestamps(messagesToClone, importBatchBuilder); - const duplicateTitle = title || originalConvo.title; - const result = importBatchBuilder.finishConversation(duplicateTitle, new Date(), originalConvo); + const result = importBatchBuilder.finishConversation( + originalConvo.title, + new Date(), + originalConvo, + ); await importBatchBuilder.saveBatch(); logger.debug( - `user: ${userId} | New conversation "${duplicateTitle}" duplicated from conversation ID ${conversationId}`, + `user: ${userId} | New conversation "${originalConvo.title}" duplicated from conversation ID ${conversationId}`, ); const conversation = await getConvo(userId, result.conversation.conversationId); diff --git a/api/server/utils/import/fork.spec.js b/api/server/utils/import/fork.spec.js index 6fd108674a..552620dc89 100644 --- a/api/server/utils/import/fork.spec.js +++ b/api/server/utils/import/fork.spec.js @@ -1,10 +1,16 @@ const { Constants, ForkOptions } = require('librechat-data-provider'); -jest.mock('~/models', () => ({ +jest.mock('~/models/Conversation', () => ({ getConvo: jest.fn(), bulkSaveConvos: jest.fn(), +})); + +jest.mock('~/models/Message', () => ({ getMessages: jest.fn(), bulkSaveMessages: jest.fn(), +})); + +jest.mock('~/models/ConversationTag', () => ({ bulkIncrementTagCounts: jest.fn(), })); @@ -26,13 +32,9 @@ const { getMessagesUpToTargetLevel, cloneMessagesWithTimestamps, } = require('./fork'); -const { - bulkIncrementTagCounts, - getConvo, - bulkSaveConvos, - getMessages, - bulkSaveMessages, -} = require('~/models'); +const { bulkIncrementTagCounts } = require('~/models/ConversationTag'); +const { getConvo, bulkSaveConvos } = require('~/models/Conversation'); +const { getMessages, bulkSaveMessages } = require('~/models/Message'); const { createImportBatchBuilder } = require('./importBatchBuilder'); const BaseClient = require('~/app/clients/BaseClient'); diff --git a/api/server/utils/import/importBatchBuilder.js b/api/server/utils/import/importBatchBuilder.js index 29fbfa85a2..5e499043d2 100644 --- a/api/server/utils/import/importBatchBuilder.js +++ b/api/server/utils/import/importBatchBuilder.js @@ -1,7 +1,9 @@ const { v4: uuidv4 } = require('uuid'); const { logger } = require('@librechat/data-schemas'); const { EModelEndpoint, Constants, openAISettings } = require('librechat-data-provider'); -const { bulkIncrementTagCounts, bulkSaveConvos, bulkSaveMessages } = require('~/models'); +const { bulkIncrementTagCounts } = require('~/models/ConversationTag'); +const { bulkSaveConvos } = require('~/models/Conversation'); +const { bulkSaveMessages } = require('~/models/Message'); /** * Factory function for creating an instance of ImportBatchBuilder. diff --git a/api/server/utils/import/importConversations.js b/api/server/utils/import/importConversations.js index ad2d743f01..d9e4d4332d 100644 --- a/api/server/utils/import/importConversations.js +++ b/api/server/utils/import/importConversations.js @@ -1,30 +1,28 @@ const fs = require('fs').promises; -const { resolveImportMaxFileSize } = require('@librechat/api'); const { logger } = require('@librechat/data-schemas'); const { getImporter } = require('./importers'); -const maxFileSize = resolveImportMaxFileSize(); - /** * Job definition for importing a conversation. - * @param {{ filepath: string, requestUserId: string, userRole?: string }} job + * @param {{ filepath, requestUserId }} job - The job object. */ const importConversations = async (job) => { - const { filepath, requestUserId, userRole } = job; + const { filepath, requestUserId } = job; try { logger.debug(`user: ${requestUserId} | Importing conversation(s) from file...`); + /* error if file is too large */ const fileInfo = await fs.stat(filepath); - if (fileInfo.size > maxFileSize) { + if (fileInfo.size > process.env.CONVERSATION_IMPORT_MAX_FILE_SIZE_BYTES) { throw new Error( - `File size is ${fileInfo.size} bytes. It exceeds the maximum limit of ${maxFileSize} bytes.`, + `File size is ${fileInfo.size} bytes. It exceeds the maximum limit of ${process.env.CONVERSATION_IMPORT_MAX_FILE_SIZE_BYTES} bytes.`, ); } const fileData = await fs.readFile(filepath, 'utf8'); const jsonData = JSON.parse(fileData); const importer = getImporter(jsonData); - await importer(jsonData, requestUserId, undefined, userRole); + await importer(jsonData, requestUserId); logger.debug(`user: ${requestUserId} | Finished importing conversations`); } catch (error) { logger.error(`user: ${requestUserId} | Failed to import conversation: `, error); diff --git a/api/server/utils/import/importers-timestamp.spec.js b/api/server/utils/import/importers-timestamp.spec.js index e12c099abb..c7665dfe25 100644 --- a/api/server/utils/import/importers-timestamp.spec.js +++ b/api/server/utils/import/importers-timestamp.spec.js @@ -1,23 +1,25 @@ -const { logger } = require('@librechat/data-schemas'); const { Constants } = require('librechat-data-provider'); const { ImportBatchBuilder } = require('./importBatchBuilder'); const { getImporter } = require('./importers'); // Mock the database methods -jest.mock('~/models', () => ({ +jest.mock('~/models/Conversation', () => ({ bulkSaveConvos: jest.fn(), +})); +jest.mock('~/models/Message', () => ({ bulkSaveMessages: jest.fn(), })); - -const mockGetEndpointsConfig = jest.fn().mockResolvedValue(null); -jest.mock('~/server/services/Config', () => ({ - getEndpointsConfig: (...args) => mockGetEndpointsConfig(...args), +jest.mock('~/cache/getLogStores'); +const getLogStores = require('~/cache/getLogStores'); +const mockedCacheGet = jest.fn(); +getLogStores.mockImplementation(() => ({ + get: mockedCacheGet, })); describe('Import Timestamp Ordering', () => { beforeEach(() => { jest.clearAllMocks(); - mockGetEndpointsConfig.mockResolvedValue(null); + mockedCacheGet.mockResolvedValue(null); }); describe('LibreChat Import - Timestamp Issues', () => { @@ -366,133 +368,6 @@ 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 7bcca41e04..81a0f048df 100644 --- a/api/server/utils/import/importers.js +++ b/api/server/utils/import/importers.js @@ -1,9 +1,9 @@ const { v4: uuidv4 } = require('uuid'); -const { logger, getTenantId } = require('@librechat/data-schemas'); -const { EModelEndpoint, Constants, openAISettings } = require('librechat-data-provider'); -const { getEndpointsConfig } = require('~/server/services/Config'); +const { logger } = require('@librechat/data-schemas'); +const { EModelEndpoint, Constants, openAISettings, CacheKeys } = require('librechat-data-provider'); const { createImportBatchBuilder } = require('./importBatchBuilder'); const { cloneMessagesWithTimestamps } = require('./fork'); +const getLogStores = require('~/cache/getLogStores'); /** * Returns the appropriate importer function based on the provided JSON data. @@ -194,7 +194,6 @@ async function importLibreChatConvo( jsonData, requestUserId, builderFactory = createImportBatchBuilder, - userRole, ) { try { /** @type {ImportBatchBuilder} */ @@ -203,9 +202,8 @@ async function importLibreChatConvo( /* Endpoint configuration */ let endpoint = jsonData.endpoint ?? options.endpoint ?? EModelEndpoint.openAI; - const endpointsConfig = await getEndpointsConfig({ - user: { id: requestUserId, role: userRole, tenantId: getTenantId() }, - }); + const cache = getLogStores(CacheKeys.CONFIG_STORE); + const endpointsConfig = await cache.get(CacheKeys.ENDPOINT_CONFIG); const endpointConfig = endpointsConfig?.[endpoint]; if (!endpointConfig && endpointsConfig) { endpoint = Object.keys(endpointsConfig)[0]; @@ -326,42 +324,32 @@ function processConversation(conv, importBatchBuilder, requestUserId) { } /** - * 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. + * Helper function to find the nearest valid parent (skips system, reasoning_recap, and thoughts messages) + * @param {string} parentId - The ID of the parent message. * @returns {string} The ID of the nearest valid parent message. */ - 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 findValidParent = (parentId) => { + if (!parentId || !messageMap.has(parentId)) { + return Constants.NO_PARENT; } - return Constants.NO_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); }; /** @@ -478,10 +466,7 @@ function processConversation(conv, importBatchBuilder, requestUserId) { messages.push(message); } - const cycleDetected = adjustTimestampsForOrdering(messages); - if (cycleDetected) { - breakParentCycles(messages); - } + adjustTimestampsForOrdering(messages); for (const message of messages) { importBatchBuilder.saveMessage(message); @@ -568,30 +553,21 @@ 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(); - for (const msg of messages) { - timestampMap.set(msg.messageId, msg.createdAt); - } + messages.forEach((msg) => timestampMap.set(msg.messageId, msg.createdAt)); let hasChanges = true; - let remainingPasses = messages.length; - while (hasChanges && remainingPasses > 0) { + while (hasChanges) { 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; @@ -599,49 +575,6 @@ 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 }; diff --git a/api/server/utils/import/importers.spec.js b/api/server/utils/import/importers.spec.js index 6e712881fc..a695a31555 100644 --- a/api/server/utils/import/importers.spec.js +++ b/api/server/utils/import/importers.spec.js @@ -1,21 +1,23 @@ const fs = require('fs'); const path = require('path'); const { EModelEndpoint, Constants, openAISettings } = require('librechat-data-provider'); +const { bulkSaveConvos: _bulkSaveConvos } = require('~/models/Conversation'); const { getImporter, processAssistantMessage } = require('./importers'); const { ImportBatchBuilder } = require('./importBatchBuilder'); -const { bulkSaveMessages, bulkSaveConvos: _bulkSaveConvos } = require('~/models'); +const { bulkSaveMessages } = require('~/models/Message'); +const getLogStores = require('~/cache/getLogStores'); -const mockGetEndpointsConfig = jest.fn().mockResolvedValue({ - [EModelEndpoint.openAI]: { userProvide: false }, -}); - -jest.mock('~/server/services/Config', () => ({ - getEndpointsConfig: (...args) => mockGetEndpointsConfig(...args), +jest.mock('~/cache/getLogStores'); +const mockedCacheGet = jest.fn(); +getLogStores.mockImplementation(() => ({ + get: mockedCacheGet, })); // Mock the database methods -jest.mock('~/models', () => ({ +jest.mock('~/models/Conversation', () => ({ bulkSaveConvos: jest.fn(), +})); +jest.mock('~/models/Message', () => ({ bulkSaveMessages: jest.fn(), })); @@ -759,7 +761,7 @@ describe('importLibreChatConvo', () => { ); it('should import conversation correctly', async () => { - mockGetEndpointsConfig.mockResolvedValue({ + mockedCacheGet.mockResolvedValue({ [EModelEndpoint.openAI]: {}, }); const expectedNumberOfMessages = 6; @@ -785,7 +787,7 @@ describe('importLibreChatConvo', () => { }); it('should import linear, non-recursive thread correctly with correct endpoint', async () => { - mockGetEndpointsConfig.mockResolvedValue({ + mockedCacheGet.mockResolvedValue({ [EModelEndpoint.azureOpenAI]: {}, }); @@ -925,7 +927,7 @@ describe('importLibreChatConvo', () => { }); it('should retain properties from the original conversation as well as new settings', async () => { - mockGetEndpointsConfig.mockResolvedValue({ + mockedCacheGet.mockResolvedValue({ [EModelEndpoint.azureOpenAI]: {}, }); const requestUserId = 'user-123'; @@ -1275,9 +1277,12 @@ describe('processAssistantMessage', () => { results.push(duration); }); - // Each size should complete well under 100ms; a ReDoS would cause exponential blowup - for (let i = 0; i < results.length; i++) { - expect(results[i]).toBeLessThan(100); + // Check if processing time increases exponentially + // In a ReDoS vulnerability, time would roughly double with each size increase + for (let i = 1; i < results.length; i++) { + const ratio = results[i] / results[i - 1]; + expect(ratio).toBeLessThan(3); // Allow for CI environment variability while still catching ReDoS + console.log(`Size ${sizes[i]} processing time ratio: ${ratio}`); } // Also test with the exact payload from the security report diff --git a/api/server/utils/index.js b/api/server/utils/index.js index 59cb71625f..918ab54f85 100644 --- a/api/server/utils/index.js +++ b/api/server/utils/index.js @@ -1,3 +1,4 @@ +const removePorts = require('./removePorts'); const handleText = require('./handleText'); const sendEmail = require('./sendEmail'); const queue = require('./queue'); @@ -5,6 +6,7 @@ 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 new file mode 100644 index 0000000000..375ff1cc71 --- /dev/null +++ b/api/server/utils/removePorts.js @@ -0,0 +1 @@ +module.exports = (req) => req?.ip?.replace(/:\d+[^:]*$/, ''); diff --git a/api/server/utils/sendEmail.js b/api/server/utils/sendEmail.js index 3fa3e6fcba..432a571ffb 100644 --- a/api/server/utils/sendEmail.js +++ b/api/server/utils/sendEmail.js @@ -124,20 +124,11 @@ const sendEmail = async ({ email, subject, payload, template, throwError = true // Whether to accept unsigned certificates rejectUnauthorized: !isEnabled(process.env.EMAIL_ALLOW_SELFSIGNED), }, - }; - - const hasUsername = !!process.env.EMAIL_USERNAME; - const hasPassword = !!process.env.EMAIL_PASSWORD; - if (hasUsername && hasPassword) { - transporterOptions.auth = { + 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/api/strategies/appleStrategy.js b/api/strategies/appleStrategy.js index 6eace87bae..fbba2a1f41 100644 --- a/api/strategies/appleStrategy.js +++ b/api/strategies/appleStrategy.js @@ -34,28 +34,16 @@ const getProfileDetails = ({ idToken, profile }) => { // Initialize the social login handler for Apple const appleLogin = socialLogin('apple', getProfileDetails); -const appleAdminLogin = socialLogin('apple', getProfileDetails, { existingUsersOnly: true }); -const getAppleConfig = (callbackURL) => ({ - clientID: process.env.APPLE_CLIENT_ID, - teamID: process.env.APPLE_TEAM_ID, - callbackURL, - keyID: process.env.APPLE_KEY_ID, - privateKeyLocation: process.env.APPLE_PRIVATE_KEY_PATH, - passReqToCallback: false, -}); - -const appleStrategy = () => +module.exports = () => new AppleStrategy( - getAppleConfig(`${process.env.DOMAIN_SERVER}${process.env.APPLE_CALLBACK_URL}`), + { + clientID: process.env.APPLE_CLIENT_ID, + teamID: process.env.APPLE_TEAM_ID, + callbackURL: `${process.env.DOMAIN_SERVER}${process.env.APPLE_CALLBACK_URL}`, + keyID: process.env.APPLE_KEY_ID, + privateKeyLocation: process.env.APPLE_PRIVATE_KEY_PATH, + passReqToCallback: false, // Set to true if you need to access the request in the callback + }, appleLogin, ); - -const appleAdminStrategy = () => - new AppleStrategy( - getAppleConfig(`${process.env.DOMAIN_SERVER}/api/admin/oauth/apple/callback`), - appleAdminLogin, - ); - -module.exports = appleStrategy; -module.exports.appleAdminLogin = appleAdminStrategy; diff --git a/api/strategies/discordStrategy.js b/api/strategies/discordStrategy.js index 7fb68280d5..dc7cb05ac6 100644 --- a/api/strategies/discordStrategy.js +++ b/api/strategies/discordStrategy.js @@ -22,27 +22,15 @@ const getProfileDetails = ({ profile }) => { }; const discordLogin = socialLogin('discord', getProfileDetails); -const discordAdminLogin = socialLogin('discord', getProfileDetails, { existingUsersOnly: true }); -const getDiscordConfig = (callbackURL) => ({ - clientID: process.env.DISCORD_CLIENT_ID, - clientSecret: process.env.DISCORD_CLIENT_SECRET, - callbackURL, - scope: ['identify', 'email'], - authorizationURL: 'https://discord.com/api/oauth2/authorize?prompt=none', -}); - -const discordStrategy = () => +module.exports = () => new DiscordStrategy( - getDiscordConfig(`${process.env.DOMAIN_SERVER}${process.env.DISCORD_CALLBACK_URL}`), + { + clientID: process.env.DISCORD_CLIENT_ID, + clientSecret: process.env.DISCORD_CLIENT_SECRET, + callbackURL: `${process.env.DOMAIN_SERVER}${process.env.DISCORD_CALLBACK_URL}`, + scope: ['identify', 'email'], + authorizationURL: 'https://discord.com/api/oauth2/authorize?prompt=none', + }, discordLogin, ); - -const discordAdminStrategy = () => - new DiscordStrategy( - getDiscordConfig(`${process.env.DOMAIN_SERVER}/api/admin/oauth/discord/callback`), - discordAdminLogin, - ); - -module.exports = discordStrategy; -module.exports.discordAdminLogin = discordAdminStrategy; diff --git a/api/strategies/facebookStrategy.js b/api/strategies/facebookStrategy.js index f638c3bfdb..e5d1b054db 100644 --- a/api/strategies/facebookStrategy.js +++ b/api/strategies/facebookStrategy.js @@ -11,28 +11,16 @@ const getProfileDetails = ({ profile }) => ({ }); const facebookLogin = socialLogin('facebook', getProfileDetails); -const facebookAdminLogin = socialLogin('facebook', getProfileDetails, { existingUsersOnly: true }); -const getFacebookConfig = (callbackURL) => ({ - clientID: process.env.FACEBOOK_CLIENT_ID, - clientSecret: process.env.FACEBOOK_CLIENT_SECRET, - callbackURL, - proxy: true, - scope: ['public_profile'], - profileFields: ['id', 'email', 'name'], -}); - -const facebookStrategy = () => +module.exports = () => new FacebookStrategy( - getFacebookConfig(`${process.env.DOMAIN_SERVER}${process.env.FACEBOOK_CALLBACK_URL}`), + { + clientID: process.env.FACEBOOK_CLIENT_ID, + clientSecret: process.env.FACEBOOK_CLIENT_SECRET, + callbackURL: `${process.env.DOMAIN_SERVER}${process.env.FACEBOOK_CALLBACK_URL}`, + proxy: true, + scope: ['public_profile'], + profileFields: ['id', 'email', 'name'], + }, facebookLogin, ); - -const facebookAdminStrategy = () => - new FacebookStrategy( - getFacebookConfig(`${process.env.DOMAIN_SERVER}/api/admin/oauth/facebook/callback`), - facebookAdminLogin, - ); - -module.exports = facebookStrategy; -module.exports.facebookAdminLogin = facebookAdminStrategy; diff --git a/api/strategies/githubStrategy.js b/api/strategies/githubStrategy.js index 363acbfcdb..1c3937381e 100644 --- a/api/strategies/githubStrategy.js +++ b/api/strategies/githubStrategy.js @@ -11,36 +11,24 @@ const getProfileDetails = ({ profile }) => ({ }); const githubLogin = socialLogin('github', getProfileDetails); -const githubAdminLogin = socialLogin('github', getProfileDetails, { existingUsersOnly: true }); -const getGitHubConfig = (callbackURL) => ({ - clientID: process.env.GITHUB_CLIENT_ID, - clientSecret: process.env.GITHUB_CLIENT_SECRET, - callbackURL, - proxy: false, - scope: ['user:email'], - ...(process.env.GITHUB_ENTERPRISE_BASE_URL && { - authorizationURL: `${process.env.GITHUB_ENTERPRISE_BASE_URL}/login/oauth/authorize`, - tokenURL: `${process.env.GITHUB_ENTERPRISE_BASE_URL}/login/oauth/access_token`, - userProfileURL: `${process.env.GITHUB_ENTERPRISE_BASE_URL}/api/v3/user`, - userEmailURL: `${process.env.GITHUB_ENTERPRISE_BASE_URL}/api/v3/user/emails`, - ...(process.env.GITHUB_ENTERPRISE_USER_AGENT && { - userAgent: process.env.GITHUB_ENTERPRISE_USER_AGENT, - }), - }), -}); - -const githubStrategy = () => +module.exports = () => new GitHubStrategy( - getGitHubConfig(`${process.env.DOMAIN_SERVER}${process.env.GITHUB_CALLBACK_URL}`), + { + clientID: process.env.GITHUB_CLIENT_ID, + clientSecret: process.env.GITHUB_CLIENT_SECRET, + callbackURL: `${process.env.DOMAIN_SERVER}${process.env.GITHUB_CALLBACK_URL}`, + proxy: false, + scope: ['user:email'], + ...(process.env.GITHUB_ENTERPRISE_BASE_URL && { + authorizationURL: `${process.env.GITHUB_ENTERPRISE_BASE_URL}/login/oauth/authorize`, + tokenURL: `${process.env.GITHUB_ENTERPRISE_BASE_URL}/login/oauth/access_token`, + userProfileURL: `${process.env.GITHUB_ENTERPRISE_BASE_URL}/api/v3/user`, + userEmailURL: `${process.env.GITHUB_ENTERPRISE_BASE_URL}/api/v3/user/emails`, + ...(process.env.GITHUB_ENTERPRISE_USER_AGENT && { + userAgent: process.env.GITHUB_ENTERPRISE_USER_AGENT, + }), + }), + }, githubLogin, ); - -const githubAdminStrategy = () => - new GitHubStrategy( - getGitHubConfig(`${process.env.DOMAIN_SERVER}/api/admin/oauth/github/callback`), - githubAdminLogin, - ); - -module.exports = githubStrategy; -module.exports.githubAdminLogin = githubAdminStrategy; diff --git a/api/strategies/googleStrategy.js b/api/strategies/googleStrategy.js index bee9a061a2..fd65823327 100644 --- a/api/strategies/googleStrategy.js +++ b/api/strategies/googleStrategy.js @@ -11,26 +11,14 @@ const getProfileDetails = ({ profile }) => ({ }); const googleLogin = socialLogin('google', getProfileDetails); -const googleAdminLogin = socialLogin('google', getProfileDetails, { existingUsersOnly: true }); -const getGoogleConfig = (callbackURL) => ({ - clientID: process.env.GOOGLE_CLIENT_ID, - clientSecret: process.env.GOOGLE_CLIENT_SECRET, - callbackURL, - proxy: true, -}); - -const googleStrategy = () => +module.exports = () => new GoogleStrategy( - getGoogleConfig(`${process.env.DOMAIN_SERVER}${process.env.GOOGLE_CALLBACK_URL}`), + { + clientID: process.env.GOOGLE_CLIENT_ID, + clientSecret: process.env.GOOGLE_CLIENT_SECRET, + callbackURL: `${process.env.DOMAIN_SERVER}${process.env.GOOGLE_CALLBACK_URL}`, + proxy: true, + }, googleLogin, ); - -const googleAdminStrategy = () => - new GoogleStrategy( - getGoogleConfig(`${process.env.DOMAIN_SERVER}/api/admin/oauth/google/callback`), - googleAdminLogin, - ); - -module.exports = googleStrategy; -module.exports.googleAdminLogin = googleAdminStrategy; diff --git a/api/strategies/index.js b/api/strategies/index.js index c15bbc4ce5..b4f7bd3cac 100644 --- a/api/strategies/index.js +++ b/api/strategies/index.js @@ -1,36 +1,25 @@ -const { setupOpenId, getOpenIdConfig, getOpenIdEmail } = require('./openidStrategy'); +const { setupOpenId, getOpenIdConfig } = require('./openidStrategy'); const openIdJwtLogin = require('./openIdJwtStrategy'); const facebookLogin = require('./facebookStrategy'); -const { facebookAdminLogin } = facebookLogin; const discordLogin = require('./discordStrategy'); -const { discordAdminLogin } = discordLogin; const passportLogin = require('./localStrategy'); const googleLogin = require('./googleStrategy'); -const { googleAdminLogin } = googleLogin; const githubLogin = require('./githubStrategy'); -const { githubAdminLogin } = githubLogin; const { setupSaml } = require('./samlStrategy'); const appleLogin = require('./appleStrategy'); -const { appleAdminLogin } = appleLogin; const ldapLogin = require('./ldapStrategy'); const jwtLogin = require('./jwtStrategy'); module.exports = { appleLogin, - appleAdminLogin, passportLogin, googleLogin, - googleAdminLogin, githubLogin, - githubAdminLogin, discordLogin, - discordAdminLogin, jwtLogin, facebookLogin, - facebookAdminLogin, setupOpenId, getOpenIdConfig, - getOpenIdEmail, ldapLogin, setupSaml, openIdJwtLogin, diff --git a/api/strategies/ldapStrategy.js b/api/strategies/ldapStrategy.js index 9253f54196..dcadc26a45 100644 --- a/api/strategies/ldapStrategy.js +++ b/api/strategies/ldapStrategy.js @@ -2,12 +2,7 @@ const fs = require('fs'); const LdapStrategy = require('passport-ldapauth'); const { logger } = require('@librechat/data-schemas'); const { SystemRoles, ErrorTypes } = require('librechat-data-provider'); -const { - isEnabled, - getBalanceConfig, - isEmailDomainAllowed, - resolveAppConfigForUser, -} = require('@librechat/api'); +const { isEnabled, getBalanceConfig, isEmailDomainAllowed } = require('@librechat/api'); const { createUser, findUser, updateUser, countUsers } = require('~/models'); const { getAppConfig } = require('~/server/services/Config'); @@ -94,6 +89,16 @@ const ldapLogin = new LdapStrategy(ldapOptions, async (userinfo, done) => { const ldapId = (LDAP_ID && userinfo[LDAP_ID]) || userinfo.uid || userinfo.sAMAccountName || userinfo.mail; + let user = await findUser({ ldapId }); + if (user && user.provider !== 'ldap') { + logger.info( + `[ldapStrategy] User ${user.email} already exists with provider ${user.provider}`, + ); + return done(null, false, { + message: ErrorTypes.AUTH_FAILED, + }); + } + const fullNameAttributes = LDAP_FULL_NAME && LDAP_FULL_NAME.split(','); const fullName = fullNameAttributes && fullNameAttributes.length > 0 @@ -117,31 +122,7 @@ const ldapLogin = new LdapStrategy(ldapOptions, async (userinfo, done) => { ); } - // Domain check before findUser for two-phase fast-fail (consistent with SAML/OpenID/social). - // This means cross-provider users from blocked domains get 'Email domain not allowed' - // instead of AUTH_FAILED — both deny access. - const baseConfig = await getAppConfig({ baseOnly: true }); - if (!isEmailDomainAllowed(mail, baseConfig?.registration?.allowedDomains)) { - logger.error( - `[LDAP Strategy] Authentication blocked - email domain not allowed [Email: ${mail}]`, - ); - return done(null, false, { message: 'Email domain not allowed' }); - } - - let user = await findUser({ ldapId }); - if (user && user.provider !== 'ldap') { - logger.info( - `[ldapStrategy] User ${user.email} already exists with provider ${user.provider}`, - ); - return done(null, false, { - message: ErrorTypes.AUTH_FAILED, - }); - } - - const appConfig = user?.tenantId - ? await resolveAppConfigForUser(getAppConfig, user) - : baseConfig; - + const appConfig = await getAppConfig(); if (!isEmailDomainAllowed(mail, appConfig?.registration?.allowedDomains)) { logger.error( `[LDAP Strategy] Authentication blocked - email domain not allowed [Email: ${mail}]`, diff --git a/api/strategies/ldapStrategy.spec.js b/api/strategies/ldapStrategy.spec.js index 876d70f845..a00e9b14b7 100644 --- a/api/strategies/ldapStrategy.spec.js +++ b/api/strategies/ldapStrategy.spec.js @@ -9,10 +9,10 @@ jest.mock('@librechat/data-schemas', () => ({ })); jest.mock('@librechat/api', () => ({ + // isEnabled used for TLS flags isEnabled: jest.fn(() => false), isEmailDomainAllowed: jest.fn(() => true), getBalanceConfig: jest.fn(() => ({ enabled: false })), - resolveAppConfigForUser: jest.fn(async (_getAppConfig, _user) => ({})), })); jest.mock('~/models', () => ({ @@ -30,15 +30,14 @@ jest.mock('~/server/services/Config', () => ({ let verifyCallback; jest.mock('passport-ldapauth', () => { return jest.fn().mockImplementation((options, verify) => { - verifyCallback = verify; + verifyCallback = verify; // capture the strategy verify function return { name: 'ldap', options, verify }; }); }); const { ErrorTypes } = require('librechat-data-provider'); -const { isEmailDomainAllowed, resolveAppConfigForUser } = require('@librechat/api'); +const { isEmailDomainAllowed } = require('@librechat/api'); const { findUser, createUser, updateUser, countUsers } = require('~/models'); -const { getAppConfig } = require('~/server/services/Config'); // Helper to call the verify callback and wrap in a Promise for convenience const callVerify = (userinfo) => @@ -118,7 +117,6 @@ describe('ldapStrategy', () => { expect(user).toBe(false); expect(info).toEqual({ message: ErrorTypes.AUTH_FAILED }); expect(createUser).not.toHaveBeenCalled(); - expect(resolveAppConfigForUser).not.toHaveBeenCalled(); }); it('updates an existing ldap user with current LDAP info', async () => { @@ -160,6 +158,7 @@ describe('ldapStrategy', () => { uid: 'uid999', givenName: 'John', cn: 'John Doe', + // no mail and no custom LDAP_EMAIL }; const { user } = await callVerify(userinfo); @@ -181,66 +180,4 @@ describe('ldapStrategy', () => { expect(user).toBe(false); expect(info).toEqual({ message: 'Email domain not allowed' }); }); - - it('passes getAppConfig and found user to resolveAppConfigForUser', async () => { - const existing = { - _id: 'u3', - provider: 'ldap', - email: 'tenant@example.com', - ldapId: 'uid-tenant', - username: 'tenantuser', - name: 'Tenant User', - tenantId: 'tenant-a', - role: 'USER', - }; - findUser.mockResolvedValue(existing); - - const userinfo = { - uid: 'uid-tenant', - mail: 'tenant@example.com', - givenName: 'Tenant', - cn: 'Tenant User', - }; - - await callVerify(userinfo); - - expect(resolveAppConfigForUser).toHaveBeenCalledWith(getAppConfig, existing); - }); - - it('uses baseConfig for new user without calling resolveAppConfigForUser', async () => { - findUser.mockResolvedValue(null); - - const userinfo = { - uid: 'uid-new', - mail: 'newuser@example.com', - givenName: 'New', - cn: 'New User', - }; - - await callVerify(userinfo); - - expect(resolveAppConfigForUser).not.toHaveBeenCalled(); - expect(getAppConfig).toHaveBeenCalledWith({ baseOnly: true }); - }); - - it('should block login when tenant config restricts the domain', async () => { - const existing = { - _id: 'u-blocked', - provider: 'ldap', - ldapId: 'uid-tenant', - tenantId: 'tenant-strict', - role: 'USER', - }; - findUser.mockResolvedValue(existing); - resolveAppConfigForUser.mockResolvedValue({ - registration: { allowedDomains: ['other.com'] }, - }); - isEmailDomainAllowed.mockReturnValueOnce(true).mockReturnValueOnce(false); - - const userinfo = { uid: 'uid-tenant', mail: 'user@example.com', givenName: 'Test', cn: 'Test' }; - const { user, info } = await callVerify(userinfo); - - expect(user).toBe(false); - expect(info).toEqual({ message: 'Email domain not allowed' }); - }); }); diff --git a/api/strategies/localStrategy.js b/api/strategies/localStrategy.js index 5d725c0907..0d220ead25 100644 --- a/api/strategies/localStrategy.js +++ b/api/strategies/localStrategy.js @@ -1,9 +1,8 @@ -const bcrypt = require('bcryptjs'); const { logger } = require('@librechat/data-schemas'); const { errorsToString } = require('librechat-data-provider'); +const { isEnabled, checkEmailConfig } = require('@librechat/api'); const { Strategy: PassportLocalStrategy } = require('passport-local'); -const { isEnabled, checkEmailConfig, comparePassword } = require('@librechat/api'); -const { findUser, updateUser } = require('~/models'); +const { findUser, comparePassword, updateUser } = require('~/models'); const { loginSchema } = require('./validators'); // Unix timestamp for 2024-06-07 15:20:18 Eastern Time @@ -36,7 +35,7 @@ async function passportLogin(req, email, password, done) { return done(null, false, { message: 'Email does not exist.' }); } - const isMatch = await comparePassword(user, password, { compare: bcrypt.compare }); + const isMatch = await comparePassword(user, password); if (!isMatch) { logError('Passport Local Strategy - Password does not match', { isMatch }); logger.error(`[Login] [Login failed] [Username: ${email}] [Request-IP: ${req.ip}]`); diff --git a/api/strategies/openIdJwtStrategy.js b/api/strategies/openIdJwtStrategy.js index 83a40bf948..997dcec397 100644 --- a/api/strategies/openIdJwtStrategy.js +++ b/api/strategies/openIdJwtStrategy.js @@ -5,7 +5,6 @@ const { HttpsProxyAgent } = require('https-proxy-agent'); const { SystemRoles } = require('librechat-data-provider'); const { isEnabled, findOpenIDUser, math } = require('@librechat/api'); const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt'); -const { getOpenIdEmail } = require('./openidStrategy'); const { updateUser, findUser } = require('~/models'); /** @@ -54,7 +53,7 @@ const openIdJwtLogin = (openIdConfig) => { const { user, error, migration } = await findOpenIDUser({ findUser, - email: payload ? getOpenIdEmail(payload) : undefined, + email: payload?.email, openidId: payload?.sub, idOnTheSource: payload?.oid, strategyName: 'openIdJwtLogin', diff --git a/api/strategies/openIdJwtStrategy.spec.js b/api/strategies/openIdJwtStrategy.spec.js index fd710f1ebd..566afe5a90 100644 --- a/api/strategies/openIdJwtStrategy.spec.js +++ b/api/strategies/openIdJwtStrategy.spec.js @@ -29,21 +29,10 @@ jest.mock('~/models', () => ({ findUser: jest.fn(), updateUser: jest.fn(), })); -jest.mock('~/server/services/Files/strategies', () => ({ - getStrategyFunctions: jest.fn(() => ({ - saveBuffer: jest.fn().mockResolvedValue('/fake/path/to/avatar.png'), - })), -})); -jest.mock('~/server/services/Config', () => ({ - getAppConfig: jest.fn().mockResolvedValue({}), -})); -jest.mock('~/cache/getLogStores', () => - jest.fn().mockReturnValue({ get: jest.fn(), set: jest.fn() }), -); const { findOpenIDUser } = require('@librechat/api'); +const { updateUser } = require('~/models'); const openIdJwtLogin = require('./openIdJwtStrategy'); -const { findUser, updateUser } = require('~/models'); // Helper: build a mock openIdConfig const mockOpenIdConfig = { @@ -192,182 +181,3 @@ describe('openIdJwtStrategy – token source handling', () => { expect(user.federatedTokens.access_token).not.toBe(user.federatedTokens.id_token); }); }); - -describe('openIdJwtStrategy – OPENID_EMAIL_CLAIM', () => { - const payload = { - sub: 'oidc-123', - email: 'test@example.com', - preferred_username: 'testuser', - upn: 'test@corp.example.com', - exp: 9999999999, - }; - - beforeEach(() => { - jest.clearAllMocks(); - delete process.env.OPENID_EMAIL_CLAIM; - - // Use real findOpenIDUser so it delegates to the findUser mock - const realFindOpenIDUser = jest.requireActual('@librechat/api').findOpenIDUser; - findOpenIDUser.mockImplementation(realFindOpenIDUser); - - findUser.mockResolvedValue(null); - updateUser.mockResolvedValue({}); - - openIdJwtLogin(mockOpenIdConfig); - }); - - afterEach(() => { - delete process.env.OPENID_EMAIL_CLAIM; - }); - - it('should use the default email when OPENID_EMAIL_CLAIM is not set', async () => { - const existingUser = { - _id: 'user-id-1', - provider: 'openid', - openidId: payload.sub, - email: payload.email, - role: SystemRoles.USER, - }; - findUser.mockImplementation(async (query) => { - if (query.$or && query.$or.some((c) => c.openidId === payload.sub)) { - return existingUser; - } - return null; - }); - - const req = { headers: { authorization: 'Bearer tok' }, session: {} }; - await invokeVerify(req, payload); - - expect(findUser).toHaveBeenCalledWith( - expect.objectContaining({ - $or: expect.arrayContaining([{ openidId: payload.sub }]), - }), - ); - }); - - it('should use OPENID_EMAIL_CLAIM when set for email lookup', async () => { - process.env.OPENID_EMAIL_CLAIM = 'upn'; - findUser.mockResolvedValue(null); - - const req = { headers: { authorization: 'Bearer tok' }, session: {} }; - const { user } = await invokeVerify(req, payload); - - expect(findUser).toHaveBeenCalledTimes(2); - expect(findUser.mock.calls[0][0]).toMatchObject({ - $or: expect.arrayContaining([{ openidId: payload.sub }]), - }); - expect(findUser.mock.calls[1][0]).toEqual({ email: 'test@corp.example.com' }); - expect(user).toBe(false); - }); - - it('should fall back to default chain when OPENID_EMAIL_CLAIM points to missing claim', async () => { - process.env.OPENID_EMAIL_CLAIM = 'nonexistent_claim'; - findUser.mockResolvedValue(null); - - const req = { headers: { authorization: 'Bearer tok' }, session: {} }; - const { user } = await invokeVerify(req, payload); - - expect(findUser).toHaveBeenCalledWith({ email: payload.email }); - 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); - - const req = { headers: { authorization: 'Bearer tok' }, session: {} }; - await invokeVerify(req, payload); - - expect(findUser).toHaveBeenCalledWith({ email: 'test@corp.example.com' }); - }); - - it('should ignore empty string OPENID_EMAIL_CLAIM and use default fallback', async () => { - process.env.OPENID_EMAIL_CLAIM = ''; - findUser.mockResolvedValue(null); - - const req = { headers: { authorization: 'Bearer tok' }, session: {} }; - await invokeVerify(req, payload); - - expect(findUser).toHaveBeenCalledWith({ email: payload.email }); - }); - - it('should ignore whitespace-only OPENID_EMAIL_CLAIM and use default fallback', async () => { - process.env.OPENID_EMAIL_CLAIM = ' '; - findUser.mockResolvedValue(null); - - const req = { headers: { authorization: 'Bearer tok' }, session: {} }; - await invokeVerify(req, payload); - - expect(findUser).toHaveBeenCalledWith({ email: payload.email }); - }); - - it('should resolve undefined email when payload is null', async () => { - const req = { headers: { authorization: 'Bearer tok' }, session: {} }; - const { user } = await invokeVerify(req, null); - - expect(user).toBe(false); - }); - - it('should attempt email lookup via preferred_username fallback when email claim is absent', async () => { - const payloadNoEmail = { - sub: 'oidc-new-sub', - preferred_username: 'legacy@corp.com', - upn: 'legacy@corp.com', - exp: 9999999999, - }; - - const legacyUser = { - _id: 'legacy-db-id', - email: 'legacy@corp.com', - openidId: null, - role: SystemRoles.USER, - }; - - findUser.mockImplementation(async (query) => { - if (query.$or) { - return null; - } - if (query.email === 'legacy@corp.com') { - return legacyUser; - } - return null; - }); - - const req = { headers: { authorization: 'Bearer tok' }, session: {} }; - const { user } = await invokeVerify(req, payloadNoEmail); - - expect(findUser).toHaveBeenCalledTimes(2); - expect(findUser.mock.calls[1][0]).toEqual({ email: 'legacy@corp.com' }); - expect(user).toBeTruthy(); - expect(updateUser).toHaveBeenCalledWith( - 'legacy-db-id', - expect.objectContaining({ provider: 'openid', openidId: payloadNoEmail.sub }), - ); - }); -}); diff --git a/api/strategies/openidStrategy.js b/api/strategies/openidStrategy.js index 7314a84e15..198c8735ae 100644 --- a/api/strategies/openidStrategy.js +++ b/api/strategies/openidStrategy.js @@ -15,7 +15,6 @@ const { findOpenIDUser, getBalanceConfig, isEmailDomainAllowed, - resolveAppConfigForUser, } = require('@librechat/api'); const { getStrategyFunctions } = require('~/server/services/Files/strategies'); const { findUser, createUser, updateUser } = require('~/models'); @@ -268,34 +267,6 @@ function getFullName(userinfo) { return userinfo.username || userinfo.email; } -/** - * Resolves the user identifier from OpenID claims. - * Configurable via OPENID_EMAIL_CLAIM; defaults to: email -> preferred_username -> upn. - * - * @param {Object} userinfo - The user information object from OpenID Connect - * @returns {string|undefined} The resolved identifier string - */ -function getOpenIdEmail(userinfo) { - const claimKey = process.env.OPENID_EMAIL_CLAIM?.trim(); - if (claimKey) { - const value = userinfo[claimKey]; - if (typeof value === 'string' && value) { - return value; - } - if (value !== undefined && value !== null) { - logger.warn( - `[openidStrategy] OPENID_EMAIL_CLAIM="${claimKey}" resolved to a non-string value (type: ${typeof value}). Falling back to: email -> preferred_username -> upn.`, - ); - } else { - logger.warn( - `[openidStrategy] OPENID_EMAIL_CLAIM="${claimKey}" not present in userinfo. Falling back to: email -> preferred_username -> upn.`, - ); - } - } - const fallback = userinfo.email || userinfo.preferred_username || userinfo.upn; - return typeof fallback === 'string' ? fallback : undefined; -} - /** * Converts an input into a string suitable for a username. * If the input is a string, it will be returned as is. @@ -316,85 +287,24 @@ function convertToUsername(input, defaultValue = '') { return defaultValue; } -/** - * Exchange the access token for a Graph-scoped token using the On-Behalf-Of (OBO) flow. - * - * The original access token has the app's own audience (api://), which Microsoft Graph - * rejects. This exchange produces a token with audience https://graph.microsoft.com and the - * minimum delegated scope (User.Read) required by /me/getMemberObjects. - * - * Uses a dedicated cache key (`${sub}:overage`) to avoid collisions with other OBO exchanges - * in the codebase (userinfo, Graph principal search). - * - * @param {string} accessToken - The original access token from the OpenID tokenset - * @param {string} sub - The subject identifier for cache keying - * @returns {Promise} A Graph-scoped access token - * @see https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-on-behalf-of-flow - */ -async function exchangeTokenForOverage(accessToken, sub) { - if (!openidConfig) { - throw new Error('[openidStrategy] OpenID config not initialized; cannot exchange OBO token'); - } - - const tokensCache = getLogStores(CacheKeys.OPENID_EXCHANGED_TOKENS); - const cacheKey = `${sub}:overage`; - - const cached = await tokensCache.get(cacheKey); - if (cached?.access_token) { - logger.debug('[openidStrategy] Using cached Graph token for overage resolution'); - return cached.access_token; - } - - const grantResponse = await client.genericGrantRequest( - openidConfig, - 'urn:ietf:params:oauth:grant-type:jwt-bearer', - { - scope: 'https://graph.microsoft.com/User.Read', - assertion: accessToken, - requested_token_use: 'on_behalf_of', - }, - ); - - if (!grantResponse.access_token) { - throw new Error( - '[openidStrategy] OBO exchange succeeded but returned no access_token; cannot call Graph API', - ); - } - - const ttlMs = - Number.isFinite(grantResponse.expires_in) && grantResponse.expires_in > 0 - ? grantResponse.expires_in * 1000 - : 3600 * 1000; - - await tokensCache.set(cacheKey, { access_token: grantResponse.access_token }, ttlMs); - - return grantResponse.access_token; -} - /** * Resolve Azure AD groups when group overage is in effect (groups moved to _claim_names/_claim_sources). * * NOTE: Microsoft recommends treating _claim_names/_claim_sources as a signal only and using Microsoft Graph * to resolve group membership instead of calling the endpoint in _claim_sources directly. * - * Before calling Graph, the access token is exchanged via the OBO flow to obtain a token with the - * correct audience (https://graph.microsoft.com) and User.Read scope. - * - * @param {string} accessToken - Access token from the OpenID tokenset (app audience) - * @param {string} sub - The subject identifier of the user (for OBO exchange and cache keying) + * @param {string} accessToken - Access token with Microsoft Graph permissions * @returns {Promise} Resolved group IDs or null on failure * @see https://learn.microsoft.com/en-us/entra/identity-platform/access-token-claims-reference#groups-overage-claim * @see https://learn.microsoft.com/en-us/graph/api/directoryobject-getmemberobjects */ -async function resolveGroupsFromOverage(accessToken, sub) { +async function resolveGroupsFromOverage(accessToken) { try { if (!accessToken) { logger.error('[openidStrategy] Access token missing; cannot resolve group overage'); return null; } - const graphToken = await exchangeTokenForOverage(accessToken, sub); - // Use /me/getMemberObjects so least-privileged delegated permission User.Read is sufficient // when resolving the signed-in user's group membership. const url = 'https://graph.microsoft.com/v1.0/me/getMemberObjects'; @@ -406,7 +316,7 @@ async function resolveGroupsFromOverage(accessToken, sub) { const fetchOptions = { method: 'POST', headers: { - Authorization: `Bearer ${graphToken}`, + Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ securityEnabledOnly: false }), @@ -426,7 +336,6 @@ async function resolveGroupsFromOverage(accessToken, sub) { } const data = await response.json(); - const values = Array.isArray(data?.value) ? data.value : null; if (!values) { logger.error( @@ -469,12 +378,12 @@ async function processOpenIDAuth(tokenset, existingUsersOnly = false) { Object.assign(userinfo, providerUserinfo); } - const email = getOpenIdEmail(userinfo); - - const baseConfig = await getAppConfig({ baseOnly: true }); - if (!isEmailDomainAllowed(email, baseConfig?.registration?.allowedDomains)) { + const appConfig = await getAppConfig(); + /** Azure AD sometimes doesn't return email, use preferred_username as fallback */ + const email = userinfo.email || userinfo.preferred_username || userinfo.upn; + if (!isEmailDomainAllowed(email, appConfig?.registration?.allowedDomains)) { logger.error( - `[OpenID Strategy] Authentication blocked - email domain not allowed [Identifier: ${email}]`, + `[OpenID Strategy] Authentication blocked - email domain not allowed [Email: ${userinfo.email}]`, ); throw new Error('Email domain not allowed'); } @@ -493,20 +402,9 @@ async function processOpenIDAuth(tokenset, existingUsersOnly = false) { throw new Error(ErrorTypes.AUTH_FAILED); } - const appConfig = user?.tenantId ? await resolveAppConfigForUser(getAppConfig, user) : baseConfig; - - if (!isEmailDomainAllowed(email, appConfig?.registration?.allowedDomains)) { - logger.error( - `[OpenID Strategy] Authentication blocked - email domain not allowed [Identifier: ${email}]`, - ); - throw new Error('Email domain not allowed'); - } - const fullName = getFullName(userinfo); const requiredRole = process.env.OPENID_REQUIRED_ROLE; - let resolvedOverageGroups = null; - if (requiredRole) { const requiredRoles = requiredRole .split(',') @@ -526,21 +424,19 @@ async function processOpenIDAuth(tokenset, existingUsersOnly = false) { // Handle Azure AD group overage for ID token groups: when hasgroups or _claim_* indicate overage, // resolve groups via Microsoft Graph instead of relying on token group values. - const hasOverage = - decodedToken?.hasgroups || - (decodedToken?._claim_names?.groups && - decodedToken?._claim_sources?.[decodedToken._claim_names.groups]); - if ( + !Array.isArray(roles) && + typeof roles !== 'string' && requiredRoleTokenKind === 'id' && requiredRoleParameterPath === 'groups' && decodedToken && - hasOverage + (decodedToken.hasgroups || + (decodedToken._claim_names?.groups && + decodedToken._claim_sources?.[decodedToken._claim_names.groups])) ) { - const overageGroups = await resolveGroupsFromOverage(tokenset.access_token, claims.sub); + const overageGroups = await resolveGroupsFromOverage(tokenset.access_token); if (overageGroups) { roles = overageGroups; - resolvedOverageGroups = overageGroups; } } @@ -555,7 +451,7 @@ async function processOpenIDAuth(tokenset, existingUsersOnly = false) { throw new Error(`You must have ${rolesList} role to log in.`); } - const roleValues = Array.isArray(roles) ? roles : roles.split(/[\s,]+/).filter(Boolean); + const roleValues = Array.isArray(roles) ? roles : [roles]; if (!requiredRoles.some((role) => roleValues.includes(role))) { const rolesList = @@ -627,33 +523,14 @@ async function processOpenIDAuth(tokenset, existingUsersOnly = false) { throw new Error('Invalid admin role token kind'); } - let adminRoles = get(adminRoleObject, adminRoleParameterPath); + const adminRoles = get(adminRoleObject, adminRoleParameterPath); - // Handle Azure AD group overage for admin role when using ID token groups - if (adminRoleTokenKind === 'id' && adminRoleParameterPath === 'groups' && adminRoleObject) { - const hasAdminOverage = - adminRoleObject.hasgroups || - (adminRoleObject._claim_names?.groups && - adminRoleObject._claim_sources?.[adminRoleObject._claim_names.groups]); - - if (hasAdminOverage) { - const overageGroups = - resolvedOverageGroups || - (await resolveGroupsFromOverage(tokenset.access_token, claims.sub)); - if (overageGroups) { - adminRoles = overageGroups; - } - } - } - - let adminRoleValues = []; - if (Array.isArray(adminRoles)) { - adminRoleValues = adminRoles; - } else if (typeof adminRoles === 'string') { - adminRoleValues = adminRoles.split(/[\s,]+/).filter(Boolean); - } - - if (adminRoles && (adminRoles === true || adminRoleValues.includes(adminRole))) { + if ( + adminRoles && + (adminRoles === true || + adminRoles === adminRole || + (Array.isArray(adminRoles) && adminRoles.includes(adminRole))) + ) { user.role = SystemRoles.ADMIN; logger.info(`[openidStrategy] User ${username} is an admin based on role: ${adminRole}`); } else if (user.role === SystemRoles.ADMIN) { @@ -850,5 +727,4 @@ function getOpenIdConfig() { module.exports = { setupOpenId, getOpenIdConfig, - getOpenIdEmail, }; diff --git a/api/strategies/openidStrategy.spec.js b/api/strategies/openidStrategy.spec.js index 6d824176f7..b1dc54d77b 100644 --- a/api/strategies/openidStrategy.spec.js +++ b/api/strategies/openidStrategy.spec.js @@ -1,1873 +1,1309 @@ -const undici = require('undici'); -const fetch = require('node-fetch'); -const jwtDecode = require('jsonwebtoken/decode'); -const { ErrorTypes } = require('librechat-data-provider'); -const { findUser, createUser, updateUser } = require('~/models'); -const { resolveAppConfigForUser } = require('@librechat/api'); -const { getAppConfig } = require('~/server/services/Config'); -const { setupOpenId } = require('./openidStrategy'); - -// --- Mocks --- -jest.mock('node-fetch'); -jest.mock('jsonwebtoken/decode'); -jest.mock('undici', () => ({ - fetch: jest.fn(), - ProxyAgent: jest.fn(), -})); -jest.mock('~/server/services/Files/strategies', () => ({ - getStrategyFunctions: jest.fn(() => ({ - saveBuffer: jest.fn().mockResolvedValue('/fake/path/to/avatar.png'), - })), -})); -jest.mock('~/server/services/Config', () => ({ - getAppConfig: jest.fn().mockResolvedValue({}), -})); -jest.mock('@librechat/api', () => ({ - ...jest.requireActual('@librechat/api'), - isEnabled: jest.fn(() => false), - isEmailDomainAllowed: jest.fn(() => true), - findOpenIDUser: jest.requireActual('@librechat/api').findOpenIDUser, - getBalanceConfig: jest.fn(() => ({ - enabled: false, - })), - resolveAppConfigForUser: jest.fn(async (_getAppConfig, _user) => ({})), -})); -jest.mock('~/models', () => ({ - findUser: jest.fn(), - createUser: jest.fn(), - updateUser: jest.fn(), -})); -jest.mock('@librechat/data-schemas', () => ({ - ...jest.requireActual('@librechat/api'), - logger: { - info: jest.fn(), - warn: jest.fn(), - debug: jest.fn(), - error: jest.fn(), - }, - hashToken: jest.fn().mockResolvedValue('hashed-token'), -})); -jest.mock('~/cache/getLogStores', () => - jest.fn(() => ({ - get: jest.fn(), - set: jest.fn(), - })), -); - -// Mock the openid-client module and all its dependencies -jest.mock('openid-client', () => { - return { - discovery: jest.fn().mockResolvedValue({ - clientId: 'fake_client_id', - clientSecret: 'fake_client_secret', - issuer: 'https://fake-issuer.com', - // Add any other properties needed by the implementation - }), - fetchUserInfo: jest.fn().mockImplementation(() => { - // Only return additional properties, but don't override any claims - return Promise.resolve({}); - }), - genericGrantRequest: jest.fn().mockResolvedValue({ - access_token: 'exchanged_graph_token', - expires_in: 3600, - }), - customFetch: Symbol('customFetch'), - }; -}); - -jest.mock('openid-client/passport', () => { - /** Store callbacks by strategy name - 'openid' and 'openidAdmin' */ - const verifyCallbacks = {}; - let lastVerifyCallback; - - const mockStrategy = jest.fn((options, verify) => { - lastVerifyCallback = verify; - return { name: 'openid', options, verify }; - }); - - return { - Strategy: mockStrategy, - /** Get the last registered callback (for backward compatibility) */ - __getVerifyCallback: () => lastVerifyCallback, - /** Store callback by name when passport.use is called */ - __setVerifyCallback: (name, callback) => { - verifyCallbacks[name] = callback; - }, - /** Get callback by strategy name */ - __getVerifyCallbackByName: (name) => verifyCallbacks[name], - }; -}); - -// Mock passport - capture strategy name and callback -jest.mock('passport', () => ({ - use: jest.fn((name, strategy) => { - const passportMock = require('openid-client/passport'); - if (strategy && strategy.verify) { - passportMock.__setVerifyCallback(name, strategy.verify); - } - }), -})); - -describe('setupOpenId', () => { - // Store a reference to the verify callback once it's set up - let verifyCallback; - - // Helper to wrap the verify callback in a promise - const validate = (tokenset) => - new Promise((resolve, reject) => { - verifyCallback(tokenset, (err, user, details) => { - if (err) { - reject(err); - } else { - resolve({ user, details }); - } - }); - }); - - const tokenset = { - id_token: 'fake_id_token', - access_token: 'fake_access_token', - claims: () => ({ - sub: '1234', - email: 'test@example.com', - email_verified: true, - given_name: 'First', - family_name: 'Last', - name: 'My Full', - preferred_username: 'testusername', - username: 'flast', - picture: 'https://example.com/avatar.png', - }), - }; - - beforeEach(async () => { - // Clear previous mock calls and reset implementations - jest.clearAllMocks(); - - // Reset environment variables needed by the strategy - process.env.OPENID_ISSUER = 'https://fake-issuer.com'; - process.env.OPENID_CLIENT_ID = 'fake_client_id'; - process.env.OPENID_CLIENT_SECRET = 'fake_client_secret'; - process.env.DOMAIN_SERVER = 'https://example.com'; - process.env.OPENID_CALLBACK_URL = '/callback'; - process.env.OPENID_SCOPE = 'openid profile email'; - process.env.OPENID_REQUIRED_ROLE = 'requiredRole'; - process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'roles'; - process.env.OPENID_REQUIRED_ROLE_TOKEN_KIND = 'id'; - process.env.OPENID_ADMIN_ROLE = 'admin'; - process.env.OPENID_ADMIN_ROLE_PARAMETER_PATH = 'permissions'; - process.env.OPENID_ADMIN_ROLE_TOKEN_KIND = 'id'; - delete process.env.OPENID_USERNAME_CLAIM; - delete process.env.OPENID_NAME_CLAIM; - delete process.env.OPENID_EMAIL_CLAIM; - delete process.env.PROXY; - delete process.env.OPENID_USE_PKCE; - - // Default jwtDecode mock returns a token that includes the required role. - jwtDecode.mockReturnValue({ - roles: ['requiredRole'], - permissions: ['admin'], - }); - - // By default, assume that no user is found, so createUser will be called - findUser.mockResolvedValue(null); - createUser.mockImplementation(async (userData) => { - // simulate created user with an _id property - return { _id: 'newUserId', ...userData }; - }); - updateUser.mockImplementation(async (id, userData) => { - return { _id: id, ...userData }; - }); - - // For image download, simulate a successful response - const fakeBuffer = Buffer.from('fake image'); - const fakeResponse = { - ok: true, - buffer: jest.fn().mockResolvedValue(fakeBuffer), - }; - fetch.mockResolvedValue(fakeResponse); - - // Call the setup function and capture the verify callback for the regular 'openid' strategy - // (not 'openidAdmin' which requires existing users) - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - }); - - it('should create a new user with correct username when preferred_username claim exists', async () => { - // Arrange – our userinfo already has preferred_username 'testusername' - const userinfo = tokenset.claims(); - - // Act - const { user } = await validate(tokenset); - - // Assert - expect(user.username).toBe(userinfo.preferred_username); - expect(createUser).toHaveBeenCalledWith( - expect.objectContaining({ - provider: 'openid', - openidId: userinfo.sub, - username: userinfo.preferred_username, - email: userinfo.email, - name: `${userinfo.given_name} ${userinfo.family_name}`, - }), - { enabled: false }, - true, - true, - ); - }); - - it('should use username as username when preferred_username claim is missing', async () => { - // Arrange – remove preferred_username from userinfo - const userinfo = { ...tokenset.claims() }; - delete userinfo.preferred_username; - // Expect the username to be the "username" - const expectUsername = userinfo.username; - - // Act - const { user } = await validate({ ...tokenset, claims: () => userinfo }); - - // Assert - expect(user.username).toBe(expectUsername); - expect(createUser).toHaveBeenCalledWith( - expect.objectContaining({ username: expectUsername }), - { enabled: false }, - true, - true, - ); - }); - - it('should use email as username when username and preferred_username are missing', async () => { - // Arrange – remove username and preferred_username - const userinfo = { ...tokenset.claims() }; - delete userinfo.username; - delete userinfo.preferred_username; - const expectUsername = userinfo.email; - - // Act - const { user } = await validate({ ...tokenset, claims: () => userinfo }); - - // Assert - expect(user.username).toBe(expectUsername); - expect(createUser).toHaveBeenCalledWith( - expect.objectContaining({ username: expectUsername }), - { enabled: false }, - true, - true, - ); - }); - - it('should override username with OPENID_USERNAME_CLAIM when set', async () => { - // Arrange – set OPENID_USERNAME_CLAIM so that the sub claim is used - process.env.OPENID_USERNAME_CLAIM = 'sub'; - const userinfo = tokenset.claims(); - - // Act - const { user } = await validate(tokenset); - - // Assert – username should equal the sub (converted as-is) - expect(user.username).toBe(userinfo.sub); - expect(createUser).toHaveBeenCalledWith( - expect.objectContaining({ username: userinfo.sub }), - { enabled: false }, - true, - true, - ); - }); - - it('should set the full name correctly when given_name and family_name exist', async () => { - // Arrange - const userinfo = tokenset.claims(); - const expectedFullName = `${userinfo.given_name} ${userinfo.family_name}`; - - // Act - const { user } = await validate(tokenset); - - // Assert - expect(user.name).toBe(expectedFullName); - }); - - it('should override full name with OPENID_NAME_CLAIM when set', async () => { - // Arrange – use the name claim as the full name - process.env.OPENID_NAME_CLAIM = 'name'; - const userinfo = { ...tokenset.claims(), name: 'Custom Name' }; - - // Act - const { user } = await validate({ ...tokenset, claims: () => userinfo }); - - // Assert - expect(user.name).toBe('Custom Name'); - }); - - it('should update an existing user on login', async () => { - // Arrange – simulate that a user already exists with openid provider - const existingUser = { - _id: 'existingUserId', - provider: 'openid', - email: tokenset.claims().email, - openidId: '', - username: '', - name: '', - }; - findUser.mockImplementation(async (query) => { - if (query.openidId === tokenset.claims().sub || query.email === tokenset.claims().email) { - return existingUser; - } - return null; - }); - - const userinfo = tokenset.claims(); - - // Act - await validate(tokenset); - - // Assert – updateUser should be called and the user object updated - expect(updateUser).toHaveBeenCalledWith( - existingUser._id, - expect.objectContaining({ - provider: 'openid', - openidId: userinfo.sub, - username: userinfo.preferred_username, - name: `${userinfo.given_name} ${userinfo.family_name}`, - }), - ); - }); - - it('should block login when email exists with different provider', async () => { - // Arrange – simulate that a user exists with same email but different provider - const existingUser = { - _id: 'existingUserId', - provider: 'google', - email: tokenset.claims().email, - googleId: 'some-google-id', - username: 'existinguser', - name: 'Existing User', - }; - findUser.mockImplementation(async (query) => { - if (query.email === tokenset.claims().email && !query.provider) { - return existingUser; - } - return null; - }); - - // Act - const result = await validate(tokenset); - - // Assert – verify that the strategy rejects login - expect(result.user).toBe(false); - expect(result.details.message).toBe(ErrorTypes.AUTH_FAILED); - expect(createUser).not.toHaveBeenCalled(); - 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({ - roles: ['SomeOtherRole'], - }); - - // Act - const { user, details } = await validate(tokenset); - - // Assert – verify that the strategy rejects login - expect(user).toBe(false); - expect(details.message).toBe('You must have "requiredRole" role to log in.'); - }); - - it('should not treat substring matches in string roles as satisfying required role', async () => { - // Arrange – override required role to "read" then re-setup - process.env.OPENID_REQUIRED_ROLE = 'read'; - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - // Token contains "bread" which *contains* "read" as a substring - jwtDecode.mockReturnValue({ - roles: 'bread', - }); - - // Act - const { user, details } = await validate(tokenset); - - // Assert – verify that substring match does not grant access - expect(user).toBe(false); - expect(details.message).toBe('You must have "read" role to log in.'); - }); - - it('should allow login when roles claim is a space-separated string containing the required role', async () => { - // Arrange – IdP returns roles as a space-delimited string - jwtDecode.mockReturnValue({ - roles: 'role1 role2 requiredRole', - }); - - // Act - const { user } = await validate(tokenset); - - // Assert – login succeeds when required role is present after splitting - expect(user).toBeTruthy(); - expect(createUser).toHaveBeenCalled(); - }); - - it('should allow login when roles claim is a comma-separated string containing the required role', async () => { - // Arrange – IdP returns roles as a comma-delimited string - jwtDecode.mockReturnValue({ - roles: 'role1,role2,requiredRole', - }); - - // Act - const { user } = await validate(tokenset); - - // Assert – login succeeds when required role is present after splitting - expect(user).toBeTruthy(); - expect(createUser).toHaveBeenCalled(); - }); - - it('should allow login when roles claim is a mixed comma-and-space-separated string containing the required role', async () => { - // Arrange – IdP returns roles with comma-and-space delimiters - jwtDecode.mockReturnValue({ - roles: 'role1, role2, requiredRole', - }); - - // Act - const { user } = await validate(tokenset); - - // Assert – login succeeds when required role is present after splitting - expect(user).toBeTruthy(); - expect(createUser).toHaveBeenCalled(); - }); - - it('should reject login when roles claim is a space-separated string that does not contain the required role', async () => { - // Arrange – IdP returns a delimited string but required role is absent - jwtDecode.mockReturnValue({ - roles: 'role1 role2 otherRole', - }); - - // Act - const { user, details } = await validate(tokenset); - - // Assert – login is rejected with the correct error message - expect(user).toBe(false); - expect(details.message).toBe('You must have "requiredRole" role to log in.'); - }); - - it('should allow login when single required role is present (backward compatibility)', async () => { - // Arrange – ensure single role configuration (as set in beforeEach) - // OPENID_REQUIRED_ROLE = 'requiredRole' - // Default jwtDecode mock in beforeEach already returns this role - jwtDecode.mockReturnValue({ - roles: ['requiredRole', 'anotherRole'], - }); - - // Act - const { user } = await validate(tokenset); - - // Assert – verify that login succeeds with single role configuration - expect(user).toBeTruthy(); - expect(user.email).toBe(tokenset.claims().email); - expect(user.username).toBe(tokenset.claims().preferred_username); - expect(createUser).toHaveBeenCalled(); - }); - - describe('group overage and groups handling', () => { - it.each([ - ['groups array contains required group', ['group-required', 'other-group'], true, undefined], - [ - 'groups array missing required group', - ['other-group'], - false, - 'You must have "group-required" role to log in.', - ], - ['groups string equals required group', 'group-required', true, undefined], - [ - 'groups string is other group', - 'other-group', - false, - 'You must have "group-required" role to log in.', - ], - ])( - 'uses groups claim directly when %s (no overage)', - async (_label, groupsClaim, expectedAllowed, expectedMessage) => { - process.env.OPENID_REQUIRED_ROLE = 'group-required'; - process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'groups'; - process.env.OPENID_REQUIRED_ROLE_TOKEN_KIND = 'id'; - - jwtDecode.mockReturnValue({ - groups: groupsClaim, - permissions: ['admin'], - }); - - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - const { user, details } = await validate(tokenset); - - expect(undici.fetch).not.toHaveBeenCalled(); - expect(Boolean(user)).toBe(expectedAllowed); - expect(details?.message).toBe(expectedMessage); - }, - ); - - it.each([ - ['token kind is not id', { kind: 'access', path: 'groups', decoded: { hasgroups: true } }], - ['parameter path is not groups', { kind: 'id', path: 'roles', decoded: { hasgroups: true } }], - ['decoded token is falsy', { kind: 'id', path: 'groups', decoded: null }], - [ - 'no overage indicators in decoded token', - { - kind: 'id', - path: 'groups', - decoded: { - permissions: ['admin'], - }, - }, - ], - [ - 'only _claim_names present (no _claim_sources)', - { - kind: 'id', - path: 'groups', - decoded: { - _claim_names: { groups: 'src1' }, - permissions: ['admin'], - }, - }, - ], - [ - 'only _claim_sources present (no _claim_names)', - { - kind: 'id', - path: 'groups', - decoded: { - _claim_sources: { src1: { endpoint: 'https://graph.windows.net/ignored' } }, - permissions: ['admin'], - }, - }, - ], - ])('does not attempt overage resolution when %s', async (_label, cfg) => { - process.env.OPENID_REQUIRED_ROLE = 'group-required'; - process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = cfg.path; - process.env.OPENID_REQUIRED_ROLE_TOKEN_KIND = cfg.kind; - - jwtDecode.mockReturnValue(cfg.decoded); - - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - const { user, details } = await validate(tokenset); - - expect(undici.fetch).not.toHaveBeenCalled(); - expect(user).toBe(false); - expect(details.message).toBe('You must have "group-required" role to log in.'); - const { logger } = require('@librechat/data-schemas'); - const expectedTokenKind = cfg.kind === 'access' ? 'access token' : 'id token'; - expect(logger.error).toHaveBeenCalledWith( - expect.stringContaining(`Key '${cfg.path}' not found in ${expectedTokenKind}!`), - ); - }); - }); - - describe('resolving groups via Microsoft Graph', () => { - it('denies login and does not call Graph when access token is missing', async () => { - process.env.OPENID_REQUIRED_ROLE = 'group-required'; - process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'groups'; - process.env.OPENID_REQUIRED_ROLE_TOKEN_KIND = 'id'; - - const { logger } = require('@librechat/data-schemas'); - - jwtDecode.mockReturnValue({ - hasgroups: true, - permissions: ['admin'], - }); - - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - const tokensetWithoutAccess = { - ...tokenset, - access_token: undefined, - }; - - const { user, details } = await validate(tokensetWithoutAccess); - - expect(user).toBe(false); - expect(details.message).toBe('You must have "group-required" role to log in.'); - - expect(undici.fetch).not.toHaveBeenCalled(); - expect(logger.error).toHaveBeenCalledWith( - expect.stringContaining('Access token missing; cannot resolve group overage'), - ); - }); - - it.each([ - [ - 'Graph returns HTTP error', - async () => ({ - ok: false, - status: 403, - statusText: 'Forbidden', - json: async () => ({}), - }), - [ - '[openidStrategy] Failed to resolve groups via Microsoft Graph getMemberObjects: HTTP 403 Forbidden', - ], - ], - [ - 'Graph network error', - async () => { - throw new Error('network error'); - }, - [ - '[openidStrategy] Error resolving groups via Microsoft Graph getMemberObjects:', - expect.any(Error), - ], - ], - [ - 'Graph returns unexpected shape (no value)', - async () => ({ - ok: true, - status: 200, - statusText: 'OK', - json: async () => ({}), - }), - [ - '[openidStrategy] Unexpected response format when resolving groups via Microsoft Graph getMemberObjects', - ], - ], - [ - 'Graph returns invalid value type', - async () => ({ - ok: true, - status: 200, - statusText: 'OK', - json: async () => ({ value: 'not-an-array' }), - }), - [ - '[openidStrategy] Unexpected response format when resolving groups via Microsoft Graph getMemberObjects', - ], - ], - ])( - 'denies login when overage resolution fails because %s', - async (_label, setupFetch, expectedErrorArgs) => { - process.env.OPENID_REQUIRED_ROLE = 'group-required'; - process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'groups'; - process.env.OPENID_REQUIRED_ROLE_TOKEN_KIND = 'id'; - - const { logger } = require('@librechat/data-schemas'); - - jwtDecode.mockReturnValue({ - hasgroups: true, - permissions: ['admin'], - }); - - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - undici.fetch.mockImplementation(setupFetch); - - const { user, details } = await validate(tokenset); - - expect(undici.fetch).toHaveBeenCalled(); - expect(user).toBe(false); - expect(details.message).toBe('You must have "group-required" role to log in.'); - - expect(logger.error).toHaveBeenCalledWith(...expectedErrorArgs); - }, - ); - - it.each([ - [ - 'hasgroups overage and Graph contains required group', - { - hasgroups: true, - }, - ['group-required', 'some-other-group'], - true, - ], - [ - '_claim_* overage and Graph contains required group', - { - _claim_names: { groups: 'src1' }, - _claim_sources: { src1: { endpoint: 'https://graph.windows.net/ignored' } }, - }, - ['group-required', 'some-other-group'], - true, - ], - [ - 'hasgroups overage and Graph does NOT contain required group', - { - hasgroups: true, - }, - ['some-other-group'], - false, - ], - [ - '_claim_* overage and Graph does NOT contain required group', - { - _claim_names: { groups: 'src1' }, - _claim_sources: { src1: { endpoint: 'https://graph.windows.net/ignored' } }, - }, - ['some-other-group'], - false, - ], - ])( - 'resolves groups via Microsoft Graph when %s', - async (_label, decodedTokenValue, graphGroups, expectedAllowed) => { - process.env.OPENID_REQUIRED_ROLE = 'group-required'; - process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'groups'; - process.env.OPENID_REQUIRED_ROLE_TOKEN_KIND = 'id'; - - const { logger } = require('@librechat/data-schemas'); - - jwtDecode.mockReturnValue(decodedTokenValue); - - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - undici.fetch.mockResolvedValue({ - ok: true, - status: 200, - statusText: 'OK', - json: async () => ({ - value: graphGroups, - }), - }); - - const { user } = await validate(tokenset); - - expect(undici.fetch).toHaveBeenCalledWith( - 'https://graph.microsoft.com/v1.0/me/getMemberObjects', - expect.objectContaining({ - method: 'POST', - headers: expect.objectContaining({ - Authorization: 'Bearer exchanged_graph_token', - }), - }), - ); - expect(Boolean(user)).toBe(expectedAllowed); - - expect(logger.debug).toHaveBeenCalledWith( - expect.stringContaining( - `Successfully resolved ${graphGroups.length} groups via Microsoft Graph getMemberObjects`, - ), - ); - }, - ); - }); - - describe('OBO token exchange for overage', () => { - it('exchanges access token via OBO before calling Graph API', async () => { - const openidClient = require('openid-client'); - process.env.OPENID_REQUIRED_ROLE = 'group-required'; - process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'groups'; - process.env.OPENID_REQUIRED_ROLE_TOKEN_KIND = 'id'; - - jwtDecode.mockReturnValue({ hasgroups: true }); - - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - undici.fetch.mockResolvedValue({ - ok: true, - status: 200, - statusText: 'OK', - json: async () => ({ value: ['group-required'] }), - }); - - await validate(tokenset); - - expect(openidClient.genericGrantRequest).toHaveBeenCalledWith( - expect.anything(), - 'urn:ietf:params:oauth:grant-type:jwt-bearer', - expect.objectContaining({ - scope: 'https://graph.microsoft.com/User.Read', - assertion: tokenset.access_token, - requested_token_use: 'on_behalf_of', - }), - ); - - expect(undici.fetch).toHaveBeenCalledWith( - 'https://graph.microsoft.com/v1.0/me/getMemberObjects', - expect.objectContaining({ - headers: expect.objectContaining({ - Authorization: 'Bearer exchanged_graph_token', - }), - }), - ); - }); - - it('caches the exchanged token and reuses it on subsequent calls', async () => { - const openidClient = require('openid-client'); - const getLogStores = require('~/cache/getLogStores'); - const mockSet = jest.fn(); - const mockGet = jest - .fn() - .mockResolvedValueOnce(undefined) - .mockResolvedValueOnce({ access_token: 'exchanged_graph_token' }); - getLogStores.mockReturnValue({ get: mockGet, set: mockSet }); - - process.env.OPENID_REQUIRED_ROLE = 'group-required'; - process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'groups'; - process.env.OPENID_REQUIRED_ROLE_TOKEN_KIND = 'id'; - - jwtDecode.mockReturnValue({ hasgroups: true }); - - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - undici.fetch.mockResolvedValue({ - ok: true, - status: 200, - statusText: 'OK', - json: async () => ({ value: ['group-required'] }), - }); - - // First call: cache miss → OBO exchange → cache set - await validate(tokenset); - expect(mockSet).toHaveBeenCalledWith( - '1234:overage', - { access_token: 'exchanged_graph_token' }, - 3600000, - ); - expect(openidClient.genericGrantRequest).toHaveBeenCalledTimes(1); - - // Second call: cache hit → no new OBO exchange - openidClient.genericGrantRequest.mockClear(); - await validate(tokenset); - expect(openidClient.genericGrantRequest).not.toHaveBeenCalled(); - }); - }); - - describe('admin role group overage', () => { - it('resolves admin groups via Graph when overage is detected for admin role', async () => { - process.env.OPENID_REQUIRED_ROLE = 'group-required'; - process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'groups'; - process.env.OPENID_REQUIRED_ROLE_TOKEN_KIND = 'id'; - process.env.OPENID_ADMIN_ROLE = 'admin-group-id'; - process.env.OPENID_ADMIN_ROLE_PARAMETER_PATH = 'groups'; - process.env.OPENID_ADMIN_ROLE_TOKEN_KIND = 'id'; - - jwtDecode.mockReturnValue({ hasgroups: true }); - - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - undici.fetch.mockResolvedValue({ - ok: true, - status: 200, - statusText: 'OK', - json: async () => ({ value: ['group-required', 'admin-group-id'] }), - }); - - const { user } = await validate(tokenset); - - expect(user.role).toBe('ADMIN'); - }); - - it('does not grant admin when overage groups do not contain admin role', async () => { - process.env.OPENID_REQUIRED_ROLE = 'group-required'; - process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'groups'; - process.env.OPENID_REQUIRED_ROLE_TOKEN_KIND = 'id'; - process.env.OPENID_ADMIN_ROLE = 'admin-group-id'; - process.env.OPENID_ADMIN_ROLE_PARAMETER_PATH = 'groups'; - process.env.OPENID_ADMIN_ROLE_TOKEN_KIND = 'id'; - - jwtDecode.mockReturnValue({ hasgroups: true }); - - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - undici.fetch.mockResolvedValue({ - ok: true, - status: 200, - statusText: 'OK', - json: async () => ({ value: ['group-required', 'other-group'] }), - }); - - const { user } = await validate(tokenset); - - expect(user).toBeTruthy(); - expect(user.role).toBeUndefined(); - }); - - it('reuses already-resolved overage groups for admin role check (no duplicate Graph call)', async () => { - process.env.OPENID_REQUIRED_ROLE = 'group-required'; - process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'groups'; - process.env.OPENID_REQUIRED_ROLE_TOKEN_KIND = 'id'; - process.env.OPENID_ADMIN_ROLE = 'admin-group-id'; - process.env.OPENID_ADMIN_ROLE_PARAMETER_PATH = 'groups'; - process.env.OPENID_ADMIN_ROLE_TOKEN_KIND = 'id'; - - jwtDecode.mockReturnValue({ hasgroups: true }); - - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - undici.fetch.mockResolvedValue({ - ok: true, - status: 200, - statusText: 'OK', - json: async () => ({ value: ['group-required', 'admin-group-id'] }), - }); - - await validate(tokenset); - - // Graph API should be called only once (for required role), admin role reuses the result - expect(undici.fetch).toHaveBeenCalledTimes(1); - }); - - it('demotes existing admin when overage groups no longer contain admin role', async () => { - process.env.OPENID_REQUIRED_ROLE = 'group-required'; - process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'groups'; - process.env.OPENID_REQUIRED_ROLE_TOKEN_KIND = 'id'; - process.env.OPENID_ADMIN_ROLE = 'admin-group-id'; - process.env.OPENID_ADMIN_ROLE_PARAMETER_PATH = 'groups'; - process.env.OPENID_ADMIN_ROLE_TOKEN_KIND = 'id'; - - const existingAdminUser = { - _id: 'existingAdminId', - provider: 'openid', - email: tokenset.claims().email, - openidId: tokenset.claims().sub, - username: 'adminuser', - name: 'Admin User', - role: 'ADMIN', - }; - - findUser.mockImplementation(async (query) => { - if (query.openidId === tokenset.claims().sub || query.email === tokenset.claims().email) { - return existingAdminUser; - } - return null; - }); - - jwtDecode.mockReturnValue({ hasgroups: true }); - - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - undici.fetch.mockResolvedValue({ - ok: true, - status: 200, - statusText: 'OK', - json: async () => ({ value: ['group-required'] }), - }); - - const { user } = await validate(tokenset); - - expect(user.role).toBe('USER'); - }); - - it('does not attempt overage for admin role when token kind is not id', async () => { - process.env.OPENID_REQUIRED_ROLE = 'requiredRole'; - process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'roles'; - process.env.OPENID_REQUIRED_ROLE_TOKEN_KIND = 'id'; - process.env.OPENID_ADMIN_ROLE = 'admin'; - process.env.OPENID_ADMIN_ROLE_PARAMETER_PATH = 'groups'; - process.env.OPENID_ADMIN_ROLE_TOKEN_KIND = 'access'; - - jwtDecode.mockReturnValue({ - roles: ['requiredRole'], - hasgroups: true, - }); - - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - const { user } = await validate(tokenset); - - // No Graph call since admin uses access token (not id) - expect(undici.fetch).not.toHaveBeenCalled(); - expect(user.role).toBeUndefined(); - }); - - it('resolves admin via Graph independently when OPENID_REQUIRED_ROLE is not configured', async () => { - delete process.env.OPENID_REQUIRED_ROLE; - process.env.OPENID_ADMIN_ROLE = 'admin-group-id'; - process.env.OPENID_ADMIN_ROLE_PARAMETER_PATH = 'groups'; - process.env.OPENID_ADMIN_ROLE_TOKEN_KIND = 'id'; - - jwtDecode.mockReturnValue({ hasgroups: true }); - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - undici.fetch.mockResolvedValue({ - ok: true, - status: 200, - statusText: 'OK', - json: async () => ({ value: ['admin-group-id'] }), - }); - - const { user } = await validate(tokenset); - expect(user.role).toBe('ADMIN'); - expect(undici.fetch).toHaveBeenCalledTimes(1); - }); - - it('denies admin when OPENID_REQUIRED_ROLE is absent and Graph does not contain admin group', async () => { - delete process.env.OPENID_REQUIRED_ROLE; - process.env.OPENID_ADMIN_ROLE = 'admin-group-id'; - process.env.OPENID_ADMIN_ROLE_PARAMETER_PATH = 'groups'; - process.env.OPENID_ADMIN_ROLE_TOKEN_KIND = 'id'; - - jwtDecode.mockReturnValue({ hasgroups: true }); - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - undici.fetch.mockResolvedValue({ - ok: true, - status: 200, - statusText: 'OK', - json: async () => ({ value: ['other-group'] }), - }); - - const { user } = await validate(tokenset); - expect(user).toBeTruthy(); - expect(user.role).toBeUndefined(); - }); - - it('denies login and logs error when OBO exchange throws', async () => { - const openidClient = require('openid-client'); - process.env.OPENID_REQUIRED_ROLE = 'group-required'; - process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'groups'; - process.env.OPENID_REQUIRED_ROLE_TOKEN_KIND = 'id'; - - jwtDecode.mockReturnValue({ hasgroups: true }); - openidClient.genericGrantRequest.mockRejectedValueOnce(new Error('OBO exchange rejected')); - - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - const { user, details } = await validate(tokenset); - expect(user).toBe(false); - expect(details.message).toBe('You must have "group-required" role to log in.'); - expect(undici.fetch).not.toHaveBeenCalled(); - }); - - it('denies login when OBO exchange returns no access_token', async () => { - const openidClient = require('openid-client'); - process.env.OPENID_REQUIRED_ROLE = 'group-required'; - process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'groups'; - process.env.OPENID_REQUIRED_ROLE_TOKEN_KIND = 'id'; - - jwtDecode.mockReturnValue({ hasgroups: true }); - openidClient.genericGrantRequest.mockResolvedValueOnce({ expires_in: 3600 }); - - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - const { user, details } = await validate(tokenset); - expect(user).toBe(false); - expect(details.message).toBe('You must have "group-required" role to log in.'); - expect(undici.fetch).not.toHaveBeenCalled(); - }); - }); - - it('should attempt to download and save the avatar if picture is provided', async () => { - // Act - const { user } = await validate(tokenset); - - // Assert – verify that download was attempted and the avatar field was set via updateUser - expect(fetch).toHaveBeenCalled(); - // Our mock getStrategyFunctions.saveBuffer returns '/fake/path/to/avatar.png' - expect(user.avatar).toBe('/fake/path/to/avatar.png'); - }); - - it('should not attempt to download avatar if picture is not provided', async () => { - // Arrange – remove picture - const userinfo = { ...tokenset.claims() }; - delete userinfo.picture; - - // Act - await validate({ ...tokenset, claims: () => userinfo }); - - // Assert – fetch should not be called and avatar should remain undefined or empty - expect(fetch).not.toHaveBeenCalled(); - // Depending on your implementation, user.avatar may be undefined or an empty string. - }); - - it('should support comma-separated multiple roles', async () => { - // Arrange - process.env.OPENID_REQUIRED_ROLE = 'someRole,anotherRole,admin'; - await setupOpenId(); // Re-initialize the strategy - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - jwtDecode.mockReturnValue({ - roles: ['anotherRole', 'aThirdRole'], - }); - - // Act - const { user } = await validate(tokenset); - - // Assert - expect(user).toBeTruthy(); - expect(user.email).toBe(tokenset.claims().email); - }); - - it('should reject login when user has none of the required multiple roles', async () => { - // Arrange - process.env.OPENID_REQUIRED_ROLE = 'someRole,anotherRole,admin'; - await setupOpenId(); // Re-initialize the strategy - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - jwtDecode.mockReturnValue({ - roles: ['aThirdRole', 'aFourthRole'], - }); - - // Act - const { user, details } = await validate(tokenset); - - // Assert - expect(user).toBe(false); - expect(details.message).toBe( - 'You must have one of: "someRole", "anotherRole", "admin" role to log in.', - ); - }); - - it('should handle spaces in comma-separated roles', async () => { - // Arrange - process.env.OPENID_REQUIRED_ROLE = ' someRole , anotherRole , admin '; - await setupOpenId(); // Re-initialize the strategy - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - jwtDecode.mockReturnValue({ - roles: ['someRole'], - }); - - // Act - const { user } = await validate(tokenset); - - // Assert - expect(user).toBeTruthy(); - }); - - it('should default to usePKCE false when OPENID_USE_PKCE is not defined', async () => { - const OpenIDStrategy = require('openid-client/passport').Strategy; - - delete process.env.OPENID_USE_PKCE; - await setupOpenId(); - - const callOptions = OpenIDStrategy.mock.calls[OpenIDStrategy.mock.calls.length - 1][0]; - expect(callOptions.usePKCE).toBe(false); - expect(callOptions.params?.code_challenge_method).toBeUndefined(); - }); - - it('should attach federatedTokens to user object for token propagation', async () => { - // Arrange - setup tokenset with access token, id token, refresh token, and expiration - const tokensetWithTokens = { - ...tokenset, - access_token: 'mock_access_token_abc123', - id_token: 'mock_id_token_def456', - refresh_token: 'mock_refresh_token_xyz789', - expires_at: 1234567890, - }; - - // Act - validate with the tokenset containing tokens - const { user } = await validate(tokensetWithTokens); - - // Assert - verify federatedTokens object is attached with correct values - expect(user.federatedTokens).toBeDefined(); - expect(user.federatedTokens).toEqual({ - access_token: 'mock_access_token_abc123', - id_token: 'mock_id_token_def456', - refresh_token: 'mock_refresh_token_xyz789', - expires_at: 1234567890, - }); - }); - - it('should include id_token in federatedTokens distinct from access_token', async () => { - // Arrange - use different values for access_token and id_token - const tokensetWithTokens = { - ...tokenset, - access_token: 'the_access_token', - id_token: 'the_id_token', - refresh_token: 'the_refresh_token', - expires_at: 9999999999, - }; - - // Act - const { user } = await validate(tokensetWithTokens); - - // Assert - id_token and access_token must be different values - expect(user.federatedTokens.access_token).toBe('the_access_token'); - expect(user.federatedTokens.id_token).toBe('the_id_token'); - expect(user.federatedTokens.id_token).not.toBe(user.federatedTokens.access_token); - }); - - it('should include tokenset along with federatedTokens', async () => { - // Arrange - const tokensetWithTokens = { - ...tokenset, - access_token: 'test_access_token', - id_token: 'test_id_token', - refresh_token: 'test_refresh_token', - expires_at: 9999999999, - }; - - // Act - const { user } = await validate(tokensetWithTokens); - - // Assert - both tokenset and federatedTokens should be present - expect(user.tokenset).toBeDefined(); - expect(user.federatedTokens).toBeDefined(); - expect(user.tokenset.access_token).toBe('test_access_token'); - expect(user.tokenset.id_token).toBe('test_id_token'); - expect(user.federatedTokens.access_token).toBe('test_access_token'); - expect(user.federatedTokens.id_token).toBe('test_id_token'); - }); - - it('should set role to "ADMIN" if OPENID_ADMIN_ROLE is set and user has that role', async () => { - // Act - const { user } = await validate(tokenset); - - // Assert – verify that the user role is set to "ADMIN" - expect(user.role).toBe('ADMIN'); - }); - - it('should not set user role if OPENID_ADMIN_ROLE is set but the user does not have that role', async () => { - // Arrange – simulate a token without the admin permission - jwtDecode.mockReturnValue({ - roles: ['requiredRole'], - permissions: ['not-admin'], - }); - - // Act - const { user } = await validate(tokenset); - - // Assert – verify that the user role is not defined - expect(user.role).toBeUndefined(); - }); - - it('should demote existing admin user when admin role is removed from token', async () => { - // Arrange – simulate an existing user who is currently an admin - const existingAdminUser = { - _id: 'existingAdminId', - provider: 'openid', - email: tokenset.claims().email, - openidId: tokenset.claims().sub, - username: 'adminuser', - name: 'Admin User', - role: 'ADMIN', - }; - - findUser.mockImplementation(async (query) => { - if (query.openidId === tokenset.claims().sub || query.email === tokenset.claims().email) { - return existingAdminUser; - } - return null; - }); - - // Token without admin permission - jwtDecode.mockReturnValue({ - roles: ['requiredRole'], - permissions: ['not-admin'], - }); - - const { logger } = require('@librechat/data-schemas'); - - // Act - const { user } = await validate(tokenset); - - // Assert – verify that the user was demoted - expect(user.role).toBe('USER'); - expect(updateUser).toHaveBeenCalledWith( - existingAdminUser._id, - expect.objectContaining({ - role: 'USER', - }), - ); - expect(logger.info).toHaveBeenCalledWith( - expect.stringContaining('demoted from admin - role no longer present in token'), - ); - }); - - it('should NOT demote admin user when admin role env vars are not configured', async () => { - // Arrange – remove admin role env vars - delete process.env.OPENID_ADMIN_ROLE; - delete process.env.OPENID_ADMIN_ROLE_PARAMETER_PATH; - delete process.env.OPENID_ADMIN_ROLE_TOKEN_KIND; - - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - // Simulate an existing admin user - const existingAdminUser = { - _id: 'existingAdminId', - provider: 'openid', - email: tokenset.claims().email, - openidId: tokenset.claims().sub, - username: 'adminuser', - name: 'Admin User', - role: 'ADMIN', - }; - - findUser.mockImplementation(async (query) => { - if (query.openidId === tokenset.claims().sub || query.email === tokenset.claims().email) { - return existingAdminUser; - } - return null; - }); - - jwtDecode.mockReturnValue({ - roles: ['requiredRole'], - }); - - // Act - const { user } = await validate(tokenset); - - // Assert – verify that the admin user was NOT demoted - expect(user.role).toBe('ADMIN'); - expect(updateUser).toHaveBeenCalledWith( - existingAdminUser._id, - expect.objectContaining({ - role: 'ADMIN', - }), - ); - }); - - describe('lodash get - nested path extraction', () => { - it('should extract roles from deeply nested token path', async () => { - process.env.OPENID_REQUIRED_ROLE = 'app-user'; - process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'resource_access.my-client.roles'; - - jwtDecode.mockReturnValue({ - resource_access: { - 'my-client': { - roles: ['app-user', 'viewer'], - }, - }, - }); - - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - const { user } = await validate(tokenset); - - expect(user).toBeTruthy(); - expect(user.email).toBe(tokenset.claims().email); - }); - - it('should extract roles from three-level nested path', async () => { - process.env.OPENID_REQUIRED_ROLE = 'editor'; - process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'data.access.permissions.roles'; - - jwtDecode.mockReturnValue({ - data: { - access: { - permissions: { - roles: ['editor', 'reader'], - }, - }, - }, - }); - - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - const { user } = await validate(tokenset); - - expect(user).toBeTruthy(); - }); - - it('should log error and reject login when required role path does not exist in token', async () => { - const { logger } = require('@librechat/data-schemas'); - process.env.OPENID_REQUIRED_ROLE = 'app-user'; - process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'resource_access.nonexistent.roles'; - - jwtDecode.mockReturnValue({ - resource_access: { - 'my-client': { - roles: ['app-user'], - }, - }, - }); - - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - const { user, details } = await validate(tokenset); - - expect(logger.error).toHaveBeenCalledWith( - expect.stringContaining("Key 'resource_access.nonexistent.roles' not found in id token!"), - ); - expect(user).toBe(false); - expect(details.message).toContain('role to log in'); - }); - - it('should handle missing intermediate nested path gracefully', async () => { - const { logger } = require('@librechat/data-schemas'); - process.env.OPENID_REQUIRED_ROLE = 'user'; - process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'org.team.roles'; - - jwtDecode.mockReturnValue({ - org: { - other: 'value', - }, - }); - - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - const { user } = await validate(tokenset); - - expect(logger.error).toHaveBeenCalledWith( - expect.stringContaining("Key 'org.team.roles' not found in id token!"), - ); - expect(user).toBe(false); - }); - - it('should extract admin role from nested path in access token', async () => { - process.env.OPENID_ADMIN_ROLE = 'admin'; - process.env.OPENID_ADMIN_ROLE_PARAMETER_PATH = 'realm_access.roles'; - process.env.OPENID_ADMIN_ROLE_TOKEN_KIND = 'access'; - - jwtDecode.mockImplementation((token) => { - if (token === 'fake_access_token') { - return { - realm_access: { - roles: ['admin', 'user'], - }, - }; - } - return { - roles: ['requiredRole'], - }; - }); - - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - const { user } = await validate(tokenset); - - expect(user.role).toBe('ADMIN'); - }); - - it('should extract admin role from nested path in userinfo', async () => { - process.env.OPENID_ADMIN_ROLE = 'admin'; - process.env.OPENID_ADMIN_ROLE_PARAMETER_PATH = 'organization.permissions'; - process.env.OPENID_ADMIN_ROLE_TOKEN_KIND = 'userinfo'; - - const userinfoWithNestedGroups = { - ...tokenset.claims(), - organization: { - permissions: ['admin', 'write'], - }, - }; - - require('openid-client').fetchUserInfo.mockResolvedValue({ - organization: { - permissions: ['admin', 'write'], - }, - }); - - jwtDecode.mockReturnValue({ - roles: ['requiredRole'], - }); - - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - const { user } = await validate({ - ...tokenset, - claims: () => userinfoWithNestedGroups, - }); - - expect(user.role).toBe('ADMIN'); - }); - - it('should handle boolean admin role value', async () => { - process.env.OPENID_ADMIN_ROLE = 'admin'; - process.env.OPENID_ADMIN_ROLE_PARAMETER_PATH = 'is_admin'; - - jwtDecode.mockReturnValue({ - roles: ['requiredRole'], - is_admin: true, - }); - - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - const { user } = await validate(tokenset); - - expect(user.role).toBe('ADMIN'); - }); - - it('should handle string admin role value matching exactly', async () => { - process.env.OPENID_ADMIN_ROLE = 'super-admin'; - process.env.OPENID_ADMIN_ROLE_PARAMETER_PATH = 'role'; - - jwtDecode.mockReturnValue({ - roles: ['requiredRole'], - role: 'super-admin', - }); - - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - const { user } = await validate(tokenset); - - expect(user.role).toBe('ADMIN'); - }); - - it('should not set admin role when string value does not match', async () => { - process.env.OPENID_ADMIN_ROLE = 'super-admin'; - process.env.OPENID_ADMIN_ROLE_PARAMETER_PATH = 'role'; - - jwtDecode.mockReturnValue({ - roles: ['requiredRole'], - role: 'regular-user', - }); - - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - const { user } = await validate(tokenset); - - expect(user.role).toBeUndefined(); - }); - - it('should handle array admin role value', async () => { - process.env.OPENID_ADMIN_ROLE = 'site-admin'; - process.env.OPENID_ADMIN_ROLE_PARAMETER_PATH = 'app_roles'; - - jwtDecode.mockReturnValue({ - roles: ['requiredRole'], - app_roles: ['user', 'site-admin', 'moderator'], - }); - - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - const { user } = await validate(tokenset); - - expect(user.role).toBe('ADMIN'); - }); - - it('should not set admin when role is not in array', async () => { - process.env.OPENID_ADMIN_ROLE = 'site-admin'; - process.env.OPENID_ADMIN_ROLE_PARAMETER_PATH = 'app_roles'; - - jwtDecode.mockReturnValue({ - roles: ['requiredRole'], - app_roles: ['user', 'moderator'], - }); - - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - const { user } = await validate(tokenset); - - expect(user.role).toBeUndefined(); - }); - - it('should grant admin when admin role claim is a space-separated string containing the admin role', async () => { - // Arrange – IdP returns admin roles as a space-delimited string - process.env.OPENID_ADMIN_ROLE = 'site-admin'; - process.env.OPENID_ADMIN_ROLE_PARAMETER_PATH = 'app_roles'; - - jwtDecode.mockReturnValue({ - roles: ['requiredRole'], - app_roles: 'user site-admin moderator', - }); - - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - // Act - const { user } = await validate(tokenset); - - // Assert – admin role is granted after splitting the delimited string - expect(user.role).toBe('ADMIN'); - }); - - it('should not grant admin when admin role claim is a space-separated string that does not contain the admin role', async () => { - // Arrange – delimited string present but admin role is absent - process.env.OPENID_ADMIN_ROLE = 'site-admin'; - process.env.OPENID_ADMIN_ROLE_PARAMETER_PATH = 'app_roles'; - - jwtDecode.mockReturnValue({ - roles: ['requiredRole'], - app_roles: 'user moderator', - }); - - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - // Act - const { user } = await validate(tokenset); - - // Assert – admin role is not granted - expect(user.role).toBeUndefined(); - }); - - it('should handle nested path with special characters in keys', async () => { - process.env.OPENID_REQUIRED_ROLE = 'app-user'; - process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'resource_access.my-app-123.roles'; - - jwtDecode.mockReturnValue({ - resource_access: { - 'my-app-123': { - roles: ['app-user'], - }, - }, - }); - - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - const { user } = await validate(tokenset); - - expect(user).toBeTruthy(); - }); - - it('should handle empty object at nested path', async () => { - const { logger } = require('@librechat/data-schemas'); - process.env.OPENID_REQUIRED_ROLE = 'user'; - process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'access.roles'; - - jwtDecode.mockReturnValue({ - access: {}, - }); - - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - const { user } = await validate(tokenset); - - expect(logger.error).toHaveBeenCalledWith( - expect.stringContaining("Key 'access.roles' not found in id token!"), - ); - expect(user).toBe(false); - }); - - it('should handle null value at intermediate path', async () => { - const { logger } = require('@librechat/data-schemas'); - process.env.OPENID_REQUIRED_ROLE = 'user'; - process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'data.roles'; - - jwtDecode.mockReturnValue({ - data: null, - }); - - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - const { user } = await validate(tokenset); - - expect(logger.error).toHaveBeenCalledWith( - expect.stringContaining("Key 'data.roles' not found in id token!"), - ); - expect(user).toBe(false); - }); - - it('should reject login with invalid admin role token kind', async () => { - process.env.OPENID_ADMIN_ROLE = 'admin'; - process.env.OPENID_ADMIN_ROLE_PARAMETER_PATH = 'roles'; - process.env.OPENID_ADMIN_ROLE_TOKEN_KIND = 'invalid'; - - const { logger } = require('@librechat/data-schemas'); - - jwtDecode.mockReturnValue({ - roles: ['requiredRole', 'admin'], - }); - - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - await expect(validate(tokenset)).rejects.toThrow('Invalid admin role token kind'); - - expect(logger.error).toHaveBeenCalledWith( - expect.stringContaining( - "Invalid admin role token kind: invalid. Must be one of 'access', 'id', or 'userinfo'", - ), - ); - }); - - it('should reject login when roles path returns invalid type (object)', async () => { - const { logger } = require('@librechat/data-schemas'); - process.env.OPENID_REQUIRED_ROLE = 'app-user'; - process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'roles'; - - jwtDecode.mockReturnValue({ - roles: { admin: true, user: false }, - }); - - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - const { user, details } = await validate(tokenset); - - expect(logger.error).toHaveBeenCalledWith( - expect.stringContaining("Key 'roles' not found in id token!"), - ); - expect(user).toBe(false); - expect(details.message).toContain('role to log in'); - }); - - it('should reject login when roles path returns invalid type (number)', async () => { - const { logger } = require('@librechat/data-schemas'); - process.env.OPENID_REQUIRED_ROLE = 'user'; - process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'roleCount'; - - jwtDecode.mockReturnValue({ - roleCount: 5, - }); - - await setupOpenId(); - verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); - - const { user } = await validate(tokenset); - - expect(logger.error).toHaveBeenCalledWith( - expect.stringContaining("Key 'roleCount' not found in id token!"), - ); - expect(user).toBe(false); - }); - }); - - describe('OPENID_EMAIL_CLAIM', () => { - it('should use the default email when OPENID_EMAIL_CLAIM is not set', async () => { - const { user } = await validate(tokenset); - expect(user.email).toBe('test@example.com'); - }); - - it('should use the configured claim when OPENID_EMAIL_CLAIM is set', async () => { - process.env.OPENID_EMAIL_CLAIM = 'upn'; - const userinfo = { ...tokenset.claims(), upn: 'user@corp.example.com' }; - - const { user } = await validate({ ...tokenset, claims: () => userinfo }); - - expect(user.email).toBe('user@corp.example.com'); - expect(createUser).toHaveBeenCalledWith( - expect.objectContaining({ email: 'user@corp.example.com' }), - expect.anything(), - true, - true, - ); - }); - - it('should fall back to preferred_username when email is missing and OPENID_EMAIL_CLAIM is not set', async () => { - const userinfo = { ...tokenset.claims() }; - delete userinfo.email; - - const { user } = await validate({ ...tokenset, claims: () => userinfo }); - - expect(user.email).toBe('testusername'); - }); - - it('should fall back to upn when email and preferred_username are missing and OPENID_EMAIL_CLAIM is not set', async () => { - const userinfo = { ...tokenset.claims(), upn: 'user@corp.example.com' }; - delete userinfo.email; - delete userinfo.preferred_username; - - const { user } = await validate({ ...tokenset, claims: () => userinfo }); - - expect(user.email).toBe('user@corp.example.com'); - }); - - it('should ignore empty string OPENID_EMAIL_CLAIM and use default fallback', async () => { - process.env.OPENID_EMAIL_CLAIM = ''; - - const { user } = await validate(tokenset); - - expect(user.email).toBe('test@example.com'); - }); - - it('should trim whitespace from OPENID_EMAIL_CLAIM and resolve correctly', async () => { - process.env.OPENID_EMAIL_CLAIM = ' upn '; - const userinfo = { ...tokenset.claims(), upn: 'user@corp.example.com' }; - - const { user } = await validate({ ...tokenset, claims: () => userinfo }); - - expect(user.email).toBe('user@corp.example.com'); - }); - - it('should ignore whitespace-only OPENID_EMAIL_CLAIM and use default fallback', async () => { - process.env.OPENID_EMAIL_CLAIM = ' '; - - const { user } = await validate(tokenset); - - expect(user.email).toBe('test@example.com'); - }); - - it('should fall back to default chain with warning when configured claim is missing from userinfo', async () => { - const { logger } = require('@librechat/data-schemas'); - process.env.OPENID_EMAIL_CLAIM = 'nonexistent_claim'; - - const { user } = await validate(tokenset); - - expect(user.email).toBe('test@example.com'); - expect(logger.warn).toHaveBeenCalledWith( - expect.stringContaining('OPENID_EMAIL_CLAIM="nonexistent_claim" not present in userinfo'), - ); - }); - }); - - describe('Tenant-scoped config', () => { - it('should call resolveAppConfigForUser for tenant user', async () => { - const existingUser = { - _id: 'openid-tenant-user', - provider: 'openid', - openidId: '1234', - email: 'test@example.com', - tenantId: 'tenant-d', - role: 'USER', - }; - findUser.mockResolvedValue(existingUser); - - await validate(tokenset); - - expect(resolveAppConfigForUser).toHaveBeenCalledWith(getAppConfig, existingUser); - }); - - it('should use baseConfig for new user without calling resolveAppConfigForUser', async () => { - findUser.mockResolvedValue(null); - - await validate(tokenset); - - expect(resolveAppConfigForUser).not.toHaveBeenCalled(); - expect(getAppConfig).toHaveBeenCalledWith({ baseOnly: true }); - }); - - it('should block login when tenant config restricts the domain', async () => { - const { isEmailDomainAllowed } = require('@librechat/api'); - const existingUser = { - _id: 'openid-tenant-blocked', - provider: 'openid', - openidId: '1234', - email: 'test@example.com', - tenantId: 'tenant-restrict', - role: 'USER', - }; - findUser.mockResolvedValue(existingUser); - resolveAppConfigForUser.mockResolvedValue({ - registration: { allowedDomains: ['other.com'] }, - }); - isEmailDomainAllowed.mockReturnValueOnce(true).mockReturnValueOnce(false); - - const { user, details } = await validate(tokenset); - expect(user).toBe(false); - expect(details).toEqual({ message: 'Email domain not allowed' }); - }); - }); -}); +const fetch = require('node-fetch'); +const jwtDecode = require('jsonwebtoken/decode'); +const undici = require('undici'); +const { ErrorTypes } = require('librechat-data-provider'); +const { findUser, createUser, updateUser } = require('~/models'); +const { setupOpenId } = require('./openidStrategy'); + +// --- Mocks --- +jest.mock('node-fetch'); +jest.mock('jsonwebtoken/decode'); +jest.mock('undici', () => ({ + fetch: jest.fn(), + ProxyAgent: jest.fn(), +})); +jest.mock('~/server/services/Files/strategies', () => ({ + getStrategyFunctions: jest.fn(() => ({ + saveBuffer: jest.fn().mockResolvedValue('/fake/path/to/avatar.png'), + })), +})); +jest.mock('~/server/services/Config', () => ({ + getAppConfig: jest.fn().mockResolvedValue({}), +})); +jest.mock('@librechat/api', () => ({ + ...jest.requireActual('@librechat/api'), + isEnabled: jest.fn(() => false), + isEmailDomainAllowed: jest.fn(() => true), + findOpenIDUser: jest.requireActual('@librechat/api').findOpenIDUser, + getBalanceConfig: jest.fn(() => ({ + enabled: false, + })), +})); +jest.mock('~/models', () => ({ + findUser: jest.fn(), + createUser: jest.fn(), + updateUser: jest.fn(), +})); +jest.mock('@librechat/data-schemas', () => ({ + ...jest.requireActual('@librechat/api'), + logger: { + info: jest.fn(), + warn: jest.fn(), + debug: jest.fn(), + error: jest.fn(), + }, + hashToken: jest.fn().mockResolvedValue('hashed-token'), +})); +jest.mock('~/cache/getLogStores', () => + jest.fn(() => ({ + get: jest.fn(), + set: jest.fn(), + })), +); + +// Mock the openid-client module and all its dependencies +jest.mock('openid-client', () => { + return { + discovery: jest.fn().mockResolvedValue({ + clientId: 'fake_client_id', + clientSecret: 'fake_client_secret', + issuer: 'https://fake-issuer.com', + // Add any other properties needed by the implementation + }), + fetchUserInfo: jest.fn().mockImplementation(() => { + // Only return additional properties, but don't override any claims + return Promise.resolve({}); + }), + customFetch: Symbol('customFetch'), + }; +}); + +jest.mock('openid-client/passport', () => { + /** Store callbacks by strategy name - 'openid' and 'openidAdmin' */ + const verifyCallbacks = {}; + let lastVerifyCallback; + + const mockStrategy = jest.fn((options, verify) => { + lastVerifyCallback = verify; + return { name: 'openid', options, verify }; + }); + + return { + Strategy: mockStrategy, + /** Get the last registered callback (for backward compatibility) */ + __getVerifyCallback: () => lastVerifyCallback, + /** Store callback by name when passport.use is called */ + __setVerifyCallback: (name, callback) => { + verifyCallbacks[name] = callback; + }, + /** Get callback by strategy name */ + __getVerifyCallbackByName: (name) => verifyCallbacks[name], + }; +}); + +// Mock passport - capture strategy name and callback +jest.mock('passport', () => ({ + use: jest.fn((name, strategy) => { + const passportMock = require('openid-client/passport'); + if (strategy && strategy.verify) { + passportMock.__setVerifyCallback(name, strategy.verify); + } + }), +})); + +describe('setupOpenId', () => { + // Store a reference to the verify callback once it's set up + let verifyCallback; + + // Helper to wrap the verify callback in a promise + const validate = (tokenset) => + new Promise((resolve, reject) => { + verifyCallback(tokenset, (err, user, details) => { + if (err) { + reject(err); + } else { + resolve({ user, details }); + } + }); + }); + + const tokenset = { + id_token: 'fake_id_token', + access_token: 'fake_access_token', + claims: () => ({ + sub: '1234', + email: 'test@example.com', + email_verified: true, + given_name: 'First', + family_name: 'Last', + name: 'My Full', + preferred_username: 'testusername', + username: 'flast', + picture: 'https://example.com/avatar.png', + }), + }; + + beforeEach(async () => { + // Clear previous mock calls and reset implementations + jest.clearAllMocks(); + + // Reset environment variables needed by the strategy + process.env.OPENID_ISSUER = 'https://fake-issuer.com'; + process.env.OPENID_CLIENT_ID = 'fake_client_id'; + process.env.OPENID_CLIENT_SECRET = 'fake_client_secret'; + process.env.DOMAIN_SERVER = 'https://example.com'; + process.env.OPENID_CALLBACK_URL = '/callback'; + process.env.OPENID_SCOPE = 'openid profile email'; + process.env.OPENID_REQUIRED_ROLE = 'requiredRole'; + process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'roles'; + process.env.OPENID_REQUIRED_ROLE_TOKEN_KIND = 'id'; + process.env.OPENID_ADMIN_ROLE = 'admin'; + process.env.OPENID_ADMIN_ROLE_PARAMETER_PATH = 'permissions'; + process.env.OPENID_ADMIN_ROLE_TOKEN_KIND = 'id'; + delete process.env.OPENID_USERNAME_CLAIM; + delete process.env.OPENID_NAME_CLAIM; + delete process.env.PROXY; + delete process.env.OPENID_USE_PKCE; + + // Default jwtDecode mock returns a token that includes the required role. + jwtDecode.mockReturnValue({ + roles: ['requiredRole'], + permissions: ['admin'], + }); + + // By default, assume that no user is found, so createUser will be called + findUser.mockResolvedValue(null); + createUser.mockImplementation(async (userData) => { + // simulate created user with an _id property + return { _id: 'newUserId', ...userData }; + }); + updateUser.mockImplementation(async (id, userData) => { + return { _id: id, ...userData }; + }); + + // For image download, simulate a successful response + const fakeBuffer = Buffer.from('fake image'); + const fakeResponse = { + ok: true, + buffer: jest.fn().mockResolvedValue(fakeBuffer), + }; + fetch.mockResolvedValue(fakeResponse); + + // Call the setup function and capture the verify callback for the regular 'openid' strategy + // (not 'openidAdmin' which requires existing users) + await setupOpenId(); + verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); + }); + + it('should create a new user with correct username when preferred_username claim exists', async () => { + // Arrange – our userinfo already has preferred_username 'testusername' + const userinfo = tokenset.claims(); + + // Act + const { user } = await validate(tokenset); + + // Assert + expect(user.username).toBe(userinfo.preferred_username); + expect(createUser).toHaveBeenCalledWith( + expect.objectContaining({ + provider: 'openid', + openidId: userinfo.sub, + username: userinfo.preferred_username, + email: userinfo.email, + name: `${userinfo.given_name} ${userinfo.family_name}`, + }), + { enabled: false }, + true, + true, + ); + }); + + it('should use username as username when preferred_username claim is missing', async () => { + // Arrange – remove preferred_username from userinfo + const userinfo = { ...tokenset.claims() }; + delete userinfo.preferred_username; + // Expect the username to be the "username" + const expectUsername = userinfo.username; + + // Act + const { user } = await validate({ ...tokenset, claims: () => userinfo }); + + // Assert + expect(user.username).toBe(expectUsername); + expect(createUser).toHaveBeenCalledWith( + expect.objectContaining({ username: expectUsername }), + { enabled: false }, + true, + true, + ); + }); + + it('should use email as username when username and preferred_username are missing', async () => { + // Arrange – remove username and preferred_username + const userinfo = { ...tokenset.claims() }; + delete userinfo.username; + delete userinfo.preferred_username; + const expectUsername = userinfo.email; + + // Act + const { user } = await validate({ ...tokenset, claims: () => userinfo }); + + // Assert + expect(user.username).toBe(expectUsername); + expect(createUser).toHaveBeenCalledWith( + expect.objectContaining({ username: expectUsername }), + { enabled: false }, + true, + true, + ); + }); + + it('should override username with OPENID_USERNAME_CLAIM when set', async () => { + // Arrange – set OPENID_USERNAME_CLAIM so that the sub claim is used + process.env.OPENID_USERNAME_CLAIM = 'sub'; + const userinfo = tokenset.claims(); + + // Act + const { user } = await validate(tokenset); + + // Assert – username should equal the sub (converted as-is) + expect(user.username).toBe(userinfo.sub); + expect(createUser).toHaveBeenCalledWith( + expect.objectContaining({ username: userinfo.sub }), + { enabled: false }, + true, + true, + ); + }); + + it('should set the full name correctly when given_name and family_name exist', async () => { + // Arrange + const userinfo = tokenset.claims(); + const expectedFullName = `${userinfo.given_name} ${userinfo.family_name}`; + + // Act + const { user } = await validate(tokenset); + + // Assert + expect(user.name).toBe(expectedFullName); + }); + + it('should override full name with OPENID_NAME_CLAIM when set', async () => { + // Arrange – use the name claim as the full name + process.env.OPENID_NAME_CLAIM = 'name'; + const userinfo = { ...tokenset.claims(), name: 'Custom Name' }; + + // Act + const { user } = await validate({ ...tokenset, claims: () => userinfo }); + + // Assert + expect(user.name).toBe('Custom Name'); + }); + + it('should update an existing user on login', async () => { + // Arrange – simulate that a user already exists with openid provider + const existingUser = { + _id: 'existingUserId', + provider: 'openid', + email: tokenset.claims().email, + openidId: '', + username: '', + name: '', + }; + findUser.mockImplementation(async (query) => { + if (query.openidId === tokenset.claims().sub || query.email === tokenset.claims().email) { + return existingUser; + } + return null; + }); + + const userinfo = tokenset.claims(); + + // Act + await validate(tokenset); + + // Assert – updateUser should be called and the user object updated + expect(updateUser).toHaveBeenCalledWith( + existingUser._id, + expect.objectContaining({ + provider: 'openid', + openidId: userinfo.sub, + username: userinfo.preferred_username, + name: `${userinfo.given_name} ${userinfo.family_name}`, + }), + ); + }); + + it('should block login when email exists with different provider', async () => { + // Arrange – simulate that a user exists with same email but different provider + const existingUser = { + _id: 'existingUserId', + provider: 'google', + email: tokenset.claims().email, + googleId: 'some-google-id', + username: 'existinguser', + name: 'Existing User', + }; + findUser.mockImplementation(async (query) => { + if (query.email === tokenset.claims().email && !query.provider) { + return existingUser; + } + return null; + }); + + // Act + const result = await validate(tokenset); + + // Assert – verify that the strategy rejects login + 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({ + roles: ['SomeOtherRole'], + }); + + // Act + const { user, details } = await validate(tokenset); + + // Assert – verify that the strategy rejects login + expect(user).toBe(false); + expect(details.message).toBe('You must have "requiredRole" role to log in.'); + }); + + it('should not treat substring matches in string roles as satisfying required role', async () => { + // Arrange – override required role to "read" then re-setup + process.env.OPENID_REQUIRED_ROLE = 'read'; + await setupOpenId(); + verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); + + // Token contains "bread" which *contains* "read" as a substring + jwtDecode.mockReturnValue({ + roles: 'bread', + }); + + // Act + const { user, details } = await validate(tokenset); + + // Assert – verify that substring match does not grant access + expect(user).toBe(false); + expect(details.message).toBe('You must have "read" role to log in.'); + }); + + it('should allow login when single required role is present (backward compatibility)', async () => { + // Arrange – ensure single role configuration (as set in beforeEach) + // OPENID_REQUIRED_ROLE = 'requiredRole' + // Default jwtDecode mock in beforeEach already returns this role + jwtDecode.mockReturnValue({ + roles: ['requiredRole', 'anotherRole'], + }); + + // Act + const { user } = await validate(tokenset); + + // Assert – verify that login succeeds with single role configuration + expect(user).toBeTruthy(); + expect(user.email).toBe(tokenset.claims().email); + expect(user.username).toBe(tokenset.claims().preferred_username); + expect(createUser).toHaveBeenCalled(); + }); + + describe('group overage and groups handling', () => { + it.each([ + ['groups array contains required group', ['group-required', 'other-group'], true, undefined], + [ + 'groups array missing required group', + ['other-group'], + false, + 'You must have "group-required" role to log in.', + ], + ['groups string equals required group', 'group-required', true, undefined], + [ + 'groups string is other group', + 'other-group', + false, + 'You must have "group-required" role to log in.', + ], + ])( + 'uses groups claim directly when %s (no overage)', + async (_label, groupsClaim, expectedAllowed, expectedMessage) => { + process.env.OPENID_REQUIRED_ROLE = 'group-required'; + process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'groups'; + process.env.OPENID_REQUIRED_ROLE_TOKEN_KIND = 'id'; + + jwtDecode.mockReturnValue({ + groups: groupsClaim, + permissions: ['admin'], + }); + + await setupOpenId(); + verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); + + const { user, details } = await validate(tokenset); + + expect(undici.fetch).not.toHaveBeenCalled(); + expect(Boolean(user)).toBe(expectedAllowed); + expect(details?.message).toBe(expectedMessage); + }, + ); + + it.each([ + ['token kind is not id', { kind: 'access', path: 'groups', decoded: { hasgroups: true } }], + ['parameter path is not groups', { kind: 'id', path: 'roles', decoded: { hasgroups: true } }], + ['decoded token is falsy', { kind: 'id', path: 'groups', decoded: null }], + [ + 'no overage indicators in decoded token', + { + kind: 'id', + path: 'groups', + decoded: { + permissions: ['admin'], + }, + }, + ], + [ + 'only _claim_names present (no _claim_sources)', + { + kind: 'id', + path: 'groups', + decoded: { + _claim_names: { groups: 'src1' }, + permissions: ['admin'], + }, + }, + ], + [ + 'only _claim_sources present (no _claim_names)', + { + kind: 'id', + path: 'groups', + decoded: { + _claim_sources: { src1: { endpoint: 'https://graph.windows.net/ignored' } }, + permissions: ['admin'], + }, + }, + ], + ])('does not attempt overage resolution when %s', async (_label, cfg) => { + process.env.OPENID_REQUIRED_ROLE = 'group-required'; + process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = cfg.path; + process.env.OPENID_REQUIRED_ROLE_TOKEN_KIND = cfg.kind; + + jwtDecode.mockReturnValue(cfg.decoded); + + await setupOpenId(); + verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); + + const { user, details } = await validate(tokenset); + + expect(undici.fetch).not.toHaveBeenCalled(); + expect(user).toBe(false); + expect(details.message).toBe('You must have "group-required" role to log in.'); + const { logger } = require('@librechat/data-schemas'); + const expectedTokenKind = cfg.kind === 'access' ? 'access token' : 'id token'; + expect(logger.error).toHaveBeenCalledWith( + expect.stringContaining(`Key '${cfg.path}' not found in ${expectedTokenKind}!`), + ); + }); + }); + + describe('resolving groups via Microsoft Graph', () => { + it('denies login and does not call Graph when access token is missing', async () => { + process.env.OPENID_REQUIRED_ROLE = 'group-required'; + process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'groups'; + process.env.OPENID_REQUIRED_ROLE_TOKEN_KIND = 'id'; + + const { logger } = require('@librechat/data-schemas'); + + jwtDecode.mockReturnValue({ + hasgroups: true, + permissions: ['admin'], + }); + + await setupOpenId(); + verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); + + const tokensetWithoutAccess = { + ...tokenset, + access_token: undefined, + }; + + const { user, details } = await validate(tokensetWithoutAccess); + + expect(user).toBe(false); + expect(details.message).toBe('You must have "group-required" role to log in.'); + + expect(undici.fetch).not.toHaveBeenCalled(); + expect(logger.error).toHaveBeenCalledWith( + expect.stringContaining('Access token missing; cannot resolve group overage'), + ); + }); + + it.each([ + [ + 'Graph returns HTTP error', + async () => ({ + ok: false, + status: 403, + statusText: 'Forbidden', + json: async () => ({}), + }), + [ + '[openidStrategy] Failed to resolve groups via Microsoft Graph getMemberObjects: HTTP 403 Forbidden', + ], + ], + [ + 'Graph network error', + async () => { + throw new Error('network error'); + }, + [ + '[openidStrategy] Error resolving groups via Microsoft Graph getMemberObjects:', + expect.any(Error), + ], + ], + [ + 'Graph returns unexpected shape (no value)', + async () => ({ + ok: true, + status: 200, + statusText: 'OK', + json: async () => ({}), + }), + [ + '[openidStrategy] Unexpected response format when resolving groups via Microsoft Graph getMemberObjects', + ], + ], + [ + 'Graph returns invalid value type', + async () => ({ + ok: true, + status: 200, + statusText: 'OK', + json: async () => ({ value: 'not-an-array' }), + }), + [ + '[openidStrategy] Unexpected response format when resolving groups via Microsoft Graph getMemberObjects', + ], + ], + ])( + 'denies login when overage resolution fails because %s', + async (_label, setupFetch, expectedErrorArgs) => { + process.env.OPENID_REQUIRED_ROLE = 'group-required'; + process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'groups'; + process.env.OPENID_REQUIRED_ROLE_TOKEN_KIND = 'id'; + + const { logger } = require('@librechat/data-schemas'); + + jwtDecode.mockReturnValue({ + hasgroups: true, + permissions: ['admin'], + }); + + await setupOpenId(); + verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); + + undici.fetch.mockImplementation(setupFetch); + + const { user, details } = await validate(tokenset); + + expect(undici.fetch).toHaveBeenCalled(); + expect(user).toBe(false); + expect(details.message).toBe('You must have "group-required" role to log in.'); + + expect(logger.error).toHaveBeenCalledWith(...expectedErrorArgs); + }, + ); + + it.each([ + [ + 'hasgroups overage and Graph contains required group', + { + hasgroups: true, + }, + ['group-required', 'some-other-group'], + true, + ], + [ + '_claim_* overage and Graph contains required group', + { + _claim_names: { groups: 'src1' }, + _claim_sources: { src1: { endpoint: 'https://graph.windows.net/ignored' } }, + }, + ['group-required', 'some-other-group'], + true, + ], + [ + 'hasgroups overage and Graph does NOT contain required group', + { + hasgroups: true, + }, + ['some-other-group'], + false, + ], + [ + '_claim_* overage and Graph does NOT contain required group', + { + _claim_names: { groups: 'src1' }, + _claim_sources: { src1: { endpoint: 'https://graph.windows.net/ignored' } }, + }, + ['some-other-group'], + false, + ], + ])( + 'resolves groups via Microsoft Graph when %s', + async (_label, decodedTokenValue, graphGroups, expectedAllowed) => { + process.env.OPENID_REQUIRED_ROLE = 'group-required'; + process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'groups'; + process.env.OPENID_REQUIRED_ROLE_TOKEN_KIND = 'id'; + + const { logger } = require('@librechat/data-schemas'); + + jwtDecode.mockReturnValue(decodedTokenValue); + + await setupOpenId(); + verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); + + undici.fetch.mockResolvedValue({ + ok: true, + status: 200, + statusText: 'OK', + json: async () => ({ + value: graphGroups, + }), + }); + + const { user } = await validate(tokenset); + + expect(undici.fetch).toHaveBeenCalledWith( + 'https://graph.microsoft.com/v1.0/me/getMemberObjects', + expect.objectContaining({ + method: 'POST', + headers: expect.objectContaining({ + Authorization: `Bearer ${tokenset.access_token}`, + }), + }), + ); + expect(Boolean(user)).toBe(expectedAllowed); + + expect(logger.debug).toHaveBeenCalledWith( + expect.stringContaining( + `Successfully resolved ${graphGroups.length} groups via Microsoft Graph getMemberObjects`, + ), + ); + }, + ); + }); + + it('should attempt to download and save the avatar if picture is provided', async () => { + // Act + const { user } = await validate(tokenset); + + // Assert – verify that download was attempted and the avatar field was set via updateUser + expect(fetch).toHaveBeenCalled(); + // Our mock getStrategyFunctions.saveBuffer returns '/fake/path/to/avatar.png' + expect(user.avatar).toBe('/fake/path/to/avatar.png'); + }); + + it('should not attempt to download avatar if picture is not provided', async () => { + // Arrange – remove picture + const userinfo = { ...tokenset.claims() }; + delete userinfo.picture; + + // Act + await validate({ ...tokenset, claims: () => userinfo }); + + // Assert – fetch should not be called and avatar should remain undefined or empty + expect(fetch).not.toHaveBeenCalled(); + // Depending on your implementation, user.avatar may be undefined or an empty string. + }); + + it('should support comma-separated multiple roles', async () => { + // Arrange + process.env.OPENID_REQUIRED_ROLE = 'someRole,anotherRole,admin'; + await setupOpenId(); // Re-initialize the strategy + verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); + jwtDecode.mockReturnValue({ + roles: ['anotherRole', 'aThirdRole'], + }); + + // Act + const { user } = await validate(tokenset); + + // Assert + expect(user).toBeTruthy(); + expect(user.email).toBe(tokenset.claims().email); + }); + + it('should reject login when user has none of the required multiple roles', async () => { + // Arrange + process.env.OPENID_REQUIRED_ROLE = 'someRole,anotherRole,admin'; + await setupOpenId(); // Re-initialize the strategy + verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); + jwtDecode.mockReturnValue({ + roles: ['aThirdRole', 'aFourthRole'], + }); + + // Act + const { user, details } = await validate(tokenset); + + // Assert + expect(user).toBe(false); + expect(details.message).toBe( + 'You must have one of: "someRole", "anotherRole", "admin" role to log in.', + ); + }); + + it('should handle spaces in comma-separated roles', async () => { + // Arrange + process.env.OPENID_REQUIRED_ROLE = ' someRole , anotherRole , admin '; + await setupOpenId(); // Re-initialize the strategy + verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); + jwtDecode.mockReturnValue({ + roles: ['someRole'], + }); + + // Act + const { user } = await validate(tokenset); + + // Assert + expect(user).toBeTruthy(); + }); + + it('should default to usePKCE false when OPENID_USE_PKCE is not defined', async () => { + const OpenIDStrategy = require('openid-client/passport').Strategy; + + delete process.env.OPENID_USE_PKCE; + await setupOpenId(); + + const callOptions = OpenIDStrategy.mock.calls[OpenIDStrategy.mock.calls.length - 1][0]; + expect(callOptions.usePKCE).toBe(false); + expect(callOptions.params?.code_challenge_method).toBeUndefined(); + }); + + it('should attach federatedTokens to user object for token propagation', async () => { + // Arrange - setup tokenset with access token, id token, refresh token, and expiration + const tokensetWithTokens = { + ...tokenset, + access_token: 'mock_access_token_abc123', + id_token: 'mock_id_token_def456', + refresh_token: 'mock_refresh_token_xyz789', + expires_at: 1234567890, + }; + + // Act - validate with the tokenset containing tokens + const { user } = await validate(tokensetWithTokens); + + // Assert - verify federatedTokens object is attached with correct values + expect(user.federatedTokens).toBeDefined(); + expect(user.federatedTokens).toEqual({ + access_token: 'mock_access_token_abc123', + id_token: 'mock_id_token_def456', + refresh_token: 'mock_refresh_token_xyz789', + expires_at: 1234567890, + }); + }); + + it('should include id_token in federatedTokens distinct from access_token', async () => { + // Arrange - use different values for access_token and id_token + const tokensetWithTokens = { + ...tokenset, + access_token: 'the_access_token', + id_token: 'the_id_token', + refresh_token: 'the_refresh_token', + expires_at: 9999999999, + }; + + // Act + const { user } = await validate(tokensetWithTokens); + + // Assert - id_token and access_token must be different values + expect(user.federatedTokens.access_token).toBe('the_access_token'); + expect(user.federatedTokens.id_token).toBe('the_id_token'); + expect(user.federatedTokens.id_token).not.toBe(user.federatedTokens.access_token); + }); + + it('should include tokenset along with federatedTokens', async () => { + // Arrange + const tokensetWithTokens = { + ...tokenset, + access_token: 'test_access_token', + id_token: 'test_id_token', + refresh_token: 'test_refresh_token', + expires_at: 9999999999, + }; + + // Act + const { user } = await validate(tokensetWithTokens); + + // Assert - both tokenset and federatedTokens should be present + expect(user.tokenset).toBeDefined(); + expect(user.federatedTokens).toBeDefined(); + expect(user.tokenset.access_token).toBe('test_access_token'); + expect(user.tokenset.id_token).toBe('test_id_token'); + expect(user.federatedTokens.access_token).toBe('test_access_token'); + expect(user.federatedTokens.id_token).toBe('test_id_token'); + }); + + it('should set role to "ADMIN" if OPENID_ADMIN_ROLE is set and user has that role', async () => { + // Act + const { user } = await validate(tokenset); + + // Assert – verify that the user role is set to "ADMIN" + expect(user.role).toBe('ADMIN'); + }); + + it('should not set user role if OPENID_ADMIN_ROLE is set but the user does not have that role', async () => { + // Arrange – simulate a token without the admin permission + jwtDecode.mockReturnValue({ + roles: ['requiredRole'], + permissions: ['not-admin'], + }); + + // Act + const { user } = await validate(tokenset); + + // Assert – verify that the user role is not defined + expect(user.role).toBeUndefined(); + }); + + it('should demote existing admin user when admin role is removed from token', async () => { + // Arrange – simulate an existing user who is currently an admin + const existingAdminUser = { + _id: 'existingAdminId', + provider: 'openid', + email: tokenset.claims().email, + openidId: tokenset.claims().sub, + username: 'adminuser', + name: 'Admin User', + role: 'ADMIN', + }; + + findUser.mockImplementation(async (query) => { + if (query.openidId === tokenset.claims().sub || query.email === tokenset.claims().email) { + return existingAdminUser; + } + return null; + }); + + // Token without admin permission + jwtDecode.mockReturnValue({ + roles: ['requiredRole'], + permissions: ['not-admin'], + }); + + const { logger } = require('@librechat/data-schemas'); + + // Act + const { user } = await validate(tokenset); + + // Assert – verify that the user was demoted + expect(user.role).toBe('USER'); + expect(updateUser).toHaveBeenCalledWith( + existingAdminUser._id, + expect.objectContaining({ + role: 'USER', + }), + ); + expect(logger.info).toHaveBeenCalledWith( + expect.stringContaining('demoted from admin - role no longer present in token'), + ); + }); + + it('should NOT demote admin user when admin role env vars are not configured', async () => { + // Arrange – remove admin role env vars + delete process.env.OPENID_ADMIN_ROLE; + delete process.env.OPENID_ADMIN_ROLE_PARAMETER_PATH; + delete process.env.OPENID_ADMIN_ROLE_TOKEN_KIND; + + await setupOpenId(); + verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); + + // Simulate an existing admin user + const existingAdminUser = { + _id: 'existingAdminId', + provider: 'openid', + email: tokenset.claims().email, + openidId: tokenset.claims().sub, + username: 'adminuser', + name: 'Admin User', + role: 'ADMIN', + }; + + findUser.mockImplementation(async (query) => { + if (query.openidId === tokenset.claims().sub || query.email === tokenset.claims().email) { + return existingAdminUser; + } + return null; + }); + + jwtDecode.mockReturnValue({ + roles: ['requiredRole'], + }); + + // Act + const { user } = await validate(tokenset); + + // Assert – verify that the admin user was NOT demoted + expect(user.role).toBe('ADMIN'); + expect(updateUser).toHaveBeenCalledWith( + existingAdminUser._id, + expect.objectContaining({ + role: 'ADMIN', + }), + ); + }); + + describe('lodash get - nested path extraction', () => { + it('should extract roles from deeply nested token path', async () => { + process.env.OPENID_REQUIRED_ROLE = 'app-user'; + process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'resource_access.my-client.roles'; + + jwtDecode.mockReturnValue({ + resource_access: { + 'my-client': { + roles: ['app-user', 'viewer'], + }, + }, + }); + + await setupOpenId(); + verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); + + const { user } = await validate(tokenset); + + expect(user).toBeTruthy(); + expect(user.email).toBe(tokenset.claims().email); + }); + + it('should extract roles from three-level nested path', async () => { + process.env.OPENID_REQUIRED_ROLE = 'editor'; + process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'data.access.permissions.roles'; + + jwtDecode.mockReturnValue({ + data: { + access: { + permissions: { + roles: ['editor', 'reader'], + }, + }, + }, + }); + + await setupOpenId(); + verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); + + const { user } = await validate(tokenset); + + expect(user).toBeTruthy(); + }); + + it('should log error and reject login when required role path does not exist in token', async () => { + const { logger } = require('@librechat/data-schemas'); + process.env.OPENID_REQUIRED_ROLE = 'app-user'; + process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'resource_access.nonexistent.roles'; + + jwtDecode.mockReturnValue({ + resource_access: { + 'my-client': { + roles: ['app-user'], + }, + }, + }); + + await setupOpenId(); + verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); + + const { user, details } = await validate(tokenset); + + expect(logger.error).toHaveBeenCalledWith( + expect.stringContaining("Key 'resource_access.nonexistent.roles' not found in id token!"), + ); + expect(user).toBe(false); + expect(details.message).toContain('role to log in'); + }); + + it('should handle missing intermediate nested path gracefully', async () => { + const { logger } = require('@librechat/data-schemas'); + process.env.OPENID_REQUIRED_ROLE = 'user'; + process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'org.team.roles'; + + jwtDecode.mockReturnValue({ + org: { + other: 'value', + }, + }); + + await setupOpenId(); + verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); + + const { user } = await validate(tokenset); + + expect(logger.error).toHaveBeenCalledWith( + expect.stringContaining("Key 'org.team.roles' not found in id token!"), + ); + expect(user).toBe(false); + }); + + it('should extract admin role from nested path in access token', async () => { + process.env.OPENID_ADMIN_ROLE = 'admin'; + process.env.OPENID_ADMIN_ROLE_PARAMETER_PATH = 'realm_access.roles'; + process.env.OPENID_ADMIN_ROLE_TOKEN_KIND = 'access'; + + jwtDecode.mockImplementation((token) => { + if (token === 'fake_access_token') { + return { + realm_access: { + roles: ['admin', 'user'], + }, + }; + } + return { + roles: ['requiredRole'], + }; + }); + + await setupOpenId(); + verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); + + const { user } = await validate(tokenset); + + expect(user.role).toBe('ADMIN'); + }); + + it('should extract admin role from nested path in userinfo', async () => { + process.env.OPENID_ADMIN_ROLE = 'admin'; + process.env.OPENID_ADMIN_ROLE_PARAMETER_PATH = 'organization.permissions'; + process.env.OPENID_ADMIN_ROLE_TOKEN_KIND = 'userinfo'; + + const userinfoWithNestedGroups = { + ...tokenset.claims(), + organization: { + permissions: ['admin', 'write'], + }, + }; + + require('openid-client').fetchUserInfo.mockResolvedValue({ + organization: { + permissions: ['admin', 'write'], + }, + }); + + jwtDecode.mockReturnValue({ + roles: ['requiredRole'], + }); + + await setupOpenId(); + verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); + + const { user } = await validate({ + ...tokenset, + claims: () => userinfoWithNestedGroups, + }); + + expect(user.role).toBe('ADMIN'); + }); + + it('should handle boolean admin role value', async () => { + process.env.OPENID_ADMIN_ROLE = 'admin'; + process.env.OPENID_ADMIN_ROLE_PARAMETER_PATH = 'is_admin'; + + jwtDecode.mockReturnValue({ + roles: ['requiredRole'], + is_admin: true, + }); + + await setupOpenId(); + verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); + + const { user } = await validate(tokenset); + + expect(user.role).toBe('ADMIN'); + }); + + it('should handle string admin role value matching exactly', async () => { + process.env.OPENID_ADMIN_ROLE = 'super-admin'; + process.env.OPENID_ADMIN_ROLE_PARAMETER_PATH = 'role'; + + jwtDecode.mockReturnValue({ + roles: ['requiredRole'], + role: 'super-admin', + }); + + await setupOpenId(); + verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); + + const { user } = await validate(tokenset); + + expect(user.role).toBe('ADMIN'); + }); + + it('should not set admin role when string value does not match', async () => { + process.env.OPENID_ADMIN_ROLE = 'super-admin'; + process.env.OPENID_ADMIN_ROLE_PARAMETER_PATH = 'role'; + + jwtDecode.mockReturnValue({ + roles: ['requiredRole'], + role: 'regular-user', + }); + + await setupOpenId(); + verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); + + const { user } = await validate(tokenset); + + expect(user.role).toBeUndefined(); + }); + + it('should handle array admin role value', async () => { + process.env.OPENID_ADMIN_ROLE = 'site-admin'; + process.env.OPENID_ADMIN_ROLE_PARAMETER_PATH = 'app_roles'; + + jwtDecode.mockReturnValue({ + roles: ['requiredRole'], + app_roles: ['user', 'site-admin', 'moderator'], + }); + + await setupOpenId(); + verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); + + const { user } = await validate(tokenset); + + expect(user.role).toBe('ADMIN'); + }); + + it('should not set admin when role is not in array', async () => { + process.env.OPENID_ADMIN_ROLE = 'site-admin'; + process.env.OPENID_ADMIN_ROLE_PARAMETER_PATH = 'app_roles'; + + jwtDecode.mockReturnValue({ + roles: ['requiredRole'], + app_roles: ['user', 'moderator'], + }); + + await setupOpenId(); + verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); + + const { user } = await validate(tokenset); + + expect(user.role).toBeUndefined(); + }); + + it('should handle nested path with special characters in keys', async () => { + process.env.OPENID_REQUIRED_ROLE = 'app-user'; + process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'resource_access.my-app-123.roles'; + + jwtDecode.mockReturnValue({ + resource_access: { + 'my-app-123': { + roles: ['app-user'], + }, + }, + }); + + await setupOpenId(); + verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); + + const { user } = await validate(tokenset); + + expect(user).toBeTruthy(); + }); + + it('should handle empty object at nested path', async () => { + const { logger } = require('@librechat/data-schemas'); + process.env.OPENID_REQUIRED_ROLE = 'user'; + process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'access.roles'; + + jwtDecode.mockReturnValue({ + access: {}, + }); + + await setupOpenId(); + verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); + + const { user } = await validate(tokenset); + + expect(logger.error).toHaveBeenCalledWith( + expect.stringContaining("Key 'access.roles' not found in id token!"), + ); + expect(user).toBe(false); + }); + + it('should handle null value at intermediate path', async () => { + const { logger } = require('@librechat/data-schemas'); + process.env.OPENID_REQUIRED_ROLE = 'user'; + process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'data.roles'; + + jwtDecode.mockReturnValue({ + data: null, + }); + + await setupOpenId(); + verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); + + const { user } = await validate(tokenset); + + expect(logger.error).toHaveBeenCalledWith( + expect.stringContaining("Key 'data.roles' not found in id token!"), + ); + expect(user).toBe(false); + }); + + it('should reject login with invalid admin role token kind', async () => { + process.env.OPENID_ADMIN_ROLE = 'admin'; + process.env.OPENID_ADMIN_ROLE_PARAMETER_PATH = 'roles'; + process.env.OPENID_ADMIN_ROLE_TOKEN_KIND = 'invalid'; + + const { logger } = require('@librechat/data-schemas'); + + jwtDecode.mockReturnValue({ + roles: ['requiredRole', 'admin'], + }); + + await setupOpenId(); + verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); + + await expect(validate(tokenset)).rejects.toThrow('Invalid admin role token kind'); + + expect(logger.error).toHaveBeenCalledWith( + expect.stringContaining( + "Invalid admin role token kind: invalid. Must be one of 'access', 'id', or 'userinfo'", + ), + ); + }); + + it('should reject login when roles path returns invalid type (object)', async () => { + const { logger } = require('@librechat/data-schemas'); + process.env.OPENID_REQUIRED_ROLE = 'app-user'; + process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'roles'; + + jwtDecode.mockReturnValue({ + roles: { admin: true, user: false }, + }); + + await setupOpenId(); + verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); + + const { user, details } = await validate(tokenset); + + expect(logger.error).toHaveBeenCalledWith( + expect.stringContaining("Key 'roles' not found in id token!"), + ); + expect(user).toBe(false); + expect(details.message).toContain('role to log in'); + }); + + it('should reject login when roles path returns invalid type (number)', async () => { + const { logger } = require('@librechat/data-schemas'); + process.env.OPENID_REQUIRED_ROLE = 'user'; + process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH = 'roleCount'; + + jwtDecode.mockReturnValue({ + roleCount: 5, + }); + + await setupOpenId(); + verifyCallback = require('openid-client/passport').__getVerifyCallbackByName('openid'); + + const { user } = await validate(tokenset); + + expect(logger.error).toHaveBeenCalledWith( + expect.stringContaining("Key 'roleCount' not found in id token!"), + ); + expect(user).toBe(false); + }); + }); +}); diff --git a/api/strategies/samlStrategy.js b/api/strategies/samlStrategy.js index 4f4bfac158..843baf8a64 100644 --- a/api/strategies/samlStrategy.js +++ b/api/strategies/samlStrategy.js @@ -5,11 +5,7 @@ const passport = require('passport'); const { ErrorTypes } = require('librechat-data-provider'); const { hashToken, logger } = require('@librechat/data-schemas'); const { Strategy: SamlStrategy } = require('@node-saml/passport-saml'); -const { - getBalanceConfig, - isEmailDomainAllowed, - resolveAppConfigForUser, -} = require('@librechat/api'); +const { getBalanceConfig, isEmailDomainAllowed } = require('@librechat/api'); const { getStrategyFunctions } = require('~/server/services/Files/strategies'); const { findUser, createUser, updateUser } = require('~/models'); const { getAppConfig } = require('~/server/services/Config'); @@ -178,179 +174,126 @@ function convertToUsername(input, defaultValue = '') { return defaultValue; } -/** - * Creates a SAML authentication callback. - * @param {boolean} [existingUsersOnly=false] - If true, only existing users will be authenticated. - * @returns {Function} The SAML callback function for passport. - */ -function createSamlCallback(existingUsersOnly = false) { - return async (profile, done) => { - try { - logger.info(`[samlStrategy] SAML authentication received for NameID: ${profile.nameID}`); - logger.debug('[samlStrategy] SAML profile:', profile); - - const userEmail = getEmail(profile) || ''; - - const baseConfig = await getAppConfig({ baseOnly: true }); - if (!isEmailDomainAllowed(userEmail, baseConfig?.registration?.allowedDomains)) { - logger.error( - `[SAML Strategy] Authentication blocked - email domain not allowed [Email: ${userEmail}]`, - ); - return done(null, false, { message: 'Email domain not allowed' }); - } - - let user = await findUser({ samlId: profile.nameID }); - logger.info( - `[samlStrategy] User ${user ? 'found' : 'not found'} with SAML ID: ${profile.nameID}`, - ); - - if (!user) { - user = await findUser({ email: userEmail }); - logger.info(`[samlStrategy] User ${user ? 'found' : 'not found'} with email: ${userEmail}`); - } - - if (user && user.provider !== 'saml') { - logger.info( - `[samlStrategy] User ${user.email} already exists with provider ${user.provider}`, - ); - return done(null, false, { - message: ErrorTypes.AUTH_FAILED, - }); - } - - const appConfig = user?.tenantId - ? await resolveAppConfigForUser(getAppConfig, user) - : baseConfig; - - if (!isEmailDomainAllowed(userEmail, appConfig?.registration?.allowedDomains)) { - logger.error( - `[SAML Strategy] Authentication blocked - email domain not allowed [Email: ${userEmail}]`, - ); - return done(null, false, { message: 'Email domain not allowed' }); - } - - const fullName = getFullName(profile); - - const username = convertToUsername( - getUserName(profile) || getGivenName(profile) || getEmail(profile), - ); - - if (!user) { - if (existingUsersOnly) { - logger.error( - `[samlStrategy] Admin auth blocked - user does not exist [Email: ${userEmail}]`, - ); - return done(null, false, { message: 'User does not exist' }); - } - - user = { - provider: 'saml', - samlId: profile.nameID, - username, - email: userEmail, - emailVerified: true, - name: fullName, - }; - const balanceConfig = getBalanceConfig(appConfig); - user = await createUser(user, balanceConfig, true, true); - } else { - user.provider = 'saml'; - user.samlId = profile.nameID; - user.username = username; - user.name = fullName; - } - - const picture = getPicture(profile); - if (picture && !user.avatar?.includes('manual=true')) { - const imageBuffer = await downloadImage(profile.picture); - if (imageBuffer) { - let fileName; - if (crypto) { - fileName = (await hashToken(profile.nameID)) + '.png'; - } else { - fileName = profile.nameID + '.png'; - } - - const { saveBuffer } = getStrategyFunctions( - appConfig?.fileStrategy ?? process.env.CDN_PROVIDER, - ); - const imagePath = await saveBuffer({ - fileName, - userId: user._id.toString(), - buffer: imageBuffer, - }); - user.avatar = imagePath ?? ''; - } - } - - user = await updateUser(user._id, user); - - logger.info( - `[samlStrategy] Login success SAML ID: ${user.samlId} | email: ${user.email} | username: ${user.username}`, - { - user: { - samlId: user.samlId, - username: user.username, - email: user.email, - name: user.name, - }, - }, - ); - - done(null, user); - } catch (err) { - logger.error('[samlStrategy] Login failed', err); - done(err); - } - }; -} - -/** - * Returns the base SAML configuration shared by both regular and admin strategies. - * @returns {object} The SAML configuration object. - */ -function getBaseSamlConfig() { - return { - entryPoint: process.env.SAML_ENTRY_POINT, - issuer: process.env.SAML_ISSUER, - idpCert: getCertificateContent(process.env.SAML_CERT), - wantAssertionsSigned: process.env.SAML_USE_AUTHN_RESPONSE_SIGNED === 'true' ? false : true, - wantAuthnResponseSigned: process.env.SAML_USE_AUTHN_RESPONSE_SIGNED === 'true' ? true : false, - }; -} - async function setupSaml() { try { - const baseConfig = getBaseSamlConfig(); const samlConfig = { - ...baseConfig, + entryPoint: process.env.SAML_ENTRY_POINT, + issuer: process.env.SAML_ISSUER, callbackUrl: process.env.SAML_CALLBACK_URL, + idpCert: getCertificateContent(process.env.SAML_CERT), + wantAssertionsSigned: process.env.SAML_USE_AUTHN_RESPONSE_SIGNED === 'true' ? false : true, + wantAuthnResponseSigned: process.env.SAML_USE_AUTHN_RESPONSE_SIGNED === 'true' ? true : false, }; - passport.use('saml', new SamlStrategy(samlConfig, createSamlCallback(false))); - setupSamlAdmin(baseConfig); + passport.use( + 'saml', + new SamlStrategy(samlConfig, async (profile, done) => { + try { + logger.info(`[samlStrategy] SAML authentication received for NameID: ${profile.nameID}`); + logger.debug('[samlStrategy] SAML profile:', profile); + + const userEmail = getEmail(profile) || ''; + const appConfig = await getAppConfig(); + + if (!isEmailDomainAllowed(userEmail, appConfig?.registration?.allowedDomains)) { + logger.error( + `[SAML Strategy] Authentication blocked - email domain not allowed [Email: ${userEmail}]`, + ); + return done(null, false, { message: 'Email domain not allowed' }); + } + + let user = await findUser({ samlId: profile.nameID }); + logger.info( + `[samlStrategy] User ${user ? 'found' : 'not found'} with SAML ID: ${profile.nameID}`, + ); + + if (!user) { + user = await findUser({ email: userEmail }); + logger.info( + `[samlStrategy] User ${user ? 'found' : 'not found'} with email: ${userEmail}`, + ); + } + + if (user && user.provider !== 'saml') { + logger.info( + `[samlStrategy] User ${user.email} already exists with provider ${user.provider}`, + ); + return done(null, false, { + message: ErrorTypes.AUTH_FAILED, + }); + } + + const fullName = getFullName(profile); + + const username = convertToUsername( + getUserName(profile) || getGivenName(profile) || getEmail(profile), + ); + + if (!user) { + user = { + provider: 'saml', + samlId: profile.nameID, + username, + email: userEmail, + emailVerified: true, + name: fullName, + }; + const balanceConfig = getBalanceConfig(appConfig); + user = await createUser(user, balanceConfig, true, true); + } else { + user.provider = 'saml'; + user.samlId = profile.nameID; + user.username = username; + user.name = fullName; + } + + const picture = getPicture(profile); + if (picture && !user.avatar?.includes('manual=true')) { + const imageBuffer = await downloadImage(profile.picture); + if (imageBuffer) { + let fileName; + if (crypto) { + fileName = (await hashToken(profile.nameID)) + '.png'; + } else { + fileName = profile.nameID + '.png'; + } + + const { saveBuffer } = getStrategyFunctions( + appConfig?.fileStrategy ?? process.env.CDN_PROVIDER, + ); + const imagePath = await saveBuffer({ + fileName, + userId: user._id.toString(), + buffer: imageBuffer, + }); + user.avatar = imagePath ?? ''; + } + } + + user = await updateUser(user._id, user); + + logger.info( + `[samlStrategy] Login success SAML ID: ${user.samlId} | email: ${user.email} | username: ${user.username}`, + { + user: { + samlId: user.samlId, + username: user.username, + email: user.email, + name: user.name, + }, + }, + ); + + done(null, user); + } catch (err) { + logger.error('[samlStrategy] Login failed', err); + done(err); + } + }), + ); } catch (err) { logger.error('[samlStrategy]', err); } } -/** - * Sets up the SAML strategy specifically for admin authentication. - * Rejects users that don't already exist. - * @param {object} [baseConfig] - Pre-parsed base SAML config to avoid redundant cert parsing. - */ -function setupSamlAdmin(baseConfig) { - try { - const samlAdminConfig = { - ...(baseConfig ?? getBaseSamlConfig()), - callbackUrl: `${process.env.DOMAIN_SERVER}/api/admin/oauth/saml/callback`, - }; - - passport.use('samlAdmin', new SamlStrategy(samlAdminConfig, createSamlCallback(true))); - logger.info('[samlStrategy] Admin SAML strategy registered.'); - } catch (err) { - logger.error('[samlStrategy] setupSamlAdmin', err); - } -} - module.exports = { setupSaml, getCertificateContent }; diff --git a/api/strategies/samlStrategy.spec.js b/api/strategies/samlStrategy.spec.js index 965fb157ef..06c969ce46 100644 --- a/api/strategies/samlStrategy.spec.js +++ b/api/strategies/samlStrategy.spec.js @@ -1,4 +1,5 @@ // --- Mocks --- +jest.mock('tiktoken'); jest.mock('fs'); jest.mock('path'); jest.mock('node-fetch'); @@ -30,7 +31,6 @@ jest.mock('@librechat/api', () => ({ tokenCredits: 1000, startBalance: 1000, })), - resolveAppConfigForUser: jest.fn(async (_getAppConfig, _user) => ({})), })); jest.mock('~/server/services/Config/EndpointService', () => ({ config: {}, @@ -48,9 +48,6 @@ const fs = require('fs'); const path = require('path'); const fetch = require('node-fetch'); const { Strategy: SamlStrategy } = require('@node-saml/passport-saml'); -const { findUser } = require('~/models'); -const { resolveAppConfigForUser } = require('@librechat/api'); -const { getAppConfig } = require('~/server/services/Config'); const { setupSaml, getCertificateContent } = require('./samlStrategy'); // Configure fs mock @@ -58,14 +55,10 @@ jest.mocked(fs).existsSync = jest.fn(); jest.mocked(fs).statSync = jest.fn(); jest.mocked(fs).readFileSync = jest.fn(); -// To capture the verify callback from the strategy, we grab it from the mock constructor. -// setupSaml() registers both 'saml' (regular) and 'samlAdmin' strategies, so we capture -// only the first callback per setupSaml() call (the regular one). +// To capture the verify callback from the strategy, we grab it from the mock constructor let verifyCallback; SamlStrategy.mockImplementation((options, verify) => { - if (!verifyCallback) { - verifyCallback = verify; - } + verifyCallback = verify; return { name: 'saml', options, verify }; }); @@ -223,8 +216,6 @@ describe('setupSaml', () => { beforeEach(async () => { jest.clearAllMocks(); - // Reset so the mock captures the regular (non-admin) callback on next setupSaml() call - verifyCallback = null; // Configure mocks const { findUser, createUser, updateUser } = require('~/models'); @@ -450,50 +441,4 @@ u7wlOSk+oFzDIO/UILIA expect(fetch).not.toHaveBeenCalled(); }); - - it('should pass the found user to resolveAppConfigForUser', async () => { - const existingUser = { - _id: 'tenant-user-id', - provider: 'saml', - samlId: 'saml-1234', - email: 'test@example.com', - tenantId: 'tenant-c', - role: 'USER', - }; - findUser.mockResolvedValue(existingUser); - - const profile = { ...baseProfile }; - await validate(profile); - - expect(resolveAppConfigForUser).toHaveBeenCalledWith(getAppConfig, existingUser); - }); - - it('should use baseConfig for new SAML user without calling resolveAppConfigForUser', async () => { - const profile = { ...baseProfile }; - await validate(profile); - - expect(resolveAppConfigForUser).not.toHaveBeenCalled(); - expect(getAppConfig).toHaveBeenCalledWith({ baseOnly: true }); - }); - - it('should block login when tenant config restricts the domain', async () => { - const { isEmailDomainAllowed } = require('@librechat/api'); - const existingUser = { - _id: 'tenant-blocked', - provider: 'saml', - samlId: 'saml-1234', - email: 'test@example.com', - tenantId: 'tenant-restrict', - role: 'USER', - }; - findUser.mockResolvedValue(existingUser); - resolveAppConfigForUser.mockResolvedValue({ - registration: { allowedDomains: ['other.com'] }, - }); - isEmailDomainAllowed.mockReturnValueOnce(true).mockReturnValueOnce(false); - - const profile = { ...baseProfile }; - const { user } = await validate(profile); - expect(user).toBe(false); - }); }); diff --git a/api/strategies/socialLogin.js b/api/strategies/socialLogin.js index 580e4f3d7e..88fb347042 100644 --- a/api/strategies/socialLogin.js +++ b/api/strategies/socialLogin.js @@ -1,21 +1,21 @@ const { logger } = require('@librechat/data-schemas'); const { ErrorTypes } = require('librechat-data-provider'); -const { isEnabled, isEmailDomainAllowed, resolveAppConfigForUser } = require('@librechat/api'); +const { isEnabled, isEmailDomainAllowed } = require('@librechat/api'); const { createSocialUser, handleExistingUser } = require('./process'); const { getAppConfig } = require('~/server/services/Config'); const { findUser } = require('~/models'); const socialLogin = - (provider, getProfileDetails, options = {}) => - async (accessToken, refreshToken, idToken, profile, cb) => { + (provider, getProfileDetails) => async (accessToken, refreshToken, idToken, profile, cb) => { try { const { email, id, avatarUrl, username, name, emailVerified } = getProfileDetails({ idToken, profile, }); - const baseConfig = await getAppConfig({ baseOnly: true }); - if (!isEmailDomainAllowed(email, baseConfig?.registration?.allowedDomains)) { + const appConfig = await getAppConfig(); + + if (!isEmailDomainAllowed(email, appConfig?.registration?.allowedDomains)) { logger.error( `[${provider}Login] Authentication blocked - email domain not allowed [Email: ${email}]`, ); @@ -41,20 +41,6 @@ const socialLogin = } } - const appConfig = existingUser?.tenantId - ? await resolveAppConfigForUser(getAppConfig, existingUser) - : baseConfig; - - if (!isEmailDomainAllowed(email, appConfig?.registration?.allowedDomains)) { - logger.error( - `[${provider}Login] Authentication blocked - email domain not allowed [Email: ${email}]`, - ); - const error = new Error(ErrorTypes.AUTH_FAILED); - error.code = ErrorTypes.AUTH_FAILED; - error.message = 'Email domain not allowed'; - return cb(error); - } - if (existingUser?.provider === provider) { await handleExistingUser(existingUser, avatarUrl, appConfig, email); return cb(null, existingUser); @@ -68,13 +54,6 @@ const socialLogin = return cb(error); } - if (options.existingUsersOnly) { - logger.error( - `[${provider}Login] Admin auth blocked - user does not exist [Email: ${email}]`, - ); - return cb(null, false, { message: 'User does not exist' }); - } - const ALLOW_SOCIAL_REGISTRATION = isEnabled(process.env.ALLOW_SOCIAL_REGISTRATION); if (!ALLOW_SOCIAL_REGISTRATION) { logger.error( diff --git a/api/strategies/socialLogin.test.js b/api/strategies/socialLogin.test.js index 4fde397d55..ba4778c8b1 100644 --- a/api/strategies/socialLogin.test.js +++ b/api/strategies/socialLogin.test.js @@ -3,8 +3,6 @@ const { ErrorTypes } = require('librechat-data-provider'); const { createSocialUser, handleExistingUser } = require('./process'); const socialLogin = require('./socialLogin'); const { findUser } = require('~/models'); -const { resolveAppConfigForUser } = require('@librechat/api'); -const { getAppConfig } = require('~/server/services/Config'); jest.mock('@librechat/data-schemas', () => { const actualModule = jest.requireActual('@librechat/data-schemas'); @@ -27,10 +25,6 @@ jest.mock('@librechat/api', () => ({ ...jest.requireActual('@librechat/api'), isEnabled: jest.fn().mockReturnValue(true), isEmailDomainAllowed: jest.fn().mockReturnValue(true), - resolveAppConfigForUser: jest.fn().mockResolvedValue({ - fileStrategy: 'local', - balance: { enabled: false }, - }), })); jest.mock('~/models', () => ({ @@ -72,7 +66,10 @@ describe('socialLogin', () => { googleId: googleId, }; - findUser.mockResolvedValueOnce(existingUser).mockResolvedValueOnce(null); + /** Mock findUser to return user on first call (by googleId), null on second call */ + findUser + .mockResolvedValueOnce(existingUser) // First call: finds by googleId + .mockResolvedValueOnce(null); // Second call would be by email, but won't be reached const mockProfile = { id: googleId, @@ -86,9 +83,13 @@ describe('socialLogin', () => { await loginFn(null, null, null, mockProfile, callback); + /** Verify it searched by googleId first */ expect(findUser).toHaveBeenNthCalledWith(1, { googleId: googleId }); + + /** Verify it did NOT search by email (because it found user by googleId) */ expect(findUser).toHaveBeenCalledTimes(1); + /** Verify handleExistingUser was called with the new email */ expect(handleExistingUser).toHaveBeenCalledWith( existingUser, 'https://example.com/avatar.png', @@ -96,6 +97,7 @@ describe('socialLogin', () => { newEmail, ); + /** Verify callback was called with success */ expect(callback).toHaveBeenCalledWith(null, existingUser); }); @@ -111,7 +113,7 @@ describe('socialLogin', () => { facebookId: facebookId, }; - findUser.mockResolvedValue(existingUser); + findUser.mockResolvedValue(existingUser); // Always returns user const mockProfile = { id: facebookId, @@ -125,6 +127,7 @@ describe('socialLogin', () => { await loginFn(null, null, null, mockProfile, callback); + /** Verify it searched by facebookId first */ expect(findUser).toHaveBeenCalledWith({ facebookId: facebookId }); expect(findUser.mock.calls[0]).toEqual([{ facebookId: facebookId }]); @@ -147,10 +150,13 @@ describe('socialLogin', () => { _id: 'user789', email: email, provider: 'google', - googleId: 'old-google-id', + googleId: 'old-google-id', // Different googleId (edge case) }; - findUser.mockResolvedValueOnce(null).mockResolvedValueOnce(existingUser); + /** First call (by googleId) returns null, second call (by email) returns user */ + findUser + .mockResolvedValueOnce(null) // By googleId + .mockResolvedValueOnce(existingUser); // By email const mockProfile = { id: googleId, @@ -164,10 +170,13 @@ describe('socialLogin', () => { await loginFn(null, null, null, mockProfile, callback); + /** Verify both searches happened */ expect(findUser).toHaveBeenNthCalledWith(1, { googleId: googleId }); + /** Email passed as-is; findUser implementation handles case normalization */ expect(findUser).toHaveBeenNthCalledWith(2, { email: email }); expect(findUser).toHaveBeenCalledTimes(2); + /** Verify warning log */ expect(logger.warn).toHaveBeenCalledWith( `[${provider}Login] User found by email: ${email} but not by ${provider}Id`, ); @@ -188,6 +197,7 @@ describe('socialLogin', () => { googleId: googleId, }; + /** Both searches return null */ findUser.mockResolvedValue(null); createSocialUser.mockResolvedValue(newUser); @@ -203,8 +213,10 @@ describe('socialLogin', () => { await loginFn(null, null, null, mockProfile, callback); + /** Verify both searches happened */ expect(findUser).toHaveBeenCalledTimes(2); + /** Verify createSocialUser was called */ expect(createSocialUser).toHaveBeenCalledWith({ email: email, avatarUrl: 'https://example.com/avatar.png', @@ -230,10 +242,12 @@ describe('socialLogin', () => { const existingUser = { _id: 'user123', email: email, - provider: 'local', + provider: 'local', // Different provider }; - findUser.mockResolvedValueOnce(null).mockResolvedValueOnce(existingUser); + findUser + .mockResolvedValueOnce(null) // By googleId + .mockResolvedValueOnce(existingUser); // By email const mockProfile = { id: googleId, @@ -247,6 +261,7 @@ describe('socialLogin', () => { await loginFn(null, null, null, mockProfile, callback); + /** Verify error callback */ expect(callback).toHaveBeenCalledWith( expect.objectContaining({ code: ErrorTypes.AUTH_FAILED, @@ -259,104 +274,4 @@ describe('socialLogin', () => { ); }); }); - - describe('Tenant-scoped config', () => { - it('should call resolveAppConfigForUser for tenant user', async () => { - const provider = 'google'; - const googleId = 'google-tenant-user'; - const email = 'tenant@example.com'; - - const existingUser = { - _id: 'userTenant', - email, - provider: 'google', - googleId, - tenantId: 'tenant-b', - role: 'USER', - }; - - findUser.mockResolvedValue(existingUser); - - const mockProfile = { - id: googleId, - emails: [{ value: email, verified: true }], - photos: [{ value: 'https://example.com/avatar.png' }], - name: { givenName: 'Tenant', familyName: 'User' }, - }; - - const loginFn = socialLogin(provider, mockGetProfileDetails); - const callback = jest.fn(); - - await loginFn(null, null, null, mockProfile, callback); - - expect(resolveAppConfigForUser).toHaveBeenCalledWith(getAppConfig, existingUser); - }); - - it('should use baseConfig for non-tenant user without calling resolveAppConfigForUser', async () => { - const provider = 'google'; - const googleId = 'google-new-tenant'; - const email = 'new@example.com'; - - findUser.mockResolvedValue(null); - createSocialUser.mockResolvedValue({ - _id: 'newUser', - email, - provider: 'google', - googleId, - }); - - const mockProfile = { - id: googleId, - emails: [{ value: email, verified: true }], - photos: [{ value: 'https://example.com/avatar.png' }], - name: { givenName: 'New', familyName: 'User' }, - }; - - const loginFn = socialLogin(provider, mockGetProfileDetails); - const callback = jest.fn(); - - await loginFn(null, null, null, mockProfile, callback); - - expect(resolveAppConfigForUser).not.toHaveBeenCalled(); - expect(getAppConfig).toHaveBeenCalledWith({ baseOnly: true }); - }); - - it('should block login when tenant config restricts the domain', async () => { - const { isEmailDomainAllowed } = require('@librechat/api'); - const provider = 'google'; - const googleId = 'google-tenant-blocked'; - const email = 'blocked@example.com'; - - const existingUser = { - _id: 'userBlocked', - email, - provider: 'google', - googleId, - tenantId: 'tenant-restrict', - role: 'USER', - }; - - findUser.mockResolvedValue(existingUser); - resolveAppConfigForUser.mockResolvedValue({ - registration: { allowedDomains: ['other.com'] }, - }); - isEmailDomainAllowed.mockReturnValueOnce(true).mockReturnValueOnce(false); - - const mockProfile = { - id: googleId, - emails: [{ value: email, verified: true }], - photos: [{ value: 'https://example.com/avatar.png' }], - name: { givenName: 'Blocked', familyName: 'User' }, - }; - - const loginFn = socialLogin(provider, mockGetProfileDetails); - const callback = jest.fn(); - - await loginFn(null, null, null, mockProfile, callback); - - expect(callback).toHaveBeenCalledWith( - expect.objectContaining({ message: 'Email domain not allowed' }), - ); - }); - }); }); diff --git a/api/test/server/middleware/checkBan.test.js b/api/test/server/middleware/checkBan.test.js deleted file mode 100644 index 518153be67..0000000000 --- a/api/test/server/middleware/checkBan.test.js +++ /dev/null @@ -1,426 +0,0 @@ -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(); - }); - }); -}); diff --git a/api/test/server/services/Files/S3/crud.test.js b/api/test/server/services/Files/S3/crud.test.js new file mode 100644 index 0000000000..d847a82cf0 --- /dev/null +++ b/api/test/server/services/Files/S3/crud.test.js @@ -0,0 +1,72 @@ +const { getS3URL } = require('../../../../../server/services/Files/S3/crud'); + +// Mock AWS SDK +jest.mock('@aws-sdk/client-s3', () => ({ + S3Client: jest.fn(() => ({ + send: jest.fn(), + })), + GetObjectCommand: jest.fn(), +})); + +jest.mock('@aws-sdk/s3-request-presigner', () => ({ + getSignedUrl: jest.fn(), +})); + +jest.mock('../../../../../config', () => ({ + logger: { + error: jest.fn(), + }, +})); + +const { getSignedUrl } = require('@aws-sdk/s3-request-presigner'); +const { GetObjectCommand } = require('@aws-sdk/client-s3'); + +describe('S3 crud.js - test only new parameter changes', () => { + beforeEach(() => { + jest.clearAllMocks(); + process.env.AWS_BUCKET_NAME = 'test-bucket'; + }); + + // Test only the new customFilename parameter + it('should include customFilename in response headers when provided', async () => { + getSignedUrl.mockResolvedValue('https://test-presigned-url.com'); + + await getS3URL({ + userId: 'user123', + fileName: 'test.pdf', + customFilename: 'cleaned_filename.pdf', + }); + + // Verify the new ResponseContentDisposition parameter is added to GetObjectCommand + const commandArgs = GetObjectCommand.mock.calls[0][0]; + expect(commandArgs.ResponseContentDisposition).toBe( + 'attachment; filename="cleaned_filename.pdf"', + ); + }); + + // Test only the new contentType parameter + it('should include contentType in response headers when provided', async () => { + getSignedUrl.mockResolvedValue('https://test-presigned-url.com'); + + await getS3URL({ + userId: 'user123', + fileName: 'test.pdf', + contentType: 'application/pdf', + }); + + // Verify the new ResponseContentType parameter is added to GetObjectCommand + const commandArgs = GetObjectCommand.mock.calls[0][0]; + expect(commandArgs.ResponseContentType).toBe('application/pdf'); + }); + + it('should work without new parameters (backward compatibility)', async () => { + getSignedUrl.mockResolvedValue('https://test-presigned-url.com'); + + const result = await getS3URL({ + userId: 'user123', + fileName: 'test.pdf', + }); + + expect(result).toBe('https://test-presigned-url.com'); + }); +}); diff --git a/api/test/services/Files/processFileCitations.test.js b/api/test/services/Files/processFileCitations.test.js index 8dd588afe9..e9fe850ebd 100644 --- a/api/test/services/Files/processFileCitations.test.js +++ b/api/test/services/Files/processFileCitations.test.js @@ -7,7 +7,12 @@ const { // Mock dependencies jest.mock('~/models', () => ({ - getFiles: jest.fn().mockResolvedValue([]), + Files: { + find: jest.fn().mockResolvedValue([]), + }, +})); + +jest.mock('~/models/Role', () => ({ getRoleByName: jest.fn(), })); @@ -174,7 +179,7 @@ describe('processFileCitations', () => { }); describe('enhanceSourcesWithMetadata', () => { - const { getFiles } = require('~/models'); + const { Files } = require('~/models'); const mockCustomConfig = { fileStrategy: 'local', }; @@ -199,7 +204,7 @@ describe('processFileCitations', () => { }, ]; - getFiles.mockResolvedValue([ + Files.find.mockResolvedValue([ { file_id: 'file_123', filename: 'example_from_db.pdf', @@ -214,7 +219,7 @@ describe('processFileCitations', () => { const result = await enhanceSourcesWithMetadata(sources, mockCustomConfig); - expect(getFiles).toHaveBeenCalledWith({ file_id: { $in: ['file_123', 'file_456'] } }); + expect(Files.find).toHaveBeenCalledWith({ file_id: { $in: ['file_123', 'file_456'] } }); expect(result).toHaveLength(2); expect(result[0]).toEqual({ @@ -253,7 +258,7 @@ describe('processFileCitations', () => { }, ]; - getFiles.mockResolvedValue([ + Files.find.mockResolvedValue([ { file_id: 'file_123', filename: 'example_from_db.pdf', @@ -287,7 +292,7 @@ describe('processFileCitations', () => { }, ]; - getFiles.mockResolvedValue([]); + Files.find.mockResolvedValue([]); const result = await enhanceSourcesWithMetadata(sources, mockCustomConfig); @@ -312,7 +317,7 @@ describe('processFileCitations', () => { }, ]; - getFiles.mockRejectedValue(new Error('Database error')); + Files.find.mockRejectedValue(new Error('Database error')); const result = await enhanceSourcesWithMetadata(sources, mockCustomConfig); @@ -334,14 +339,14 @@ describe('processFileCitations', () => { { fileId: 'file_456', fileName: 'doc2.pdf', relevance: 0.7, type: 'file' }, ]; - getFiles.mockResolvedValue([ + Files.find.mockResolvedValue([ { file_id: 'file_123', filename: 'document1.pdf', source: 's3' }, { file_id: 'file_456', filename: 'document2.pdf', source: 'local' }, ]); await enhanceSourcesWithMetadata(sources, mockCustomConfig); - expect(getFiles).toHaveBeenCalledWith({ file_id: { $in: ['file_123', 'file_456'] } }); + expect(Files.find).toHaveBeenCalledWith({ file_id: { $in: ['file_123', 'file_456'] } }); }); }); }); diff --git a/api/utils/tokens.spec.js b/api/utils/tokens.spec.js index dfa6762ee5..18905d6d18 100644 --- a/api/utils/tokens.spec.js +++ b/api/utils/tokens.spec.js @@ -200,39 +200,6 @@ describe('getModelMaxTokens', () => { ); }); - test('should return correct tokens for gpt-5.3 matches', () => { - expect(getModelMaxTokens('gpt-5.3')).toBe(maxTokensMap[EModelEndpoint.openAI]['gpt-5.3']); - expect(getModelMaxTokens('gpt-5.3-codex')).toBe(maxTokensMap[EModelEndpoint.openAI]['gpt-5.3']); - expect(getModelMaxTokens('openai/gpt-5.3')).toBe( - maxTokensMap[EModelEndpoint.openAI]['gpt-5.3'], - ); - expect(getModelMaxTokens('gpt-5.3-2025-03-01')).toBe( - maxTokensMap[EModelEndpoint.openAI]['gpt-5.3'], - ); - expect(getModelMaxTokens('gpt-5.3-preview')).toBe( - maxTokensMap[EModelEndpoint.openAI]['gpt-5.3'], - ); - }); - - test('should return correct tokens for gpt-5.4 matches', () => { - expect(getModelMaxTokens('gpt-5.4')).toBe(maxTokensMap[EModelEndpoint.openAI]['gpt-5.4']); - expect(getModelMaxTokens('gpt-5.4-thinking')).toBe( - maxTokensMap[EModelEndpoint.openAI]['gpt-5.4'], - ); - expect(getModelMaxTokens('openai/gpt-5.4')).toBe( - maxTokensMap[EModelEndpoint.openAI]['gpt-5.4'], - ); - }); - - test('should return correct tokens for gpt-5.4-pro matches', () => { - expect(getModelMaxTokens('gpt-5.4-pro')).toBe( - maxTokensMap[EModelEndpoint.openAI]['gpt-5.4-pro'], - ); - expect(getModelMaxTokens('openai/gpt-5.4-pro')).toBe( - maxTokensMap[EModelEndpoint.openAI]['gpt-5.4-pro'], - ); - }); - test('should return correct tokens for Anthropic models', () => { const models = [ 'claude-2.1', @@ -270,6 +237,16 @@ describe('getModelMaxTokens', () => { }); }); + // Tests for Google models + test('should return correct tokens for exact match - Google models', () => { + expect(getModelMaxTokens('text-bison-32k', EModelEndpoint.google)).toBe( + maxTokensMap[EModelEndpoint.google]['text-bison-32k'], + ); + expect(getModelMaxTokens('codechat-bison-32k', EModelEndpoint.google)).toBe( + maxTokensMap[EModelEndpoint.google]['codechat-bison-32k'], + ); + }); + test('should return undefined for no match - Google models', () => { expect(getModelMaxTokens('unknown-google-model', EModelEndpoint.google)).toBeUndefined(); }); @@ -302,12 +279,6 @@ describe('getModelMaxTokens', () => { expect(getModelMaxTokens('gemini-3', EModelEndpoint.google)).toBe( maxTokensMap[EModelEndpoint.google]['gemini-3'], ); - expect(getModelMaxTokens('gemini-3.1-pro-preview', EModelEndpoint.google)).toBe( - maxTokensMap[EModelEndpoint.google]['gemini-3.1'], - ); - expect(getModelMaxTokens('gemini-3.1-pro-preview-customtools', EModelEndpoint.google)).toBe( - maxTokensMap[EModelEndpoint.google]['gemini-3.1'], - ); expect(getModelMaxTokens('gemini-2.5-pro', EModelEndpoint.google)).toBe( maxTokensMap[EModelEndpoint.google]['gemini-2.5-pro'], ); @@ -326,6 +297,12 @@ describe('getModelMaxTokens', () => { expect(getModelMaxTokens('gemini-pro', EModelEndpoint.google)).toBe( maxTokensMap[EModelEndpoint.google]['gemini'], ); + expect(getModelMaxTokens('code-', EModelEndpoint.google)).toBe( + maxTokensMap[EModelEndpoint.google]['code-'], + ); + expect(getModelMaxTokens('chat-', EModelEndpoint.google)).toBe( + maxTokensMap[EModelEndpoint.google]['chat-'], + ); }); test('should return correct tokens for partial match - Cohere models', () => { @@ -509,19 +486,7 @@ describe('getModelMaxTokens', () => { test('should return correct max output tokens for GPT-5 models', () => { const { getModelMaxOutputTokens } = require('@librechat/api'); - const gpt5Models = [ - 'gpt-5', - 'gpt-5.1', - 'gpt-5.2', - 'gpt-5.3', - 'gpt-5.4', - 'gpt-5.4-pro', - 'gpt-5-mini', - 'gpt-5-nano', - 'gpt-5-pro', - 'gpt-5.2-pro', - ]; - for (const model of gpt5Models) { + ['gpt-5', 'gpt-5-mini', 'gpt-5-nano', 'gpt-5-pro'].forEach((model) => { expect(getModelMaxOutputTokens(model)).toBe(maxOutputTokensMap[EModelEndpoint.openAI][model]); expect(getModelMaxOutputTokens(model, EModelEndpoint.openAI)).toBe( maxOutputTokensMap[EModelEndpoint.openAI][model], @@ -529,7 +494,7 @@ describe('getModelMaxTokens', () => { expect(getModelMaxOutputTokens(model, EModelEndpoint.azureOpenAI)).toBe( maxOutputTokensMap[EModelEndpoint.azureOpenAI][model], ); - } + }); }); test('should return correct max output tokens for GPT-OSS models', () => { @@ -546,184 +511,6 @@ describe('getModelMaxTokens', () => { }); }); -describe('findMatchingPattern - longest match wins', () => { - test('should prefer longer matching key over shorter cross-provider pattern', () => { - const result = findMatchingPattern( - 'gpt-5.2-chat-2025-12-11', - maxTokensMap[EModelEndpoint.openAI], - ); - expect(result).toBe('gpt-5.2'); - }); - - test('should match gpt-5.2 tokens for date-suffixed chat variant', () => { - expect(getModelMaxTokens('gpt-5.2-chat-2025-12-11')).toBe( - maxTokensMap[EModelEndpoint.openAI]['gpt-5.2'], - ); - }); - - test('should match gpt-5.2-pro over shorter patterns', () => { - expect(getModelMaxTokens('gpt-5.2-pro-chat-2025-12-11')).toBe( - maxTokensMap[EModelEndpoint.openAI]['gpt-5.2-pro'], - ); - }); - - test('should match gpt-5-mini over gpt-5 for mini variants', () => { - expect(getModelMaxTokens('gpt-5-mini-chat-2025-01-01')).toBe( - maxTokensMap[EModelEndpoint.openAI]['gpt-5-mini'], - ); - }); - - test('should prefer gpt-4-1106 over gpt-4 for versioned model names', () => { - const result = findMatchingPattern('gpt-4-1106-preview', maxTokensMap[EModelEndpoint.openAI]); - expect(result).toBe('gpt-4-1106'); - }); - - test('should prefer gpt-4-32k-0613 over gpt-4-32k for exact versioned names', () => { - const result = findMatchingPattern('gpt-4-32k-0613', maxTokensMap[EModelEndpoint.openAI]); - expect(result).toBe('gpt-4-32k-0613'); - }); - - test('should prefer claude-3-5-sonnet over claude-3', () => { - const result = findMatchingPattern( - 'claude-3-5-sonnet-20241022', - maxTokensMap[EModelEndpoint.anthropic], - ); - expect(result).toBe('claude-3-5-sonnet'); - }); - - test('should prefer gemini-2.0-flash-lite over gemini-2.0-flash', () => { - const result = findMatchingPattern( - 'gemini-2.0-flash-lite-preview', - maxTokensMap[EModelEndpoint.google], - ); - expect(result).toBe('gemini-2.0-flash-lite'); - }); -}); - -describe('findMatchingPattern - bestLength selection', () => { - test('should return the longest matching key when multiple keys match', () => { - const tokensMap = { short: 100, 'short-med': 200, 'short-med-long': 300 }; - expect(findMatchingPattern('short-med-long-extra', tokensMap)).toBe('short-med-long'); - }); - - test('should return the longest match regardless of key insertion order', () => { - const tokensMap = { 'a-b-c': 300, a: 100, 'a-b': 200 }; - expect(findMatchingPattern('a-b-c-d', tokensMap)).toBe('a-b-c'); - }); - - test('should return null when no key matches', () => { - const tokensMap = { alpha: 100, beta: 200 }; - expect(findMatchingPattern('gamma-delta', tokensMap)).toBeNull(); - }); - - test('should return the single matching key when only one matches', () => { - const tokensMap = { alpha: 100, beta: 200, gamma: 300 }; - expect(findMatchingPattern('beta-extended', tokensMap)).toBe('beta'); - }); - - test('should match case-insensitively against model name', () => { - const tokensMap = { 'gpt-5': 400000 }; - expect(findMatchingPattern('GPT-5-turbo', tokensMap)).toBe('gpt-5'); - }); - - test('should select the longest key among overlapping substring matches', () => { - const tokensMap = { 'gpt-': 100, 'gpt-5': 200, 'gpt-5.2': 300, 'gpt-5.2-pro': 400 }; - expect(findMatchingPattern('gpt-5.2-pro-2025-01-01', tokensMap)).toBe('gpt-5.2-pro'); - expect(findMatchingPattern('gpt-5.2-chat-2025-01-01', tokensMap)).toBe('gpt-5.2'); - expect(findMatchingPattern('gpt-5.1-preview', tokensMap)).toBe('gpt-5'); - expect(findMatchingPattern('gpt-unknown', tokensMap)).toBe('gpt-'); - }); - - test('should not be confused by a short key that appears later in the model name', () => { - const tokensMap = { 'model-v2': 200, v2: 100 }; - expect(findMatchingPattern('model-v2-extended', tokensMap)).toBe('model-v2'); - }); - - test('should handle exact-length match as the best match', () => { - const tokensMap = { 'exact-model': 500, exact: 100 }; - expect(findMatchingPattern('exact-model', tokensMap)).toBe('exact-model'); - }); - - test('should return null for empty model name', () => { - expect(findMatchingPattern('', { 'gpt-5': 400000 })).toBeNull(); - }); - - test('should prefer last-defined key on same-length ties', () => { - const tokensMap = { 'aa-bb': 100, 'cc-dd': 200 }; - // model name contains both 5-char keys; last-defined wins in reverse iteration - expect(findMatchingPattern('aa-bb-cc-dd', tokensMap)).toBe('cc-dd'); - }); - - test('longest match beats short cross-provider pattern even when both present', () => { - const tokensMap = { 'gpt-5.2': 400000, 'chat-': 8187 }; - expect(findMatchingPattern('gpt-5.2-chat-2025-12-11', tokensMap)).toBe('gpt-5.2'); - }); - - test('should match case-insensitively against keys', () => { - const tokensMap = { 'GPT-5': 400000 }; - expect(findMatchingPattern('gpt-5-turbo', tokensMap)).toBe('GPT-5'); - }); -}); - -describe('findMatchingPattern - iteration performance', () => { - let includesSpy; - - beforeEach(() => { - includesSpy = jest.spyOn(String.prototype, 'includes'); - }); - - afterEach(() => { - includesSpy.mockRestore(); - }); - - test('exact match early-exits with minimal includes() checks', () => { - const openAIMap = maxTokensMap[EModelEndpoint.openAI]; - const keys = Object.keys(openAIMap); - const lastKey = keys[keys.length - 1]; - includesSpy.mockClear(); - const result = findMatchingPattern(lastKey, openAIMap); - const exactCalls = includesSpy.mock.calls.length; - - expect(result).toBe(lastKey); - expect(exactCalls).toBe(1); - }); - - test('bestLength check skips includes() for shorter keys after a long match', () => { - const openAIMap = maxTokensMap[EModelEndpoint.openAI]; - includesSpy.mockClear(); - findMatchingPattern('gpt-3.5-turbo-0301-test', openAIMap); - const longKeyCalls = includesSpy.mock.calls.length; - - includesSpy.mockClear(); - findMatchingPattern('gpt-5.3-chat-latest', openAIMap); - const shortKeyCalls = includesSpy.mock.calls.length; - - // gpt-3.5-turbo-0301 (20 chars) matches early, then bestLength prunes most keys - // gpt-5.3 (7 chars) is short, so fewer keys are pruned by the length check - expect(longKeyCalls).toBeLessThan(shortKeyCalls); - }); - - test('last-defined keys are checked first in reverse iteration', () => { - const tokensMap = { first: 100, second: 200, third: 300 }; - includesSpy.mockClear(); - const result = findMatchingPattern('third', tokensMap); - const calls = includesSpy.mock.calls.length; - - // 'third' is last key, found on first reverse check, exact match exits immediately - expect(result).toBe('third'); - expect(calls).toBe(1); - }); -}); - -describe('deprecated PaLM2/Codey model removal', () => { - test('deprecated PaLM2/Codey models no longer have token entries', () => { - expect(getModelMaxTokens('text-bison-32k', EModelEndpoint.google)).toBeUndefined(); - expect(getModelMaxTokens('codechat-bison-32k', EModelEndpoint.google)).toBeUndefined(); - expect(getModelMaxTokens('code-bison', EModelEndpoint.google)).toBeUndefined(); - expect(getModelMaxTokens('chat-bison', EModelEndpoint.google)).toBeUndefined(); - }); -}); - describe('matchModelName', () => { it('should return the exact model name if it exists in maxTokensMap', () => { expect(matchModelName('gpt-4-32k-0613')).toBe('gpt-4-32k-0613'); @@ -819,16 +606,10 @@ describe('matchModelName', () => { expect(matchModelName('gpt-5-pro-2025-01-30-0130')).toBe('gpt-5-pro'); }); - it('should return the closest matching key for gpt-5.3 matches', () => { - expect(matchModelName('openai/gpt-5.3')).toBe('gpt-5.3'); - expect(matchModelName('gpt-5.3-codex')).toBe('gpt-5.3'); - expect(matchModelName('gpt-5.3-2025-03-01')).toBe('gpt-5.3'); - }); - - it('should return the closest matching key for gpt-5.4 matches', () => { - expect(matchModelName('openai/gpt-5.4')).toBe('gpt-5.4'); - expect(matchModelName('gpt-5.4-thinking')).toBe('gpt-5.4'); - expect(matchModelName('gpt-5.4-pro')).toBe('gpt-5.4-pro'); + // Tests for Google models + it('should return the exact model name if it exists in maxTokensMap - Google models', () => { + expect(matchModelName('text-bison-32k', EModelEndpoint.google)).toBe('text-bison-32k'); + expect(matchModelName('codechat-bison-32k', EModelEndpoint.google)).toBe('codechat-bison-32k'); }); it('should return the input model name if no match is found - Google models', () => { @@ -836,6 +617,11 @@ describe('matchModelName', () => { 'unknown-google-model', ); }); + + it('should return the closest matching key for partial matches - Google models', () => { + expect(matchModelName('code-', EModelEndpoint.google)).toBe('code-'); + expect(matchModelName('chat-', EModelEndpoint.google)).toBe('chat-'); + }); }); describe('Meta Models Tests', () => { @@ -1813,57 +1599,3 @@ describe('GLM Model Tests (Zhipu AI)', () => { }); }); }); - -describe('Mistral Model Tests', () => { - describe('getModelMaxTokens', () => { - test('should return correct tokens for mistral-large-3 (256k context)', () => { - expect(getModelMaxTokens('mistral-large-3', EModelEndpoint.custom)).toBe( - maxTokensMap[EModelEndpoint.custom]['mistral-large-3'], - ); - }); - - test('should match mistral-large-3 for suffixed variants', () => { - expect(getModelMaxTokens('mistral-large-3-instruct', EModelEndpoint.custom)).toBe( - maxTokensMap[EModelEndpoint.custom]['mistral-large-3'], - ); - }); - - test('should not match mistral-large-3 for generic mistral-large', () => { - expect(getModelMaxTokens('mistral-large', EModelEndpoint.custom)).toBe( - maxTokensMap[EModelEndpoint.custom]['mistral-large'], - ); - expect(getModelMaxTokens('mistral-large-latest', EModelEndpoint.custom)).toBe( - maxTokensMap[EModelEndpoint.custom]['mistral-large'], - ); - }); - }); - - describe('matchModelName', () => { - test('should match mistral-large-3 exactly', () => { - expect(matchModelName('mistral-large-3', EModelEndpoint.custom)).toBe('mistral-large-3'); - }); - - test('should match mistral-large-3 for prefixed/suffixed variants', () => { - expect(matchModelName('mistral/mistral-large-3', EModelEndpoint.custom)).toBe( - 'mistral-large-3', - ); - expect(matchModelName('mistral-large-3-instruct', EModelEndpoint.custom)).toBe( - 'mistral-large-3', - ); - }); - - test('should match generic mistral-large for non-3 variants', () => { - expect(matchModelName('mistral-large-latest', EModelEndpoint.custom)).toBe('mistral-large'); - }); - }); - - describe('findMatchingPattern', () => { - test('should prefer mistral-large-3 over mistral-large for mistral-large-3 variants', () => { - const result = findMatchingPattern( - 'mistral-large-3-instruct', - maxTokensMap[EModelEndpoint.custom], - ); - expect(result).toBe('mistral-large-3'); - }); - }); -}); diff --git a/bun.lock b/bun.lock index fb1ec00840..c6a5dd01a1 100644 --- a/bun.lock +++ b/bun.lock @@ -7,7 +7,7 @@ "devDependencies": { "@axe-core/playwright": "^4.10.1", "@eslint/compat": "^1.2.6", - "@eslint/eslintrc": "^3.3.4", + "@eslint/eslintrc": "^3.3.1", "@eslint/js": "^9.20.0", "@playwright/test": "^1.56.1", "@types/react-virtualized": "^9.22.0", @@ -31,33 +31,30 @@ "lint-staged": "^15.4.3", "prettier": "^3.5.0", "prettier-plugin-tailwindcss": "^0.6.11", - "turbo": "^2.8.12", "typescript-eslint": "^8.24.0", }, }, "api": { "name": "@librechat/backend", - "version": "0.8.4", + "version": "0.8.300", "dependencies": { - "@anthropic-ai/vertex-sdk": "^0.14.3", - "@aws-sdk/client-bedrock-runtime": "^3.980.0", - "@aws-sdk/client-s3": "^3.980.0", + "@aws-sdk/client-bedrock-runtime": "^3.941.0", + "@aws-sdk/client-s3": "^3.758.0", "@aws-sdk/s3-request-presigner": "^3.758.0", "@azure/identity": "^4.7.0", "@azure/search-documents": "^12.0.0", - "@azure/storage-blob": "^12.30.0", - "@google/genai": "^1.19.0", + "@azure/storage-blob": "^12.27.0", + "@googleapis/youtube": "^20.0.0", "@keyv/redis": "^4.3.3", - "@langchain/core": "^0.3.80", - "@librechat/agents": "^3.1.57", + "@langchain/core": "^0.3.79", + "@librechat/agents": "^3.0.50", "@librechat/api": "*", "@librechat/data-schemas": "*", "@microsoft/microsoft-graph-client": "^3.0.7", - "@modelcontextprotocol/sdk": "^1.27.1", + "@modelcontextprotocol/sdk": "^1.24.3", "@node-saml/passport-saml": "^5.1.0", "@smithy/node-http-handler": "^4.4.5", - "ai-tokenizer": "^1.0.6", - "axios": "^1.13.5", + "axios": "^1.12.1", "bcryptjs": "^2.4.3", "compression": "^1.8.1", "connect-redis": "^8.1.0", @@ -67,12 +64,12 @@ "dedent": "^1.5.3", "dotenv": "^16.0.3", "eventsource": "^3.0.2", - "express": "^5.2.1", + "express": "^5.1.0", "express-mongo-sanitize": "^2.2.0", - "express-rate-limit": "^8.3.0", + "express-rate-limit": "^8.2.1", "express-session": "^1.18.2", "express-static-gzip": "^2.2.0", - "file-type": "^21.3.2", + "file-type": "^18.7.0", "firebase": "^11.0.2", "form-data": "^4.0.4", "handlebars": "^4.7.7", @@ -85,15 +82,13 @@ "keyv-file": "^5.1.2", "klona": "^2.0.6", "librechat-data-provider": "*", - "lodash": "^4.17.23", - "mammoth": "^1.11.0", - "mathjs": "^15.1.0", + "lodash": "^4.17.21", "meilisearch": "^0.38.0", "memorystore": "^1.6.7", "mime": "^3.0.0", "module-alias": "^2.2.3", "mongoose": "^8.12.1", - "multer": "^2.1.1", + "multer": "^2.0.2", "nanoid": "^3.3.7", "node-fetch": "^2.7.0", "nodemailer": "^7.0.11", @@ -109,16 +104,15 @@ "passport-jwt": "^4.0.1", "passport-ldapauth": "^3.0.1", "passport-local": "^1.0.0", - "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.24.1", + "undici": "^7.10.0", "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", + "youtube-transcript": "^1.2.1", "zod": "^3.22.4", }, "devDependencies": { @@ -130,23 +124,22 @@ }, "client": { "name": "@librechat/frontend", - "version": "0.8.4", + "version": "0.8.300", "dependencies": { "@ariakit/react": "^0.4.15", "@ariakit/react-core": "^0.4.17", "@codesandbox/sandpack-react": "^2.19.10", - "@dicebear/collection": "^9.4.1", - "@dicebear/core": "^9.4.1", + "@dicebear/collection": "^9.2.2", + "@dicebear/core": "^9.2.2", "@headlessui/react": "^2.1.2", "@librechat/client": "*", "@marsidev/react-turnstile": "^1.1.0", "@mcp-ui/client": "^5.7.0", - "@monaco-editor/react": "^4.7.0", "@radix-ui/react-accordion": "^1.1.2", - "@radix-ui/react-alert-dialog": "1.0.2", + "@radix-ui/react-alert-dialog": "^1.1.15", "@radix-ui/react-checkbox": "^1.0.3", "@radix-ui/react-collapsible": "^1.0.3", - "@radix-ui/react-dialog": "1.0.2", + "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-hover-card": "^1.0.5", "@radix-ui/react-icons": "^1.3.0", @@ -181,10 +174,9 @@ "jotai": "^2.12.5", "js-cookie": "^3.0.5", "librechat-data-provider": "*", - "lodash": "^4.17.23", + "lodash": "^4.17.21", "lucide-react": "^0.394.0", "match-sorter": "^8.1.0", - "mermaid": "^11.13.0", "micromark-extension-llm-math": "^3.1.0", "qrcode.react": "^4.2.0", "rc-input-number": "^7.4.2", @@ -197,9 +189,10 @@ "react-gtm-module": "^2.0.11", "react-hook-form": "^7.43.9", "react-i18next": "^15.4.0", + "react-lazy-load-image-component": "^1.6.0", "react-markdown": "^9.0.1", "react-resizable-panels": "^3.0.6", - "react-router-dom": "^6.30.3", + "react-router-dom": "^6.11.2", "react-speech-recognition": "^3.10.0", "react-textarea-autosize": "^8.4.0", "react-transition-group": "^4.4.5", @@ -213,11 +206,9 @@ "remark-math": "^6.0.0", "remark-supersub": "^1.0.0", "sse.js": "^2.5.0", - "swr": "^2.3.8", "tailwind-merge": "^1.9.1", "tailwindcss-animate": "^1.0.5", "tailwindcss-radix": "^2.8.0", - "ts-md5": "^1.3.1", "zod": "^3.22.4", }, "devDependencies": { @@ -225,7 +216,6 @@ "@babel/preset-env": "^7.22.15", "@babel/preset-react": "^7.22.15", "@babel/preset-typescript": "^7.22.15", - "@happy-dom/jest-environment": "^20.8.3", "@tanstack/react-query-devtools": "^4.29.0", "@testing-library/dom": "^9.3.0", "@testing-library/jest-dom": "^5.16.5", @@ -234,10 +224,10 @@ "@types/jest": "^29.5.14", "@types/js-cookie": "^3.0.6", "@types/lodash": "^4.17.15", - "@types/node": "^20.19.35", + "@types/node": "^20.3.0", "@types/react": "^18.2.11", "@types/react-dom": "^18.2.4", - "@vitejs/plugin-react": "^5.1.4", + "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.20", "babel-plugin-replace-ts-export-assignment": "^0.0.2", "babel-plugin-root-import": "^6.6.0", @@ -248,23 +238,23 @@ "identity-obj-proxy": "^3.0.0", "jest": "^30.2.0", "jest-canvas-mock": "^2.5.2", - "jest-environment-jsdom": "^30.2.0", + "jest-environment-jsdom": "^29.7.0", "jest-file-loader": "^1.0.3", "jest-junit": "^16.0.0", - "monaco-editor": "^0.55.1", "postcss": "^8.4.31", - "postcss-preset-env": "^11.2.0", + "postcss-loader": "^7.1.0", + "postcss-preset-env": "^8.2.0", "tailwindcss": "^3.4.1", "typescript": "^5.3.3", - "vite": "^7.3.1", + "vite": "^6.4.1", "vite-plugin-compression2": "^2.2.1", - "vite-plugin-node-polyfills": "^0.25.0", - "vite-plugin-pwa": "^1.2.0", + "vite-plugin-node-polyfills": "^0.23.0", + "vite-plugin-pwa": "^0.21.2", }, }, "packages/api": { "name": "@librechat/api", - "version": "1.7.27", + "version": "1.7.23", "devDependencies": { "@babel/preset-env": "^7.21.5", "@babel/preset-react": "^7.18.6", @@ -276,6 +266,7 @@ "@rollup/plugin-replace": "^5.0.5", "@rollup/plugin-typescript": "^12.1.2", "@types/bun": "^1.2.15", + "@types/diff": "^6.0.0", "@types/express": "^5.0.0", "@types/express-session": "^1.18.2", "@types/jest": "^29.5.2", @@ -285,67 +276,52 @@ "@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", - "pdfjs-dist": "^5.4.624", - "rimraf": "^6.1.3", - "rollup": "^4.34.9", + "rimraf": "^6.1.2", + "rollup": "^4.22.4", "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", - "yauzl": "^3.2.1", }, "peerDependencies": { - "@anthropic-ai/vertex-sdk": "^0.14.3", - "@aws-sdk/client-bedrock-runtime": "^3.970.0", - "@aws-sdk/client-s3": "^3.980.0", + "@aws-sdk/client-s3": "^3.758.0", "@azure/identity": "^4.7.0", "@azure/search-documents": "^12.0.0", - "@azure/storage-blob": "^12.30.0", - "@google/genai": "^1.19.0", + "@azure/storage-blob": "^12.27.0", "@keyv/redis": "^4.3.3", - "@langchain/core": "^0.3.80", - "@librechat/agents": "^3.1.57", + "@langchain/core": "^0.3.79", + "@librechat/agents": "^3.0.50", "@librechat/data-schemas": "*", - "@modelcontextprotocol/sdk": "^1.27.1", - "@smithy/node-http-handler": "^4.4.5", - "ai-tokenizer": "^1.0.6", - "axios": "^1.13.5", + "@modelcontextprotocol/sdk": "^1.24.3", + "axios": "^1.12.1", "connect-redis": "^8.1.0", + "diff": "^7.0.0", "eventsource": "^3.0.2", "express": "^5.1.0", "express-session": "^1.18.2", "firebase": "^11.0.2", "form-data": "^4.0.4", - "google-auth-library": "^9.15.1", - "https-proxy-agent": "^7.0.6", "ioredis": "^5.3.2", "js-yaml": "^4.1.1", "jsonwebtoken": "^9.0.0", "keyv": "^5.3.2", "keyv-file": "^5.1.2", "librechat-data-provider": "*", - "mammoth": "^1.11.0", - "mathjs": "^15.1.0", "memorystore": "^1.6.7", "mongoose": "^8.12.1", "node-fetch": "2.7.0", - "pdfjs-dist": "^5.4.624", "rate-limit-redis": "^4.2.0", - "undici": "^7.24.1", - "yauzl": "^3.2.1", + "tiktoken": "^1.0.15", + "undici": "^7.10.0", "zod": "^3.22.4", }, }, "packages/client": { "name": "@librechat/client", - "version": "0.4.56", + "version": "0.4.52", "devDependencies": { "@babel/core": "^7.28.5", "@babel/preset-env": "^7.28.5", @@ -375,8 +351,8 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-i18next": "^15.4.0", - "rimraf": "^6.1.3", - "rollup": "^4.34.9", + "rimraf": "^6.1.2", + "rollup": "^4.0.0", "rollup-plugin-peer-deps-external": "^2.2.4", "rollup-plugin-postcss": "^4.0.2", "rollup-plugin-typescript2": "^0.35.0", @@ -386,14 +362,14 @@ "peerDependencies": { "@ariakit/react": "^0.4.16", "@ariakit/react-core": "^0.4.17", - "@dicebear/collection": "^9.4.1", - "@dicebear/core": "^9.4.1", + "@dicebear/collection": "^9.2.2", + "@dicebear/core": "^9.2.2", "@headlessui/react": "^2.1.2", "@radix-ui/react-accordion": "^1.2.11", - "@radix-ui/react-alert-dialog": "1.0.2", + "@radix-ui/react-alert-dialog": "^1.1.15", "@radix-ui/react-checkbox": "^1.0.3", "@radix-ui/react-collapsible": "^1.1.11", - "@radix-ui/react-dialog": "1.0.2", + "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-hover-card": "^1.0.5", "@radix-ui/react-icons": "^1.3.0", @@ -433,9 +409,9 @@ }, "packages/data-provider": { "name": "librechat-data-provider", - "version": "0.8.401", + "version": "0.8.300", "dependencies": { - "axios": "^1.13.5", + "axios": "^1.12.1", "dayjs": "^1.11.13", "js-yaml": "^4.1.1", "zod": "^3.22.4", @@ -444,6 +420,7 @@ "@babel/preset-env": "^7.21.5", "@babel/preset-react": "^7.18.6", "@babel/preset-typescript": "^7.21.0", + "@langchain/core": "^0.3.62", "@rollup/plugin-alias": "^5.1.0", "@rollup/plugin-commonjs": "^29.0.0", "@rollup/plugin-json": "^6.1.0", @@ -458,8 +435,8 @@ "jest": "^30.2.0", "jest-junit": "^16.0.0", "openapi-types": "^12.1.3", - "rimraf": "^6.1.3", - "rollup": "^4.34.9", + "rimraf": "^6.1.2", + "rollup": "^4.22.4", "rollup-plugin-peer-deps-external": "^2.2.4", "rollup-plugin-typescript2": "^0.35.0", "typescript": "^5.0.4", @@ -470,7 +447,7 @@ }, "packages/data-schemas": { "name": "@librechat/data-schemas", - "version": "0.0.40", + "version": "0.0.36", "devDependencies": { "@rollup/plugin-alias": "^5.1.0", "@rollup/plugin-commonjs": "^29.0.0", @@ -479,14 +456,15 @@ "@rollup/plugin-replace": "^5.0.5", "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "^12.1.2", + "@types/diff": "^6.0.0", "@types/express": "^5.0.0", "@types/jest": "^29.5.2", "@types/node": "^20.3.0", "jest": "^30.2.0", "jest-junit": "^16.0.0", "mongodb-memory-server": "^10.1.4", - "rimraf": "^6.1.3", - "rollup": "^4.34.9", + "rimraf": "^6.1.2", + "rollup": "^4.22.4", "rollup-plugin-peer-deps-external": "^2.2.4", "rollup-plugin-typescript2": "^0.35.0", "ts-node": "^10.9.2", @@ -496,7 +474,7 @@ "jsonwebtoken": "^9.0.2", "klona": "^2.0.6", "librechat-data-provider": "*", - "lodash": "^4.17.23", + "lodash": "^4.17.21", "meilisearch": "^0.38.0", "mongoose": "^8.12.1", "nanoid": "^3.3.7", @@ -506,19 +484,11 @@ }, }, "overrides": { - "@anthropic-ai/sdk": "0.73.0", - "@hono/node-server": "^1.19.10", + "axios": "1.12.1", "elliptic": "^6.6.1", - "fast-xml-parser": "5.5.7", "form-data": "^4.0.4", - "hono": "^4.12.4", "katex": "^0.16.21", - "langsmith": "0.4.12", "mdast-util-gfm-autolink-literal": "2.0.0", - "serialize-javascript": "^7.0.3", - "svgo": "^2.8.2", - "tslib": "^2.8.1", - "underscore": "1.13.8", }, "packages": { "@aashutoshrathi/word-wrap": ["@aashutoshrathi/word-wrap@1.2.6", "", {}, "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA=="], @@ -527,11 +497,7 @@ "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], - "@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="], - - "@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.73.0", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-URURVzhxXGJDGUGFunIOtBlSl7KWvZiAAKY/ttTkZAkXT9bTPqdk2eK0b8qqSxXpikh3QKPnPYpiyX98zf5ebw=="], - - "@anthropic-ai/vertex-sdk": ["@anthropic-ai/vertex-sdk@0.14.4", "", { "dependencies": { "@anthropic-ai/sdk": ">=0.50.3 <1", "google-auth-library": "^9.4.2" } }, "sha512-BZUPRWghZxfSFtAxU563wH+jfWBPoedAwsVxG35FhmNsjeV8tyfN+lFriWhCpcZApxA4NdT6Soov+PzfnxxD5g=="], + "@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.65.0", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-zIdPOcrCVEI8t3Di40nH4z9EoeyGZfXbYSvWdDLsB/KkaSYMnEgC7gmcgWu83g2NTn1ZTpbMvpdttWDGGIk6zw=="], "@apideck/better-ajv-errors": ["@apideck/better-ajv-errors@0.3.6", "", { "dependencies": { "json-schema": "^0.4.0", "jsonpointer": "^5.0.0", "leven": "^3.1.0" }, "peerDependencies": { "ajv": ">=8" } }, "sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA=="], @@ -559,21 +525,19 @@ "@aws-sdk/client-bedrock-agent-runtime": ["@aws-sdk/client-bedrock-agent-runtime@3.927.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.927.0", "@aws-sdk/credential-provider-node": "3.927.0", "@aws-sdk/middleware-host-header": "3.922.0", "@aws-sdk/middleware-logger": "3.922.0", "@aws-sdk/middleware-recursion-detection": "3.922.0", "@aws-sdk/middleware-user-agent": "3.927.0", "@aws-sdk/region-config-resolver": "3.925.0", "@aws-sdk/types": "3.922.0", "@aws-sdk/util-endpoints": "3.922.0", "@aws-sdk/util-user-agent-browser": "3.922.0", "@aws-sdk/util-user-agent-node": "3.927.0", "@smithy/config-resolver": "^4.4.2", "@smithy/core": "^3.17.2", "@smithy/eventstream-serde-browser": "^4.2.4", "@smithy/eventstream-serde-config-resolver": "^4.3.4", "@smithy/eventstream-serde-node": "^4.2.4", "@smithy/fetch-http-handler": "^5.3.5", "@smithy/hash-node": "^4.2.4", "@smithy/invalid-dependency": "^4.2.4", "@smithy/middleware-content-length": "^4.2.4", "@smithy/middleware-endpoint": "^4.3.6", "@smithy/middleware-retry": "^4.4.6", "@smithy/middleware-serde": "^4.2.4", "@smithy/middleware-stack": "^4.2.4", "@smithy/node-config-provider": "^4.3.4", "@smithy/node-http-handler": "^4.4.4", "@smithy/protocol-http": "^5.3.4", "@smithy/smithy-client": "^4.9.2", "@smithy/types": "^4.8.1", "@smithy/url-parser": "^4.2.4", "@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.5", "@smithy/util-defaults-mode-node": "^4.2.8", "@smithy/util-endpoints": "^3.2.4", "@smithy/util-middleware": "^4.2.4", "@smithy/util-retry": "^4.2.4", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-k2UeG/+Ka74jztHDzYNrpNLDSsMCst+ph3+e7uAX5Jmo40tVKa+sVu4DkV3BIXuktc6jqM1ewtfPNug79kN6JQ=="], - "@aws-sdk/client-bedrock-runtime": ["@aws-sdk/client-bedrock-runtime@3.1013.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.22", "@aws-sdk/credential-provider-node": "^3.972.23", "@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.23", "@aws-sdk/middleware-websocket": "^3.972.13", "@aws-sdk/region-config-resolver": "^3.972.8", "@aws-sdk/token-providers": "3.1013.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.9", "@smithy/config-resolver": "^4.4.11", "@smithy/core": "^3.23.12", "@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.26", "@smithy/middleware-retry": "^4.4.43", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.6", "@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.42", "@smithy/util-defaults-mode-node": "^4.2.45", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-LU80q1avpBwQ0eVAGbQpPApdVY4vcdBEIycY5iaznI10mdabeG83nrFySJrZ8knX7G6hl5d5KIOSjcpnolMKSA=="], + "@aws-sdk/client-bedrock-runtime": ["@aws-sdk/client-bedrock-runtime@3.952.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.947.0", "@aws-sdk/credential-provider-node": "3.952.0", "@aws-sdk/eventstream-handler-node": "3.936.0", "@aws-sdk/middleware-eventstream": "3.936.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.948.0", "@aws-sdk/middleware-user-agent": "3.947.0", "@aws-sdk/middleware-websocket": "3.936.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/token-providers": "3.952.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.947.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.7", "@smithy/eventstream-serde-browser": "^4.2.5", "@smithy/eventstream-serde-config-resolver": "^4.3.5", "@smithy/eventstream-serde-node": "^4.2.5", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.14", "@smithy/middleware-retry": "^4.4.14", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@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.13", "@smithy/util-defaults-mode-node": "^4.2.16", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-stream": "^4.5.6", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Xc1xqIz/OdFd23UQ6cvROD+3tfvDpp5dabMqUYXFiKlk5psMNM9xhzLwWK7DE1tr1ra/dui77w8JOiLA1dC7AA=="], "@aws-sdk/client-cognito-identity": ["@aws-sdk/client-cognito-identity@3.623.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/client-sso-oidc": "3.623.0", "@aws-sdk/core": "3.623.0", "@aws-sdk/credential-provider-node": "3.623.0", "@aws-sdk/middleware-host-header": "3.620.0", "@aws-sdk/middleware-logger": "3.609.0", "@aws-sdk/middleware-recursion-detection": "3.620.0", "@aws-sdk/middleware-user-agent": "3.620.0", "@aws-sdk/region-config-resolver": "3.614.0", "@aws-sdk/types": "3.609.0", "@aws-sdk/util-endpoints": "3.614.0", "@aws-sdk/util-user-agent-browser": "3.609.0", "@aws-sdk/util-user-agent-node": "3.614.0", "@smithy/config-resolver": "^3.0.5", "@smithy/core": "^2.3.2", "@smithy/fetch-http-handler": "^3.2.4", "@smithy/hash-node": "^3.0.3", "@smithy/invalid-dependency": "^3.0.3", "@smithy/middleware-content-length": "^3.0.5", "@smithy/middleware-endpoint": "^3.1.0", "@smithy/middleware-retry": "^3.0.14", "@smithy/middleware-serde": "^3.0.3", "@smithy/middleware-stack": "^3.0.3", "@smithy/node-config-provider": "^3.1.4", "@smithy/node-http-handler": "^3.1.4", "@smithy/protocol-http": "^4.1.0", "@smithy/smithy-client": "^3.1.12", "@smithy/types": "^3.3.0", "@smithy/url-parser": "^3.0.3", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", "@smithy/util-defaults-mode-browser": "^3.0.14", "@smithy/util-defaults-mode-node": "^3.0.14", "@smithy/util-endpoints": "^2.0.5", "@smithy/util-middleware": "^3.0.3", "@smithy/util-retry": "^3.0.3", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" } }, "sha512-kGYnTzXTMGdjko5+GZ1PvWvfXA7quiOp5iMo5gbh5b55pzIdc918MHN0pvaqplVGWYlaFJF4YzxUT5Nbxd7Xeg=="], "@aws-sdk/client-kendra": ["@aws-sdk/client-kendra@3.927.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.927.0", "@aws-sdk/credential-provider-node": "3.927.0", "@aws-sdk/middleware-host-header": "3.922.0", "@aws-sdk/middleware-logger": "3.922.0", "@aws-sdk/middleware-recursion-detection": "3.922.0", "@aws-sdk/middleware-user-agent": "3.927.0", "@aws-sdk/region-config-resolver": "3.925.0", "@aws-sdk/types": "3.922.0", "@aws-sdk/util-endpoints": "3.922.0", "@aws-sdk/util-user-agent-browser": "3.922.0", "@aws-sdk/util-user-agent-node": "3.927.0", "@smithy/config-resolver": "^4.4.2", "@smithy/core": "^3.17.2", "@smithy/fetch-http-handler": "^5.3.5", "@smithy/hash-node": "^4.2.4", "@smithy/invalid-dependency": "^4.2.4", "@smithy/middleware-content-length": "^4.2.4", "@smithy/middleware-endpoint": "^4.3.6", "@smithy/middleware-retry": "^4.4.6", "@smithy/middleware-serde": "^4.2.4", "@smithy/middleware-stack": "^4.2.4", "@smithy/node-config-provider": "^4.3.4", "@smithy/node-http-handler": "^4.4.4", "@smithy/protocol-http": "^5.3.4", "@smithy/smithy-client": "^4.9.2", "@smithy/types": "^4.8.1", "@smithy/url-parser": "^4.2.4", "@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.5", "@smithy/util-defaults-mode-node": "^4.2.8", "@smithy/util-endpoints": "^3.2.4", "@smithy/util-middleware": "^4.2.4", "@smithy/util-retry": "^4.2.4", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-DWyNlC6BFhzoDkyKZ3xv0BC/xcXF3Tpq6j6Z42DXO9KEUjiGmC3se9l/GFEVtRLh/DR4p7cTJsxzA2QNuthRNg=="], - "@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.1004.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.18", "@aws-sdk/credential-provider-node": "^3.972.18", "@aws-sdk/middleware-bucket-endpoint": "^3.972.7", "@aws-sdk/middleware-expect-continue": "^3.972.7", "@aws-sdk/middleware-flexible-checksums": "^3.973.4", "@aws-sdk/middleware-host-header": "^3.972.7", "@aws-sdk/middleware-location-constraint": "^3.972.7", "@aws-sdk/middleware-logger": "^3.972.7", "@aws-sdk/middleware-recursion-detection": "^3.972.7", "@aws-sdk/middleware-sdk-s3": "^3.972.18", "@aws-sdk/middleware-ssec": "^3.972.7", "@aws-sdk/middleware-user-agent": "^3.972.19", "@aws-sdk/region-config-resolver": "^3.972.7", "@aws-sdk/signature-v4-multi-region": "^3.996.6", "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-endpoints": "^3.996.4", "@aws-sdk/util-user-agent-browser": "^3.972.7", "@aws-sdk/util-user-agent-node": "^3.973.4", "@smithy/config-resolver": "^4.4.10", "@smithy/core": "^3.23.8", "@smithy/eventstream-serde-browser": "^4.2.11", "@smithy/eventstream-serde-config-resolver": "^4.3.11", "@smithy/eventstream-serde-node": "^4.2.11", "@smithy/fetch-http-handler": "^5.3.13", "@smithy/hash-blob-browser": "^4.2.12", "@smithy/hash-node": "^4.2.11", "@smithy/hash-stream-node": "^4.2.11", "@smithy/invalid-dependency": "^4.2.11", "@smithy/md5-js": "^4.2.11", "@smithy/middleware-content-length": "^4.2.11", "@smithy/middleware-endpoint": "^4.4.22", "@smithy/middleware-retry": "^4.4.39", "@smithy/middleware-serde": "^4.2.12", "@smithy/middleware-stack": "^4.2.11", "@smithy/node-config-provider": "^4.3.11", "@smithy/node-http-handler": "^4.4.14", "@smithy/protocol-http": "^5.3.11", "@smithy/smithy-client": "^4.12.2", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@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.38", "@smithy/util-defaults-mode-node": "^4.2.41", "@smithy/util-endpoints": "^3.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-retry": "^4.2.11", "@smithy/util-stream": "^4.5.17", "@smithy/util-utf8": "^4.2.2", "@smithy/util-waiter": "^4.2.11", "tslib": "^2.6.2" } }, "sha512-m0zNfpsona9jQdX1cHtHArOiuvSGZPsgp/KRZS2YjJhKah96G2UN3UNGZQ6aVjXIQjCY6UanCJo0uW9Xf2U41w=="], + "@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.758.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.758.0", "@aws-sdk/credential-provider-node": "3.758.0", "@aws-sdk/middleware-bucket-endpoint": "3.734.0", "@aws-sdk/middleware-expect-continue": "3.734.0", "@aws-sdk/middleware-flexible-checksums": "3.758.0", "@aws-sdk/middleware-host-header": "3.734.0", "@aws-sdk/middleware-location-constraint": "3.734.0", "@aws-sdk/middleware-logger": "3.734.0", "@aws-sdk/middleware-recursion-detection": "3.734.0", "@aws-sdk/middleware-sdk-s3": "3.758.0", "@aws-sdk/middleware-ssec": "3.734.0", "@aws-sdk/middleware-user-agent": "3.758.0", "@aws-sdk/region-config-resolver": "3.734.0", "@aws-sdk/signature-v4-multi-region": "3.758.0", "@aws-sdk/types": "3.734.0", "@aws-sdk/util-endpoints": "3.743.0", "@aws-sdk/util-user-agent-browser": "3.734.0", "@aws-sdk/util-user-agent-node": "3.758.0", "@aws-sdk/xml-builder": "3.734.0", "@smithy/config-resolver": "^4.0.1", "@smithy/core": "^3.1.5", "@smithy/eventstream-serde-browser": "^4.0.1", "@smithy/eventstream-serde-config-resolver": "^4.0.1", "@smithy/eventstream-serde-node": "^4.0.1", "@smithy/fetch-http-handler": "^5.0.1", "@smithy/hash-blob-browser": "^4.0.1", "@smithy/hash-node": "^4.0.1", "@smithy/hash-stream-node": "^4.0.1", "@smithy/invalid-dependency": "^4.0.1", "@smithy/md5-js": "^4.0.1", "@smithy/middleware-content-length": "^4.0.1", "@smithy/middleware-endpoint": "^4.0.6", "@smithy/middleware-retry": "^4.0.7", "@smithy/middleware-serde": "^4.0.2", "@smithy/middleware-stack": "^4.0.1", "@smithy/node-config-provider": "^4.0.1", "@smithy/node-http-handler": "^4.0.3", "@smithy/protocol-http": "^5.0.1", "@smithy/smithy-client": "^4.1.6", "@smithy/types": "^4.1.0", "@smithy/url-parser": "^4.0.1", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.7", "@smithy/util-defaults-mode-node": "^4.0.7", "@smithy/util-endpoints": "^3.0.1", "@smithy/util-middleware": "^4.0.1", "@smithy/util-retry": "^4.0.1", "@smithy/util-stream": "^4.1.2", "@smithy/util-utf8": "^4.0.0", "@smithy/util-waiter": "^4.0.2", "tslib": "^2.6.2" } }, "sha512-f8SlhU9/93OC/WEI6xVJf/x/GoQFj9a/xXK6QCtr5fvCjfSLgMVFmKTiIl/tgtDRzxUDc8YS6EGtbHjJ3Y/atg=="], "@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.623.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.623.0", "@aws-sdk/middleware-host-header": "3.620.0", "@aws-sdk/middleware-logger": "3.609.0", "@aws-sdk/middleware-recursion-detection": "3.620.0", "@aws-sdk/middleware-user-agent": "3.620.0", "@aws-sdk/region-config-resolver": "3.614.0", "@aws-sdk/types": "3.609.0", "@aws-sdk/util-endpoints": "3.614.0", "@aws-sdk/util-user-agent-browser": "3.609.0", "@aws-sdk/util-user-agent-node": "3.614.0", "@smithy/config-resolver": "^3.0.5", "@smithy/core": "^2.3.2", "@smithy/fetch-http-handler": "^3.2.4", "@smithy/hash-node": "^3.0.3", "@smithy/invalid-dependency": "^3.0.3", "@smithy/middleware-content-length": "^3.0.5", "@smithy/middleware-endpoint": "^3.1.0", "@smithy/middleware-retry": "^3.0.14", "@smithy/middleware-serde": "^3.0.3", "@smithy/middleware-stack": "^3.0.3", "@smithy/node-config-provider": "^3.1.4", "@smithy/node-http-handler": "^3.1.4", "@smithy/protocol-http": "^4.1.0", "@smithy/smithy-client": "^3.1.12", "@smithy/types": "^3.3.0", "@smithy/url-parser": "^3.0.3", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", "@smithy/util-defaults-mode-browser": "^3.0.14", "@smithy/util-defaults-mode-node": "^3.0.14", "@smithy/util-endpoints": "^2.0.5", "@smithy/util-middleware": "^3.0.3", "@smithy/util-retry": "^3.0.3", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" } }, "sha512-oEACriysQMnHIVcNp7TD6D1nzgiHfYK0tmMBMbUxgoFuCBkW9g9QYvspHN+S9KgoePfMEXHuPUe9mtG9AH9XeA=="], "@aws-sdk/client-sso-oidc": ["@aws-sdk/client-sso-oidc@3.623.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.623.0", "@aws-sdk/credential-provider-node": "3.623.0", "@aws-sdk/middleware-host-header": "3.620.0", "@aws-sdk/middleware-logger": "3.609.0", "@aws-sdk/middleware-recursion-detection": "3.620.0", "@aws-sdk/middleware-user-agent": "3.620.0", "@aws-sdk/region-config-resolver": "3.614.0", "@aws-sdk/types": "3.609.0", "@aws-sdk/util-endpoints": "3.614.0", "@aws-sdk/util-user-agent-browser": "3.609.0", "@aws-sdk/util-user-agent-node": "3.614.0", "@smithy/config-resolver": "^3.0.5", "@smithy/core": "^2.3.2", "@smithy/fetch-http-handler": "^3.2.4", "@smithy/hash-node": "^3.0.3", "@smithy/invalid-dependency": "^3.0.3", "@smithy/middleware-content-length": "^3.0.5", "@smithy/middleware-endpoint": "^3.1.0", "@smithy/middleware-retry": "^3.0.14", "@smithy/middleware-serde": "^3.0.3", "@smithy/middleware-stack": "^3.0.3", "@smithy/node-config-provider": "^3.1.4", "@smithy/node-http-handler": "^3.1.4", "@smithy/protocol-http": "^4.1.0", "@smithy/smithy-client": "^3.1.12", "@smithy/types": "^3.3.0", "@smithy/url-parser": "^3.0.3", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", "@smithy/util-defaults-mode-browser": "^3.0.14", "@smithy/util-defaults-mode-node": "^3.0.14", "@smithy/util-endpoints": "^2.0.5", "@smithy/util-middleware": "^3.0.3", "@smithy/util-retry": "^3.0.3", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" } }, "sha512-lMFEXCa6ES/FGV7hpyrppT1PiAkqQb51AbG0zVU3TIgI2IO4XX02uzMUXImRSRqRpGymRCbJCaCs9LtKvS/37Q=="], - "@aws-sdk/core": ["@aws-sdk/core@3.973.22", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws-sdk/xml-builder": "^3.972.14", "@smithy/core": "^3.23.12", "@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.6", "@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" } }, "sha512-lY6g5L95jBNgOUitUhfV2N/W+i08jHEl3xuLODYSQH5Sf50V+LkVYBSyZRLtv2RyuXZXiV7yQ+acpswK1tlrOA=="], - - "@aws-sdk/crc64-nvme": ["@aws-sdk/crc64-nvme@3.972.4", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-HKZIZLbRyvzo/bXZU7Zmk6XqU+1C9DjI56xd02vwuDIxedxBEqP17t9ExhbP9QFeNq/a3l9GOcyirFXxmbDhmw=="], + "@aws-sdk/core": ["@aws-sdk/core@3.758.0", "", { "dependencies": { "@aws-sdk/types": "3.734.0", "@smithy/core": "^3.1.5", "@smithy/node-config-provider": "^4.0.1", "@smithy/property-provider": "^4.0.1", "@smithy/protocol-http": "^5.0.1", "@smithy/signature-v4": "^5.0.1", "@smithy/smithy-client": "^4.1.6", "@smithy/types": "^4.1.0", "@smithy/util-middleware": "^4.0.1", "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" } }, "sha512-0RswbdR9jt/XKemaLNuxi2gGr4xGlHyGxkTdhSQzCyUe9A9OPCoLl3rIESRguQEech+oJnbHk/wuiwHqTuP9sg=="], "@aws-sdk/credential-provider-cognito-identity": ["@aws-sdk/credential-provider-cognito-identity@3.623.0", "", { "dependencies": { "@aws-sdk/client-cognito-identity": "3.623.0", "@aws-sdk/types": "3.609.0", "@smithy/property-provider": "^3.1.3", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" } }, "sha512-sXU2KtWpFzIzE4iffSIUbl4mgbeN1Rta6BnuKtS3rrVrryku9akAxY//pulbsIsYfXRzOwZzULsa+cxQN00lrw=="], @@ -583,9 +547,9 @@ "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.623.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.620.1", "@aws-sdk/credential-provider-http": "3.622.0", "@aws-sdk/credential-provider-process": "3.620.1", "@aws-sdk/credential-provider-sso": "3.623.0", "@aws-sdk/credential-provider-web-identity": "3.621.0", "@aws-sdk/types": "3.609.0", "@smithy/credential-provider-imds": "^3.2.0", "@smithy/property-provider": "^3.1.3", "@smithy/shared-ini-file-loader": "^3.1.4", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" } }, "sha512-kvXA1SwGneqGzFwRZNpESitnmaENHGFFuuTvgGwtMe7mzXWuA/LkXdbiHmdyAzOo0iByKTCD8uetuwh3CXy4Pw=="], - "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.22", "", { "dependencies": { "@aws-sdk/core": "^3.973.22", "@aws-sdk/nested-clients": "^3.996.12", "@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" } }, "sha512-u33CO9zeNznlVSg9tWTCRYxaGkqr1ufU6qeClpmzAabXZa8RZxQoVXxL5T53oZJFzQYj+FImORCSsi7H7B77gQ=="], + "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.952.0", "", { "dependencies": { "@aws-sdk/core": "3.947.0", "@aws-sdk/nested-clients": "3.952.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-jL9zc+e+7sZeJrHzYKK9GOjl1Ktinh0ORU3cM2uRBi7fuH/0zV9pdMN8PQnGXz0i4tJaKcZ1lrE4V0V6LB9NQg=="], - "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.23", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.20", "@aws-sdk/credential-provider-http": "^3.972.22", "@aws-sdk/credential-provider-ini": "^3.972.22", "@aws-sdk/credential-provider-process": "^3.972.20", "@aws-sdk/credential-provider-sso": "^3.972.22", "@aws-sdk/credential-provider-web-identity": "^3.972.22", "@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" } }, "sha512-U8tyLbLOZItuVWTH0ay9gWo4xMqZwqQbg1oMzdU4FQSkTpqXemm4X0uoKBR6llqAStgBp30ziKFJHTA43l4qMw=="], + "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.758.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.758.0", "@aws-sdk/credential-provider-http": "3.758.0", "@aws-sdk/credential-provider-ini": "3.758.0", "@aws-sdk/credential-provider-process": "3.758.0", "@aws-sdk/credential-provider-sso": "3.758.0", "@aws-sdk/credential-provider-web-identity": "3.758.0", "@aws-sdk/types": "3.734.0", "@smithy/credential-provider-imds": "^4.0.1", "@smithy/property-provider": "^4.0.1", "@smithy/shared-ini-file-loader": "^4.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-+DaMv63wiq7pJrhIQzZYMn4hSarKiizDoJRvyR7WGhnn0oQ/getX9Z0VNCV3i7lIFoLNTb7WMmQ9k7+z/uD5EQ=="], "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.620.1", "", { "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/property-provider": "^3.1.3", "@smithy/shared-ini-file-loader": "^3.1.4", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" } }, "sha512-hWqFMidqLAkaV9G460+1at6qa9vySbjQKKc04p59OT7lZ5cO5VH5S4aI05e+m4j364MBROjjk2ugNvfNf/8ILg=="], @@ -595,57 +559,57 @@ "@aws-sdk/credential-providers": ["@aws-sdk/credential-providers@3.623.0", "", { "dependencies": { "@aws-sdk/client-cognito-identity": "3.623.0", "@aws-sdk/client-sso": "3.623.0", "@aws-sdk/credential-provider-cognito-identity": "3.623.0", "@aws-sdk/credential-provider-env": "3.620.1", "@aws-sdk/credential-provider-http": "3.622.0", "@aws-sdk/credential-provider-ini": "3.623.0", "@aws-sdk/credential-provider-node": "3.623.0", "@aws-sdk/credential-provider-process": "3.620.1", "@aws-sdk/credential-provider-sso": "3.623.0", "@aws-sdk/credential-provider-web-identity": "3.621.0", "@aws-sdk/types": "3.609.0", "@smithy/credential-provider-imds": "^3.2.0", "@smithy/property-provider": "^3.1.3", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" } }, "sha512-abtlH1hkVWAkzuOX79Q47l0ztWOV2Q7l7J4JwQgzEQm7+zCk5iUAiwqKyDzr+ByCyo4I3IWFjy+e1gBdL7rXQQ=="], - "@aws-sdk/eventstream-handler-node": ["@aws-sdk/eventstream-handler-node@3.972.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/eventstream-codec": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-2IrLrOruRr1NhTK0vguBL1gCWv1pu4bf4KaqpsA+/vCJpFEbvXFawn71GvCzk1wyjnDUsemtKypqoKGv4cSGbA=="], + "@aws-sdk/eventstream-handler-node": ["@aws-sdk/eventstream-handler-node@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/eventstream-codec": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-4zIbhdRmol2KosIHmU31ATvNP0tkJhDlRj9GuawVJoEnMvJA1pd2U3SRdiOImJU3j8pT46VeS4YMmYxfjGHByg=="], - "@aws-sdk/middleware-bucket-endpoint": ["@aws-sdk/middleware-bucket-endpoint@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-arn-parser": "^3.972.3", "@smithy/node-config-provider": "^4.3.11", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-goX+axlJ6PQlRnzE2bQisZ8wVrlm6dXJfBzMJhd8LhAIBan/w1Kl73fJnalM/S+18VnpzIHumyV6DtgmvqG5IA=="], + "@aws-sdk/middleware-bucket-endpoint": ["@aws-sdk/middleware-bucket-endpoint@3.734.0", "", { "dependencies": { "@aws-sdk/types": "3.734.0", "@aws-sdk/util-arn-parser": "3.723.0", "@smithy/node-config-provider": "^4.0.1", "@smithy/protocol-http": "^5.0.1", "@smithy/types": "^4.1.0", "@smithy/util-config-provider": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-etC7G18aF7KdZguW27GE/wpbrNmYLVT755EsFc8kXpZj8D6AFKxc7OuveinJmiy0bYXAMspJUWsF6CrGpOw6CQ=="], - "@aws-sdk/middleware-eventstream": ["@aws-sdk/middleware-eventstream@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-r+oP+tbCxgqXVC3pu3MUVePgSY0ILMjA+aEwOosS77m3/DRbtvHrHwqvMcw+cjANMeGzJ+i0ar+n77KXpRA8RQ=="], + "@aws-sdk/middleware-eventstream": ["@aws-sdk/middleware-eventstream@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-XQSH8gzLkk8CDUDxyt4Rdm9owTpRIPdtg2yw9Y2Wl5iSI55YQSiC3x8nM3c4Y4WqReJprunFPK225ZUDoYCfZA=="], - "@aws-sdk/middleware-expect-continue": ["@aws-sdk/middleware-expect-continue@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-mvWqvm61bmZUKmmrtl2uWbokqpenY3Mc3Jf4nXB/Hse6gWxLPaCQThmhPBDzsPSV8/Odn8V6ovWt3pZ7vy4BFQ=="], + "@aws-sdk/middleware-expect-continue": ["@aws-sdk/middleware-expect-continue@3.734.0", "", { "dependencies": { "@aws-sdk/types": "3.734.0", "@smithy/protocol-http": "^5.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-P38/v1l6HjuB2aFUewt7ueAW5IvKkFcv5dalPtbMGRhLeyivBOHwbCyuRKgVs7z7ClTpu9EaViEGki2jEQqEsQ=="], - "@aws-sdk/middleware-flexible-checksums": ["@aws-sdk/middleware-flexible-checksums@3.973.4", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", "@aws-sdk/core": "^3.973.18", "@aws-sdk/crc64-nvme": "^3.972.4", "@aws-sdk/types": "^3.973.5", "@smithy/is-array-buffer": "^4.2.2", "@smithy/node-config-provider": "^4.3.11", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "@smithy/util-middleware": "^4.2.11", "@smithy/util-stream": "^4.5.17", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-7CH2jcGmkvkHc5Buz9IGbdjq1729AAlgYJiAvGq7qhCHqYleCsriWdSnmsqWTwdAfXHMT+pkxX3w6v5tJNcSug=="], + "@aws-sdk/middleware-flexible-checksums": ["@aws-sdk/middleware-flexible-checksums@3.758.0", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", "@aws-sdk/core": "3.758.0", "@aws-sdk/types": "3.734.0", "@smithy/is-array-buffer": "^4.0.0", "@smithy/node-config-provider": "^4.0.1", "@smithy/protocol-http": "^5.0.1", "@smithy/types": "^4.1.0", "@smithy/util-middleware": "^4.0.1", "@smithy/util-stream": "^4.1.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-o8Rk71S08YTKLoSobucjnbj97OCGaXgpEDNKXpXaavUM5xLNoHCLSUPRCiEN86Ivqxg1n17Y2nSRhfbsveOXXA=="], - "@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wAr2REfKsqoKQ+OkNqvOShnBoh+nkPurDKW7uAeVSu6kUECnWlSJiPvnoqxGlfousEY/v9LfS9sNc46hjSYDIQ=="], + "@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.734.0", "", { "dependencies": { "@aws-sdk/types": "3.734.0", "@smithy/protocol-http": "^5.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-LW7RRgSOHHBzWZnigNsDIzu3AiwtjeI2X66v+Wn1P1u+eXssy1+up4ZY/h+t2sU4LU36UvEf+jrZti9c6vRnFw=="], - "@aws-sdk/middleware-location-constraint": ["@aws-sdk/middleware-location-constraint@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-vdK1LJfffBp87Lj0Bw3WdK1rJk9OLDYdQpqoKgmpIZPe+4+HawZ6THTbvjhJt4C4MNnRrHTKHQjkwBiIpDBoig=="], + "@aws-sdk/middleware-location-constraint": ["@aws-sdk/middleware-location-constraint@3.734.0", "", { "dependencies": { "@aws-sdk/types": "3.734.0", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-EJEIXwCQhto/cBfHdm3ZOeLxd2NlJD+X2F+ZTOxzokuhBtY0IONfC/91hOo5tWQweerojwshSMHRCKzRv1tlwg=="], - "@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-CWl5UCM57WUFaFi5kB7IBY1UmOeLvNZAZ2/OZ5l20ldiJ3TiIz1pC65gYj8X0BCPWkeR1E32mpsCk1L1I4n+lA=="], + "@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.734.0", "", { "dependencies": { "@aws-sdk/types": "3.734.0", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-mUMFITpJUW3LcKvFok176eI5zXAUomVtahb9IQBwLzkqFYOrMJvWAvoV4yuxrJ8TlQBG8gyEnkb9SnhZvjg67w=="], - "@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-BnnvYs2ZEpdlmZ2PNlV2ZyQ8j8AEkMTjN79y/YA475ER1ByFYrkVR85qmhni8oeTaJcDqbx364wDpitDAA/wCA=="], + "@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.734.0", "", { "dependencies": { "@aws-sdk/types": "3.734.0", "@smithy/protocol-http": "^5.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-CUat2d9ITsFc2XsmeiRQO96iWpxSKYFjxvj27Hc7vo87YUHRnfMfnc8jw1EpxEwMcvBD7LsRa6vDNky6AjcrFA=="], - "@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.972.18", "", { "dependencies": { "@aws-sdk/core": "^3.973.18", "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-arn-parser": "^3.972.3", "@smithy/core": "^3.23.8", "@smithy/node-config-provider": "^4.3.11", "@smithy/protocol-http": "^5.3.11", "@smithy/signature-v4": "^5.3.11", "@smithy/smithy-client": "^4.12.2", "@smithy/types": "^4.13.0", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-stream": "^4.5.17", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-5E3XxaElrdyk6ZJ0TjH7Qm6ios4b/qQCiLr6oQ8NK7e4Kn6JBTJCaYioQCQ65BpZ1+l1mK5wTAac2+pEz0Smpw=="], + "@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.758.0", "", { "dependencies": { "@aws-sdk/core": "3.758.0", "@aws-sdk/types": "3.734.0", "@aws-sdk/util-arn-parser": "3.723.0", "@smithy/core": "^3.1.5", "@smithy/node-config-provider": "^4.0.1", "@smithy/protocol-http": "^5.0.1", "@smithy/signature-v4": "^5.0.1", "@smithy/smithy-client": "^4.1.6", "@smithy/types": "^4.1.0", "@smithy/util-config-provider": "^4.0.0", "@smithy/util-middleware": "^4.0.1", "@smithy/util-stream": "^4.1.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-6mJ2zyyHPYSV6bAcaFpsdoXZJeQlR1QgBnZZ6juY/+dcYiuyWCdyLUbGzSZSE7GTfx6i+9+QWFeoIMlWKgU63A=="], - "@aws-sdk/middleware-ssec": ["@aws-sdk/middleware-ssec@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-G9clGVuAml7d8DYzY6DnRi7TIIDRvZ3YpqJPz/8wnWS5fYx/FNWNmkO6iJVlVkQg9BfeMzd+bVPtPJOvC4B+nQ=="], + "@aws-sdk/middleware-ssec": ["@aws-sdk/middleware-ssec@3.734.0", "", { "dependencies": { "@aws-sdk/types": "3.734.0", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-d4yd1RrPW/sspEXizq2NSOUivnheac6LPeLSLnaeTbBG9g1KqIqvCzP1TfXEqv2CrWfHEsWtJpX7oyjySSPvDQ=="], - "@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.23", "", { "dependencies": { "@aws-sdk/core": "^3.973.22", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@smithy/core": "^3.23.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-retry": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-HQu8QoqGZZTvg0Spl9H39QTsSMFwgu+8yz/QGKndXFLk9FZMiCiIgBCVlTVKMDvVbgqIzD9ig+/HmXsIL2Rb+g=="], + "@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.758.0", "", { "dependencies": { "@aws-sdk/core": "3.758.0", "@aws-sdk/types": "3.734.0", "@aws-sdk/util-endpoints": "3.743.0", "@smithy/core": "^3.1.5", "@smithy/protocol-http": "^5.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-iNyehQXtQlj69JCgfaOssgZD4HeYGOwxcaKeG6F+40cwBjTAi0+Ph1yfDwqk2qiBPIRWJ/9l2LodZbxiBqgrwg=="], - "@aws-sdk/middleware-websocket": ["@aws-sdk/middleware-websocket@3.972.13", "", { "dependencies": { "@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" } }, "sha512-Gp6EWIqHX5wmsOR5ZxWyyzEU8P0xBdSxkm6VHEwXwBqScKZ7QWRoj6ZmHpr+S44EYb5tuzGya4ottsogSu2W3A=="], + "@aws-sdk/middleware-websocket": ["@aws-sdk/middleware-websocket@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@aws-sdk/util-format-url": "3.936.0", "@smithy/eventstream-codec": "^4.2.5", "@smithy/eventstream-serde-browser": "^4.2.5", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-bPe3rqeugyj/MmjP0yBSZox2v1Wa8Dv39KN+RxVbQroLO8VUitBo6xyZ0oZebhZ5sASwSg58aDcMlX0uFLQnTA=="], - "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.12", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.22", "@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.23", "@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.9", "@smithy/config-resolver": "^4.4.11", "@smithy/core": "^3.23.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.26", "@smithy/middleware-retry": "^4.4.43", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.6", "@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.42", "@smithy/util-defaults-mode-node": "^4.2.45", "@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" } }, "sha512-KLdQGJPSm98uLINolQ0Tol8OAbk7g0Y7zplHJ1K83vbMIH13aoCvR6Tho66xueW4l4aZlEgVGLWBnD8ifUMsGQ=="], + "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.952.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.947.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.948.0", "@aws-sdk/middleware-user-agent": "3.947.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.947.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.7", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.14", "@smithy/middleware-retry": "^4.4.14", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@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.13", "@smithy/util-defaults-mode-node": "^4.2.16", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-OtuirjxuOqZyDcI0q4WtoyWfkq3nSnbH41JwJQsXJefduWcww1FQe5TL1JfYCU7seUxHzK8rg2nFxUBuqUlZtg=="], - "@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.8", "", { "dependencies": { "@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" } }, "sha512-1eD4uhTDeambO/PNIDVG19A6+v4NdD7xzwLHDutHsUqz0B+i661MwQB2eYO4/crcCvCiQG4SRm1k81k54FEIvw=="], + "@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.734.0", "", { "dependencies": { "@aws-sdk/types": "3.734.0", "@smithy/node-config-provider": "^4.0.1", "@smithy/types": "^4.1.0", "@smithy/util-config-provider": "^4.0.0", "@smithy/util-middleware": "^4.0.1", "tslib": "^2.6.2" } }, "sha512-Lvj1kPRC5IuJBr9DyJ9T9/plkh+EfKLy+12s/mykOy1JaKHDpvj+XGy2YO6YgYVOb8JFtaqloid+5COtje4JTQ=="], "@aws-sdk/s3-request-presigner": ["@aws-sdk/s3-request-presigner@3.758.0", "", { "dependencies": { "@aws-sdk/signature-v4-multi-region": "3.758.0", "@aws-sdk/types": "3.734.0", "@aws-sdk/util-format-url": "3.734.0", "@smithy/middleware-endpoint": "^4.0.6", "@smithy/protocol-http": "^5.0.1", "@smithy/smithy-client": "^4.1.6", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-dVyItwu/J1InfJBbCPpHRV9jrsBfI7L0RlDGyS3x/xqBwnm5qpvgNZQasQiyqIl+WJB4f5rZRZHgHuwftqINbA=="], - "@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.6", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "^3.972.18", "@aws-sdk/types": "^3.973.5", "@smithy/protocol-http": "^5.3.11", "@smithy/signature-v4": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-NnsOQsVmJXy4+IdPFUjRCWPn9qNH1TzS/f7MiWgXeoHs903tJpAWQWQtoFvLccyPoBgomKP9L89RRr2YsT/L0g=="], + "@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.758.0", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "3.758.0", "@aws-sdk/types": "3.734.0", "@smithy/protocol-http": "^5.0.1", "@smithy/signature-v4": "^5.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-0RPCo8fYJcrenJ6bRtiUbFOSgQ1CX/GpvwtLU2Fam1tS9h2klKK8d74caeV6A1mIUvBU7bhyQ0wMGlwMtn3EYw=="], - "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1013.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.22", "@aws-sdk/nested-clients": "^3.996.12", "@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" } }, "sha512-IL1c54UvbuERrs9oLm5rvkzMciwhhpn1FL0SlC3XUMoLlFhdBsWJgQKK8O5fsQLxbFVqjbjFx9OBkrn44X9PHw=="], + "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.952.0", "", { "dependencies": { "@aws-sdk/core": "3.947.0", "@aws-sdk/nested-clients": "3.952.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-IpQVC9WOeXQlCEcFVNXWDIKy92CH1Az37u9K0H3DF/HT56AjhyDVKQQfHUy00nt7bHFe3u0K5+zlwErBeKy5ZA=="], - "@aws-sdk/types": ["@aws-sdk/types@3.973.6", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw=="], + "@aws-sdk/types": ["@aws-sdk/types@3.734.0", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-o11tSPTT70nAkGV1fN9wm/hAIiLPyWX6SuGf+9JyTp7S/rC2cFWhR26MvA69nplcjNaXVzB0f+QFrLXXjOqCrg=="], - "@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.972.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA=="], + "@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.723.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-ZhEfvUwNliOQROcAk34WJWVYTlTa4694kSVhDSjW6lE1bMataPnIN8A0ycukEzBXmd8ZSoBcQLn6lKGl7XIJ5w=="], - "@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.5", "", { "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" } }, "sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw=="], + "@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.743.0", "", { "dependencies": { "@aws-sdk/types": "3.734.0", "@smithy/types": "^4.1.0", "@smithy/util-endpoints": "^3.0.1", "tslib": "^2.6.2" } }, "sha512-sN1l559zrixeh5x+pttrnd0A3+r34r0tmPkJ/eaaMaAzXqsmKU/xYre9K3FNnsSS1J1k4PEfk/nHDTVUgFYjnw=="], "@aws-sdk/util-format-url": ["@aws-sdk/util-format-url@3.734.0", "", { "dependencies": { "@aws-sdk/types": "3.734.0", "@smithy/querystring-builder": "^4.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-TxZMVm8V4aR/QkW9/NhujvYpPZjUYqzLwSge5imKZbWFR806NP7RMwc5ilVuHF/bMOln/cVHkl42kATElWBvNw=="], "@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.568.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-3nh4TINkXYr+H41QaPelCceEB2FXP3fxp93YZXB/kqJvX0U9j0N0Uk45gvsjmEPzG8XxkPEeLIfT2I1M7A6Lig=="], - "@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-B3KGXJviV2u6Cdw2SDY2aDhoJkVfY/Q/Trwk2CMSkikE1Oi6gRzxhvhIfiRpHfmIsAhV4EA54TVEX8K6CbHbkA=="], + "@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.734.0", "", { "dependencies": { "@aws-sdk/types": "3.734.0", "@smithy/types": "^4.1.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-xQTCus6Q9LwUuALW+S76OL0jcWtMOVu14q+GoLnWPUM7QeUw963oQcLhF7oq0CtaLLKyl4GOUfcwc773Zmwwng=="], - "@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.9", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.23", "@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" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-jeFqqp8KD/P5O+qeKxyGeu7WEVIZFNprnkaDjGmBOjwxYwafCBhpxTgV1TlW6L8e76Vh/siNylNmN/OmSIFBUQ=="], + "@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.758.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.758.0", "@aws-sdk/types": "3.734.0", "@smithy/node-config-provider": "^4.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-A5EZw85V6WhoKMV2hbuFRvb9NPlxEErb4HPO6/SPXYY4QrjprIzScHxikqcWv1w4J3apB1wto9LPU3IMsYtfrw=="], - "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.14", "", { "dependencies": { "@smithy/types": "^4.13.1", "fast-xml-parser": "5.5.6", "tslib": "^2.6.2" } }, "sha512-G/Yd8Bnnyh8QrqLf8jWJbixEnScUFW24e/wOBGYdw1Cl4r80KX/DvHyM2GVZ2vTp7J4gTEr8IXJlTadA8+UfuQ=="], + "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.734.0", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-Zrjxi5qwGEcUsJ0ru7fRtW74WcTS0rbLcehoFB+rN1GRi2hbLcFaYs4PwVA5diLeAJH0gszv3x4Hr/S87MfbKQ=="], "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.2", "", {}, "sha512-C0NBLsIqzDIae8HFw9YIrIBsbc0xTiOtt7fAukGPnqQ/+zZNaq+4jhuccltK0QuWHBnNm/a6kLIRA6GFiM10eg=="], @@ -683,45 +647,55 @@ "@azure/search-documents": ["@azure/search-documents@12.0.0", "", { "dependencies": { "@azure/core-auth": "^1.3.0", "@azure/core-client": "^1.3.0", "@azure/core-http-compat": "^2.0.1", "@azure/core-paging": "^1.1.1", "@azure/core-rest-pipeline": "^1.3.0", "@azure/core-tracing": "^1.0.0", "@azure/logger": "^1.0.0", "events": "^3.0.0", "tslib": "^2.2.0" } }, "sha512-d9d53f2WWBpLHifk+LVn+AG52zuXvjgxJAdaH6kuT2qwrO1natcigtTgBM8qrI3iDYaDXsQhJSIMEgg9WKSoWA=="], - "@azure/storage-blob": ["@azure/storage-blob@12.31.0", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.9.0", "@azure/core-client": "^1.9.3", "@azure/core-http-compat": "^2.2.0", "@azure/core-lro": "^2.2.0", "@azure/core-paging": "^1.6.2", "@azure/core-rest-pipeline": "^1.19.1", "@azure/core-tracing": "^1.2.0", "@azure/core-util": "^1.11.0", "@azure/core-xml": "^1.4.5", "@azure/logger": "^1.1.4", "@azure/storage-common": "^12.3.0", "events": "^3.0.0", "tslib": "^2.8.1" } }, "sha512-DBgNv10aCSxopt92DkTDD0o9xScXeBqPKGmR50FPZQaEcH4JLQ+GEOGEDv19V5BMkB7kxr+m4h6il/cCDPvmHg=="], + "@azure/storage-blob": ["@azure/storage-blob@12.27.0", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.4.0", "@azure/core-client": "^1.6.2", "@azure/core-http-compat": "^2.0.0", "@azure/core-lro": "^2.2.0", "@azure/core-paging": "^1.1.1", "@azure/core-rest-pipeline": "^1.10.1", "@azure/core-tracing": "^1.1.2", "@azure/core-util": "^1.6.1", "@azure/core-xml": "^1.4.3", "@azure/logger": "^1.0.0", "events": "^3.0.0", "tslib": "^2.2.0" } }, "sha512-IQjj9RIzAKatmNca3D6bT0qJ+Pkox1WZGOg2esJF2YLHb45pQKOwGPIAV+w3rfgkj7zV3RMxpn/c6iftzSOZJQ=="], - "@azure/storage-common": ["@azure/storage-common@12.3.0", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.9.0", "@azure/core-http-compat": "^2.2.0", "@azure/core-rest-pipeline": "^1.19.1", "@azure/core-tracing": "^1.2.0", "@azure/core-util": "^1.11.0", "@azure/logger": "^1.1.4", "events": "^3.3.0", "tslib": "^2.8.1" } }, "sha512-/OFHhy86aG5Pe8dP5tsp+BuJ25JOAl9yaMU3WZbkeoiFMHFtJ7tu5ili7qEdBXNW9G5lDB19trwyI6V49F/8iQ=="], - - "@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=="], + "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], "@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="], - "@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=="], + "@babel/core": ["@babel/core@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@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-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw=="], - "@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="], + "@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], - "@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + "@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.25.9", "", { "dependencies": { "@babel/types": "^7.25.9" } }, "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g=="], + + "@babel/helper-builder-binary-assignment-operator-visitor": ["@babel/helper-builder-binary-assignment-operator-visitor@7.22.15", "", { "dependencies": { "@babel/types": "^7.22.15" } }, "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw=="], "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="], - "@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ=="], + "@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.26.9", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.25.9", "@babel/helper-member-expression-to-functions": "^7.25.9", "@babel/helper-optimise-call-expression": "^7.25.9", "@babel/helper-replace-supers": "^7.26.5", "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", "@babel/traverse": "^7.26.9", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-ubbUqCofvxPRurw5L8WTsCLSkQiVpov4Qx0WMA+jUN+nXBK8ADPlJO1grkFw5CWKC5+sZSOfuGMdX1aI1iT9Sg=="], "@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.26.3", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.25.9", "regexpu-core": "^6.2.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong=="], - "@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.5", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", "debug": "^4.4.1", "lodash.debounce": "^4.0.8", "resolve": "^1.22.10" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg=="], + "@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.3", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", "@babel/helper-plugin-utils": "^7.22.5", "debug": "^4.1.1", "lodash.debounce": "^4.0.8", "resolve": "^1.14.2" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg=="], + + "@babel/helper-environment-visitor": ["@babel/helper-environment-visitor@7.22.20", "", {}, "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA=="], + + "@babel/helper-function-name": ["@babel/helper-function-name@7.23.0", "", { "dependencies": { "@babel/template": "^7.22.15", "@babel/types": "^7.23.0" } }, "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw=="], "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], - "@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="], + "@babel/helper-hoist-variables": ["@babel/helper-hoist-variables@7.22.5", "", { "dependencies": { "@babel/types": "^7.22.5" } }, "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw=="], + + "@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.25.9", "", { "dependencies": { "@babel/traverse": "^7.25.9", "@babel/types": "^7.25.9" } }, "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ=="], "@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="], - "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="], + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="], - "@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="], + "@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.25.9", "", { "dependencies": { "@babel/types": "^7.25.9" } }, "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ=="], "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], - "@babel/helper-remap-async-to-generator": ["@babel/helper-remap-async-to-generator@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-wrap-function": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA=="], + "@babel/helper-remap-async-to-generator": ["@babel/helper-remap-async-to-generator@7.25.9", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.25.9", "@babel/helper-wrap-function": "^7.25.9", "@babel/traverse": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw=="], - "@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.27.1", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA=="], + "@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.26.5", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.25.9", "@babel/helper-optimise-call-expression": "^7.25.9", "@babel/traverse": "^7.26.5" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg=="], - "@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], + "@babel/helper-simple-access": ["@babel/helper-simple-access@7.24.7", "", { "dependencies": { "@babel/traverse": "^7.24.7", "@babel/types": "^7.24.7" } }, "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg=="], + + "@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.25.9", "", { "dependencies": { "@babel/traverse": "^7.25.9", "@babel/types": "^7.25.9" } }, "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA=="], + + "@babel/helper-split-export-declaration": ["@babel/helper-split-export-declaration@7.22.6", "", { "dependencies": { "@babel/types": "^7.22.5" } }, "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g=="], "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], @@ -729,21 +703,21 @@ "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], - "@babel/helper-wrap-function": ["@babel/helper-wrap-function@7.28.3", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.3", "@babel/types": "^7.28.2" } }, "sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g=="], + "@babel/helper-wrap-function": ["@babel/helper-wrap-function@7.25.9", "", { "dependencies": { "@babel/template": "^7.25.9", "@babel/traverse": "^7.25.9", "@babel/types": "^7.25.9" } }, "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g=="], - "@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="], + "@babel/helpers": ["@babel/helpers@7.28.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" } }, "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w=="], - "@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="], + "@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": ["@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q=="], + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": ["@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9", "@babel/traverse": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g=="], - "@babel/plugin-bugfix-safari-class-field-initializer-scope": ["@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA=="], + "@babel/plugin-bugfix-safari-class-field-initializer-scope": ["@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw=="], - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": ["@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA=="], + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": ["@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug=="], - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": ["@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/plugin-transform-optional-chaining": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.13.0" } }, "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw=="], + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": ["@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9", "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", "@babel/plugin-transform-optional-chaining": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.13.0" } }, "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g=="], - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": ["@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw=="], + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": ["@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9", "@babel/traverse": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg=="], "@babel/plugin-proposal-private-property-in-object": ["@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w=="], @@ -755,7 +729,11 @@ "@babel/plugin-syntax-class-static-block": ["@babel/plugin-syntax-class-static-block@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw=="], - "@babel/plugin-syntax-import-assertions": ["@babel/plugin-syntax-import-assertions@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg=="], + "@babel/plugin-syntax-dynamic-import": ["@babel/plugin-syntax-dynamic-import@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ=="], + + "@babel/plugin-syntax-export-namespace-from": ["@babel/plugin-syntax-export-namespace-from@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.3" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q=="], + + "@babel/plugin-syntax-import-assertions": ["@babel/plugin-syntax-import-assertions@7.26.0", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg=="], "@babel/plugin-syntax-import-attributes": ["@babel/plugin-syntax-import-attributes@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww=="], @@ -785,158 +763,144 @@ "@babel/plugin-syntax-unicode-sets-regex": ["@babel/plugin-syntax-unicode-sets-regex@7.18.6", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg=="], - "@babel/plugin-transform-arrow-functions": ["@babel/plugin-transform-arrow-functions@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA=="], + "@babel/plugin-transform-arrow-functions": ["@babel/plugin-transform-arrow-functions@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg=="], - "@babel/plugin-transform-async-generator-functions": ["@babel/plugin-transform-async-generator-functions@7.28.0", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-remap-async-to-generator": "^7.27.1", "@babel/traverse": "^7.28.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q=="], + "@babel/plugin-transform-async-generator-functions": ["@babel/plugin-transform-async-generator-functions@7.26.8", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.26.5", "@babel/helper-remap-async-to-generator": "^7.25.9", "@babel/traverse": "^7.26.8" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-He9Ej2X7tNf2zdKMAGOsmg2MrFc+hfoAhd3po4cWfo/NWjzEAKa0oQruj1ROVUdl0e6fb6/kE/G3SSxE0lRJOg=="], - "@babel/plugin-transform-async-to-generator": ["@babel/plugin-transform-async-to-generator@7.27.1", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-remap-async-to-generator": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA=="], + "@babel/plugin-transform-async-to-generator": ["@babel/plugin-transform-async-to-generator@7.25.9", "", { "dependencies": { "@babel/helper-module-imports": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9", "@babel/helper-remap-async-to-generator": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ=="], - "@babel/plugin-transform-block-scoped-functions": ["@babel/plugin-transform-block-scoped-functions@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg=="], + "@babel/plugin-transform-block-scoped-functions": ["@babel/plugin-transform-block-scoped-functions@7.26.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.26.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ=="], - "@babel/plugin-transform-block-scoping": ["@babel/plugin-transform-block-scoping@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g=="], + "@babel/plugin-transform-block-scoping": ["@babel/plugin-transform-block-scoping@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg=="], - "@babel/plugin-transform-class-properties": ["@babel/plugin-transform-class-properties@7.27.1", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA=="], + "@babel/plugin-transform-class-properties": ["@babel/plugin-transform-class-properties@7.25.9", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q=="], - "@babel/plugin-transform-class-static-block": ["@babel/plugin-transform-class-static-block@7.28.3", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.3", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.12.0" } }, "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg=="], + "@babel/plugin-transform-class-static-block": ["@babel/plugin-transform-class-static-block@7.26.0", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.12.0" } }, "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ=="], - "@babel/plugin-transform-classes": ["@babel/plugin-transform-classes@7.28.4", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-globals": "^7.28.0", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/traverse": "^7.28.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA=="], + "@babel/plugin-transform-classes": ["@babel/plugin-transform-classes@7.25.9", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.25.9", "@babel/helper-compilation-targets": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9", "@babel/helper-replace-supers": "^7.25.9", "@babel/traverse": "^7.25.9", "globals": "^11.1.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg=="], - "@babel/plugin-transform-computed-properties": ["@babel/plugin-transform-computed-properties@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/template": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw=="], + "@babel/plugin-transform-computed-properties": ["@babel/plugin-transform-computed-properties@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9", "@babel/template": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA=="], - "@babel/plugin-transform-destructuring": ["@babel/plugin-transform-destructuring@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw=="], + "@babel/plugin-transform-destructuring": ["@babel/plugin-transform-destructuring@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ=="], - "@babel/plugin-transform-dotall-regex": ["@babel/plugin-transform-dotall-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw=="], + "@babel/plugin-transform-dotall-regex": ["@babel/plugin-transform-dotall-regex@7.25.9", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA=="], - "@babel/plugin-transform-duplicate-keys": ["@babel/plugin-transform-duplicate-keys@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q=="], + "@babel/plugin-transform-duplicate-keys": ["@babel/plugin-transform-duplicate-keys@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw=="], - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": ["@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ=="], + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": ["@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog=="], - "@babel/plugin-transform-dynamic-import": ["@babel/plugin-transform-dynamic-import@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A=="], + "@babel/plugin-transform-dynamic-import": ["@babel/plugin-transform-dynamic-import@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg=="], "@babel/plugin-transform-explicit-resource-management": ["@babel/plugin-transform-explicit-resource-management@7.28.0", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/plugin-transform-destructuring": "^7.28.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ=="], - "@babel/plugin-transform-exponentiation-operator": ["@babel/plugin-transform-exponentiation-operator@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw=="], + "@babel/plugin-transform-exponentiation-operator": ["@babel/plugin-transform-exponentiation-operator@7.26.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ=="], - "@babel/plugin-transform-export-namespace-from": ["@babel/plugin-transform-export-namespace-from@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ=="], + "@babel/plugin-transform-export-namespace-from": ["@babel/plugin-transform-export-namespace-from@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww=="], - "@babel/plugin-transform-for-of": ["@babel/plugin-transform-for-of@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw=="], + "@babel/plugin-transform-for-of": ["@babel/plugin-transform-for-of@7.26.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.26.5", "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Hry8AusVm8LW5BVFgiyUReuoGzPUpdHQQqJY5bZnbbf+ngOHWuCuYFKw/BqaaWlvEUrF91HMhDtEaI1hZzNbLg=="], - "@babel/plugin-transform-function-name": ["@babel/plugin-transform-function-name@7.27.1", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ=="], + "@babel/plugin-transform-function-name": ["@babel/plugin-transform-function-name@7.25.9", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9", "@babel/traverse": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA=="], - "@babel/plugin-transform-json-strings": ["@babel/plugin-transform-json-strings@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q=="], + "@babel/plugin-transform-json-strings": ["@babel/plugin-transform-json-strings@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw=="], - "@babel/plugin-transform-literals": ["@babel/plugin-transform-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA=="], + "@babel/plugin-transform-literals": ["@babel/plugin-transform-literals@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ=="], - "@babel/plugin-transform-logical-assignment-operators": ["@babel/plugin-transform-logical-assignment-operators@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA=="], + "@babel/plugin-transform-logical-assignment-operators": ["@babel/plugin-transform-logical-assignment-operators@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q=="], - "@babel/plugin-transform-member-expression-literals": ["@babel/plugin-transform-member-expression-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ=="], + "@babel/plugin-transform-member-expression-literals": ["@babel/plugin-transform-member-expression-literals@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA=="], - "@babel/plugin-transform-modules-amd": ["@babel/plugin-transform-modules-amd@7.27.1", "", { "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA=="], + "@babel/plugin-transform-modules-amd": ["@babel/plugin-transform-modules-amd@7.25.9", "", { "dependencies": { "@babel/helper-module-transforms": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw=="], - "@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.27.1", "", { "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw=="], + "@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.26.3", "", { "dependencies": { "@babel/helper-module-transforms": "^7.26.0", "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ=="], - "@babel/plugin-transform-modules-systemjs": ["@babel/plugin-transform-modules-systemjs@7.28.5", "", { "dependencies": { "@babel/helper-module-transforms": "^7.28.3", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew=="], + "@babel/plugin-transform-modules-systemjs": ["@babel/plugin-transform-modules-systemjs@7.25.9", "", { "dependencies": { "@babel/helper-module-transforms": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9", "@babel/traverse": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA=="], - "@babel/plugin-transform-modules-umd": ["@babel/plugin-transform-modules-umd@7.27.1", "", { "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w=="], + "@babel/plugin-transform-modules-umd": ["@babel/plugin-transform-modules-umd@7.25.9", "", { "dependencies": { "@babel/helper-module-transforms": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw=="], - "@babel/plugin-transform-named-capturing-groups-regex": ["@babel/plugin-transform-named-capturing-groups-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng=="], + "@babel/plugin-transform-named-capturing-groups-regex": ["@babel/plugin-transform-named-capturing-groups-regex@7.25.9", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA=="], - "@babel/plugin-transform-new-target": ["@babel/plugin-transform-new-target@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ=="], + "@babel/plugin-transform-new-target": ["@babel/plugin-transform-new-target@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ=="], - "@babel/plugin-transform-nullish-coalescing-operator": ["@babel/plugin-transform-nullish-coalescing-operator@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA=="], + "@babel/plugin-transform-nullish-coalescing-operator": ["@babel/plugin-transform-nullish-coalescing-operator@7.26.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.26.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw=="], - "@babel/plugin-transform-numeric-separator": ["@babel/plugin-transform-numeric-separator@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw=="], + "@babel/plugin-transform-numeric-separator": ["@babel/plugin-transform-numeric-separator@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q=="], - "@babel/plugin-transform-object-rest-spread": ["@babel/plugin-transform-object-rest-spread@7.28.4", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", "@babel/plugin-transform-destructuring": "^7.28.0", "@babel/plugin-transform-parameters": "^7.27.7", "@babel/traverse": "^7.28.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew=="], + "@babel/plugin-transform-object-rest-spread": ["@babel/plugin-transform-object-rest-spread@7.25.9", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9", "@babel/plugin-transform-parameters": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg=="], - "@babel/plugin-transform-object-super": ["@babel/plugin-transform-object-super@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng=="], + "@babel/plugin-transform-object-super": ["@babel/plugin-transform-object-super@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9", "@babel/helper-replace-supers": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A=="], - "@babel/plugin-transform-optional-catch-binding": ["@babel/plugin-transform-optional-catch-binding@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q=="], + "@babel/plugin-transform-optional-catch-binding": ["@babel/plugin-transform-optional-catch-binding@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g=="], - "@babel/plugin-transform-optional-chaining": ["@babel/plugin-transform-optional-chaining@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ=="], + "@babel/plugin-transform-optional-chaining": ["@babel/plugin-transform-optional-chaining@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9", "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A=="], - "@babel/plugin-transform-parameters": ["@babel/plugin-transform-parameters@7.27.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg=="], + "@babel/plugin-transform-parameters": ["@babel/plugin-transform-parameters@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g=="], - "@babel/plugin-transform-private-methods": ["@babel/plugin-transform-private-methods@7.27.1", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA=="], + "@babel/plugin-transform-private-methods": ["@babel/plugin-transform-private-methods@7.25.9", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw=="], - "@babel/plugin-transform-private-property-in-object": ["@babel/plugin-transform-private-property-in-object@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-create-class-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ=="], + "@babel/plugin-transform-private-property-in-object": ["@babel/plugin-transform-private-property-in-object@7.25.9", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.25.9", "@babel/helper-create-class-features-plugin": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw=="], - "@babel/plugin-transform-property-literals": ["@babel/plugin-transform-property-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ=="], + "@babel/plugin-transform-property-literals": ["@babel/plugin-transform-property-literals@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA=="], - "@babel/plugin-transform-react-display-name": ["@babel/plugin-transform-react-display-name@7.28.0", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA=="], + "@babel/plugin-transform-react-display-name": ["@babel/plugin-transform-react-display-name@7.23.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-GnvhtVfA2OAtzdX58FJxU19rhoGeQzyVndw3GgtdECQvQFXPEZIOVULHVZGAYmOgmqjXpVpfocAbSjh99V/Fqw=="], - "@babel/plugin-transform-react-jsx": ["@babel/plugin-transform-react-jsx@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-module-imports": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/types": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw=="], + "@babel/plugin-transform-react-jsx": ["@babel/plugin-transform-react-jsx@7.23.4", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-module-imports": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-jsx": "^7.23.3", "@babel/types": "^7.23.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA=="], - "@babel/plugin-transform-react-jsx-development": ["@babel/plugin-transform-react-jsx-development@7.27.1", "", { "dependencies": { "@babel/plugin-transform-react-jsx": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q=="], + "@babel/plugin-transform-react-jsx-development": ["@babel/plugin-transform-react-jsx-development@7.22.5", "", { "dependencies": { "@babel/plugin-transform-react-jsx": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A=="], - "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="], + "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg=="], - "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="], + "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg=="], - "@babel/plugin-transform-react-pure-annotations": ["@babel/plugin-transform-react-pure-annotations@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA=="], + "@babel/plugin-transform-react-pure-annotations": ["@babel/plugin-transform-react-pure-annotations@7.23.3", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-qMFdSS+TUhB7Q/3HVPnEdYJDQIk57jkntAwSuz9xfSE4n+3I+vHYCli3HoHawN1Z3RfCz/y1zXA/JXjG6cVImQ=="], - "@babel/plugin-transform-regenerator": ["@babel/plugin-transform-regenerator@7.28.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA=="], + "@babel/plugin-transform-regenerator": ["@babel/plugin-transform-regenerator@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9", "regenerator-transform": "^0.15.2" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg=="], - "@babel/plugin-transform-regexp-modifiers": ["@babel/plugin-transform-regexp-modifiers@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA=="], + "@babel/plugin-transform-regexp-modifiers": ["@babel/plugin-transform-regexp-modifiers@7.26.0", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw=="], - "@babel/plugin-transform-reserved-words": ["@babel/plugin-transform-reserved-words@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw=="], + "@babel/plugin-transform-reserved-words": ["@babel/plugin-transform-reserved-words@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg=="], "@babel/plugin-transform-runtime": ["@babel/plugin-transform-runtime@7.23.9", "", { "dependencies": { "@babel/helper-module-imports": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", "babel-plugin-polyfill-corejs2": "^0.4.8", "babel-plugin-polyfill-corejs3": "^0.9.0", "babel-plugin-polyfill-regenerator": "^0.5.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-A7clW3a0aSjm3ONU9o2HAILSegJCYlEZmOhmBRReVtIpY/Z/p7yIZ+wR41Z+UipwdGuqwtID/V/dOdZXjwi9gQ=="], - "@babel/plugin-transform-shorthand-properties": ["@babel/plugin-transform-shorthand-properties@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ=="], + "@babel/plugin-transform-shorthand-properties": ["@babel/plugin-transform-shorthand-properties@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng=="], - "@babel/plugin-transform-spread": ["@babel/plugin-transform-spread@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q=="], + "@babel/plugin-transform-spread": ["@babel/plugin-transform-spread@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9", "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A=="], - "@babel/plugin-transform-sticky-regex": ["@babel/plugin-transform-sticky-regex@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g=="], + "@babel/plugin-transform-sticky-regex": ["@babel/plugin-transform-sticky-regex@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA=="], - "@babel/plugin-transform-template-literals": ["@babel/plugin-transform-template-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg=="], + "@babel/plugin-transform-template-literals": ["@babel/plugin-transform-template-literals@7.26.8", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.26.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-OmGDL5/J0CJPJZTHZbi2XpO0tyT2Ia7fzpW5GURwdtp2X3fMmN8au/ej6peC/T33/+CRiIpA8Krse8hFGVmT5Q=="], - "@babel/plugin-transform-typeof-symbol": ["@babel/plugin-transform-typeof-symbol@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw=="], + "@babel/plugin-transform-typeof-symbol": ["@babel/plugin-transform-typeof-symbol@7.26.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.26.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jfoTXXZTgGg36BmhqT3cAYK5qkmqvJpvNrPhaK/52Vgjhw4Rq29s9UqpWWV0D6yuRmgiFH/BUVlkl96zJWqnaw=="], - "@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-create-class-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA=="], + "@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.23.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-create-class-features-plugin": "^7.23.6", "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-typescript": "^7.23.3" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6cBG5mBvUu4VUD04OHKnYzbuHNP8huDsD3EDqqpIpsswTDoqHCjLoHb6+QgsV1WsT2nipRqCPgxD3LXnEO7XfA=="], - "@babel/plugin-transform-unicode-escapes": ["@babel/plugin-transform-unicode-escapes@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg=="], + "@babel/plugin-transform-unicode-escapes": ["@babel/plugin-transform-unicode-escapes@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q=="], - "@babel/plugin-transform-unicode-property-regex": ["@babel/plugin-transform-unicode-property-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q=="], + "@babel/plugin-transform-unicode-property-regex": ["@babel/plugin-transform-unicode-property-regex@7.25.9", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg=="], - "@babel/plugin-transform-unicode-regex": ["@babel/plugin-transform-unicode-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw=="], + "@babel/plugin-transform-unicode-regex": ["@babel/plugin-transform-unicode-regex@7.25.9", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA=="], - "@babel/plugin-transform-unicode-sets-regex": ["@babel/plugin-transform-unicode-sets-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw=="], + "@babel/plugin-transform-unicode-sets-regex": ["@babel/plugin-transform-unicode-sets-regex@7.25.9", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ=="], - "@babel/preset-env": ["@babel/preset-env@7.28.5", "", { "dependencies": { "@babel/compat-data": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-import-assertions": "^7.27.1", "@babel/plugin-syntax-import-attributes": "^7.27.1", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.27.1", "@babel/plugin-transform-async-generator-functions": "^7.28.0", "@babel/plugin-transform-async-to-generator": "^7.27.1", "@babel/plugin-transform-block-scoped-functions": "^7.27.1", "@babel/plugin-transform-block-scoping": "^7.28.5", "@babel/plugin-transform-class-properties": "^7.27.1", "@babel/plugin-transform-class-static-block": "^7.28.3", "@babel/plugin-transform-classes": "^7.28.4", "@babel/plugin-transform-computed-properties": "^7.27.1", "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-dotall-regex": "^7.27.1", "@babel/plugin-transform-duplicate-keys": "^7.27.1", "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", "@babel/plugin-transform-dynamic-import": "^7.27.1", "@babel/plugin-transform-explicit-resource-management": "^7.28.0", "@babel/plugin-transform-exponentiation-operator": "^7.28.5", "@babel/plugin-transform-export-namespace-from": "^7.27.1", "@babel/plugin-transform-for-of": "^7.27.1", "@babel/plugin-transform-function-name": "^7.27.1", "@babel/plugin-transform-json-strings": "^7.27.1", "@babel/plugin-transform-literals": "^7.27.1", "@babel/plugin-transform-logical-assignment-operators": "^7.28.5", "@babel/plugin-transform-member-expression-literals": "^7.27.1", "@babel/plugin-transform-modules-amd": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", "@babel/plugin-transform-modules-systemjs": "^7.28.5", "@babel/plugin-transform-modules-umd": "^7.27.1", "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", "@babel/plugin-transform-new-target": "^7.27.1", "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", "@babel/plugin-transform-numeric-separator": "^7.27.1", "@babel/plugin-transform-object-rest-spread": "^7.28.4", "@babel/plugin-transform-object-super": "^7.27.1", "@babel/plugin-transform-optional-catch-binding": "^7.27.1", "@babel/plugin-transform-optional-chaining": "^7.28.5", "@babel/plugin-transform-parameters": "^7.27.7", "@babel/plugin-transform-private-methods": "^7.27.1", "@babel/plugin-transform-private-property-in-object": "^7.27.1", "@babel/plugin-transform-property-literals": "^7.27.1", "@babel/plugin-transform-regenerator": "^7.28.4", "@babel/plugin-transform-regexp-modifiers": "^7.27.1", "@babel/plugin-transform-reserved-words": "^7.27.1", "@babel/plugin-transform-shorthand-properties": "^7.27.1", "@babel/plugin-transform-spread": "^7.27.1", "@babel/plugin-transform-sticky-regex": "^7.27.1", "@babel/plugin-transform-template-literals": "^7.27.1", "@babel/plugin-transform-typeof-symbol": "^7.27.1", "@babel/plugin-transform-unicode-escapes": "^7.27.1", "@babel/plugin-transform-unicode-property-regex": "^7.27.1", "@babel/plugin-transform-unicode-regex": "^7.27.1", "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.14", "babel-plugin-polyfill-corejs3": "^0.13.0", "babel-plugin-polyfill-regenerator": "^0.6.5", "core-js-compat": "^3.43.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg=="], + "@babel/preset-env": ["@babel/preset-env@7.26.9", "", { "dependencies": { "@babel/compat-data": "^7.26.8", "@babel/helper-compilation-targets": "^7.26.5", "@babel/helper-plugin-utils": "^7.26.5", "@babel/helper-validator-option": "^7.25.9", "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.9", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.9", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.9", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.9", "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.9", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-import-assertions": "^7.26.0", "@babel/plugin-syntax-import-attributes": "^7.26.0", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.25.9", "@babel/plugin-transform-async-generator-functions": "^7.26.8", "@babel/plugin-transform-async-to-generator": "^7.25.9", "@babel/plugin-transform-block-scoped-functions": "^7.26.5", "@babel/plugin-transform-block-scoping": "^7.25.9", "@babel/plugin-transform-class-properties": "^7.25.9", "@babel/plugin-transform-class-static-block": "^7.26.0", "@babel/plugin-transform-classes": "^7.25.9", "@babel/plugin-transform-computed-properties": "^7.25.9", "@babel/plugin-transform-destructuring": "^7.25.9", "@babel/plugin-transform-dotall-regex": "^7.25.9", "@babel/plugin-transform-duplicate-keys": "^7.25.9", "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.9", "@babel/plugin-transform-dynamic-import": "^7.25.9", "@babel/plugin-transform-exponentiation-operator": "^7.26.3", "@babel/plugin-transform-export-namespace-from": "^7.25.9", "@babel/plugin-transform-for-of": "^7.26.9", "@babel/plugin-transform-function-name": "^7.25.9", "@babel/plugin-transform-json-strings": "^7.25.9", "@babel/plugin-transform-literals": "^7.25.9", "@babel/plugin-transform-logical-assignment-operators": "^7.25.9", "@babel/plugin-transform-member-expression-literals": "^7.25.9", "@babel/plugin-transform-modules-amd": "^7.25.9", "@babel/plugin-transform-modules-commonjs": "^7.26.3", "@babel/plugin-transform-modules-systemjs": "^7.25.9", "@babel/plugin-transform-modules-umd": "^7.25.9", "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.9", "@babel/plugin-transform-new-target": "^7.25.9", "@babel/plugin-transform-nullish-coalescing-operator": "^7.26.6", "@babel/plugin-transform-numeric-separator": "^7.25.9", "@babel/plugin-transform-object-rest-spread": "^7.25.9", "@babel/plugin-transform-object-super": "^7.25.9", "@babel/plugin-transform-optional-catch-binding": "^7.25.9", "@babel/plugin-transform-optional-chaining": "^7.25.9", "@babel/plugin-transform-parameters": "^7.25.9", "@babel/plugin-transform-private-methods": "^7.25.9", "@babel/plugin-transform-private-property-in-object": "^7.25.9", "@babel/plugin-transform-property-literals": "^7.25.9", "@babel/plugin-transform-regenerator": "^7.25.9", "@babel/plugin-transform-regexp-modifiers": "^7.26.0", "@babel/plugin-transform-reserved-words": "^7.25.9", "@babel/plugin-transform-shorthand-properties": "^7.25.9", "@babel/plugin-transform-spread": "^7.25.9", "@babel/plugin-transform-sticky-regex": "^7.25.9", "@babel/plugin-transform-template-literals": "^7.26.8", "@babel/plugin-transform-typeof-symbol": "^7.26.7", "@babel/plugin-transform-unicode-escapes": "^7.25.9", "@babel/plugin-transform-unicode-property-regex": "^7.25.9", "@babel/plugin-transform-unicode-regex": "^7.25.9", "@babel/plugin-transform-unicode-sets-regex": "^7.25.9", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.10", "babel-plugin-polyfill-corejs3": "^0.11.0", "babel-plugin-polyfill-regenerator": "^0.6.1", "core-js-compat": "^3.40.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-vX3qPGE8sEKEAZCWk05k3cpTAE3/nOYca++JA+Rd0z2NCNzabmYvEiSShKzm10zdquOIAVXsy2Ei/DTW34KlKQ=="], "@babel/preset-modules": ["@babel/preset-modules@0.1.6-no-external-plugins", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/types": "^7.4.4", "esutils": "^2.0.2" }, "peerDependencies": { "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA=="], - "@babel/preset-react": ["@babel/preset-react@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-transform-react-display-name": "^7.28.0", "@babel/plugin-transform-react-jsx": "^7.27.1", "@babel/plugin-transform-react-jsx-development": "^7.27.1", "@babel/plugin-transform-react-pure-annotations": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ=="], + "@babel/preset-react": ["@babel/preset-react@7.23.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-validator-option": "^7.22.15", "@babel/plugin-transform-react-display-name": "^7.23.3", "@babel/plugin-transform-react-jsx": "^7.22.15", "@babel/plugin-transform-react-jsx-development": "^7.22.5", "@babel/plugin-transform-react-pure-annotations": "^7.23.3" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tbkHOS9axH6Ysf2OUEqoSZ6T3Fa2SrNH6WTWSPBboxKzdxNc9qOICeLXkNG0ZEwbQ1HY8liwOce4aN/Ceyuq6w=="], - "@babel/preset-typescript": ["@babel/preset-typescript@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", "@babel/plugin-transform-typescript": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g=="], + "@babel/preset-typescript": ["@babel/preset-typescript@7.23.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-validator-option": "^7.22.15", "@babel/plugin-syntax-jsx": "^7.23.3", "@babel/plugin-transform-modules-commonjs": "^7.23.3", "@babel/plugin-transform-typescript": "^7.23.3" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-17oIGVlqz6CchO9RFYn5U6ZpWRZIngayYCtrPRSgANSwC2V1Jb+iP74nVxzzXJte8b8BYxrL1yY96xfhTBrNNQ=="], "@babel/runtime": ["@babel/runtime@7.26.10", "", { "dependencies": { "regenerator-runtime": "^0.14.0" } }, "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw=="], - "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + "@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], - "@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="], + "@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], - "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + "@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], "@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=="], - "@chevrotain/cst-dts-gen": ["@chevrotain/cst-dts-gen@11.1.2", "", { "dependencies": { "@chevrotain/gast": "11.1.2", "@chevrotain/types": "11.1.2", "lodash-es": "4.17.23" } }, "sha512-XTsjvDVB5nDZBQB8o0o/0ozNelQtn2KrUVteIHSlPd2VAV2utEb6JzyCJaJ8tGxACR4RiBNWy5uYUHX2eji88Q=="], - - "@chevrotain/gast": ["@chevrotain/gast@11.1.2", "", { "dependencies": { "@chevrotain/types": "11.1.2", "lodash-es": "4.17.23" } }, "sha512-Z9zfXR5jNZb1Hlsd/p+4XWeUFugrHirq36bKzPWDSIacV+GPSVXdk+ahVWZTwjhNwofAWg/sZg58fyucKSQx5g=="], - - "@chevrotain/regexp-to-ast": ["@chevrotain/regexp-to-ast@11.1.2", "", {}, "sha512-nMU3Uj8naWer7xpZTYJdxbAs6RIv/dxYzkYU8GSwgUtcAAlzjcPfX1w+RKRcYG8POlzMeayOQ/znfwxEGo5ulw=="], - - "@chevrotain/types": ["@chevrotain/types@11.1.2", "", {}, "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw=="], - - "@chevrotain/utils": ["@chevrotain/utils@11.1.2", "", {}, "sha512-4mudFAQ6H+MqBTfqLmU7G1ZwRzCLfJEooL/fsF6rCX5eePMbGhoy5n4g+G4vlh2muDcsCTJtL+uKbOzWxs5LHA=="], - "@codemirror/autocomplete": ["@codemirror/autocomplete@6.18.0", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0" }, "peerDependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "@lezer/common": "^1.0.0" } }, "sha512-5DbOvBbY4qW5l57cjDsmmpDh3/TeK1vXfTHa+BUMrRzdWdcxKZ4U4V7vQaTtOpApNU4kLS4FQ6cINtLg245LXA=="], "@codemirror/commands": ["@codemirror/commands@6.6.0", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.4.0", "@codemirror/view": "^6.27.0", "@lezer/common": "^1.1.0" } }, "sha512-qnY+b7j1UNcTS31Eenuc/5YJB6gQOzkUoNmJQc0rznwqSRpeaWWpjkWy2C/MPTcePpsKJEM26hXrOXl1+nceXg=="], @@ -965,177 +929,133 @@ "@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="], - "@csstools/cascade-layer-name-parser": ["@csstools/cascade-layer-name-parser@3.0.0", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-/3iksyevwRfSJx5yH0RkcrcYXwuhMQx3Juqf40t97PeEy2/Mz2TItZ/z/216qpe4GgOyFBP8MKIwVvytzHmfIQ=="], + "@csstools/cascade-layer-name-parser": ["@csstools/cascade-layer-name-parser@1.0.7", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^2.5.0", "@csstools/css-tokenizer": "^2.2.3" } }, "sha512-9J4aMRJ7A2WRjaRLvsMeWrL69FmEuijtiW1XlK/sG+V0UJiHVYUyvj9mY4WAXfU/hGIiGOgL8e0jJcRyaZTjDQ=="], - "@csstools/color-helpers": ["@csstools/color-helpers@6.0.2", "", {}, "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q=="], + "@csstools/color-helpers": ["@csstools/color-helpers@2.1.0", "", {}, "sha512-OWkqBa7PDzZuJ3Ha7T5bxdSVfSCfTq6K1mbAhbO1MD+GSULGjrp45i5RudyJOedstSarN/3mdwu9upJE7gDXfw=="], - "@csstools/css-calc": ["@csstools/css-calc@3.1.1", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ=="], + "@csstools/css-calc": ["@csstools/css-calc@1.1.6", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^2.5.0", "@csstools/css-tokenizer": "^2.2.3" } }, "sha512-YHPAuFg5iA4qZGzMzvrQwzkvJpesXXyIUyaONflQrjtHB+BcFFbgltJkIkb31dMGO4SE9iZFA4HYpdk7+hnYew=="], - "@csstools/css-color-parser": ["@csstools/css-color-parser@4.0.2", "", { "dependencies": { "@csstools/color-helpers": "^6.0.2", "@csstools/css-calc": "^3.1.1" }, "peerDependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw=="], + "@csstools/css-color-parser": ["@csstools/css-color-parser@1.5.1", "", { "dependencies": { "@csstools/color-helpers": "^4.0.0", "@csstools/css-calc": "^1.1.6" }, "peerDependencies": { "@csstools/css-parser-algorithms": "^2.5.0", "@csstools/css-tokenizer": "^2.2.3" } }, "sha512-x+SajGB2paGrTjPOUorGi8iCztF008YMKXTn+XzGVDBEIVJ/W1121pPerpneJYGOe1m6zWLPLnzOPaznmQxKFw=="], - "@csstools/css-parser-algorithms": ["@csstools/css-parser-algorithms@4.0.0", "", { "peerDependencies": { "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w=="], + "@csstools/css-parser-algorithms": ["@csstools/css-parser-algorithms@2.5.0", "", { "peerDependencies": { "@csstools/css-tokenizer": "^2.2.3" } }, "sha512-abypo6m9re3clXA00eu5syw+oaPHbJTPapu9C4pzNsJ4hdZDzushT50Zhu+iIYXgEe1CxnRMn7ngsbV+MLrlpQ=="], - "@csstools/css-tokenizer": ["@csstools/css-tokenizer@4.0.0", "", {}, "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA=="], + "@csstools/css-tokenizer": ["@csstools/css-tokenizer@2.2.3", "", {}, "sha512-pp//EvZ9dUmGuGtG1p+n17gTHEOqu9jO+FiCUjNN3BDmyhdA2Jq9QsVeR7K8/2QCK17HSsioPlTW9ZkzoWb3Lg=="], - "@csstools/media-query-list-parser": ["@csstools/media-query-list-parser@5.0.0", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-T9lXmZOfnam3eMERPsszjY5NK0jX8RmThmmm99FZ8b7z8yMaFZWKwLWGZuTwdO3ddRY5fy13GmmEYZXB4I98Eg=="], + "@csstools/media-query-list-parser": ["@csstools/media-query-list-parser@2.1.7", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^2.5.0", "@csstools/css-tokenizer": "^2.2.3" } }, "sha512-lHPKJDkPUECsyAvD60joYfDmp8UERYxHGkFfyLJFTVK/ERJe0sVlIFLXU5XFxdjNDTerp5L4KeaKG+Z5S94qxQ=="], - "@csstools/postcss-alpha-function": ["@csstools/postcss-alpha-function@2.0.3", "", { "dependencies": { "@csstools/css-color-parser": "^4.0.2", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0", "@csstools/postcss-progressive-custom-properties": "^5.0.0", "@csstools/utilities": "^3.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-8GqzD3JnfpKJSVxPIC0KadyAfB5VRzPZdv7XQ4zvK1q0ku+uHVUAS2N/IDavQkW40gkuUci64O0ea6QB/zgCSw=="], + "@csstools/postcss-cascade-layers": ["@csstools/postcss-cascade-layers@3.0.1", "", { "dependencies": { "@csstools/selector-specificity": "^2.0.2", "postcss-selector-parser": "^6.0.10" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-dD8W98dOYNOH/yX4V4HXOhfCOnvVAg8TtsL+qCGNoKXuq5z2C/d026wGWgySgC8cajXXo/wNezS31Glj5GcqrA=="], - "@csstools/postcss-cascade-layers": ["@csstools/postcss-cascade-layers@6.0.0", "", { "dependencies": { "@csstools/selector-specificity": "^6.0.0", "postcss-selector-parser": "^7.1.1" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-WhsECqmrEZQGqaPlBA7JkmF/CJ2/+wetL4fkL9sOPccKd32PQ1qToFM6gqSI5rkpmYqubvbxjEJhyMTHYK0vZQ=="], + "@csstools/postcss-color-function": ["@csstools/postcss-color-function@2.2.3", "", { "dependencies": { "@csstools/css-color-parser": "^1.2.0", "@csstools/css-parser-algorithms": "^2.1.1", "@csstools/css-tokenizer": "^2.1.1", "@csstools/postcss-progressive-custom-properties": "^2.3.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-b1ptNkr1UWP96EEHqKBWWaV5m/0hgYGctgA/RVZhONeP1L3T/8hwoqDm9bB23yVCfOgE9U93KI9j06+pEkJTvw=="], - "@csstools/postcss-color-function": ["@csstools/postcss-color-function@5.0.2", "", { "dependencies": { "@csstools/css-color-parser": "^4.0.2", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0", "@csstools/postcss-progressive-custom-properties": "^5.0.0", "@csstools/utilities": "^3.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-CjBdFemUFcAh3087MEJhZcO+QT1b8S75agysa1rU9TEC1YecznzwV+jpMxUc0JRBEV4ET2PjLssqmndR9IygeA=="], + "@csstools/postcss-color-mix-function": ["@csstools/postcss-color-mix-function@1.0.3", "", { "dependencies": { "@csstools/css-color-parser": "^1.2.0", "@csstools/css-parser-algorithms": "^2.1.1", "@csstools/css-tokenizer": "^2.1.1", "@csstools/postcss-progressive-custom-properties": "^2.3.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-QGXjGugTluqFZWzVf+S3wCiRiI0ukXlYqCi7OnpDotP/zaVTyl/aqZujLFzTOXy24BoWnu89frGMc79ohY5eog=="], - "@csstools/postcss-color-function-display-p3-linear": ["@csstools/postcss-color-function-display-p3-linear@2.0.2", "", { "dependencies": { "@csstools/css-color-parser": "^4.0.2", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0", "@csstools/postcss-progressive-custom-properties": "^5.0.0", "@csstools/utilities": "^3.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-TWUwSe1+2KdYGGWTx5LR4JQN07vKHAeSho+bGYRgow+9cs3dqgOqS1f/a1odiX30ESmZvwIudJ86wzeiDR6UGg=="], + "@csstools/postcss-font-format-keywords": ["@csstools/postcss-font-format-keywords@2.0.2", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-iKYZlIs6JsNT7NKyRjyIyezTCHLh4L4BBB3F5Nx7Dc4Z/QmBgX+YJFuUSar8IM6KclGiAUFGomXFdYxAwJydlA=="], - "@csstools/postcss-color-mix-function": ["@csstools/postcss-color-mix-function@4.0.2", "", { "dependencies": { "@csstools/css-color-parser": "^4.0.2", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0", "@csstools/postcss-progressive-custom-properties": "^5.0.0", "@csstools/utilities": "^3.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-PFKQKswFqZrYKpajZsP4lhqjU/6+J5PTOWq1rKiFnniKsf4LgpGXrgHS/C6nn5Rc51LX0n4dWOWqY5ZN2i5IjA=="], + "@csstools/postcss-gradients-interpolation-method": ["@csstools/postcss-gradients-interpolation-method@3.0.6", "", { "dependencies": { "@csstools/css-color-parser": "^1.2.0", "@csstools/css-parser-algorithms": "^2.1.1", "@csstools/css-tokenizer": "^2.1.1", "@csstools/postcss-progressive-custom-properties": "^2.3.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-rBOBTat/YMmB0G8VHwKqDEx+RZ4KCU9j42K8LwS0IpZnyThalZZF7BCSsZ6TFlZhcRZKlZy3LLFI2pLqjNVGGA=="], - "@csstools/postcss-color-mix-variadic-function-arguments": ["@csstools/postcss-color-mix-variadic-function-arguments@2.0.2", "", { "dependencies": { "@csstools/css-color-parser": "^4.0.2", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0", "@csstools/postcss-progressive-custom-properties": "^5.0.0", "@csstools/utilities": "^3.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-zEchsghpDH/6SytyjKu9TIPm4hiiWcur102cENl54cyIwTZsa+2MBJl/vtyALZ+uQ17h27L4waD+0Ow96sgZow=="], + "@csstools/postcss-hwb-function": ["@csstools/postcss-hwb-function@2.2.2", "", { "dependencies": { "@csstools/css-color-parser": "^1.2.0", "@csstools/css-parser-algorithms": "^2.1.1", "@csstools/css-tokenizer": "^2.1.1" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-W5Y5oaJ382HSlbdGfPf60d7dAK6Hqf10+Be1yZbd/TNNrQ/3dDdV1c07YwOXPQ3PZ6dvFMhxbIbn8EC3ki3nEg=="], - "@csstools/postcss-content-alt-text": ["@csstools/postcss-content-alt-text@3.0.0", "", { "dependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0", "@csstools/postcss-progressive-custom-properties": "^5.0.0", "@csstools/utilities": "^3.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-OHa+4aCcrJtHpPWB3zptScHwpS1TUbeLR4uO0ntIz0Su/zw9SoWkVu+tDMSySSAsNtNSI3kut4fTliFwIsrHxA=="], + "@csstools/postcss-ic-unit": ["@csstools/postcss-ic-unit@2.0.4", "", { "dependencies": { "@csstools/postcss-progressive-custom-properties": "^2.3.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-9W2ZbV7whWnr1Gt4qYgxMWzbevZMOvclUczT5vk4yR6vS53W/njiiUhtm/jh/BKYwQ1W3PECZjgAd2dH4ebJig=="], - "@csstools/postcss-contrast-color-function": ["@csstools/postcss-contrast-color-function@3.0.2", "", { "dependencies": { "@csstools/css-color-parser": "^4.0.2", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0", "@csstools/postcss-progressive-custom-properties": "^5.0.0", "@csstools/utilities": "^3.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-fwOz/m+ytFPz4aIph2foQS9nEDOdOjYcN5bgwbGR2jGUV8mYaeD/EaTVMHTRb/zqB65y2qNwmcFcE6VQty69Pw=="], + "@csstools/postcss-is-pseudo-class": ["@csstools/postcss-is-pseudo-class@3.2.1", "", { "dependencies": { "@csstools/selector-specificity": "^2.0.0", "postcss-selector-parser": "^6.0.10" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-AtANdV34kJl04Al62is3eQRk/BfOfyAvEmRJvbt+nx5REqImLC+2XhuE6skgkcPli1l8ONS67wS+l1sBzySc3Q=="], - "@csstools/postcss-exponential-functions": ["@csstools/postcss-exponential-functions@3.0.1", "", { "dependencies": { "@csstools/css-calc": "^3.1.1", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-WHJ52Uk0AVUIICEYRY9xFHJZAuq0ZVg0f8xzqUN2zRFrZvGgRPpFwxK7h9FWvqKIOueOwN6hnJD23A8FwsUiVw=="], + "@csstools/postcss-logical-float-and-clear": ["@csstools/postcss-logical-float-and-clear@1.0.1", "", { "peerDependencies": { "postcss": "^8.4" } }, "sha512-eO9z2sMLddvlfFEW5Fxbjyd03zaO7cJafDurK4rCqyRt9P7aaWwha0LcSzoROlcZrw1NBV2JAp2vMKfPMQO1xw=="], - "@csstools/postcss-font-format-keywords": ["@csstools/postcss-font-format-keywords@5.0.0", "", { "dependencies": { "@csstools/utilities": "^3.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-M1EjCe/J3u8fFhOZgRci74cQhJ7R0UFBX6T+WqoEvjrr8hVfMiV+HTYrzxLY5OW8YllvXYr5Q5t5OvJbsUSeDg=="], + "@csstools/postcss-logical-resize": ["@csstools/postcss-logical-resize@1.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-x1ge74eCSvpBkDDWppl+7FuD2dL68WP+wwP2qvdUcKY17vJksz+XoE1ZRV38uJgS6FNUwC0AxrPW5gy3MxsDHQ=="], - "@csstools/postcss-font-width-property": ["@csstools/postcss-font-width-property@1.0.0", "", { "dependencies": { "@csstools/utilities": "^3.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-AvmySApdijbjYQuXXh95tb7iVnqZBbJrv3oajO927ksE/mDmJBiszm+psW8orL2lRGR8j6ZU5Uv9/ou2Z5KRKA=="], + "@csstools/postcss-logical-viewport-units": ["@csstools/postcss-logical-viewport-units@1.0.3", "", { "dependencies": { "@csstools/css-tokenizer": "^2.1.1" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-6zqcyRg9HSqIHIPMYdt6THWhRmE5/tyHKJQLysn2TeDf/ftq7Em9qwMTx98t2C/7UxIsYS8lOiHHxAVjWn2WUg=="], - "@csstools/postcss-gamut-mapping": ["@csstools/postcss-gamut-mapping@3.0.2", "", { "dependencies": { "@csstools/css-color-parser": "^4.0.2", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-IrXAW3KQ3Sxm29C3/4mYQ/iA0Q5OH9YFOPQ2w24iIlXpD06A9MHvmQapP2vAGtQI3tlp2Xw5LIdm9F8khARfOA=="], + "@csstools/postcss-media-minmax": ["@csstools/postcss-media-minmax@1.1.2", "", { "dependencies": { "@csstools/css-calc": "^1.1.6", "@csstools/css-parser-algorithms": "^2.5.0", "@csstools/css-tokenizer": "^2.2.3", "@csstools/media-query-list-parser": "^2.1.7" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-7qTRTJxW96u2yiEaTep1+8nto1O/rEDacewKqH+Riq5E6EsHTOmGHxkB4Se5Ic5xgDC4I05lLZxzzxnlnSypxA=="], - "@csstools/postcss-gradients-interpolation-method": ["@csstools/postcss-gradients-interpolation-method@6.0.2", "", { "dependencies": { "@csstools/css-color-parser": "^4.0.2", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0", "@csstools/postcss-progressive-custom-properties": "^5.0.0", "@csstools/utilities": "^3.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-saQHvD1PD/zCdn+kxCWCcQOdXZBljr8L6BKlCLs0w8GXYfo3SHdWL1HZQ+I1hVCPlU+MJPJJbZJjG/jHRJSlAw=="], + "@csstools/postcss-media-queries-aspect-ratio-number-values": ["@csstools/postcss-media-queries-aspect-ratio-number-values@1.0.4", "", { "dependencies": { "@csstools/css-parser-algorithms": "^2.2.0", "@csstools/css-tokenizer": "^2.1.1", "@csstools/media-query-list-parser": "^2.1.1" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-IwyTbyR8E2y3kh6Fhrs251KjKBJeUPV5GlnUKnpU70PRFEN2DolWbf2V4+o/B9+Oj77P/DullLTulWEQ8uFtAA=="], - "@csstools/postcss-hwb-function": ["@csstools/postcss-hwb-function@5.0.2", "", { "dependencies": { "@csstools/css-color-parser": "^4.0.2", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0", "@csstools/postcss-progressive-custom-properties": "^5.0.0", "@csstools/utilities": "^3.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-ChR0+pKc/2cs900jakiv8dLrb69aez5P3T+g+wfJx1j6mreAe8orKTiMrVBk+DZvCRqpdOA2m8VoFms64A3Dew=="], + "@csstools/postcss-nested-calc": ["@csstools/postcss-nested-calc@2.0.2", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-jbwrP8rN4e7LNaRcpx3xpMUjhtt34I9OV+zgbcsYAAk6k1+3kODXJBf95/JMYWhu9g1oif7r06QVUgfWsKxCFw=="], - "@csstools/postcss-ic-unit": ["@csstools/postcss-ic-unit@5.0.0", "", { "dependencies": { "@csstools/postcss-progressive-custom-properties": "^5.0.0", "@csstools/utilities": "^3.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-/ws5d6c4uKqfM9zIL3ugcGI+3fvZEOOkJHNzAyTAGJIdZ+aSL9BVPNlHGV4QzmL0vqBSCOdU3+rhcMEj3+KzYw=="], + "@csstools/postcss-normalize-display-values": ["@csstools/postcss-normalize-display-values@2.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-TQT5g3JQ5gPXC239YuRK8jFceXF9d25ZvBkyjzBGGoW5st5sPXFVQS8OjYb9IJ/K3CdfK4528y483cgS2DJR/w=="], - "@csstools/postcss-initial": ["@csstools/postcss-initial@3.0.0", "", { "peerDependencies": { "postcss": "^8.4" } }, "sha512-UVUrFmrTQyLomVepnjWlbBg7GoscLmXLwYFyjbcEnmpeGW7wde6lNpx5eM3eVwZI2M+7hCE3ykYnAsEPLcLa+Q=="], + "@csstools/postcss-oklab-function": ["@csstools/postcss-oklab-function@2.2.3", "", { "dependencies": { "@csstools/css-color-parser": "^1.2.0", "@csstools/css-parser-algorithms": "^2.1.1", "@csstools/css-tokenizer": "^2.1.1", "@csstools/postcss-progressive-custom-properties": "^2.3.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-AgJ2rWMnLCDcbSMTHSqBYn66DNLBym6JpBpCaqmwZ9huGdljjDRuH3DzOYzkgQ7Pm2K92IYIq54IvFHloUOdvA=="], - "@csstools/postcss-is-pseudo-class": ["@csstools/postcss-is-pseudo-class@6.0.0", "", { "dependencies": { "@csstools/selector-specificity": "^6.0.0", "postcss-selector-parser": "^7.1.1" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-1Hdy/ykg9RDo8vU8RiM2o+RaXO39WpFPaIkHxlAEJFofle/lc33tdQMKhBk3jR/Fe+uZNLOs3HlowFafyFptVw=="], + "@csstools/postcss-progressive-custom-properties": ["@csstools/postcss-progressive-custom-properties@2.3.0", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-Zd8ojyMlsL919TBExQ1I0CTpBDdyCpH/yOdqatZpuC3sd22K4SwC7+Yez3Q/vmXMWSAl+shjNeFZ7JMyxMjK+Q=="], - "@csstools/postcss-light-dark-function": ["@csstools/postcss-light-dark-function@3.0.0", "", { "dependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0", "@csstools/postcss-progressive-custom-properties": "^5.0.0", "@csstools/utilities": "^3.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-s++V5/hYazeRUCYIn2lsBVzUsxdeC46gtwpgW6lu5U/GlPOS5UTDT14kkEyPgXmFbCvaWLREqV7YTMJq1K3G6w=="], + "@csstools/postcss-relative-color-syntax": ["@csstools/postcss-relative-color-syntax@1.0.2", "", { "dependencies": { "@csstools/css-color-parser": "^1.2.0", "@csstools/css-parser-algorithms": "^2.1.1", "@csstools/css-tokenizer": "^2.1.1", "@csstools/postcss-progressive-custom-properties": "^2.3.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-juCoVInkgH2TZPfOhyx6tIal7jW37L/0Tt+Vcl1LoxqQA9sxcg3JWYZ98pl1BonDnki6s/M7nXzFQHWsWMeHgw=="], - "@csstools/postcss-logical-float-and-clear": ["@csstools/postcss-logical-float-and-clear@4.0.0", "", { "peerDependencies": { "postcss": "^8.4" } }, "sha512-NGzdIRVj/VxOa/TjVdkHeyiJoDihONV0+uB0csUdgWbFFr8xndtfqK8iIGP9IKJzco+w0hvBF2SSk2sDSTAnOQ=="], + "@csstools/postcss-scope-pseudo-class": ["@csstools/postcss-scope-pseudo-class@2.0.2", "", { "dependencies": { "postcss-selector-parser": "^6.0.10" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-6Pvo4uexUCXt+Hz5iUtemQAcIuCYnL+ePs1khFR6/xPgC92aQLJ0zGHonWoewiBE+I++4gXK3pr+R1rlOFHe5w=="], - "@csstools/postcss-logical-overflow": ["@csstools/postcss-logical-overflow@3.0.0", "", { "peerDependencies": { "postcss": "^8.4" } }, "sha512-5cRg93QXVskM0MNepHpPcL0WLSf5Hncky0DrFDQY/4ozbH5lH7SX5ejayVpNTGSX7IpOvu7ykQDLOdMMGYzwpA=="], + "@csstools/postcss-stepped-value-functions": ["@csstools/postcss-stepped-value-functions@2.1.1", "", { "dependencies": { "@csstools/css-calc": "^1.1.1", "@csstools/css-parser-algorithms": "^2.1.1", "@csstools/css-tokenizer": "^2.1.1" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-YCvdF0GCZK35nhLgs7ippcxDlRVe5QsSht3+EghqTjnYnyl3BbWIN6fYQ1dKWYTJ+7Bgi41TgqQFfJDcp9Xy/w=="], - "@csstools/postcss-logical-overscroll-behavior": ["@csstools/postcss-logical-overscroll-behavior@3.0.0", "", { "peerDependencies": { "postcss": "^8.4" } }, "sha512-82Jnl/5Wi5jb19nQE1XlBHrZcNL3PzOgcj268cDkfwf+xi10HBqufGo1Unwf5n8bbbEFhEKgyQW+vFsc9iY1jw=="], + "@csstools/postcss-text-decoration-shorthand": ["@csstools/postcss-text-decoration-shorthand@2.2.4", "", { "dependencies": { "@csstools/color-helpers": "^2.1.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-zPN56sQkS/7YTCVZhOBVCWf7AiNge8fXDl7JVaHLz2RyT4pnyK2gFjckWRLpO0A2xkm1lCgZ0bepYZTwAVd/5A=="], - "@csstools/postcss-logical-resize": ["@csstools/postcss-logical-resize@4.0.0", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-L0T3q0gei/tGetCGZU0c7VN77VTivRpz1YZRNxjXYmW+85PKeI6U9YnSvDqLU2vBT2uN4kLEzfgZ0ThIZpN18A=="], + "@csstools/postcss-trigonometric-functions": ["@csstools/postcss-trigonometric-functions@2.1.1", "", { "dependencies": { "@csstools/css-calc": "^1.1.1", "@csstools/css-parser-algorithms": "^2.1.1", "@csstools/css-tokenizer": "^2.1.1" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-XcXmHEFfHXhvYz40FtDlA4Fp4NQln2bWTsCwthd2c+MCnYArUYU3YaMqzR5CrKP3pMoGYTBnp5fMqf1HxItNyw=="], - "@csstools/postcss-logical-viewport-units": ["@csstools/postcss-logical-viewport-units@4.0.0", "", { "dependencies": { "@csstools/css-tokenizer": "^4.0.0", "@csstools/utilities": "^3.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-TA3AqVN/1IH3dKRC2UUWvprvwyOs2IeD7FDZk5Hz20w4q33yIuSg0i0gjyTUkcn90g8A4n7QpyZ2AgBrnYPnnA=="], + "@csstools/postcss-unset-value": ["@csstools/postcss-unset-value@2.0.1", "", { "peerDependencies": { "postcss": "^8.4" } }, "sha512-oJ9Xl29/yU8U7/pnMJRqAZd4YXNCfGEdcP4ywREuqm/xMqcgDNDppYRoCGDt40aaZQIEKBS79LytUDN/DHf0Ew=="], - "@csstools/postcss-media-minmax": ["@csstools/postcss-media-minmax@3.0.1", "", { "dependencies": { "@csstools/css-calc": "^3.1.1", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0", "@csstools/media-query-list-parser": "^5.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-I+CrmZt23fyejMItpLQFOg9gPXkDBBDjTqRT0UxCTZlYZfGrzZn4z+2kbXLRwDfR59OK8zaf26M4kwYwG0e1MA=="], - - "@csstools/postcss-media-queries-aspect-ratio-number-values": ["@csstools/postcss-media-queries-aspect-ratio-number-values@4.0.0", "", { "dependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0", "@csstools/media-query-list-parser": "^5.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-FDdC3lbrj8Vr0SkGIcSLTcRB7ApG6nlJFxOxkEF2C5hIZC1jtgjISFSGn/WjFdVkn8Dqe+Vx9QXI3axS2w1XHw=="], - - "@csstools/postcss-mixins": ["@csstools/postcss-mixins@1.0.0", "", { "dependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-rz6qjT2w9L3k65jGc2dX+3oGiSrYQ70EZPDrINSmSVoVys7lLBFH0tvEa8DW2sr9cbRVD/W+1sy8+7bfu0JUfg=="], - - "@csstools/postcss-nested-calc": ["@csstools/postcss-nested-calc@5.0.0", "", { "dependencies": { "@csstools/utilities": "^3.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-aPSw8P60e/i9BEfugauhikBqgjiwXcw3I9o4vXs+hktl4NSTgZRI0QHimxk9mst8N01A2TKDBxOln3mssRxiHQ=="], - - "@csstools/postcss-normalize-display-values": ["@csstools/postcss-normalize-display-values@5.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-FcbEmoxDEGYvm2W3rQzVzcuo66+dDJjzzVDs+QwRmZLHYofGmMGwIKPqzF86/YW+euMDa7sh1xjWDvz/fzByZQ=="], - - "@csstools/postcss-oklab-function": ["@csstools/postcss-oklab-function@5.0.2", "", { "dependencies": { "@csstools/css-color-parser": "^4.0.2", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0", "@csstools/postcss-progressive-custom-properties": "^5.0.0", "@csstools/utilities": "^3.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-3d/Wcnp2uW6Io0Tajl0croeUo46gwOVQI9N32PjA/HVQo6z1iL7yp19Gp+6e5E5CDKGpW7U822MsDVo2XK1z0Q=="], - - "@csstools/postcss-position-area-property": ["@csstools/postcss-position-area-property@2.0.0", "", { "peerDependencies": { "postcss": "^8.4" } }, "sha512-TeEfzsJGB23Syv7yCm8AHCD2XTFujdjr9YYu9ebH64vnfCEvY4BG319jXAYSlNlf3Yc9PNJ6WnkDkUF5XVgSKQ=="], - - "@csstools/postcss-progressive-custom-properties": ["@csstools/postcss-progressive-custom-properties@5.0.0", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-NsJoZ89rxmDrUsITf8QIk5w+lQZQ8Xw5K6cLFG+cfiffsLYHb3zcbOOrHLetGl1WIhjWWQ4Cr8MMrg46Q+oACg=="], - - "@csstools/postcss-property-rule-prelude-list": ["@csstools/postcss-property-rule-prelude-list@2.0.0", "", { "dependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-qcMAkc9AhpzHgmQCD8hoJgGYifcOAxd1exXjjxilMM6euwRE619xDa4UsKBCv/v4g+sS63sd6c29LPM8s2ylSQ=="], - - "@csstools/postcss-random-function": ["@csstools/postcss-random-function@3.0.1", "", { "dependencies": { "@csstools/css-calc": "^3.1.1", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-SvKGfmj+WHfn4bWHaBYlkXDyU3SlA3fL8aaYZ8Op6M8tunNf3iV9uZyZZGWMCbDw0sGeoTmYZW9nmKN8Qi/ctg=="], - - "@csstools/postcss-relative-color-syntax": ["@csstools/postcss-relative-color-syntax@4.0.2", "", { "dependencies": { "@csstools/css-color-parser": "^4.0.2", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0", "@csstools/postcss-progressive-custom-properties": "^5.0.0", "@csstools/utilities": "^3.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-HaMN+qMURinllszbps2AhXKaLeibg/2VW6FriYDrqE58ji82+z2S3/eLloywVOY8BQCJ9lZMdy6TcRQNbn9u3w=="], - - "@csstools/postcss-scope-pseudo-class": ["@csstools/postcss-scope-pseudo-class@5.0.0", "", { "dependencies": { "postcss-selector-parser": "^7.1.1" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-kBrBFJcAji3MSHS4qQIihPvJfJC5xCabXLbejqDMiQi+86HD4eMBiTayAo46Urg7tlEmZZQFymFiJt+GH6nvXw=="], - - "@csstools/postcss-sign-functions": ["@csstools/postcss-sign-functions@2.0.1", "", { "dependencies": { "@csstools/css-calc": "^3.1.1", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-C3br0qcHJkQ0qSGUBnDJHXQdO8XObnCpGwai5m1L2tv2nCjt0vRHG6A9aVCQHvh08OqHNM2ty1dYDNNXV99YAQ=="], - - "@csstools/postcss-stepped-value-functions": ["@csstools/postcss-stepped-value-functions@5.0.1", "", { "dependencies": { "@csstools/css-calc": "^3.1.1", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-vZf7zPzRb7xIi2o5Z9q6wyeEAjoRCg74O2QvYxmQgxYO5V5cdBv4phgJDyOAOP3JHy4abQlm2YaEUS3gtGQo0g=="], - - "@csstools/postcss-syntax-descriptor-syntax-production": ["@csstools/postcss-syntax-descriptor-syntax-production@2.0.0", "", { "dependencies": { "@csstools/css-tokenizer": "^4.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-elYcbdiBXAkPqvojB9kIBRuHY6htUhjSITtFQ+XiXnt6SvZCbNGxQmaaw6uZ7SPHu/+i/XVjzIt09/1k3SIerQ=="], - - "@csstools/postcss-system-ui-font-family": ["@csstools/postcss-system-ui-font-family@2.0.0", "", { "dependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-FyGZCgchFImFyiHS2x3rD5trAqatf/x23veBLTIgbaqyFfna6RNBD+Qf8HRSjt6HGMXOLhAjxJ3OoZg0bbn7Qw=="], - - "@csstools/postcss-text-decoration-shorthand": ["@csstools/postcss-text-decoration-shorthand@5.0.3", "", { "dependencies": { "@csstools/color-helpers": "^6.0.2", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-62fjggvIM1YYfDJPcErMUDkEZB6CByG8neTJqexnZe1hRBgCjD4dnXDLoCSSurjs1LzjBq6irFDpDaOvDZfrlw=="], - - "@csstools/postcss-trigonometric-functions": ["@csstools/postcss-trigonometric-functions@5.0.1", "", { "dependencies": { "@csstools/css-calc": "^3.1.1", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-e8me32Mhl8JeBnxVJgsQUYpV4Md4KiyvpILpQlaY/eK1Gwdb04kasiTTswPQ5q7Z8+FppJZ2Z4d8HRfn6rjD3w=="], - - "@csstools/postcss-unset-value": ["@csstools/postcss-unset-value@5.0.0", "", { "peerDependencies": { "postcss": "^8.4" } }, "sha512-EoO54sS2KCIfesvHyFYAW99RtzwHdgaJzhl7cqKZSaMYKZv3fXSOehDjAQx8WZBKn1JrMd7xJJI1T1BxPF7/jA=="], - - "@csstools/selector-resolve-nested": ["@csstools/selector-resolve-nested@4.0.0", "", { "peerDependencies": { "postcss-selector-parser": "^7.1.1" } }, "sha512-9vAPxmp+Dx3wQBIUwc1v7Mdisw1kbbaGqXUM8QLTgWg7SoPGYtXBsMXvsFs/0Bn5yoFhcktzxNZGNaUt0VjgjA=="], - - "@csstools/selector-specificity": ["@csstools/selector-specificity@6.0.0", "", { "peerDependencies": { "postcss-selector-parser": "^7.1.1" } }, "sha512-4sSgl78OtOXEX/2d++8A83zHNTgwCJMaR24FvsYL7Uf/VS8HZk9PTwR51elTbGqMuwH3szLvvOXEaVnqn0Z3zA=="], - - "@csstools/utilities": ["@csstools/utilities@3.0.0", "", { "peerDependencies": { "postcss": "^8.4" } }, "sha512-etDqA/4jYvOGBM6yfKCOsEXfH96BKztZdgGmGqKi2xHnDe0ILIBraRspwgYatJH9JsCZ5HCGoCst8w18EKOAdg=="], + "@csstools/selector-specificity": ["@csstools/selector-specificity@2.2.0", "", { "peerDependencies": { "postcss-selector-parser": "^6.0.10" } }, "sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw=="], "@dabh/diagnostics": ["@dabh/diagnostics@2.0.3", "", { "dependencies": { "colorspace": "1.1.x", "enabled": "2.0.x", "kuler": "^2.0.0" } }, "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA=="], - "@dicebear/adventurer": ["@dicebear/adventurer@9.4.2", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-jqYp834ZmGDA9HBBDQAdgF1O2UTCwHF4vVrktXWa2Dppp1JczPL5HnVOWsjtrLmXNn61Wd6OLmBb2e6rhzp3ig=="], + "@dicebear/adventurer": ["@dicebear/adventurer@9.2.4", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-Xvboay3VH1qe7lH17T+bA3qPawf5EjccssDiyhCX/VT0P21c65JyjTIUJV36Nsv08HKeyDscyP0kgt9nPTRKvA=="], - "@dicebear/adventurer-neutral": ["@dicebear/adventurer-neutral@9.4.2", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-5xgkG/mNL4j3Q4SJGQLBU/KnU90tng8Ze5ofThD+55wi0oeY/nSAUowg6UFCmHrktjifj/MEx3CQqbpcPWtfIA=="], + "@dicebear/adventurer-neutral": ["@dicebear/adventurer-neutral@9.2.4", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-I9IrB4ZYbUHSOUpWoUbfX3vG8FrjcW8htoQ4bEOR7TYOKKE11Mo1nrGMuHZ7GPfwN0CQeK1YVJhWqLTmtYn7Pg=="], - "@dicebear/avataaars": ["@dicebear/avataaars@9.4.2", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-3x9jKFkOkFSPmpTbt9xvhiU2E1GX7beCSsX0tXRUShj8x6+5Ks9yBRT1VlkySbnXrZ/GglADGg7vJ/D2uIx1Yw=="], + "@dicebear/avataaars": ["@dicebear/avataaars@9.2.4", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-QKNBtA/1QGEzR+JjS4XQyrFHYGbzdOp0oa6gjhGhUDrMegDFS8uyjdRfDQsFTebVkyLWjgBQKZEiDqKqHptB6A=="], - "@dicebear/avataaars-neutral": ["@dicebear/avataaars-neutral@9.4.2", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-/eNrp0YCNJRwQXqOloLm1+3Ss2C+pMpUQIGkbEnGsP1UK+13Ge80ggDDof1HpdqvG9HAZcKa7hnbG/0HSwyDSw=="], + "@dicebear/avataaars-neutral": ["@dicebear/avataaars-neutral@9.2.4", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-HtBvA7elRv50QTOOsBdtYB1GVimCpGEDlDgWsu1snL5Z3d1+3dIESoXQd3mXVvKTVT8Z9ciA4TEaF09WfxDjAA=="], - "@dicebear/big-ears": ["@dicebear/big-ears@9.4.2", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-mNfz3ppNA7UBq0IO3nXCiV5pFPG7c1DfzRB0foNU2Wo1XXT8FIcSY2BvDlYqorZTOUOz7dHb0vx06hqvG0HP5w=="], + "@dicebear/big-ears": ["@dicebear/big-ears@9.2.4", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-U33tbh7Io6wG6ViUMN5fkWPER7hPKMaPPaYgafaYQlCT4E7QPKF2u8X1XGag3jCKm0uf4SLXfuZ8v+YONcHmNQ=="], - "@dicebear/big-ears-neutral": ["@dicebear/big-ears-neutral@9.4.2", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-M8Ozmzza4eY4hpLOYULgJxMYmBA0CsBnrE15/xw6LZkEREXnrX5z0NJsf8hUfdyF6BWZ+RBgzoiav32DAC5zcg=="], + "@dicebear/big-ears-neutral": ["@dicebear/big-ears-neutral@9.2.4", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-pPjYu80zMFl43A9sa5+tAKPkhp4n9nd7eN878IOrA1HAowh/XePh5JN8PTkNFS9eM+rnN9m8WX08XYFe30kLYw=="], - "@dicebear/big-smile": ["@dicebear/big-smile@9.4.2", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-hmT5i7rcPPhStjZyg28pbIhdTnnMBzK3RObI0vKCpY30EFrzaPkkdDL6Ck5fAFBdvDIW1EpOJkenyR0XPmhgbQ=="], + "@dicebear/big-smile": ["@dicebear/big-smile@9.2.4", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-zeEfXOOXy7j9tfkPLzfQdLBPyQsctBetTdEfKRArc1k3RUliNPxfJG9j88+cXQC6GXrVW2pcT2X50NSPtugCFQ=="], - "@dicebear/bottts": ["@dicebear/bottts@9.4.2", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-tsx+dII7EFUCVA8URj66G1GqORCCVduCAx4dY2prEY2IeFianVpkntXuFsWZ9BBGx1NZFndvDith5oTwKMQPbQ=="], + "@dicebear/bottts": ["@dicebear/bottts@9.2.4", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-4CTqrnVg+NQm6lZ4UuCJish8gGWe8EqSJrzvHQRO5TEyAKjYxbTdVqejpkycG1xkawha4FfxsYgtlSx7UwoVMw=="], - "@dicebear/bottts-neutral": ["@dicebear/bottts-neutral@9.4.2", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-kFNwWt6j+gzZ5n5Pz7WVwePubREAQOF8ZwWA9ztwVYDVMLnOChWbAofy5FED4j5md2MXFH2EgLCFCMr5K2BmIA=="], + "@dicebear/bottts-neutral": ["@dicebear/bottts-neutral@9.2.4", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-eMVdofdD/udHsKIaeWEXShDRtiwk7vp4FjY7l0f79vIzfhkIsXKEhPcnvHKOl/yoArlDVS3Uhgjj0crWTO9RJA=="], - "@dicebear/collection": ["@dicebear/collection@9.4.2", "", { "dependencies": { "@dicebear/adventurer": "9.4.2", "@dicebear/adventurer-neutral": "9.4.2", "@dicebear/avataaars": "9.4.2", "@dicebear/avataaars-neutral": "9.4.2", "@dicebear/big-ears": "9.4.2", "@dicebear/big-ears-neutral": "9.4.2", "@dicebear/big-smile": "9.4.2", "@dicebear/bottts": "9.4.2", "@dicebear/bottts-neutral": "9.4.2", "@dicebear/croodles": "9.4.2", "@dicebear/croodles-neutral": "9.4.2", "@dicebear/dylan": "9.4.2", "@dicebear/fun-emoji": "9.4.2", "@dicebear/glass": "9.4.2", "@dicebear/icons": "9.4.2", "@dicebear/identicon": "9.4.2", "@dicebear/initials": "9.4.2", "@dicebear/lorelei": "9.4.2", "@dicebear/lorelei-neutral": "9.4.2", "@dicebear/micah": "9.4.2", "@dicebear/miniavs": "9.4.2", "@dicebear/notionists": "9.4.2", "@dicebear/notionists-neutral": "9.4.2", "@dicebear/open-peeps": "9.4.2", "@dicebear/personas": "9.4.2", "@dicebear/pixel-art": "9.4.2", "@dicebear/pixel-art-neutral": "9.4.2", "@dicebear/rings": "9.4.2", "@dicebear/shapes": "9.4.2", "@dicebear/thumbs": "9.4.2", "@dicebear/toon-head": "9.4.2" }, "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-KArubv7if8H7j9sIfpDK2hJJqrdNVR5zMPAMOSpIU2JPyXx8TC9o5wsmXb8il5wOHgaS9Q/cla7jUNIiDD7Gsg=="], + "@dicebear/collection": ["@dicebear/collection@9.2.4", "", { "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" }, "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-I1wCUp0yu5qSIeMQHmDYXQIXKkKjcja/SYBxppPkYFXpR2alxb0k9/swFDdMbkY6a1c9AT1kI1y+Pg6ywQ2rTA=="], - "@dicebear/core": ["@dicebear/core@9.4.2", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-MF0042+Z3s8PGZKZLySfhft28bUa3B1iq0e5NSjCvY8gfMi5aIH/iRJGRJa1N9Jz1BNkxYb4yvJ/N9KO8Z6Y+w=="], + "@dicebear/core": ["@dicebear/core@9.2.4", "", { "dependencies": { "@types/json-schema": "^7.0.11" } }, "sha512-hz6zArEcUwkZzGOSJkWICrvqnEZY7BKeiq9rqKzVJIc1tRVv0MkR0FGvIxSvXiK9TTIgKwu656xCWAGAl6oh+w=="], - "@dicebear/croodles": ["@dicebear/croodles@9.4.2", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-6VoO0JviIf7dKKMBTL/SMXxWhnXHaZuzufX90G0nXxS77ELG1YkGNMaZzawizN4C09Gbya2gJkozqrWiJN/aGw=="], + "@dicebear/croodles": ["@dicebear/croodles@9.2.4", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-CqT0NgVfm+5kd+VnjGY4WECNFeOrj5p7GCPTSEA7tCuN72dMQOX47P9KioD3wbExXYrIlJgOcxNrQeb/FMGc3A=="], - "@dicebear/croodles-neutral": ["@dicebear/croodles-neutral@9.4.2", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-oG5IeUdtiYshQ89gkAVcl5w3xAEi5UZX2fTzIyelpBPCG176l7VuuFzlxi2umnB3E6LVHYy06DXvUo/p+rXB2Q=="], + "@dicebear/croodles-neutral": ["@dicebear/croodles-neutral@9.2.4", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-8vAS9lIEKffSUVx256GSRAlisB8oMX38UcPWw72venO/nitLVsyZ6hZ3V7eBdII0Onrjqw1RDndslQODbVcpTw=="], - "@dicebear/dylan": ["@dicebear/dylan@9.4.2", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-1vQvRu9x9DrwFxhFaIU2rf0EUL04yDTbAt7fHyAjM0mEsKzTD4mRNf95tCRuavCoW6W48u7A/OY6jyIub6kxLQ=="], + "@dicebear/dylan": ["@dicebear/dylan@9.2.4", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-tiih1358djAq0jDDzmW3N3S4C3ynC2yn4hhlTAq/MaUAQtAi47QxdHdFGdxH0HBMZKqA4ThLdVk3yVgN4xsukg=="], - "@dicebear/fun-emoji": ["@dicebear/fun-emoji@9.4.2", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-kqB6LPkdYCdEU/mwbyz34xLzoNUKL6ARcoo3fr5ASq9D6ZE07qIKybC3xv5+CPz7VmspJ1Q3c/VVWVMDRP7Twg=="], + "@dicebear/fun-emoji": ["@dicebear/fun-emoji@9.2.4", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-Od729skczse1HvHekgEFv+mSuJKMC4sl5hENGi/izYNe6DZDqJrrD0trkGT/IVh/SLXUFbq1ZFY9I2LoUGzFZg=="], - "@dicebear/glass": ["@dicebear/glass@9.4.2", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-z5qUogHQ1b6UJ2zCqT848mU2U9DKbVDhiX6GPDjD7tYLisCCJVisH9p6WyNdHvflUd4SHkA6gRqVJIh2v2HnTA=="], + "@dicebear/glass": ["@dicebear/glass@9.2.4", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-5lxbJode1t99eoIIgW0iwZMoZU4jNMJv/6vbsgYUhAslYFX5zP0jVRscksFuo89TTtS7YKqRqZAL3eNhz4bTDw=="], - "@dicebear/icons": ["@dicebear/icons@9.4.2", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-QSMMz0NA03ypSGhXC8HQX8FSj8lYT+/5yqH+/N03OH2IjL0q7wwGZ7nqsrtlRp76O5WqMTwGfSbTUUYPjFr+Xw=="], + "@dicebear/icons": ["@dicebear/icons@9.2.4", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-bRsK1qj8u9Z76xs8XhXlgVr/oHh68tsHTJ/1xtkX9DeTQTSamo2tS26+r231IHu+oW3mePtFnwzdG9LqEPRd4A=="], - "@dicebear/identicon": ["@dicebear/identicon@9.4.2", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-JVDSmZsv11mSWqwAktK5x9Bslht2xY3TFUn8xzu6slAYe1Z7hEXZ76eb+UJ6F4qEzdwZ7xPWzAS6Nb0Y3A0pww=="], + "@dicebear/identicon": ["@dicebear/identicon@9.2.4", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-R9nw/E8fbu9HltHOqI9iL/o9i7zM+2QauXWMreQyERc39oGR9qXiwgBxsfYGcIS4C85xPyuL5B3I2RXrLBlJPg=="], - "@dicebear/initials": ["@dicebear/initials@9.4.2", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-yePuIUasmwtl9IrtB6rEzE/zb5fImKP/neW0CdcTC2MwLgMuP1GLHEGRgg1zI8exIh+PMv1YdLGyyUuRTE2Qpw=="], + "@dicebear/initials": ["@dicebear/initials@9.2.4", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-4SzHG5WoQZl1TGcpEZR4bdsSkUVqwNQCOwWSPAoBJa3BNxbVsvL08LF7I97BMgrCoknWZjQHUYt05amwTPTKtg=="], - "@dicebear/lorelei": ["@dicebear/lorelei@9.4.2", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-YMv6vnriW6VLFDsreKuOnUFFno6SRe7+7X7R7zPY0rZ+MaHX9V3jcioIG+1PSjIHEDfOLUHpr5vd1JBWv8y7UA=="], + "@dicebear/lorelei": ["@dicebear/lorelei@9.2.4", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-eS4mPYUgDpo89HvyFAx/kgqSSKh8W4zlUA8QJeIUCWTB0WpQmeqkSgIyUJjGDYSrIujWi+zEhhckksM5EwW0Dg=="], - "@dicebear/lorelei-neutral": ["@dicebear/lorelei-neutral@9.4.2", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-yspanTthA5vh6iCdeLzn6xZ4yYMYRcfcxblcgSvHTF1ut0bjAXtw5SXzZ6aJTrJWiHkzYOQuTOR6GVYiW80Q7w=="], + "@dicebear/lorelei-neutral": ["@dicebear/lorelei-neutral@9.2.4", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-bWq2/GonbcJULtT+B/MGcM2UnA7kBQoH+INw8/oW83WI3GNTZ6qEwe3/W4QnCgtSOhUsuwuiSULguAFyvtkOZQ=="], - "@dicebear/micah": ["@dicebear/micah@9.4.2", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-e4D3W/OlChSsLo7Llwsy0J18vk0azJqF/uFoY+EKACCNHBc1HGNsqVvu2CTf+OWOA8wTyAK6UkjBN5p01r7D+g=="], + "@dicebear/micah": ["@dicebear/micah@9.2.4", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-XNWJ8Mx+pncIV8Ye0XYc/VkMiax8kTxcP3hLTC5vmELQyMSLXzg/9SdpI+W/tCQghtPZRYTT3JdY9oU9IUlP2g=="], - "@dicebear/miniavs": ["@dicebear/miniavs@9.4.2", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-wLwyFNNUnDRd3BbhSBhXR0XEpX8sG0/xDA5M/OkDoapLqZnnI48YLUSDd2N5QTAVMmcSEuZOYxkcnj7WW79vlg=="], + "@dicebear/miniavs": ["@dicebear/miniavs@9.2.4", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-k7IYTAHE/4jSO6boMBRrNlqPT3bh7PLFM1atfe0nOeCDwmz/qJUBP3HdONajbf3fmo8f2IZYhELrNWTOE7Ox3Q=="], - "@dicebear/notionists": ["@dicebear/notionists@9.4.2", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-ZCySq+nxcD/x4xyYgytcj2N9uY3gxrL+qpnmOdp2BdA221KacVrxlsUPpIgEMqxS2rMmBQXfxg129Pzn4ycIpA=="], + "@dicebear/notionists": ["@dicebear/notionists@9.2.4", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-zcvpAJ93EfC0xQffaPZQuJPShwPhnu9aTcoPsaYGmw0oEDLcv2XYmDhUUdX84QYCn6LtCZH053rHLVazRW+OGw=="], - "@dicebear/notionists-neutral": ["@dicebear/notionists-neutral@9.4.2", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-AyD9kEfVxQUwDGf4Op059gVmYIOAkTKg3dtE9h9mEKP7zl/kMy5B67BFFOo7sB0mXCjzAegZ6ekGU02E8+hIHw=="], + "@dicebear/notionists-neutral": ["@dicebear/notionists-neutral@9.2.4", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-fskWzBVxQzJhCKqY24DGZbYHSBaauoRa1DgXM7+7xBuksH7mfbTmZTvnUAsAqJYBkla8IPb4ERKduDWtlWYYjQ=="], - "@dicebear/open-peeps": ["@dicebear/open-peeps@9.4.2", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-i01tLgtp2g937T81sVeAOVlqsCtiTck/Kw20g7hN80+7xrXjOUepz2HPLy3HeiMjwjMGRy5o54kSd0/8Ht4Dqg=="], + "@dicebear/open-peeps": ["@dicebear/open-peeps@9.2.4", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-s6nwdjXFsplqEI7imlsel4Gt6kFVJm6YIgtZSpry0UdwDoxUUudei5bn957j9lXwVpVUcRjJW+TuEKztYjXkKQ=="], - "@dicebear/personas": ["@dicebear/personas@9.4.2", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-NJlkvI5F5gugt6t2+7QrYNTwQC7+4IQZS3vG0dYk2BncxOHax0BuLovdSdiAesTL4ZkytFYIydWmKmV2/xcUwg=="], + "@dicebear/personas": ["@dicebear/personas@9.2.4", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-JNim8RfZYwb0MfxW6DLVfvreCFIevQg+V225Xe5tDfbFgbcYEp4OU/KaiqqO2476OBjCw7i7/8USbv2acBhjwA=="], - "@dicebear/pixel-art": ["@dicebear/pixel-art@9.4.2", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-peHf7oKICDgBZ8dUyj+txPnS7VZEWgvKE+xW4mNQqBt6dYZIjmva2shOVHn0b1JU+FDxMx3uIkWVixKdUq4WGg=="], + "@dicebear/pixel-art": ["@dicebear/pixel-art@9.2.4", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-4Ao45asieswUdlCTBZqcoF/0zHR3OWUWB0Mvhlu9b1Fbc6IlPBiOfx2vsp6bnVGVnMag58tJLecx2omeXdECBQ=="], - "@dicebear/pixel-art-neutral": ["@dicebear/pixel-art-neutral@9.4.2", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-9e9Lz554uQvWaXV2P17ss+hPa6rTyuAKBtB8zk8ECjHiZzIl61N/KcTVLZ4dILVZwj7gYriaLo16QEqvL2GJCg=="], + "@dicebear/pixel-art-neutral": ["@dicebear/pixel-art-neutral@9.2.4", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-ZITPLD1cPN4GjKkhWi80s7e5dcbXy34ijWlvmxbc4eb/V7fZSsyRa9EDUW3QStpo+xrCJLcLR+3RBE5iz0PC/A=="], - "@dicebear/rings": ["@dicebear/rings@9.4.2", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-Pc3ymWrRDQPJFNrbbLt7RJrzGvUuuxUiDkrfLhoVE+B6mZWEL1PC78DPbS1yUWYLErJOpJuM2GSwXmTbVjWf+g=="], + "@dicebear/rings": ["@dicebear/rings@9.2.4", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-teZxELYyV2ogzgb5Mvtn/rHptT0HXo9SjUGS4A52mOwhIdHSGGU71MqA1YUzfae9yJThsw6K7Z9kzuY2LlZZHA=="], - "@dicebear/shapes": ["@dicebear/shapes@9.4.2", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-AFL6jAaiLztvcqyq+ds+lWZu6Vbp3PlGWhJeJRm842jxtiluJpl6r4f6nUXP2fdMz7MNpDzXfLooQK9E04NbUQ=="], + "@dicebear/shapes": ["@dicebear/shapes@9.2.4", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-MhK9ZdFm1wUnH4zWeKPRMZ98UyApolf5OLzhCywfu38tRN6RVbwtBRHc/42ZwoN1JU1JgXr7hzjYucMqISHtbA=="], - "@dicebear/thumbs": ["@dicebear/thumbs@9.4.2", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-ccWvDBqbkWS5uzHbsg5L6uML6vBfX7jT3J3jHCQksvz8haHItxTK02w+6e1UavZUsvza4lG5X/XY3eji3siJ4Q=="], - - "@dicebear/toon-head": ["@dicebear/toon-head@9.4.2", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-lwFeSXyAnaKnCfMt9TiJwnD1cXQUGkey/0h6i/+4TVHVMCz5/Ri5u1ynovPNHy1SnBf858QwoXHkxilGLwQX/g=="], + "@dicebear/thumbs": ["@dicebear/thumbs@9.2.4", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-EL4sMqv9p2+1Xy3d8e8UxyeKZV2+cgt3X2x2RTRzEOIIhobtkL8u6lJxmJbiGbpVtVALmrt5e7gjmwqpryYDpg=="], "@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="], @@ -1143,57 +1063,55 @@ "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ=="], - "@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="], + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.1", "", { "os": "android", "cpu": "arm" }, "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q=="], - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="], + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.1", "", { "os": "android", "cpu": "arm64" }, "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA=="], - "@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="], + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.1", "", { "os": "android", "cpu": "x64" }, "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw=="], - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="], + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ=="], - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="], + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA=="], - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="], + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A=="], - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="], + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww=="], - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="], + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.1", "", { "os": "linux", "cpu": "arm" }, "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ=="], - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="], + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ=="], - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="], + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ=="], - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="], + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.1", "", { "os": "linux", "cpu": "none" }, "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg=="], - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="], + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.1", "", { "os": "linux", "cpu": "none" }, "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg=="], - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="], + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg=="], - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="], + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.1", "", { "os": "linux", "cpu": "none" }, "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ=="], - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="], + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ=="], - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="], + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.1", "", { "os": "linux", "cpu": "x64" }, "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA=="], - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="], + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.1", "", { "os": "none", "cpu": "arm64" }, "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g=="], - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="], + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.1", "", { "os": "none", "cpu": "x64" }, "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA=="], - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="], + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg=="], - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="], + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw=="], - "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="], + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg=="], - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="], + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ=="], - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="], + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A=="], - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="], - - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="], + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.1", "", { "os": "win32", "cpu": "x64" }, "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg=="], "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="], @@ -1207,7 +1125,7 @@ "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], - "@eslint/eslintrc": ["@eslint/eslintrc@3.3.5", "", { "dependencies": { "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" } }, "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg=="], + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="], "@eslint/js": ["@eslint/js@9.39.1", "", {}, "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw=="], @@ -1313,19 +1231,17 @@ "@floating-ui/utils": ["@floating-ui/utils@0.2.8", "", {}, "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig=="], - "@google/genai": ["@google/genai@1.44.0", "", { "dependencies": { "google-auth-library": "^10.3.0", "p-retry": "^4.6.2", "protobufjs": "^7.5.4", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.25.2" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-kRt9ZtuXmz+tLlcNntN/VV4LRdpl6ZOu5B1KbfNgfR65db15O6sUQcwnwLka8sT/V6qysD93fWrgJHF2L7dA9A=="], - "@google/generative-ai": ["@google/generative-ai@0.24.0", "", {}, "sha512-fnEITCGEB7NdX0BhoYZ/cq/7WPZ1QS5IzJJfC3Tg/OwkvBetMiVJciyaan297OvE4B9Jg1xvo0zIazX/9sGu1Q=="], + "@googleapis/youtube": ["@googleapis/youtube@20.0.0", "", { "dependencies": { "googleapis-common": "^7.0.0" } }, "sha512-wdt1J0JoKYhvpoS2XIRHX0g/9ul/B0fQeeJAhuuBIdYINuuLt6/oZYZZCBmkuhtkA3IllXgqgAXOjLtLRAnR2g=="], + "@grpc/grpc-js": ["@grpc/grpc-js@1.9.15", "", { "dependencies": { "@grpc/proto-loader": "^0.7.8", "@types/node": ">=12.12.47" } }, "sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ=="], "@grpc/proto-loader": ["@grpc/proto-loader@0.7.13", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.2.5", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw=="], - "@happy-dom/jest-environment": ["@happy-dom/jest-environment@20.8.3", "", { "dependencies": { "happy-dom": "^20.8.3" }, "peerDependencies": { "@jest/environment": ">=25.0.0", "@jest/fake-timers": ">=25.0.0", "@jest/types": ">=25.0.0", "jest-mock": ">=25.0.0", "jest-util": ">=25.0.0" } }, "sha512-VMOfNvF7UPPHIc7SUrFqGXqJrkONYX6Vd0ZXblmjgb1JA2RFnrc1KiVodzG0c7IT5Q0jfA0CQjvlqWjQ/BYtkQ=="], - "@headlessui/react": ["@headlessui/react@2.2.4", "", { "dependencies": { "@floating-ui/react": "^0.26.16", "@react-aria/focus": "^3.20.2", "@react-aria/interactions": "^3.25.0", "@tanstack/react-virtual": "^3.13.9", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, "sha512-lz+OGcAH1dK93rgSMzXmm1qKOJkBUqZf1L4M8TWLNplftQD3IkoEDdUFNfAn4ylsN6WOTVtWaLmvmaHOUk1dTA=="], - "@hono/node-server": ["@hono/node-server@1.19.11", "", { "peerDependencies": { "hono": "^4" } }, "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g=="], + "@hono/node-server": ["@hono/node-server@1.19.7", "", { "peerDependencies": { "hono": "^4" } }, "sha512-vUcD0uauS7EU2caukW8z5lJKtoGMokxNbJtBiwHgpqxEXokaHCBkQUmCHhjFB1VUTWdqj25QoMkMKzgjq+uhrw=="], "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], @@ -1335,10 +1251,6 @@ "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], - "@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="], - - "@iconify/utils": ["@iconify/utils@3.1.0", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "mlly": "^1.8.0" } }, "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw=="], - "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="], "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.0.4" }, "os": "darwin", "cpu": "x64" }, "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q=="], @@ -1403,7 +1315,7 @@ "@jest/expect-utils": ["@jest/expect-utils@29.7.0", "", { "dependencies": { "jest-get-type": "^29.6.3" } }, "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA=="], - "@jest/fake-timers": ["@jest/fake-timers@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@sinonjs/fake-timers": "^13.0.0", "@types/node": "*", "jest-message-util": "30.2.0", "jest-mock": "30.2.0", "jest-util": "30.2.0" } }, "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw=="], + "@jest/fake-timers": ["@jest/fake-timers@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", "jest-message-util": "^29.7.0", "jest-mock": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ=="], "@jest/get-type": ["@jest/get-type@30.1.0", "", {}, "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA=="], @@ -1447,7 +1359,7 @@ "@langchain/aws": ["@langchain/aws@0.1.15", "", { "dependencies": { "@aws-sdk/client-bedrock-agent-runtime": "^3.755.0", "@aws-sdk/client-bedrock-runtime": "^3.840.0", "@aws-sdk/client-kendra": "^3.750.0", "@aws-sdk/credential-provider-node": "^3.750.0" }, "peerDependencies": { "@langchain/core": ">=0.3.58 <0.4.0" } }, "sha512-oyOMhTHP0rxdSCVI/g5KXYCOs9Kq/FpXMZbOk1JSIUoaIzUg4p6d98lsHu7erW//8NSaT+SX09QRbVDAgt7pNA=="], - "@langchain/core": ["@langchain/core@0.3.80", "", { "dependencies": { "@cfworker/json-schema": "^4.0.2", "ansi-styles": "^5.0.0", "camelcase": "6", "decamelize": "1.2.0", "js-tiktoken": "^1.0.12", "langsmith": "^0.3.67", "mustache": "^4.2.0", "p-queue": "^6.6.2", "p-retry": "4", "uuid": "^10.0.0", "zod": "^3.25.32", "zod-to-json-schema": "^3.22.3" } }, "sha512-vcJDV2vk1AlCwSh3aBm/urQ1ZrlXFFBocv11bz/NBUfLWD5/UDNMzwPdaAd2dKvNmTWa9FM2lirLU3+JCf4cRA=="], + "@langchain/core": ["@langchain/core@0.3.79", "", { "dependencies": { "@cfworker/json-schema": "^4.0.2", "ansi-styles": "^5.0.0", "camelcase": "6", "decamelize": "1.2.0", "js-tiktoken": "^1.0.12", "langsmith": "^0.3.67", "mustache": "^4.2.0", "p-queue": "^6.6.2", "p-retry": "4", "uuid": "^10.0.0", "zod": "^3.25.32", "zod-to-json-schema": "^3.22.3" } }, "sha512-ZLAs5YMM5N2UXN3kExMglltJrKKoW7hs3KMZFlXUnD7a5DFKBYxPFMeXA4rT+uvTxuJRZPCYX0JKI5BhyAWx4A=="], "@langchain/deepseek": ["@langchain/deepseek@0.0.2", "", { "dependencies": { "@langchain/openai": "^0.5.5" }, "peerDependencies": { "@langchain/core": ">=0.3.58 <0.4.0" } }, "sha512-u13KbPUXW7uhcybbRzYdRroBgqVUSgG0SJM15c7Etld2yjRQC2c4O/ga9eQZdLh/kaDlQfH/ZITFdjHe77RnGw=="], @@ -1493,7 +1405,7 @@ "@lezer/lr": ["@lezer/lr@1.4.2", "", { "dependencies": { "@lezer/common": "^1.0.0" } }, "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA=="], - "@librechat/agents": ["@librechat/agents@3.1.62", "", { "dependencies": { "@anthropic-ai/sdk": "^0.73.0", "@aws-sdk/client-bedrock-runtime": "^3.1013.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-QBZlJ4C89GmBg9w2qoWOWl1Y1xiRypUtIMBsL6eLPIsdbKHJ+GYO+076rfSD+tMqZB5ZbrxqPWOh+gxEXK1coQ=="], + "@librechat/agents": ["@librechat/agents@3.0.50", "", { "dependencies": { "@langchain/anthropic": "^0.3.26", "@langchain/aws": "^0.1.15", "@langchain/core": "^0.3.79", "@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", "axios": "^1.12.1", "cheerio": "^1.0.0", "dotenv": "^16.4.7", "https-proxy-agent": "^7.0.6", "mathjs": "^15.1.0", "nanoid": "^3.3.7", "openai": "5.8.2" } }, "sha512-oovj3BsP/QoxPbWFAc71Ddplwd9BT8ucfYs+n+kiR37aCWtvxdvL9/XldRYfnaq9boNE324njQJyqc8v8AAPFQ=="], "@librechat/api": ["@librechat/api@workspace:packages/api"], @@ -1509,44 +1421,14 @@ "@mcp-ui/client": ["@mcp-ui/client@5.7.0", "", { "dependencies": { "@modelcontextprotocol/sdk": "*", "@quilted/threads": "^3.1.3", "@r2wc/react-to-web-component": "^2.0.4", "@remote-dom/core": "^1.8.0", "@remote-dom/react": "^1.2.2", "react": "^18.3.1", "react-dom": "^18.3.1" } }, "sha512-+HbPw3VS46WUSWmyJ34ZVnygb81QByA3luR6y0JDbyDZxjYtHw1FcIN7v9WbbE8PrfI0WcuWCSiNOO6sOGbwpQ=="], - "@mermaid-js/parser": ["@mermaid-js/parser@1.0.1", "", { "dependencies": { "langium": "^4.0.0" } }, "sha512-opmV19kN1JsK0T6HhhokHpcVkqKpF+x2pPDKKM2ThHtZAB5F4PROopk0amuVYK5qMrIA4erzpNm8gmPNJgMDxQ=="], - "@microsoft/microsoft-graph-client": ["@microsoft/microsoft-graph-client@3.0.7", "", { "dependencies": { "@babel/runtime": "^7.12.5", "tslib": "^2.2.0" } }, "sha512-/AazAV/F+HK4LIywF9C+NYHcJo038zEnWkteilcxC1FM/uK/4NVGDKGrxx7nNq1ybspAroRKT4I1FHfxQzxkUw=="], "@mistralai/mistralai": ["@mistralai/mistralai@1.10.0", "", { "dependencies": { "zod": "^3.20.0", "zod-to-json-schema": "^3.24.1" } }, "sha512-tdIgWs4Le8vpvPiUEWne6tK0qbVc+jMenujnvTqOjogrJUsCSQhus0tHTU1avDDh5//Rq2dFgP9mWRAdIEoBqg=="], - "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.27.1", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA=="], - - "@monaco-editor/loader": ["@monaco-editor/loader@1.7.0", "", { "dependencies": { "state-local": "^1.0.6" } }, "sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA=="], - - "@monaco-editor/react": ["@monaco-editor/react@4.7.0", "", { "dependencies": { "@monaco-editor/loader": "^1.5.0" }, "peerDependencies": { "monaco-editor": ">= 0.25.0 < 1", "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-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA=="], + "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.25.0", "", { "dependencies": { "@hono/node-server": "^1.19.7", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "jose": "^6.1.1", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.0" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-z0Zhn/LmQ3yz91dEfd5QgS7DpSjA4pk+3z2++zKgn5L6iDFM9QapsVoAQSbKLvlrFsZk9+ru6yHHWNq2lCYJKQ=="], "@mongodb-js/saslprep": ["@mongodb-js/saslprep@1.3.1", "", { "dependencies": { "sparse-bitfield": "^3.0.3" } }, "sha512-6nZrq5kfAz0POWyhljnbWQQJQ5uT8oE2ddX303q1uY0tWsivWKgBDXBBvuFPwOqRRalXJuVO9EjOdVtuhLX0zg=="], - "@napi-rs/canvas": ["@napi-rs/canvas@0.1.96", "", { "optionalDependencies": { "@napi-rs/canvas-android-arm64": "0.1.96", "@napi-rs/canvas-darwin-arm64": "0.1.96", "@napi-rs/canvas-darwin-x64": "0.1.96", "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.96", "@napi-rs/canvas-linux-arm64-gnu": "0.1.96", "@napi-rs/canvas-linux-arm64-musl": "0.1.96", "@napi-rs/canvas-linux-riscv64-gnu": "0.1.96", "@napi-rs/canvas-linux-x64-gnu": "0.1.96", "@napi-rs/canvas-linux-x64-musl": "0.1.96", "@napi-rs/canvas-win32-arm64-msvc": "0.1.96", "@napi-rs/canvas-win32-x64-msvc": "0.1.96" } }, "sha512-6NNmNxvoJKeucVjxaaRUt3La2i5jShgiAbaY3G/72s1Vp3U06XPrAIxkAjBxpDcamEn/t+WJ4OOlGmvILo4/Ew=="], - - "@napi-rs/canvas-android-arm64": ["@napi-rs/canvas-android-arm64@0.1.96", "", { "os": "android", "cpu": "arm64" }, "sha512-ew1sPrN3dGdZ3L4FoohPfnjq0f9/Jk7o+wP7HkQZokcXgIUD6FIyICEWGhMYzv53j63wUcPvZeAwgewX58/egg=="], - - "@napi-rs/canvas-darwin-arm64": ["@napi-rs/canvas-darwin-arm64@0.1.96", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Q/wOXZ5PzTqpdmA5eUOcegCf4Go/zz3aZ5DlzSeDpOjFmfwMKh8EzLAoweQ+mJVagcHQyzoJhaTEnrO68TNyNg=="], - - "@napi-rs/canvas-darwin-x64": ["@napi-rs/canvas-darwin-x64@0.1.96", "", { "os": "darwin", "cpu": "x64" }, "sha512-UrXiQz28tQEvGM1qvyptewOAfmUrrd5+wvi6Rzjj2VprZI8iZ2KIvBD2lTTG1bVF95AbeDeG7PJA0D9sLKaOFA=="], - - "@napi-rs/canvas-linux-arm-gnueabihf": ["@napi-rs/canvas-linux-arm-gnueabihf@0.1.96", "", { "os": "linux", "cpu": "arm" }, "sha512-I90ODxweD8aEP6XKU/NU+biso95MwCtQ2F46dUvhec1HesFi0tq/tAJkYic/1aBSiO/1kGKmSeD1B0duOHhEHQ=="], - - "@napi-rs/canvas-linux-arm64-gnu": ["@napi-rs/canvas-linux-arm64-gnu@0.1.96", "", { "os": "linux", "cpu": "arm64" }, "sha512-Dx/0+RFV++w3PcRy+4xNXkghhXjA5d0Mw1bs95emn5Llinp1vihMaA6WJt3oYv2LAHc36+gnrhIBsPhUyI2SGw=="], - - "@napi-rs/canvas-linux-arm64-musl": ["@napi-rs/canvas-linux-arm64-musl@0.1.96", "", { "os": "linux", "cpu": "arm64" }, "sha512-UvOi7fii3IE2KDfEfhh8m+LpzSRvhGK7o1eho99M2M0HTik11k3GX+2qgVx9EtujN3/bhFFS1kSO3+vPMaJ0Mg=="], - - "@napi-rs/canvas-linux-riscv64-gnu": ["@napi-rs/canvas-linux-riscv64-gnu@0.1.96", "", { "os": "linux", "cpu": "none" }, "sha512-MBSukhGCQ5nRtf9NbFYWOU080yqkZU1PbuH4o1ROvB4CbPl12fchDR35tU83Wz8gWIM9JTn99lBn9DenPIv7Ig=="], - - "@napi-rs/canvas-linux-x64-gnu": ["@napi-rs/canvas-linux-x64-gnu@0.1.96", "", { "os": "linux", "cpu": "x64" }, "sha512-I/ccu2SstyKiV3HIeVzyBIWfrJo8cN7+MSQZPnabewWV6hfJ2nY7Df2WqOHmobBRUw84uGR6zfQHsUEio/m5Vg=="], - - "@napi-rs/canvas-linux-x64-musl": ["@napi-rs/canvas-linux-x64-musl@0.1.96", "", { "os": "linux", "cpu": "x64" }, "sha512-H3uov7qnTl73GDT4h52lAqpJPsl1tIUyNPWJyhQ6gHakohNqqRq3uf80+NEpzcytKGEOENP1wX3yGwZxhjiWEQ=="], - - "@napi-rs/canvas-win32-arm64-msvc": ["@napi-rs/canvas-win32-arm64-msvc@0.1.96", "", { "os": "win32", "cpu": "arm64" }, "sha512-ATp6Y+djOjYtkfV/VRH7CZ8I1MEtkUQBmKUbuWw5zWEHHqfL0cEcInE4Cxgx7zkNAhEdBbnH8HMVrqNp+/gwxA=="], - - "@napi-rs/canvas-win32-x64-msvc": ["@napi-rs/canvas-win32-x64-msvc@0.1.96", "", { "os": "win32", "cpu": "x64" }, "sha512-UYGdTltVd+Z8mcIuoqGmAXXUvwH5CLf2M6mIB5B0/JmX5J041jETjqtSYl7gN+aj3k1by/SG6sS0hAwCqyK7zw=="], - "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="], "@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], @@ -1665,7 +1547,7 @@ "@radix-ui/react-accordion": ["@radix-ui/react-accordion@1.2.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collapsible": "1.1.11", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-l3W5D54emV2ues7jjeG1xcyN7S3jnK3zE2zHqgn0CmMsy9lNJwmgcrmaxS+7ipw15FAivzKNzH3d5EcGoFKw0A=="], - "@radix-ui/react-alert-dialog": ["@radix-ui/react-alert-dialog@1.0.2", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/primitive": "1.0.0", "@radix-ui/react-compose-refs": "1.0.0", "@radix-ui/react-context": "1.0.0", "@radix-ui/react-dialog": "1.0.2", "@radix-ui/react-primitive": "1.0.1", "@radix-ui/react-slot": "1.0.1" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" } }, "sha512-0MtxV53FaEEBOKRgyLnEqHZKKDS5BldQ9oUBsKVXWI5FHbl2jp35qs+0aJET+K5hJDsc40kQUzP7g+wC7tqrqA=="], + "@radix-ui/react-alert-dialog": ["@radix-ui/react-alert-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dialog": "1.1.15", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw=="], "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.0.3", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-primitive": "1.0.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" } }, "sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA=="], @@ -1679,17 +1561,17 @@ "@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], - "@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.0.2", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/primitive": "1.0.0", "@radix-ui/react-compose-refs": "1.0.0", "@radix-ui/react-context": "1.0.0", "@radix-ui/react-dismissable-layer": "1.0.2", "@radix-ui/react-focus-guards": "1.0.0", "@radix-ui/react-focus-scope": "1.0.1", "@radix-ui/react-id": "1.0.0", "@radix-ui/react-portal": "1.0.1", "@radix-ui/react-presence": "1.0.0", "@radix-ui/react-primitive": "1.0.1", "@radix-ui/react-slot": "1.0.1", "@radix-ui/react-use-controllable-state": "1.0.0", "aria-hidden": "^1.1.1", "react-remove-scroll": "2.5.5" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" } }, "sha512-EKxxp2WNSmUPkx4trtWNmZ4/vAYEg7JkAfa1HKBUnaubw9eHzf1Orr9B472lJYaYz327RHDrd4R95fsw7VR8DA=="], + "@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw=="], "@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw=="], - "@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.0.2", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/primitive": "1.0.0", "@radix-ui/react-compose-refs": "1.0.0", "@radix-ui/react-primitive": "1.0.1", "@radix-ui/react-use-callback-ref": "1.0.0", "@radix-ui/react-use-escape-keydown": "1.0.2" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" } }, "sha512-WjJzMrTWROozDqLB0uRWYvj4UuXsM/2L19EmQ3Au+IJWqwvwq9Bwd+P8ivo0Deg9JDPArR1I6MbWNi1CmXsskg=="], + "@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="], "@radix-ui/react-dropdown-menu": ["@radix-ui/react-dropdown-menu@2.1.1", "", { "dependencies": { "@radix-ui/primitive": "1.1.0", "@radix-ui/react-compose-refs": "1.1.0", "@radix-ui/react-context": "1.1.0", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-menu": "2.1.1", "@radix-ui/react-primitive": "2.0.0", "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-y8E+x9fBq9qvteD2Zwa4397pUVhYsh9iq44b5RD5qu1GMJWBCBuVg1hMyItbc6+zH00TxGRqd9Iot4wzf3OoBQ=="], - "@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-UagjDk4ijOAnGu4WMUPj9ahi7/zJJqNZ9ZAiGPp7waUWJO0O1aWXi/udPphI0IUjvrhBsZJGSN66dR2dsueLWQ=="], + "@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw=="], - "@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-compose-refs": "1.0.0", "@radix-ui/react-primitive": "1.0.1", "@radix-ui/react-use-callback-ref": "1.0.0" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" } }, "sha512-Ej2MQTit8IWJiS2uuujGUmxXjF/y5xZptIIQnyd2JHLwtV0R2j9NRVoRj/1j/gJ7e3REdaBw4Hjf4a1ImhkZcQ=="], + "@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw=="], "@radix-ui/react-hover-card": ["@radix-ui/react-hover-card@1.0.7", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/primitive": "1.0.1", "@radix-ui/react-compose-refs": "1.0.1", "@radix-ui/react-context": "1.0.1", "@radix-ui/react-dismissable-layer": "1.0.5", "@radix-ui/react-popper": "1.1.3", "@radix-ui/react-portal": "1.0.4", "@radix-ui/react-presence": "1.0.1", "@radix-ui/react-primitive": "1.0.3", "@radix-ui/react-use-controllable-state": "1.0.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" } }, "sha512-OcUN2FU0YpmajD/qkph3XzMcK/NmSk9hGWnjV68p6QiZMgILugusgQwnLSDs3oFSJYGKf3Y49zgFedhGh04k9A=="], @@ -1705,7 +1587,7 @@ "@radix-ui/react-popper": ["@radix-ui/react-popper@1.1.3", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.0.3", "@radix-ui/react-compose-refs": "1.0.1", "@radix-ui/react-context": "1.0.1", "@radix-ui/react-primitive": "1.0.3", "@radix-ui/react-use-callback-ref": "1.0.1", "@radix-ui/react-use-layout-effect": "1.0.1", "@radix-ui/react-use-rect": "1.0.1", "@radix-ui/react-use-size": "1.0.1", "@radix-ui/rect": "1.0.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" } }, "sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w=="], - "@radix-ui/react-portal": ["@radix-ui/react-portal@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-primitive": "1.0.1" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" } }, "sha512-NY2vUWI5WENgAT1nfC6JS7RU5xRYBfjZVLq0HmgEN1Ezy3rk/UruMV4+Rd0F40PEaFC5SrLS1ixYvcYIQrb4Ig=="], + "@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.9", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ=="], "@radix-ui/react-presence": ["@radix-ui/react-presence@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-compose-refs": "1.0.1", "@radix-ui/react-use-layout-effect": "1.0.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" } }, "sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg=="], @@ -1737,7 +1619,7 @@ "@radix-ui/react-use-effect-event": ["@radix-ui/react-use-effect-event@0.0.2", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA=="], - "@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.0.2", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-use-callback-ref": "1.0.0" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-DXGim3x74WgUv+iMNCF+cAo8xUHHeqvjx8zs7trKf+FkQKPQXLk2sX7Gx1ysH7Q76xCpZuxIJE7HLPxRE+Q+GA=="], + "@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.1", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g=="], "@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="], @@ -1791,7 +1673,7 @@ "@redis/client": ["@redis/client@1.6.0", "", { "dependencies": { "cluster-key-slot": "1.1.2", "generic-pool": "3.9.0", "yallist": "4.0.0" } }, "sha512-aR0uffYI700OEEH4gYnitAnv3vzVGXCFvYfdpu/CJKvk4pHfLPEy/JSZyrpQ+15WhXe1yJRXLtfQ84s4mEXnPg=="], - "@remix-run/router": ["@remix-run/router@1.23.2", "", {}, "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w=="], + "@remix-run/router": ["@remix-run/router@1.15.0", "", {}, "sha512-HOil5aFtme37dVQTB6M34G95kPM3MMuqSmIRVCC52eKV+Y/tGSqw9P3rWhlAx6A+mz+MoX+XxsGsNJbaI5qCgQ=="], "@remote-dom/core": ["@remote-dom/core@1.9.0", "", { "dependencies": { "@remote-dom/polyfill": "^1.4.4", "htm": "^3.1.1" }, "peerDependencies": { "@preact/signals-core": "^1.3.0" } }, "sha512-h8OO2NRns2paXO/q5hkfXrwlZKq7oKj9XedGosi7J8OP3+aW7N2Gv4MBBVVQGCfOiZPkOj5m3sQH7FdyUWl7PQ=="], @@ -1799,8 +1681,6 @@ "@remote-dom/react": ["@remote-dom/react@1.2.2", "", { "dependencies": { "@remote-dom/core": "^1.7.0", "@types/react": "^18.0.0", "htm": "^3.1.1" }, "peerDependencies": { "react": "^17.0.0 || ^18.0.0" } }, "sha512-PkvioODONTr1M0StGDYsR4Ssf5M0Rd4+IlWVvVoK3Zrw8nr7+5mJkgNofaj/z7i8Aep78L28PCW8/WduUt4unA=="], - "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="], - "@rollup/plugin-alias": ["@rollup/plugin-alias@5.1.0", "", { "dependencies": { "slash": "^4.0.0" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" } }, "sha512-lpA3RZ9PdIG7qqhEfv79tBffNaoDuukFDrmhLqg9ifv99u/ehn+lOg30x2zmhf8AQqQUZaMk/B9fZraQ6/acDQ=="], "@rollup/plugin-babel": ["@rollup/plugin-babel@5.3.1", "", { "dependencies": { "@babel/helper-module-imports": "^7.10.4", "@rollup/pluginutils": "^3.1.0" }, "peerDependencies": { "@babel/core": "^7.0.0", "@types/babel__core": "^7.1.9", "rollup": "^1.20.0||^2.0.0" } }, "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q=="], @@ -1821,167 +1701,155 @@ "@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.59.0", "", { "os": "android", "cpu": "arm" }, "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg=="], + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.37.0", "", { "os": "android", "cpu": "arm" }, "sha512-l7StVw6WAa8l3vA1ov80jyetOAEo1FtHvZDbzXDO/02Sq/QVvqlHkYoFwDJPIMj0GKiistsBudfx5tGFnwYWDQ=="], - "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.59.0", "", { "os": "android", "cpu": "arm64" }, "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q=="], + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.37.0", "", { "os": "android", "cpu": "arm64" }, "sha512-6U3SlVyMxezt8Y+/iEBcbp945uZjJwjZimu76xoG7tO1av9VO691z8PkhzQ85ith2I8R2RddEPeSfcbyPfD4hA=="], - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.59.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg=="], + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.37.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-+iTQ5YHuGmPt10NTzEyMPbayiNTcOZDWsbxZYR1ZnmLnZxG17ivrPSWFO9j6GalY0+gV3Jtwrrs12DBscxnlYA=="], - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.59.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w=="], + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.37.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-m8W2UbxLDcmRKVjgl5J/k4B8d7qX2EcJve3Sut7YGrQoPtCIQGPH5AMzuFvYRWZi0FVS0zEY4c8uttPfX6bwYQ=="], - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.59.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA=="], + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.37.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-FOMXGmH15OmtQWEt174v9P1JqqhlgYge/bUjIbiVD1nI1NeJ30HYT9SJlZMqdo1uQFyt9cz748F1BHghWaDnVA=="], - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.59.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg=="], + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.37.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-SZMxNttjPKvV14Hjck5t70xS3l63sbVwl98g3FlVVx2YIDmfUIy29jQrsw06ewEYQ8lQSuY9mpAPlmgRD2iSsA=="], - "@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-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.37.0", "", { "os": "linux", "cpu": "arm" }, "sha512-hhAALKJPidCwZcj+g+iN+38SIOkhK2a9bqtJR+EtyxrKKSt1ynCBeqrQy31z0oWU6thRZzdx53hVgEbRkuI19w=="], - "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA=="], + "@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-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA=="], + "@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-musl": ["@rollup/rollup-linux-arm64-musl@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA=="], + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.37.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-pfxLBMls+28Ey2enpX3JvjEjaJMBX5XlPCZNGxj4kdJyHduPBXtxYeb8alo0a7bqOoWZW2uKynhHxF/MWoHaGQ=="], - "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg=="], + "@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.37.0", "", { "os": "linux", "cpu": "none" }, "sha512-yCE0NnutTC/7IGUq/PUHmoeZbIwq3KRh02e9SfFh7Vmc1Z7atuJRYWhRME5fKgT8aS20mwi1RyChA23qSyRGpA=="], - "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q=="], + "@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.37.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NxcICptHk06E2Lh3a4Pu+2PEdZ6ahNHuK7o6Np9zcWkrBMuv21j10SQDJW3C9Yf/A/P7cutWoC/DptNLVsZ0VQ=="], - "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA=="], + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.37.0", "", { "os": "linux", "cpu": "none" }, "sha512-PpWwHMPCVpFZLTfLq7EWJWvrmEuLdGn1GMYcm5MV7PaRgwCEYJAwiN94uBuZev0/J/hFIIJCsYw4nLmXA9J7Pw=="], - "@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-musl": ["@rollup/rollup-linux-riscv64-musl@4.37.0", "", { "os": "linux", "cpu": "none" }, "sha512-DTNwl6a3CfhGTAOYZ4KtYbdS8b+275LSLqJVJIrPa5/JuIufWWZ/QFvkxp52gpmguN95eujrM68ZG+zVxa8zHA=="], - "@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-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.37.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-hZDDU5fgWvDdHFuExN1gBOhCuzo/8TMpidfOR+1cPZJflcEzXdCy1LjnklQdW8/Et9sryOPJAKAQRw8Jq7Tg+A=="], - "@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-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.37.0", "", { "os": "linux", "cpu": "x64" }, "sha512-pKivGpgJM5g8dwj0ywBwe/HeVAUSuVVJhUTa/URXjxvoyTT/AxsLTAbkHkDHG7qQxLoW2s3apEIl26uUe08LVQ=="], - "@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-musl": ["@rollup/rollup-linux-x64-musl@4.37.0", "", { "os": "linux", "cpu": "x64" }, "sha512-E2lPrLKE8sQbY/2bEkVTGDEk4/49UYRVWgj90MY8yPjpnGBQ+Xi1Qnr7b7UIWw1NOggdFQFOLZ8+5CzCiz143w=="], - "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg=="], + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.37.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-Jm7biMazjNzTU4PrQtr7VS8ibeys9Pn29/1bm4ph7CP2kf21950LgN+BaE2mJ1QujnvOc6p54eWWiVvn05SOBg=="], - "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg=="], + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.37.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-e3/1SFm1OjefWICB2Ucstg2dxYDkDTZGDYgwufcbsxTHyqQps1UQf33dFEChBNmeSsTOyrjw2JJq0zbG5GF6RA=="], - "@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.59.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A=="], - - "@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.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA=="], + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.37.0", "", { "os": "win32", "cpu": "x64" }, "sha512-LWbXUBwn/bcLx2sSsqy7pK5o+Nr+VCoRoAohfJ5C/aBio9nfJmGQqHAhU6pwxV/RmyTk5AqdySma7uwWGlmeuA=="], "@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="], - "@scarf/scarf": ["@scarf/scarf@1.4.0", "", {}, "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ=="], - "@sinclair/typebox": ["@sinclair/typebox@0.34.41", "", {}, "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g=="], "@sinonjs/commons": ["@sinonjs/commons@3.0.1", "", { "dependencies": { "type-detect": "4.0.8" } }, "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ=="], - "@sinonjs/fake-timers": ["@sinonjs/fake-timers@13.0.5", "", { "dependencies": { "@sinonjs/commons": "^3.0.1" } }, "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw=="], + "@sinonjs/fake-timers": ["@sinonjs/fake-timers@10.3.0", "", { "dependencies": { "@sinonjs/commons": "^3.0.0" } }, "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA=="], - "@smithy/abort-controller": ["@smithy/abort-controller@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-xolrFw6b+2iYGl6EcOL7IJY71vvyZ0DJ3mcKtpykqPe2uscwtzDZJa1uVQXyP7w9Dd+kGwYnPbMsJrGISKiY/Q=="], + "@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=="], - "@smithy/chunked-blob-reader": ["@smithy/chunked-blob-reader@5.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-St+kVicSyayWQca+I1rGitaOEH6uKgE8IUWoYnnEX26SWdWQcL6LvMSD19Lg+vYHKdT9B2Zuu7rd3i6Wnyb/iw=="], + "@smithy/chunked-blob-reader": ["@smithy/chunked-blob-reader@5.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-+sKqDBQqb036hh4NPaUiEkYFkTUGYzRsn3EuFhyfQfMy6oGHEUJDurLP9Ufb5dasr/XiAmPNMr6wa9afjQB+Gw=="], - "@smithy/chunked-blob-reader-native": ["@smithy/chunked-blob-reader-native@4.2.3", "", { "dependencies": { "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-jA5k5Udn7Y5717L86h4EIv06wIr3xn8GM1qHRi/Nf31annXcXHJjBKvgztnbn2TxH3xWrPBfgwHsOwZf0UmQWw=="], + "@smithy/chunked-blob-reader-native": ["@smithy/chunked-blob-reader-native@4.0.0", "", { "dependencies": { "@smithy/util-base64": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-R9wM2yPmfEMsUmlMlIgSzOyICs0x9uu7UTHoccMyt7BWw8shcGM8HqB355+BZCPBcySvbTYMs62EgEQkNxz2ig=="], - "@smithy/config-resolver": ["@smithy/config-resolver@4.4.13", "", { "dependencies": { "@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" } }, "sha512-iIzMC5NmOUP6WL6o8iPBjFhUhBZ9pPjpUpQYWMUFQqKyXXzOftbfK8zcQCz/jFV1Psmf05BK5ypx4K2r4Tnwdg=="], + "@smithy/config-resolver": ["@smithy/config-resolver@4.0.1", "", { "dependencies": { "@smithy/node-config-provider": "^4.0.1", "@smithy/types": "^4.1.0", "@smithy/util-config-provider": "^4.0.0", "@smithy/util-middleware": "^4.0.1", "tslib": "^2.6.2" } }, "sha512-Igfg8lKu3dRVkTSEm98QpZUvKEOa71jDX4vKRcvJVyRc3UgN3j7vFMf0s7xLQhYmKa8kyJGQgUJDOV5V3neVlQ=="], - "@smithy/core": ["@smithy/core@3.23.12", "", { "dependencies": { "@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" } }, "sha512-o9VycsYNtgC+Dy3I0yrwCqv9CWicDnke0L7EVOrZtJpjb2t0EjaEofmMrYc0T1Kn3yk32zm6cspxF9u9Bj7e5w=="], + "@smithy/core": ["@smithy/core@3.1.5", "", { "dependencies": { "@smithy/middleware-serde": "^4.0.2", "@smithy/protocol-http": "^5.0.1", "@smithy/types": "^4.1.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-middleware": "^4.0.1", "@smithy/util-stream": "^4.1.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-HLclGWPkCsekQgsyzxLhCQLa8THWXtB5PxyYN+2O6nkyLt550KQKTlbV2D1/j5dNIQapAZM1+qFnpBFxZQkgCA=="], "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@3.2.0", "", { "dependencies": { "@smithy/node-config-provider": "^3.1.4", "@smithy/property-provider": "^3.1.3", "@smithy/types": "^3.3.0", "@smithy/url-parser": "^3.0.3", "tslib": "^2.6.2" } }, "sha512-0SCIzgd8LYZ9EJxUjLXBmEKSZR/P/w6l7Rz/pab9culE/RWuqelAKGJvn5qUOl8BgX8Yj5HWM50A5hiB/RzsgA=="], - "@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.12", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FE3bZdEl62ojmy8x4FHqxq2+BuOHlcxiH5vaZ6aqHJr3AIZzwF5jfx8dEiU/X0a8RboyNDjmXjlbr8AdEyLgiA=="], + "@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.6", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.10.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-OZfsI+YRG26XZik/jKMMg37acnBSbUiK/8nETW3uM3mLj+0tMmFXdHQw1e5WEd/IHN8BGOh3te91SNDe2o4RHg=="], - "@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.12", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-XUSuMxlTxV5pp4VpqZf6Sa3vT/Q75FVkLSpSSE3KkWBvAQWeuWt1msTv8fJfgA4/jcJhrbrbMzN1AC/hvPmm5A=="], + "@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.4", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-d5T7ZS3J/r8P/PDjgmCcutmNxnSRvPH1U6iHeXjzI50sMr78GLmFcrczLw33Ap92oEKqa4CLrkAPeSSOqvGdUA=="], - "@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-7epsAZ3QvfHkngz6RXQYseyZYHlmWXSTPOfPmXkiS+zA6TBNo1awUaMFL9vxyXlGdoELmCZyZe1nQE+imbmV+Q=="], + "@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-lxfDT0UuSc1HqltOGsTEAlZ6H29gpfDSdEPTapD5G63RbnYToZ+ezjzdonCCH90j5tRRCw3aLXVbiZaBW3VRVg=="], - "@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.12", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-D1pFuExo31854eAvg89KMn9Oab/wEeJR6Buy32B49A9Ogdtx5fwZPqBHUlDzaCDpycTFk2+fSQgX689Qsk7UGA=="], + "@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.4", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-TPhiGByWnYyzcpU/K3pO5V7QgtXYpE0NaJPEZBCa1Y5jlw5SjqzMSbFiLb+ZkJhqoQc0ImGyVINqnq1ze0ZRcQ=="], - "@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.12", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-+yNuTiyBACxOJUTvbsNsSOfH9G9oKbaJE1lNL3YHpGcuucl6rPZMi3nrpehpVOVR2E07YqFFmtwpImtpzlouHQ=="], + "@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.4", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-GNI/IXaY/XBB1SkGBFmbW033uWA0tj085eCxYih0eccUe/PFR7+UBQv9HNDk2fD9TJu7UVsCWsH99TkpEPSOzQ=="], - "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.15", "", { "dependencies": { "@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" } }, "sha512-T4jFU5N/yiIfrtrsb9uOQn7RdELdM/7HbyLNr6uO/mpkj1ctiVs7CihVr51w4LyQlXWDpXFn4BElf1WmQvZu/A=="], + "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.0.1", "", { "dependencies": { "@smithy/protocol-http": "^5.0.1", "@smithy/querystring-builder": "^4.0.1", "@smithy/types": "^4.1.0", "@smithy/util-base64": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-3aS+fP28urrMW2KTjb6z9iFow6jO8n3MFfineGbndvzGZit3taZhKWtTorf+Gp5RpFDDafeHlhfsGlDCXvUnJA=="], - "@smithy/hash-blob-browser": ["@smithy/hash-blob-browser@4.2.12", "", { "dependencies": { "@smithy/chunked-blob-reader": "^5.2.2", "@smithy/chunked-blob-reader-native": "^4.2.3", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-1wQE33DsxkM/waftAhCH9VtJbUGyt1PJ9YRDpOu+q9FUi73LLFUZ2fD8A61g2mT1UY9k7b99+V1xZ41Rz4SHRQ=="], + "@smithy/hash-blob-browser": ["@smithy/hash-blob-browser@4.0.1", "", { "dependencies": { "@smithy/chunked-blob-reader": "^5.0.0", "@smithy/chunked-blob-reader-native": "^4.0.0", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-rkFIrQOKZGS6i1D3gKJ8skJ0RlXqDvb1IyAphksaFOMzkn3v3I1eJ8m7OkLj0jf1McP63rcCEoLlkAn/HjcTRw=="], - "@smithy/hash-node": ["@smithy/hash-node@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-QhBYbGrbxTkZ43QoTPrK72DoYviDeg6YKDrHTMJbbC+A0sml3kSjzFtXP7BtbyJnXojLfTQldGdUR0RGD8dA3w=="], + "@smithy/hash-node": ["@smithy/hash-node@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "@smithy/util-buffer-from": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-TJ6oZS+3r2Xu4emVse1YPB3Dq3d8RkZDKcPr71Nj/lJsdAP1c7oFzYqEn1IBc915TsgLl2xIJNuxCz+gLbLE0w=="], - "@smithy/hash-stream-node": ["@smithy/hash-stream-node@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-hQsTjwPCRY8w9GK07w1RqJi3e+myh0UaOWBBhZ1UMSDgofH/Q1fEYzU1teaX6HkpX/eWDdm7tAGR0jBPlz9QEQ=="], + "@smithy/hash-stream-node": ["@smithy/hash-stream-node@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-U1rAE1fxmReCIr6D2o/4ROqAQX+GffZpyMt3d7njtGDr2pUNmAKRWa49gsNVhCh2vVAuf3wXzWwNr2YN8PAXIw=="], - "@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-/4F1zb7Z8LOu1PalTdESFHR0RbPwHd3FcaG1sI3UEIriQTWakysgJr65lc1jj6QY5ye7aFsisajotH6UhWfm/g=="], + "@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-gdudFPf4QRQ5pzj7HEnu6FhKRi61BfH/Gk5Yf6O0KiSbr1LlVhgjThcvjdu658VE6Nve8vaIWB8/fodmS1rBPQ=="], - "@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow=="], + "@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw=="], - "@smithy/md5-js": ["@smithy/md5-js@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-350X4kGIrty0Snx2OWv7rPM6p6vM7RzryvFs6B/56Cux3w3sChOb3bymo5oidXJlPcP9fIRxGUCk7GqpiSOtng=="], + "@smithy/md5-js": ["@smithy/md5-js@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-HLZ647L27APi6zXkZlzSFZIjpo8po45YiyjMGJZM3gyDY8n7dPGdmxIIljLm4gPt/7rRvutLTTkYJpZVfG5r+A=="], - "@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-YE58Yz+cvFInWI/wOTrB+DbvUVz/pLn5mC5MvOV4fdRUc6qGwygyngcucRQjAhiCEbmfLOXX0gntSIcgMvAjmA=="], + "@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.0.1", "", { "dependencies": { "@smithy/protocol-http": "^5.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-OGXo7w5EkB5pPiac7KNzVtfCW2vKBTZNuCctn++TTSOMpe6RZO/n6WEC1AxJINn3+vWLKW49uad3lo/u0WJ9oQ=="], - "@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.27", "", { "dependencies": { "@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" } }, "sha512-T3TFfUgXQlpcg+UdzcAISdZpj4Z+XECZ/cefgA6wLBd6V4lRi0svN2hBouN/be9dXQ31X4sLWz3fAQDf+nt6BA=="], + "@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.0.6", "", { "dependencies": { "@smithy/core": "^3.1.5", "@smithy/middleware-serde": "^4.0.2", "@smithy/node-config-provider": "^4.0.1", "@smithy/shared-ini-file-loader": "^4.0.1", "@smithy/types": "^4.1.0", "@smithy/url-parser": "^4.0.1", "@smithy/util-middleware": "^4.0.1", "tslib": "^2.6.2" } }, "sha512-ftpmkTHIFqgaFugcjzLZv3kzPEFsBFSnq1JsIkr2mwFzCraZVhQk2gqN51OOeRxqhbPTkRFj39Qd2V91E/mQxg=="], - "@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.44", "", { "dependencies": { "@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.7", "@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" } }, "sha512-Y1Rav7m5CFRPQyM4CI0koD/bXjyjJu3EQxZZhtLGD88WIrBrQ7kqXM96ncd6rYnojwOo/u9MXu57JrEvu/nLrA=="], + "@smithy/middleware-retry": ["@smithy/middleware-retry@4.0.7", "", { "dependencies": { "@smithy/node-config-provider": "^4.0.1", "@smithy/protocol-http": "^5.0.1", "@smithy/service-error-classification": "^4.0.1", "@smithy/smithy-client": "^4.1.6", "@smithy/types": "^4.1.0", "@smithy/util-middleware": "^4.0.1", "@smithy/util-retry": "^4.0.1", "tslib": "^2.6.2", "uuid": "^9.0.1" } }, "sha512-58j9XbUPLkqAcV1kHzVX/kAR16GT+j7DUZJqwzsxh1jtz7G82caZiGyyFgUvogVfNTg3TeAOIJepGc8TXF4AVQ=="], - "@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.15", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-ExYhcltZSli0pgAKOpQQe1DLFBLryeZ22605y/YS+mQpdNWekum9Ujb/jMKfJKgjtz1AZldtwA/wCYuKJgjjlg=="], + "@smithy/middleware-serde": ["@smithy/middleware-serde@4.0.2", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-Sdr5lOagCn5tt+zKsaW+U2/iwr6bI9p08wOkCp6/eL6iMbgdtc2R5Ety66rf87PeohR0ExI84Txz9GYv5ou3iQ=="], - "@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-kruC5gRHwsCOuyCd4ouQxYjgRAym2uDlCvQ5acuMtRrcdfg7mFBg6blaxcJ09STpt3ziEkis6bhg1uwrWU7txw=="], + "@smithy/middleware-stack": ["@smithy/middleware-stack@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-dHwDmrtR/ln8UTHpaIavRSzeIk5+YZTBtLnKwDW3G2t6nAupCiQUvNzNoHBpik63fwUaJPtlnMzXbQrNFWssIA=="], - "@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.12", "", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw=="], + "@smithy/node-config-provider": ["@smithy/node-config-provider@4.0.1", "", { "dependencies": { "@smithy/property-provider": "^4.0.1", "@smithy/shared-ini-file-loader": "^4.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-8mRTjvCtVET8+rxvmzRNRR0hH2JjV0DFOmwXPrISmTIJEfnCBugpYYGAsCj8t41qd+RB5gbheSQ/6aKZCQvFLQ=="], - "@smithy/node-http-handler": ["@smithy/node-http-handler@4.5.0", "", { "dependencies": { "@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" } }, "sha512-Rnq9vQWiR1+/I6NZZMNzJHV6pZYyEHt2ZnuV3MG8z2NNenC4i/8Kzttz7CjZiHSmsN5frhXhg17z3Zqjjhmz1A=="], + "@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=="], "@smithy/property-provider": ["@smithy/property-provider@3.1.3", "", { "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" } }, "sha512-zahyOVR9Q4PEoguJ/NrFP4O7SMAfYO1HLhB18M+q+Z4KFd4V2obiMnlVoUFzFLSPeVt1POyNWneHHrZaTMoc/g=="], - "@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="], + "@smithy/protocol-http": ["@smithy/protocol-http@5.3.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-3sfFd2MAzVt0Q/klOmjFi3oIkxczHs0avbwrfn1aBqtc23WqQSmjvk77MBw9WkEQcwbOYIX5/2z4ULj8DuxSsw=="], - "@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg=="], + "@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=="], - "@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-P2OdvrgiAKpkPNKlKUtWbNZKB1XjPxM086NeVhK+W+wI46pIKdWBe5QyXvhUm3MEcyS/rkLvY8rZzyUdmyDZBw=="], + "@smithy/querystring-parser": ["@smithy/querystring-parser@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-Ma2XC7VS9aV77+clSFylVUnPZRindhB7BbmYiNOdr+CHt/kZNJoPP0cd3QxCnCFyPXC4eybmyE98phEHkqZ5Jw=="], - "@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1" } }, "sha512-LlP29oSQN0Tw0b6D0Xo6BIikBswuIiGYbRACy5ujw/JgWSzTdYj46U83ssf6Ux0GyNJVivs2uReU8pt7Eu9okQ=="], + "@smithy/service-error-classification": ["@smithy/service-error-classification@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0" } }, "sha512-3JNjBfOWpj/mYfjXJHB4Txc/7E4LVq32bwzE7m28GN79+M1f76XHflUaSUkhOriprPDzev9cX/M+dEB80DNDKA=="], - "@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.7", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw=="], + "@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-hC8F6qTBbuHRI/uqDgqqi6J0R4GtEZcgrZPhFQnMhfJs3MnUTGSnR1NSJCJs5VWlMydu0kJz15M640fJlRsIOw=="], - "@smithy/signature-v4": ["@smithy/signature-v4@5.3.12", "", { "dependencies": { "@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" } }, "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw=="], + "@smithy/signature-v4": ["@smithy/signature-v4@5.3.4", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.4", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-ScDCpasxH7w1HXHYbtk3jcivjvdA1VICyAdgvVqKhKKwxi+MTwZEqFw0minE+oZ7F07oF25xh4FGJxgqgShz0A=="], - "@smithy/smithy-client": ["@smithy/smithy-client@4.12.7", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/middleware-endpoint": "^4.4.27", "@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" } }, "sha512-q3gqnwml60G44FECaEEsdQMplYhDMZYCtYhMCzadCnRnnHIobZJjegmdoUo6ieLQlPUzvrMdIJUpx6DoPmzANQ=="], + "@smithy/smithy-client": ["@smithy/smithy-client@4.1.6", "", { "dependencies": { "@smithy/core": "^3.1.5", "@smithy/middleware-endpoint": "^4.0.6", "@smithy/middleware-stack": "^4.0.1", "@smithy/protocol-http": "^5.0.1", "@smithy/types": "^4.1.0", "@smithy/util-stream": "^4.1.2", "tslib": "^2.6.2" } }, "sha512-UYDolNg6h2O0L+cJjtgSyKKvEKCOa/8FHYJnBobyeoeWDmNpXjwOAtw16ezyeu1ETuuLEOZbrynK0ZY1Lx9Jbw=="], - "@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + "@smithy/types": ["@smithy/types@4.8.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-N0Zn0OT1zc+NA+UVfkYqQzviRh5ucWwO7mBV3TmHHprMnfcJNfhlPicDkBHi0ewbh+y3evR6cNAW0Raxvb01NA=="], - "@smithy/url-parser": ["@smithy/url-parser@4.2.12", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA=="], + "@smithy/url-parser": ["@smithy/url-parser@4.0.1", "", { "dependencies": { "@smithy/querystring-parser": "^4.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-gPXcIEUtw7VlK8f/QcruNXm7q+T5hhvGu9tl63LsJPZ27exB6dtNwvh2HIi0v7JcXJ5emBxB+CJxwaLEdJfA+g=="], - "@smithy/util-base64": ["@smithy/util-base64@4.3.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ=="], + "@smithy/util-base64": ["@smithy/util-base64@4.0.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg=="], - "@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ=="], + "@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA=="], - "@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g=="], + "@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg=="], - "@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.2", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q=="], + "@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.0.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug=="], - "@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ=="], + "@smithy/util-config-provider": ["@smithy/util-config-provider@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w=="], - "@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.43", "", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Qd/0wCKMaXxev/z00TvNzGCH2jlKKKxXP1aDxB6oKwSQthe3Og2dMhSayGCnsma1bK/kQX1+X7SMP99t6FgiiQ=="], + "@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.0.7", "", { "dependencies": { "@smithy/property-provider": "^4.0.1", "@smithy/smithy-client": "^4.1.6", "@smithy/types": "^4.1.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-CZgDDrYHLv0RUElOsmZtAnp1pIjwDVCSuZWOPhIOBvG36RDfX1Q9+6lS61xBf+qqvHoqRjHxgINeQz47cYFC2Q=="], - "@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.47", "", { "dependencies": { "@smithy/config-resolver": "^4.4.13", "@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.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-qSRbYp1EQ7th+sPFuVcVO05AE0QH635hycdEXlpzIahqHHf2Fyd/Zl+8v0XYMJ3cgDVPa0lkMefU7oNUjAP+DQ=="], + "@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.0.7", "", { "dependencies": { "@smithy/config-resolver": "^4.0.1", "@smithy/credential-provider-imds": "^4.0.1", "@smithy/node-config-provider": "^4.0.1", "@smithy/property-provider": "^4.0.1", "@smithy/smithy-client": "^4.1.6", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-79fQW3hnfCdrfIi1soPbK3zmooRFnLpSx3Vxi6nUlqaaQeC5dm8plt4OTNDNqEEEDkvKghZSaoti684dQFVrGQ=="], - "@smithy/util-endpoints": ["@smithy/util-endpoints@3.3.3", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig=="], + "@smithy/util-endpoints": ["@smithy/util-endpoints@3.0.1", "", { "dependencies": { "@smithy/node-config-provider": "^4.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-zVdUENQpdtn9jbpD9SCFK4+aSiavRb9BxEtw9ZGUR1TYo6bBHbIoi7VkrFQ0/RwZlzx0wRBaRmPclj8iAoJCLA=="], - "@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg=="], + "@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - "@smithy/util-middleware": ["@smithy/util-middleware@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ=="], + "@smithy/util-middleware": ["@smithy/util-middleware@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-fKGQAPAn8sgV0plRikRVo6g6aR0KyKvgzNrPuM74RZKy/wWVzx3BMk+ZWEueyN3L5v5EDg+P582mKU+sH5OAsg=="], - "@smithy/util-retry": ["@smithy/util-retry@4.2.12", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-1zopLDUEOwumjcHdJ1mwBHddubYF8GMQvstVCLC54Y46rqoHwlIU+8ZzUeaBcD+WCJHyDGSeZ2ml9YSe9aqcoQ=="], + "@smithy/util-retry": ["@smithy/util-retry@4.0.1", "", { "dependencies": { "@smithy/service-error-classification": "^4.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-WmRHqNVwn3kI3rKk1LsKcVgPBG6iLTBGC1iYOV3GQegwJ3E8yjzHytPt26VNzOWr1qu0xE03nK0Ug8S7T7oufw=="], - "@smithy/util-stream": ["@smithy/util-stream@4.5.20", "", { "dependencies": { "@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" } }, "sha512-4yXLm5n/B5SRBR2p8cZ90Sbv4zL4NKsgxdzCzp/83cXw2KxLEumt5p+GAVyRNZgQOSrzXn9ARpO0lUe8XSlSDw=="], + "@smithy/util-stream": ["@smithy/util-stream@4.1.2", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.0.1", "@smithy/node-http-handler": "^4.0.3", "@smithy/types": "^4.1.0", "@smithy/util-base64": "^4.0.0", "@smithy/util-buffer-from": "^4.0.0", "@smithy/util-hex-encoding": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-44PKEqQ303d3rlQuiDpcCcu//hV8sn+u2JBo84dWCE0rvgeiVl0IlLMagbU++o0jCWhYCsHaAt9wZuZqNe05Hw=="], - "@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw=="], + "@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="], - "@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + "@smithy/util-utf8": ["@smithy/util-utf8@4.0.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow=="], - "@smithy/util-waiter": ["@smithy/util-waiter@4.2.11", "", { "dependencies": { "@smithy/abort-controller": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-x7Rh2azQPs3XxbvCzcttRErKKvLnbZfqRf/gOjw2pb+ZscX88e5UkRPCB67bVnsFHxayvMvmePfKTqsRb+is1A=="], + "@smithy/util-waiter": ["@smithy/util-waiter@4.0.6", "", { "dependencies": { "@smithy/abort-controller": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-slcr1wdRbX7NFphXZOxtxRNA7hXAAtJAXJDE/wdoMAos27SIquVCKiSqfB6/28YzQ8FCsB5NKkhdM5gMADbqxg=="], - "@smithy/uuid": ["@smithy/uuid@1.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g=="], + "@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], "@stitches/core": ["@stitches/core@1.2.8", "", {}, "sha512-Gfkvwk9o9kE9r9XNBmJRfV8zONvXThnm1tcuojL04Uy5uRyqg93DC83lDebl0rocZCfKSjUv+fWYtMQmEDJldg=="], @@ -2013,10 +1881,12 @@ "@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=="], + "@tootallnate/once": ["@tootallnate/once@2.0.0", "", {}, "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A=="], + + "@trysound/sax": ["@trysound/sax@0.2.0", "", {}, "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA=="], + "@tsconfig/node10": ["@tsconfig/node10@1.0.11", "", {}, "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw=="], "@tsconfig/node12": ["@tsconfig/node12@1.0.11", "", {}, "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag=="], @@ -2043,70 +1913,10 @@ "@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="], - "@types/d3": ["@types/d3@7.4.3", "", { "dependencies": { "@types/d3-array": "*", "@types/d3-axis": "*", "@types/d3-brush": "*", "@types/d3-chord": "*", "@types/d3-color": "*", "@types/d3-contour": "*", "@types/d3-delaunay": "*", "@types/d3-dispatch": "*", "@types/d3-drag": "*", "@types/d3-dsv": "*", "@types/d3-ease": "*", "@types/d3-fetch": "*", "@types/d3-force": "*", "@types/d3-format": "*", "@types/d3-geo": "*", "@types/d3-hierarchy": "*", "@types/d3-interpolate": "*", "@types/d3-path": "*", "@types/d3-polygon": "*", "@types/d3-quadtree": "*", "@types/d3-random": "*", "@types/d3-scale": "*", "@types/d3-scale-chromatic": "*", "@types/d3-selection": "*", "@types/d3-shape": "*", "@types/d3-time": "*", "@types/d3-time-format": "*", "@types/d3-timer": "*", "@types/d3-transition": "*", "@types/d3-zoom": "*" } }, "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww=="], - - "@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="], - - "@types/d3-axis": ["@types/d3-axis@3.0.6", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw=="], - - "@types/d3-brush": ["@types/d3-brush@3.0.6", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A=="], - - "@types/d3-chord": ["@types/d3-chord@3.0.6", "", {}, "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg=="], - - "@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="], - - "@types/d3-contour": ["@types/d3-contour@3.0.6", "", { "dependencies": { "@types/d3-array": "*", "@types/geojson": "*" } }, "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg=="], - - "@types/d3-delaunay": ["@types/d3-delaunay@6.0.4", "", {}, "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw=="], - - "@types/d3-dispatch": ["@types/d3-dispatch@3.0.7", "", {}, "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA=="], - - "@types/d3-drag": ["@types/d3-drag@3.0.7", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ=="], - - "@types/d3-dsv": ["@types/d3-dsv@3.0.7", "", {}, "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g=="], - - "@types/d3-ease": ["@types/d3-ease@3.0.2", "", {}, "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="], - - "@types/d3-fetch": ["@types/d3-fetch@3.0.7", "", { "dependencies": { "@types/d3-dsv": "*" } }, "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA=="], - - "@types/d3-force": ["@types/d3-force@3.0.10", "", {}, "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw=="], - - "@types/d3-format": ["@types/d3-format@3.0.4", "", {}, "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g=="], - - "@types/d3-geo": ["@types/d3-geo@3.1.0", "", { "dependencies": { "@types/geojson": "*" } }, "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ=="], - - "@types/d3-hierarchy": ["@types/d3-hierarchy@3.1.7", "", {}, "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg=="], - - "@types/d3-interpolate": ["@types/d3-interpolate@3.0.4", "", { "dependencies": { "@types/d3-color": "*" } }, "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA=="], - - "@types/d3-path": ["@types/d3-path@3.1.1", "", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="], - - "@types/d3-polygon": ["@types/d3-polygon@3.0.2", "", {}, "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA=="], - - "@types/d3-quadtree": ["@types/d3-quadtree@3.0.6", "", {}, "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg=="], - - "@types/d3-random": ["@types/d3-random@3.0.3", "", {}, "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ=="], - - "@types/d3-scale": ["@types/d3-scale@4.0.9", "", { "dependencies": { "@types/d3-time": "*" } }, "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw=="], - - "@types/d3-scale-chromatic": ["@types/d3-scale-chromatic@3.1.0", "", {}, "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ=="], - - "@types/d3-selection": ["@types/d3-selection@3.0.11", "", {}, "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w=="], - - "@types/d3-shape": ["@types/d3-shape@3.1.8", "", { "dependencies": { "@types/d3-path": "*" } }, "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w=="], - - "@types/d3-time": ["@types/d3-time@3.0.4", "", {}, "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="], - - "@types/d3-time-format": ["@types/d3-time-format@4.0.3", "", {}, "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg=="], - - "@types/d3-timer": ["@types/d3-timer@3.0.2", "", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="], - - "@types/d3-transition": ["@types/d3-transition@3.0.9", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg=="], - - "@types/d3-zoom": ["@types/d3-zoom@3.0.8", "", { "dependencies": { "@types/d3-interpolate": "*", "@types/d3-selection": "*" } }, "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw=="], - "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], + "@types/diff": ["@types/diff@6.0.0", "", {}, "sha512-dhVCYGv3ZSbzmQaBSagrv1WJ6rXCdkyTcDyoNu1MD8JohI7pR7k8wdZEm+mvdxRKXyHVwckFzWU1vJc+Z29MlA=="], + "@types/estree": ["@types/estree@1.0.6", "", {}, "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="], "@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="], @@ -2117,8 +1927,6 @@ "@types/express-session": ["@types/express-session@1.18.2", "", { "dependencies": { "@types/express": "*" } }, "sha512-k+I0BxwVXsnEU2hV77cCobC08kIsn4y44C3gC0b46uxZVMaXA04lSPgRLR/bSL2w0t0ShJiG8o4jPzRG/nscFg=="], - "@types/geojson": ["@types/geojson@7946.0.16", "", {}, "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="], - "@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="], "@types/http-errors": ["@types/http-errors@2.0.4", "", {}, "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA=="], @@ -2157,7 +1965,7 @@ "@types/multer": ["@types/multer@1.4.13", "", { "dependencies": { "@types/express": "*" } }, "sha512-bhhdtPw7JqCiEfC9Jimx5LqX9BDIPJEh2q/fQ4bqbBPtyEZYr3cvF22NwG0DmPZNYA0CAf2CnqDB4KIGGpJcaw=="], - "@types/node": ["@types/node@20.19.37", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw=="], + "@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="], "@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="], @@ -2203,14 +2011,10 @@ "@types/webidl-conversions": ["@types/webidl-conversions@7.0.3", "", {}, "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA=="], - "@types/whatwg-mimetype": ["@types/whatwg-mimetype@3.0.2", "", {}, "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA=="], - "@types/whatwg-url": ["@types/whatwg-url@11.0.5", "", { "dependencies": { "@types/webidl-conversions": "*" } }, "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ=="], "@types/winston": ["@types/winston@2.4.4", "", { "dependencies": { "winston": "*" } }, "sha512-BVGCztsypW8EYwJ+Hq+QNYiT/MUyCif0ouBH+flrY66O5W+KIXAMML6E/0fJpm7VjIzgangahl5S03bJJQGrZw=="], - "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], - "@types/xml-encryption": ["@types/xml-encryption@1.2.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-I69K/WW1Dv7j6O3jh13z0X8sLWJRXbu5xnHDl9yHzUNDUBtUoBY058eb5s+x/WG6yZC1h8aKdI2EoyEPjyEh+Q=="], "@types/xml2js": ["@types/xml2js@0.4.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ=="], @@ -2219,8 +2023,6 @@ "@types/yargs-parser": ["@types/yargs-parser@21.0.3", "", {}, "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="], - "@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="], - "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.24.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.24.0", "@typescript-eslint/type-utils": "8.24.0", "@typescript-eslint/utils": "8.24.0", "@typescript-eslint/visitor-keys": "8.24.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-aFcXEJJCI4gUdXgoo/j9udUYIHgF23MFkg09LFz2dzEmU0+1Plk4rQWv/IYKvPHAtlkkGoB3m5e6oUp+JPsNaQ=="], "@typescript-eslint/parser": ["@typescript-eslint/parser@8.24.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.24.0", "@typescript-eslint/types": "8.24.0", "@typescript-eslint/typescript-estree": "8.24.0", "@typescript-eslint/visitor-keys": "8.24.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-MFDaO9CYiard9j9VepMNa9MTcqVvSny2N4hkY6roquzj8pdCBRENhErrteaQuu7Yjn1ppk0v1/ZF9CG3KIlrTA=="], @@ -2237,8 +2039,6 @@ "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.24.0", "", { "dependencies": { "@typescript-eslint/types": "8.24.0", "eslint-visitor-keys": "^4.2.0" } }, "sha512-kArLq83QxGLbuHrTMoOEWO+l2MwsNS2TGISEdx8xgqpkbytB07XmlQyQdNDrCc1ecSqx0cnmhGvpX+VBwqqSkg=="], - "@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.4", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-CI0NhTrz4EBaa0U+HaaUZrJhPoso8sG7ZFya8uQoBA57fjzrjRSv87ekCjLZOFExN+gXE/z0xuN2QfH4H2HrLQ=="], - "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], "@unrs/resolver-binding-android-arm-eabi": ["@unrs/resolver-binding-android-arm-eabi@1.11.1", "", { "os": "android", "cpu": "arm" }, "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw=="], @@ -2279,14 +2079,48 @@ "@unrs/resolver-binding-win32-x64-msvc": ["@unrs/resolver-binding-win32-x64-msvc@1.11.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g=="], - "@upsetjs/venn.js": ["@upsetjs/venn.js@2.0.0", "", { "optionalDependencies": { "d3-selection": "^3.0.0", "d3-transition": "^3.0.1" } }, "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw=="], + "@vitejs/plugin-react": ["@vitejs/plugin-react@4.3.4", "", { "dependencies": { "@babel/core": "^7.26.0", "@babel/plugin-transform-react-jsx-self": "^7.25.9", "@babel/plugin-transform-react-jsx-source": "^7.25.9", "@types/babel__core": "^7.20.5", "react-refresh": "^0.14.2" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" } }, "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug=="], - "@vitejs/plugin-react": ["@vitejs/plugin-react@5.1.4", "", { "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-rc.3", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA=="], + "@webassemblyjs/ast": ["@webassemblyjs/ast@1.12.1", "", { "dependencies": { "@webassemblyjs/helper-numbers": "1.11.6", "@webassemblyjs/helper-wasm-bytecode": "1.11.6" } }, "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg=="], + + "@webassemblyjs/floating-point-hex-parser": ["@webassemblyjs/floating-point-hex-parser@1.11.6", "", {}, "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw=="], + + "@webassemblyjs/helper-api-error": ["@webassemblyjs/helper-api-error@1.11.6", "", {}, "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q=="], + + "@webassemblyjs/helper-buffer": ["@webassemblyjs/helper-buffer@1.12.1", "", {}, "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw=="], + + "@webassemblyjs/helper-numbers": ["@webassemblyjs/helper-numbers@1.11.6", "", { "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.11.6", "@webassemblyjs/helper-api-error": "1.11.6", "@xtuc/long": "4.2.2" } }, "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g=="], + + "@webassemblyjs/helper-wasm-bytecode": ["@webassemblyjs/helper-wasm-bytecode@1.11.6", "", {}, "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA=="], + + "@webassemblyjs/helper-wasm-section": ["@webassemblyjs/helper-wasm-section@1.12.1", "", { "dependencies": { "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-buffer": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", "@webassemblyjs/wasm-gen": "1.12.1" } }, "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g=="], + + "@webassemblyjs/ieee754": ["@webassemblyjs/ieee754@1.11.6", "", { "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg=="], + + "@webassemblyjs/leb128": ["@webassemblyjs/leb128@1.11.6", "", { "dependencies": { "@xtuc/long": "4.2.2" } }, "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ=="], + + "@webassemblyjs/utf8": ["@webassemblyjs/utf8@1.11.6", "", {}, "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA=="], + + "@webassemblyjs/wasm-edit": ["@webassemblyjs/wasm-edit@1.12.1", "", { "dependencies": { "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-buffer": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", "@webassemblyjs/helper-wasm-section": "1.12.1", "@webassemblyjs/wasm-gen": "1.12.1", "@webassemblyjs/wasm-opt": "1.12.1", "@webassemblyjs/wasm-parser": "1.12.1", "@webassemblyjs/wast-printer": "1.12.1" } }, "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g=="], + + "@webassemblyjs/wasm-gen": ["@webassemblyjs/wasm-gen@1.12.1", "", { "dependencies": { "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", "@webassemblyjs/ieee754": "1.11.6", "@webassemblyjs/leb128": "1.11.6", "@webassemblyjs/utf8": "1.11.6" } }, "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w=="], + + "@webassemblyjs/wasm-opt": ["@webassemblyjs/wasm-opt@1.12.1", "", { "dependencies": { "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-buffer": "1.12.1", "@webassemblyjs/wasm-gen": "1.12.1", "@webassemblyjs/wasm-parser": "1.12.1" } }, "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg=="], + + "@webassemblyjs/wasm-parser": ["@webassemblyjs/wasm-parser@1.12.1", "", { "dependencies": { "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-api-error": "1.11.6", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", "@webassemblyjs/ieee754": "1.11.6", "@webassemblyjs/leb128": "1.11.6", "@webassemblyjs/utf8": "1.11.6" } }, "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ=="], + + "@webassemblyjs/wast-printer": ["@webassemblyjs/wast-printer@1.12.1", "", { "dependencies": { "@webassemblyjs/ast": "1.12.1", "@xtuc/long": "4.2.2" } }, "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA=="], "@xmldom/is-dom-node": ["@xmldom/is-dom-node@1.0.1", "", {}, "sha512-CJDxIgE5I0FH+ttq/Fxy6nRpxP70+e2O048EPe85J2use3XKdatVM7dDVvFNjQudd9B49NPoZ+8PG49zj4Er8Q=="], "@xmldom/xmldom": ["@xmldom/xmldom@0.8.10", "", {}, "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw=="], + "@xtuc/ieee754": ["@xtuc/ieee754@1.2.0", "", {}, "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA=="], + + "@xtuc/long": ["@xtuc/long@4.2.2", "", {}, "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="], + + "abab": ["abab@2.0.6", "", {}, "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA=="], + "abbrev": ["abbrev@1.1.1", "", {}, "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="], "abstract-logging": ["abstract-logging@2.0.1", "", {}, "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA=="], @@ -2295,6 +2129,8 @@ "acorn": ["acorn@8.15.0", "", { "bin": "bin/acorn" }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + "acorn-globals": ["acorn-globals@7.0.1", "", { "dependencies": { "acorn": "^8.1.0", "acorn-walk": "^8.0.2" } }, "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q=="], + "acorn-import-attributes": ["acorn-import-attributes@1.9.5", "", { "peerDependencies": { "acorn": "^8" } }, "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ=="], "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], @@ -2303,12 +2139,12 @@ "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": ["ajv@6.12.6", "", { "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-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" }, "peerDependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], + "ajv-keywords": ["ajv-keywords@3.5.2", "", { "peerDependencies": { "ajv": "^6.9.1" } }, "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ=="], + "anser": ["anser@2.1.1", "", {}, "sha512-nqLm4HxOTpeLOxcmB3QWmV5TcDFhW9y/fyQ+hivtDFcK4OQ+pQ5fzPnXHM1Mfcm0VkLtvVi1TCPr++Qy0Q/3EQ=="], "ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="], @@ -2325,7 +2161,7 @@ "arg": ["arg@4.1.3", "", {}, "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA=="], - "argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], "aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="], @@ -2369,13 +2205,13 @@ "at-least-node": ["at-least-node@1.0.0", "", {}, "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg=="], - "autoprefixer": ["autoprefixer@10.4.27", "", { "dependencies": { "browserslist": "^4.28.1", "caniuse-lite": "^1.0.30001774", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA=="], + "autoprefixer": ["autoprefixer@10.4.20", "", { "dependencies": { "browserslist": "^4.23.3", "caniuse-lite": "^1.0.30001646", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.0.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": "bin/autoprefixer" }, "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g=="], "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], "axe-core": ["axe-core@4.10.2", "", {}, "sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w=="], - "axios": ["axios@1.13.6", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ=="], + "axios": ["axios@1.12.1", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-Kn4kbSXpkFHCGE6rBFNwIv0GQs4AvDT80jlveJDKFxjbTYMUeB4QtsdPCv6H8Cm19Je7IU6VFtRl2zWZI0rudQ=="], "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], @@ -2387,11 +2223,11 @@ "babel-plugin-jest-hoist": ["babel-plugin-jest-hoist@30.2.0", "", { "dependencies": { "@types/babel__core": "^7.20.5" } }, "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA=="], - "babel-plugin-polyfill-corejs2": ["babel-plugin-polyfill-corejs2@0.4.14", "", { "dependencies": { "@babel/compat-data": "^7.27.7", "@babel/helper-define-polyfill-provider": "^0.6.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg=="], + "babel-plugin-polyfill-corejs2": ["babel-plugin-polyfill-corejs2@0.4.12", "", { "dependencies": { "@babel/compat-data": "^7.22.6", "@babel/helper-define-polyfill-provider": "^0.6.3", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og=="], - "babel-plugin-polyfill-corejs3": ["babel-plugin-polyfill-corejs3@0.13.0", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5", "core-js-compat": "^3.43.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A=="], + "babel-plugin-polyfill-corejs3": ["babel-plugin-polyfill-corejs3@0.11.1", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.3", "core-js-compat": "^3.40.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ=="], - "babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.5", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg=="], + "babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.3", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.3" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q=="], "babel-plugin-replace-ts-export-assignment": ["babel-plugin-replace-ts-export-assignment@0.0.2", "", {}, "sha512-BiTEG2Ro+O1spuheL5nB289y37FFmz0ISE6GjpNCG2JuA/WNcuEHSYw01+vN8quGf208sID3FnZFDwVyqX18YQ=="], @@ -2417,7 +2253,7 @@ "base64url": ["base64url@3.0.1", "", {}, "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A=="], - "baseline-browser-mapping": ["baseline-browser-mapping@2.10.0", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.8.28", "", { "bin": "dist/cli.js" }, "sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ=="], "bcryptjs": ["bcryptjs@2.4.3", "", {}, "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ=="], @@ -2425,11 +2261,9 @@ "binary-extensions": ["binary-extensions@2.2.0", "", {}, "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA=="], - "bluebird": ["bluebird@3.4.7", "", {}, "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA=="], - "bn.js": ["bn.js@4.12.1", "", {}, "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg=="], - "body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="], + "body-parser": ["body-parser@2.2.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="], "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], @@ -2455,7 +2289,7 @@ "browserify-zlib": ["browserify-zlib@0.2.0", "", { "dependencies": { "pako": "~1.0.5" } }, "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA=="], - "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], + "browserslist": ["browserslist@4.24.4", "", { "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.1" }, "bin": "cli.js" }, "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A=="], "bser": ["bser@2.1.1", "", { "dependencies": { "node-int64": "^0.4.0" } }, "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ=="], @@ -2517,12 +2351,10 @@ "cheerio-select": ["cheerio-select@2.1.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-select": "^5.1.0", "css-what": "^6.1.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1" } }, "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g=="], - "chevrotain": ["chevrotain@11.1.2", "", { "dependencies": { "@chevrotain/cst-dts-gen": "11.1.2", "@chevrotain/gast": "11.1.2", "@chevrotain/regexp-to-ast": "11.1.2", "@chevrotain/types": "11.1.2", "@chevrotain/utils": "11.1.2", "lodash-es": "4.17.23" } }, "sha512-opLQzEVriiH1uUQ4Kctsd49bRoFDXGGSC4GUqj7pGyxM3RehRhvTlZJc1FL/Flew2p5uwxa1tUDWKzI4wNM8pg=="], - - "chevrotain-allstar": ["chevrotain-allstar@0.3.1", "", { "dependencies": { "lodash-es": "^4.17.21" }, "peerDependencies": { "chevrotain": "^11.0.0" } }, "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw=="], - "chokidar": ["chokidar@3.5.3", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw=="], + "chrome-trace-event": ["chrome-trace-event@1.0.3", "", {}, "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg=="], + "ci-info": ["ci-info@4.3.1", "", {}, "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA=="], "cipher-base": ["cipher-base@1.0.6", "", { "dependencies": { "inherits": "^2.0.4", "safe-buffer": "^5.2.1" } }, "sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw=="], @@ -2587,8 +2419,6 @@ "concat-with-sourcemaps": ["concat-with-sourcemaps@1.1.0", "", { "dependencies": { "source-map": "^0.6.1" } }, "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg=="], - "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], - "connect-redis": ["connect-redis@8.1.0", "", { "peerDependencies": { "express-session": ">=1" } }, "sha512-Km0EYLDlmExF52UCss5gLGTtrukGC57G6WCC2aqEMft5Vr4xNWuM4tL+T97kWrw+vp40SXFteb6Xk/7MxgpwdA=="], "console-browserify": ["console-browserify@1.2.0", "", {}, "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA=="], @@ -2615,13 +2445,13 @@ "copy-to-clipboard": ["copy-to-clipboard@3.3.3", "", { "dependencies": { "toggle-selection": "^1.0.6" } }, "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA=="], - "core-js-compat": ["core-js-compat@3.47.0", "", { "dependencies": { "browserslist": "^4.28.0" } }, "sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ=="], + "core-js-compat": ["core-js-compat@3.40.0", "", { "dependencies": { "browserslist": "^4.24.3" } }, "sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ=="], - "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="], + "core-util-is": ["core-util-is@1.0.2", "", {}, "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="], "cors": ["cors@2.8.5", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g=="], - "cose-base": ["cose-base@1.0.3", "", { "dependencies": { "layout-base": "^1.0.0" } }, "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg=="], + "cosmiconfig": ["cosmiconfig@8.3.6", "", { "dependencies": { "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0", "path-type": "^4.0.0" }, "peerDependencies": { "typescript": ">=4.9.5" } }, "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA=="], "create-ecdh": ["create-ecdh@4.0.4", "", { "dependencies": { "bn.js": "^4.1.0", "elliptic": "^6.5.3" } }, "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A=="], @@ -2643,13 +2473,13 @@ "crypto-random-string": ["crypto-random-string@2.0.0", "", {}, "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA=="], - "css-blank-pseudo": ["css-blank-pseudo@8.0.1", "", { "dependencies": { "postcss-selector-parser": "^7.1.1" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-C5B2e5hCM4llrQkUms+KnWEMVW8K1n2XvX9G7ppfMZJQ7KAS/4rNnkP1Cs+HhWriOz1mWWTMFD4j1J7s31Dgug=="], + "css-blank-pseudo": ["css-blank-pseudo@5.0.2", "", { "dependencies": { "postcss-selector-parser": "^6.0.10" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-aCU4AZ7uEcVSUzagTlA9pHciz7aWPKA/YzrEkpdSopJ2pvhIxiQ5sYeMz1/KByxlIo4XBdvMNJAVKMg/GRnhfw=="], "css-declaration-sorter": ["css-declaration-sorter@6.4.1", "", { "peerDependencies": { "postcss": "^8.0.9" } }, "sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g=="], - "css-has-pseudo": ["css-has-pseudo@8.0.0", "", { "dependencies": { "@csstools/selector-specificity": "^6.0.0", "postcss-selector-parser": "^7.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-Uz/bsHRbOeir/5Oeuz85tq/yLJLxX+3dpoRdjNTshs6jjqwUg8XaEZGDd0ci3fw7l53Srw0EkJ8mYan0eW5uGQ=="], + "css-has-pseudo": ["css-has-pseudo@5.0.2", "", { "dependencies": { "@csstools/selector-specificity": "^2.0.1", "postcss-selector-parser": "^6.0.10", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-q+U+4QdwwB7T9VEW/LyO6CFrLAeLqOykC5mDqJXc7aKZAhDbq7BvGT13VGJe+IwBfdN2o3Xdw2kJ5IxwV1Sc9Q=="], - "css-prefers-color-scheme": ["css-prefers-color-scheme@11.0.0", "", { "peerDependencies": { "postcss": "^8.4" } }, "sha512-fv0mgtwUhh2m9iio3Kxc2CkrogjIaRdMFaaqyzSFdii17JF4cfPyMNX72B15ZW2Nrr/NZUpxI4dec1VMHYJvdw=="], + "css-prefers-color-scheme": ["css-prefers-color-scheme@8.0.2", "", { "peerDependencies": { "postcss": "^8.4" } }, "sha512-OvFghizHJ45x7nsJJUSYLyQNTzsCU8yWjxAc/nhPQg1pbs18LMoET8N3kOweFDPy0JV0OSXN2iqRFhPBHYOeMA=="], "css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="], @@ -2659,7 +2489,7 @@ "css.escape": ["css.escape@1.5.1", "", {}, "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg=="], - "cssdb": ["cssdb@8.8.0", "", {}, "sha512-QbLeyz2Bgso1iRlh7IpWk6OKa3lLNGXsujVjDMPl9rOZpxKeiG69icLpbLCFxeURwmcdIfZqQyhlooKJYM4f8Q=="], + "cssdb": ["cssdb@7.10.0", "", {}, "sha512-yGZ5tmA57gWh/uvdQBHs45wwFY0IBh3ypABk5sEubPBPSzXzkNgsWReqx7gdx6uhC+QoFBe+V8JwBB9/hQ6cIA=="], "cssesc": ["cssesc@3.0.0", "", { "bin": "bin/cssesc" }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], @@ -2673,84 +2503,14 @@ "csso": ["csso@4.2.0", "", { "dependencies": { "css-tree": "^1.1.2" } }, "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA=="], + "cssom": ["cssom@0.5.0", "", {}, "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw=="], + "cssstyle": ["cssstyle@4.6.0", "", { "dependencies": { "@asamuzakjp/css-color": "^3.2.0", "rrweb-cssom": "^0.8.0" } }, "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg=="], "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], - "cytoscape": ["cytoscape@3.33.1", "", {}, "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ=="], - - "cytoscape-cose-bilkent": ["cytoscape-cose-bilkent@4.1.0", "", { "dependencies": { "cose-base": "^1.0.0" }, "peerDependencies": { "cytoscape": "^3.2.0" } }, "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ=="], - - "cytoscape-fcose": ["cytoscape-fcose@2.2.0", "", { "dependencies": { "cose-base": "^2.2.0" }, "peerDependencies": { "cytoscape": "^3.2.0" } }, "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ=="], - "d": ["d@1.0.2", "", { "dependencies": { "es5-ext": "^0.10.64", "type": "^2.7.2" } }, "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw=="], - "d3": ["d3@7.9.0", "", { "dependencies": { "d3-array": "3", "d3-axis": "3", "d3-brush": "3", "d3-chord": "3", "d3-color": "3", "d3-contour": "4", "d3-delaunay": "6", "d3-dispatch": "3", "d3-drag": "3", "d3-dsv": "3", "d3-ease": "3", "d3-fetch": "3", "d3-force": "3", "d3-format": "3", "d3-geo": "3", "d3-hierarchy": "3", "d3-interpolate": "3", "d3-path": "3", "d3-polygon": "3", "d3-quadtree": "3", "d3-random": "3", "d3-scale": "4", "d3-scale-chromatic": "3", "d3-selection": "3", "d3-shape": "3", "d3-time": "3", "d3-time-format": "4", "d3-timer": "3", "d3-transition": "3", "d3-zoom": "3" } }, "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA=="], - - "d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="], - - "d3-axis": ["d3-axis@3.0.0", "", {}, "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw=="], - - "d3-brush": ["d3-brush@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "3", "d3-transition": "3" } }, "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ=="], - - "d3-chord": ["d3-chord@3.0.1", "", { "dependencies": { "d3-path": "1 - 3" } }, "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g=="], - - "d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="], - - "d3-contour": ["d3-contour@4.0.2", "", { "dependencies": { "d3-array": "^3.2.0" } }, "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA=="], - - "d3-delaunay": ["d3-delaunay@6.0.4", "", { "dependencies": { "delaunator": "5" } }, "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A=="], - - "d3-dispatch": ["d3-dispatch@3.0.1", "", {}, "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg=="], - - "d3-drag": ["d3-drag@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-selection": "3" } }, "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg=="], - - "d3-dsv": ["d3-dsv@3.0.1", "", { "dependencies": { "commander": "7", "iconv-lite": "0.6", "rw": "1" }, "bin": { "csv2json": "bin/dsv2json.js", "csv2tsv": "bin/dsv2dsv.js", "dsv2dsv": "bin/dsv2dsv.js", "dsv2json": "bin/dsv2json.js", "json2csv": "bin/json2dsv.js", "json2dsv": "bin/json2dsv.js", "json2tsv": "bin/json2dsv.js", "tsv2csv": "bin/dsv2dsv.js", "tsv2json": "bin/dsv2json.js" } }, "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q=="], - - "d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="], - - "d3-fetch": ["d3-fetch@3.0.1", "", { "dependencies": { "d3-dsv": "1 - 3" } }, "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw=="], - - "d3-force": ["d3-force@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-quadtree": "1 - 3", "d3-timer": "1 - 3" } }, "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg=="], - - "d3-format": ["d3-format@3.1.2", "", {}, "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg=="], - - "d3-geo": ["d3-geo@3.1.1", "", { "dependencies": { "d3-array": "2.5.0 - 3" } }, "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q=="], - - "d3-hierarchy": ["d3-hierarchy@3.1.2", "", {}, "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA=="], - - "d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="], - - "d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="], - - "d3-polygon": ["d3-polygon@3.0.1", "", {}, "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg=="], - - "d3-quadtree": ["d3-quadtree@3.0.1", "", {}, "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw=="], - - "d3-random": ["d3-random@3.0.1", "", {}, "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ=="], - - "d3-sankey": ["d3-sankey@0.12.3", "", { "dependencies": { "d3-array": "1 - 2", "d3-shape": "^1.2.0" } }, "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ=="], - - "d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="], - - "d3-scale-chromatic": ["d3-scale-chromatic@3.1.0", "", { "dependencies": { "d3-color": "1 - 3", "d3-interpolate": "1 - 3" } }, "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ=="], - - "d3-selection": ["d3-selection@3.0.0", "", {}, "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ=="], - - "d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="], - - "d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="], - - "d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="], - - "d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="], - - "d3-transition": ["d3-transition@3.0.1", "", { "dependencies": { "d3-color": "1 - 3", "d3-dispatch": "1 - 3", "d3-ease": "1 - 3", "d3-interpolate": "1 - 3", "d3-timer": "1 - 3" }, "peerDependencies": { "d3-selection": "2 - 3" } }, "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w=="], - - "d3-zoom": ["d3-zoom@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "2 - 3", "d3-transition": "2 - 3" } }, "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw=="], - - "dagre-d3-es": ["dagre-d3-es@7.0.14", "", { "dependencies": { "d3": "^7.9.0", "lodash-es": "^4.17.21" } }, "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg=="], - "damerau-levenshtein": ["damerau-levenshtein@1.0.8", "", {}, "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA=="], "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], @@ -2765,9 +2525,9 @@ "date-fns": ["date-fns@3.3.1", "", {}, "sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw=="], - "dayjs": ["dayjs@1.11.19", "", {}, "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw=="], + "dayjs": ["dayjs@1.11.13", "", {}, "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="], - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], "decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="], @@ -2793,8 +2553,6 @@ "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="], - "delaunator": ["delaunator@5.0.1", "", { "dependencies": { "robust-predicates": "^3.0.2" } }, "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw=="], - "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], "denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="], @@ -2819,14 +2577,12 @@ "didyoumean": ["didyoumean@1.2.2", "", {}, "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="], - "diff": ["diff@4.0.2", "", {}, "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A=="], + "diff": ["diff@7.0.0", "", {}, "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw=="], "diff-sequences": ["diff-sequences@29.6.3", "", {}, "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q=="], "diffie-hellman": ["diffie-hellman@5.0.3", "", { "dependencies": { "bn.js": "^4.1.0", "miller-rabin": "^4.0.0", "randombytes": "^2.0.0" } }, "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg=="], - "dingbat-to-unicode": ["dingbat-to-unicode@1.0.1", "", {}, "sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w=="], - "dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="], "dnd-core": ["dnd-core@16.0.1", "", { "dependencies": { "@react-dnd/asap": "^5.0.1", "@react-dnd/invariant": "^4.0.1", "redux": "^4.2.0" } }, "sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng=="], @@ -2843,9 +2599,11 @@ "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], + "domexception": ["domexception@4.0.0", "", { "dependencies": { "webidl-conversions": "^7.0.0" } }, "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw=="], + "domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="], - "dompurify": ["dompurify@3.3.2", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ=="], + "dompurify": ["dompurify@3.3.0", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ=="], "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], @@ -2853,8 +2611,6 @@ "downloadjs": ["downloadjs@1.4.7", "", {}, "sha512-LN1gO7+u9xjU5oEScGFKvXhYf7Y/empUIIEAGBs1LzUq/rg5duiDrkuH5A2lQGd5jfMOb9X9usDa2oVXwJ0U/Q=="], - "duck": ["duck@0.1.12", "", { "dependencies": { "underscore": "^1.13.1" } }, "sha512-wkctla1O6VfP89gQ+J/yDesM0S7B7XLXjKGzXxMDVFg7uEn706niAtyYovKbyq1oT9YwDcly721/iUWoc8MVRg=="], - "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], @@ -2865,7 +2621,7 @@ "ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": "bin/cli.js" }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="], - "electron-to-chromium": ["electron-to-chromium@1.5.307", "", {}, "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg=="], + "electron-to-chromium": ["electron-to-chromium@1.5.254", "", {}, "sha512-DcUsWpVhv9svsKRxnSCZ86SjD+sp32SGidNB37KpqXJncp1mfUgKbHvBomE89WJDbfVKw1mdv5+ikrvd43r+Bg=="], "elliptic": ["elliptic@6.6.1", "", { "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", "hash.js": "^1.0.0", "hmac-drbg": "^1.0.1", "inherits": "^2.0.4", "minimalistic-assert": "^1.0.1", "minimalistic-crypto-utils": "^1.0.1" } }, "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g=="], @@ -2883,7 +2639,7 @@ "enhanced-resolve": ["enhanced-resolve@5.17.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg=="], - "entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], + "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], "environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="], @@ -2899,6 +2655,8 @@ "es-iterator-helpers": ["es-iterator-helpers@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.0.3", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.6", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.4", "safe-array-concat": "^1.1.3" } }, "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w=="], + "es-module-lexer": ["es-module-lexer@1.6.0", "", {}, "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ=="], + "es-object-atoms": ["es-object-atoms@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw=="], "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], @@ -2913,7 +2671,7 @@ "es6-symbol": ["es6-symbol@3.1.4", "", { "dependencies": { "d": "^1.0.2", "ext": "^1.7.0" } }, "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg=="], - "esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="], + "esbuild": ["esbuild@0.25.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.1", "@esbuild/android-arm": "0.25.1", "@esbuild/android-arm64": "0.25.1", "@esbuild/android-x64": "0.25.1", "@esbuild/darwin-arm64": "0.25.1", "@esbuild/darwin-x64": "0.25.1", "@esbuild/freebsd-arm64": "0.25.1", "@esbuild/freebsd-x64": "0.25.1", "@esbuild/linux-arm": "0.25.1", "@esbuild/linux-arm64": "0.25.1", "@esbuild/linux-ia32": "0.25.1", "@esbuild/linux-loong64": "0.25.1", "@esbuild/linux-mips64el": "0.25.1", "@esbuild/linux-ppc64": "0.25.1", "@esbuild/linux-riscv64": "0.25.1", "@esbuild/linux-s390x": "0.25.1", "@esbuild/linux-x64": "0.25.1", "@esbuild/netbsd-arm64": "0.25.1", "@esbuild/netbsd-x64": "0.25.1", "@esbuild/openbsd-arm64": "0.25.1", "@esbuild/openbsd-x64": "0.25.1", "@esbuild/sunos-x64": "0.25.1", "@esbuild/win32-arm64": "0.25.1", "@esbuild/win32-ia32": "0.25.1", "@esbuild/win32-x64": "0.25.1" }, "bin": "bin/esbuild" }, "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ=="], "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], @@ -2925,6 +2683,8 @@ "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + "escodegen": ["escodegen@2.1.0", "", { "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", "esutils": "^2.0.2" }, "optionalDependencies": { "source-map": "~0.6.1" }, "bin": { "escodegen": "bin/escodegen.js", "esgenerate": "bin/esgenerate.js" } }, "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w=="], + "eslint": ["eslint@9.39.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.1", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "bin": "bin/eslint.js" }, "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g=="], "eslint-config-prettier": ["eslint-config-prettier@10.0.1", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": "build/bin/cli.js" }, "sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw=="], @@ -2995,11 +2755,11 @@ "export-from-json": ["export-from-json@1.7.4", "", {}, "sha512-FjmpluvZS2PTYyhkoMfQoyEJMfe2bfAyNpa5Apa6C9n7SWUWyJkG/VFnzERuj3q9Jjo3iwBjwVsDQ7Z7sczthA=="], - "express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], + "express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="], "express-mongo-sanitize": ["express-mongo-sanitize@2.2.0", "", {}, "sha512-PZBs5nwhD6ek9ZuP+W2xmpvcrHwXZxD5GdieX2dsjPbAbH4azOkrHbycBud2QRU+YQF1CT+pki/lZGedHgo/dQ=="], - "express-rate-limit": ["express-rate-limit@8.3.1", "", { "dependencies": { "ip-address": "10.1.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw=="], + "express-rate-limit": ["express-rate-limit@8.2.1", "", { "dependencies": { "ip-address": "10.0.1" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g=="], "express-session": ["express-session@1.18.2", "", { "dependencies": { "cookie": "0.7.2", "cookie-signature": "1.0.7", "debug": "2.6.9", "depd": "~2.0.0", "on-headers": "~1.1.0", "parseurl": "~1.3.3", "safe-buffer": "5.2.1", "uid-safe": "~2.1.5" } }, "sha512-SZjssGQC7TzTs9rpPDuUrR23GNZ9+2+IkA/+IJWmvQilTr5OSliEHGF+D9scbIpdC6yGtTI0/VhaHoVes2AN/A=="], @@ -3027,9 +2787,7 @@ "fast-uri": ["fast-uri@3.0.6", "", {}, "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw=="], - "fast-xml-builder": ["fast-xml-builder@1.1.4", "", { "dependencies": { "path-expression-matcher": "^1.1.3" } }, "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg=="], - - "fast-xml-parser": ["fast-xml-parser@5.5.7", "", { "dependencies": { "fast-xml-builder": "^1.1.4", "path-expression-matcher": "^1.1.3", "strnum": "^2.2.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-LteOsISQ2GEiDHZch6L9hB0+MLoYVLToR7xotrzU0opCICBkxOPgHAy1HxAvtxfJNXDJpgAsQN30mkrfpO2Prg=="], + "fast-xml-parser": ["fast-xml-parser@4.4.1", "", { "dependencies": { "strnum": "^1.0.5" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw=="], "fastq": ["fastq@1.17.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w=="], @@ -3049,7 +2807,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@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=="], + "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=="], "filelist": ["filelist@1.0.4", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q=="], @@ -3075,7 +2833,7 @@ "fn.name": ["fn.name@1.1.0", "", {}, "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="], - "follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="], + "follow-redirects": ["follow-redirects@1.15.9", "", {}, "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="], "for-each": ["for-each@0.3.3", "", { "dependencies": { "is-callable": "^1.1.3" } }, "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw=="], @@ -3091,7 +2849,7 @@ "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], - "fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="], + "fraction.js": ["fraction.js@4.3.7", "", {}, "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew=="], "framer-motion": ["framer-motion@12.23.9", "", { "dependencies": { "motion-dom": "^12.23.9", "motion-utils": "^12.23.6", "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-TqEHXj8LWfQSKqfdr5Y4mYltYLw96deu6/K9kGDd+ysqRJPNwF9nb5mZcrLmybHbU7gcJ+HQar41U3UTGanbbQ=="], @@ -3109,7 +2867,7 @@ "functions-have-names": ["functions-have-names@1.2.3", "", {}, "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="], - "gaxios": ["gaxios@6.2.0", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "is-stream": "^2.0.0", "node-fetch": "^2.6.9" } }, "sha512-H6+bHeoEAU5D6XNc6mPKeN5dLZqEDs9Gpk6I+SZBEzK5So58JVrHPmevNi35fRl1J9Y5TaeLW0kYx3pCJ1U2mQ=="], + "gaxios": ["gaxios@5.1.3", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^5.0.0", "is-stream": "^2.0.0", "node-fetch": "^2.6.9" } }, "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA=="], "gcp-metadata": ["gcp-metadata@5.3.0", "", { "dependencies": { "gaxios": "^5.0.0", "json-bigint": "^1.0.0" } }, "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w=="], @@ -3139,10 +2897,12 @@ "get-tsconfig": ["get-tsconfig@4.10.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A=="], - "glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="], + "glob": ["glob@13.0.0", "", { "dependencies": { "minimatch": "^10.1.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA=="], "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + "glob-to-regexp": ["glob-to-regexp@0.4.1", "", {}, "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="], + "globals": ["globals@15.14.0", "", {}, "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig=="], "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="], @@ -3151,6 +2911,8 @@ "google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="], + "googleapis-common": ["googleapis-common@7.0.1", "", { "dependencies": { "extend": "^3.0.2", "gaxios": "^6.0.3", "google-auth-library": "^9.0.0", "qs": "^6.7.0", "url-template": "^2.0.8", "uuid": "^9.0.0" } }, "sha512-mgt5zsd7zj5t5QXvDanjWguMdHAcJmmDrF9RkInCecNsyV7S7YtGqm5v2IWONNID88osb7zmx5FtrAP12JfD0w=="], + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], @@ -3159,14 +2921,10 @@ "gtoken": ["gtoken@7.1.0", "", { "dependencies": { "gaxios": "^6.0.0", "jws": "^4.0.0" } }, "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw=="], - "hachure-fill": ["hachure-fill@0.5.2", "", {}, "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg=="], - "hamt_plus": ["hamt_plus@1.0.2", "", {}, "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA=="], "handlebars": ["handlebars@4.7.8", "", { "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" }, "optionalDependencies": { "uglify-js": "^3.1.4" }, "bin": "bin/handlebars" }, "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ=="], - "happy-dom": ["happy-dom@20.8.3", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.18.3" } }, "sha512-lMHQRRwIPyJ70HV0kkFT7jH/gXzSI7yDkQFe07E2flwmNDFoWUTRMKpW2sglsnpeA7b6S2TJPp98EbQxai8eaQ=="], - "harmony-reflect": ["harmony-reflect@1.6.2", "", {}, "sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g=="], "has-bigints": ["has-bigints@1.0.2", "", {}, "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ=="], @@ -3215,7 +2973,7 @@ "hoist-non-react-statics": ["hoist-non-react-statics@3.3.2", "", { "dependencies": { "react-is": "^16.7.0" } }, "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw=="], - "hono": ["hono@4.12.5", "", {}, "sha512-3qq+FUBtlTHhtYxbxheZgY8NIFnkkC/MR8u5TTsr7YZ3wixryQ3cCwn3iZbg8p8B88iDBBAYSfZDS75t8MN7Vg=="], + "hono": ["hono@4.11.1", "", {}, "sha512-KsFcH0xxHes0J4zaQgWbYwmz3UPOOskdqZmItstUG93+Wk1ePBLkLGwbP9zlmh1BFUiL8Qp+Xfu9P7feJWpGNg=="], "hookified": ["hookified@1.12.1", "", {}, "sha512-xnKGl+iMIlhrZmGHB729MqlmPoWBznctSQTYCpFKqNsCgimJQmithcW0xSQMMFzYnV2iKUh25alswn6epgxS0Q=="], @@ -3267,8 +3025,6 @@ "ignore-by-default": ["ignore-by-default@1.0.1", "", {}, "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA=="], - "immediate": ["immediate@3.0.6", "", {}, "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="], - "import-cwd": ["import-cwd@3.0.0", "", { "dependencies": { "import-from": "^3.0.0" } }, "sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg=="], "import-fresh": ["import-fresh@3.3.0", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw=="], @@ -3293,13 +3049,11 @@ "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="], - "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], - "intersection-observer": ["intersection-observer@0.10.0", "", {}, "sha512-fn4bQ0Xq8FTej09YC/jqKZwtijpvARlRp6wxL5WTA6yPe2YWSJ5RJh7Nm79rK2qB0wr6iDQzH60XGq5V/7u8YQ=="], "ioredis": ["ioredis@5.3.2", "", { "dependencies": { "@ioredis/commands": "^1.1.1", "cluster-key-slot": "^1.1.0", "debug": "^4.3.4", "denque": "^2.1.0", "lodash.defaults": "^4.2.0", "lodash.isarguments": "^3.1.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", "standard-as-callback": "^2.1.0" } }, "sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA=="], - "ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="], + "ip-address": ["ip-address@10.0.1", "", {}, "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA=="], "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], @@ -3401,7 +3155,7 @@ "is-wsl": ["is-wsl@3.1.0", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw=="], - "isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="], + "isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], @@ -3461,7 +3215,7 @@ "jest-message-util": ["jest-message-util@30.2.0", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@jest/types": "30.2.0", "@types/stack-utils": "^2.0.3", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "micromatch": "^4.0.8", "pretty-format": "30.2.0", "slash": "^3.0.0", "stack-utils": "^2.0.6" } }, "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw=="], - "jest-mock": ["jest-mock@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "jest-util": "30.2.0" } }, "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw=="], + "jest-mock": ["jest-mock@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "jest-util": "^29.7.0" } }, "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw=="], "jest-pnp-resolver": ["jest-pnp-resolver@1.2.3", "", { "peerDependencies": { "jest-resolve": "*" } }, "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w=="], @@ -3477,7 +3231,7 @@ "jest-snapshot": ["jest-snapshot@30.2.0", "", { "dependencies": { "@babel/core": "^7.27.4", "@babel/generator": "^7.27.5", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/types": "^7.27.3", "@jest/expect-utils": "30.2.0", "@jest/get-type": "30.1.0", "@jest/snapshot-utils": "30.2.0", "@jest/transform": "30.2.0", "@jest/types": "30.2.0", "babel-preset-current-node-syntax": "^1.2.0", "chalk": "^4.1.2", "expect": "30.2.0", "graceful-fs": "^4.2.11", "jest-diff": "30.2.0", "jest-matcher-utils": "30.2.0", "jest-message-util": "30.2.0", "jest-util": "30.2.0", "pretty-format": "30.2.0", "semver": "^7.7.2", "synckit": "^0.11.8" } }, "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA=="], - "jest-util": ["jest-util@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", "graceful-fs": "^4.2.11", "picomatch": "^4.0.2" } }, "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA=="], + "jest-util": ["jest-util@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", "picomatch": "^2.2.3" } }, "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA=="], "jest-validate": ["jest-validate@30.2.0", "", { "dependencies": { "@jest/get-type": "30.1.0", "@jest/types": "30.2.0", "camelcase": "^6.3.0", "chalk": "^4.1.2", "leven": "^3.1.0", "pretty-format": "30.2.0" } }, "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw=="], @@ -3529,8 +3283,6 @@ "jsx-ast-utils": ["jsx-ast-utils@3.3.5", "", { "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", "object.assign": "^4.1.4", "object.values": "^1.1.6" } }, "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ=="], - "jszip": ["jszip@3.10.1", "", { "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", "setimmediate": "^1.0.5" } }, "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g=="], - "jwa": ["jwa@2.0.0", "", { "dependencies": { "buffer-equal-constant-time": "1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA=="], "jwks-rsa": ["jwks-rsa@3.2.0", "", { "dependencies": { "@types/express": "^4.17.20", "@types/jsonwebtoken": "^9.0.4", "debug": "^4.3.4", "jose": "^4.15.4", "limiter": "^1.1.5", "lru-memoizer": "^2.2.0" } }, "sha512-PwchfHcQK/5PSydeKCs1ylNym0w/SSv8a62DgHJ//7x2ZclCoinlsjAfDxAAbpoTPybOum/Jgy+vkvMmKz89Ww=="], @@ -3545,22 +3297,16 @@ "keyv-file": ["keyv-file@5.2.0", "", { "dependencies": { "@keyv/serialize": "^1.0.1", "tslib": "^1.14.1" } }, "sha512-5JEBqQiDzjGCQHtf7KLReJdHKchaJyUZW+9TvBu+4dc+uuTqUG9KcdA3ICMXlwky3qjKc0ecNCNefbgjyDtlAg=="], - "khroma": ["khroma@2.1.0", "", {}, "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="], - "klona": ["klona@2.0.6", "", {}, "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA=="], "kuler": ["kuler@2.0.0", "", {}, "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A=="], - "langium": ["langium@4.2.1", "", { "dependencies": { "chevrotain": "~11.1.1", "chevrotain-allstar": "~0.3.1", "vscode-languageserver": "~9.0.1", "vscode-languageserver-textdocument": "~1.0.11", "vscode-uri": "~3.1.0" } }, "sha512-zu9QWmjpzJcomzdJQAHgDVhLGq5bLosVak1KVa40NzQHXfqr4eAHupvnPOVXEoLkg6Ocefvf/93d//SB7du4YQ=="], - - "langsmith": ["langsmith@0.4.12", "", { "dependencies": { "@types/uuid": "^10.0.0", "chalk": "^4.1.2", "console-table-printer": "^2.12.1", "p-queue": "^6.6.2", "semver": "^7.6.3", "uuid": "^10.0.0" }, "peerDependencies": { "@opentelemetry/api": "*", "@opentelemetry/exporter-trace-otlp-proto": "*", "@opentelemetry/sdk-trace-base": "*", "openai": "*" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/exporter-trace-otlp-proto", "@opentelemetry/sdk-trace-base", "openai"] }, "sha512-YWt0jcGvKqjUgIvd78rd4QcdMss0lUkeUaqp0UpVRq7H2yNDx8H5jOUO/laWUmaPtWGgcip0qturykXe1g9Gqw=="], + "langsmith": ["langsmith@0.3.67", "", { "dependencies": { "@types/uuid": "^10.0.0", "chalk": "^4.1.2", "console-table-printer": "^2.12.1", "p-queue": "^6.6.2", "p-retry": "4", "semver": "^7.6.3", "uuid": "^10.0.0" }, "peerDependencies": { "@opentelemetry/api": "*", "@opentelemetry/exporter-trace-otlp-proto": "*", "@opentelemetry/sdk-trace-base": "*", "openai": "*" } }, "sha512-l4y3RmJ9yWF5a29fLg3eWZQxn6Q6dxTOgLGgQHzPGZHF3NUynn+A+airYIe/Yt4rwjGbuVrABAPsXBkVu/Hi7g=="], "language-subtag-registry": ["language-subtag-registry@0.3.23", "", {}, "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ=="], "language-tags": ["language-tags@1.0.9", "", { "dependencies": { "language-subtag-registry": "^0.3.20" } }, "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA=="], - "layout-base": ["layout-base@1.0.2", "", {}, "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg=="], - "ldap-filter": ["ldap-filter@0.3.3", "", { "dependencies": { "assert-plus": "^1.0.0" } }, "sha512-/tFkx5WIn4HuO+6w9lsfxq4FN3O+fDZeO9Mek8dCD8rTUpqzRa766BOBO7BcGkn3X86m5+cBm1/2S/Shzz7gMg=="], "ldapauth-fork": ["ldapauth-fork@5.0.5", "", { "dependencies": { "@types/ldapjs": "^2.2.2", "bcryptjs": "^2.4.0", "ldapjs": "^2.2.1", "lru-cache": "^7.10.1" } }, "sha512-LWUk76+V4AOZbny/3HIPQtGPWZyA3SW2tRhsWIBi9imP22WJktKLHV1ofd8Jo/wY7Ve6vAT7FCI5mEn3blZTjw=="], @@ -3573,8 +3319,6 @@ "librechat-data-provider": ["librechat-data-provider@workspace:packages/data-provider"], - "lie": ["lie@3.3.0", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ=="], - "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], "limiter": ["limiter@1.1.5", "", {}, "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA=="], @@ -3585,13 +3329,13 @@ "listr2": ["listr2@8.2.5", "", { "dependencies": { "cli-truncate": "^4.0.0", "colorette": "^2.0.20", "eventemitter3": "^5.0.1", "log-update": "^6.1.0", "rfdc": "^1.4.1", "wrap-ansi": "^9.0.0" } }, "sha512-iyAZCeyD+c1gPyE9qpFu8af0Y+MRtmKOncdGoA2S5EY8iFq99dmmvkNnHiWo+pj0s7yH7l3KPIgee77tKpXPWQ=="], + "loader-runner": ["loader-runner@4.3.0", "", {}, "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg=="], + "loader-utils": ["loader-utils@3.3.1", "", {}, "sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg=="], "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], - "lodash": ["lodash@4.17.23", "", {}, "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="], - - "lodash-es": ["lodash-es@4.17.23", "", {}, "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg=="], + "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], "lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="], @@ -3623,6 +3367,8 @@ "lodash.sortby": ["lodash.sortby@4.7.0", "", {}, "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA=="], + "lodash.throttle": ["lodash.throttle@4.1.1", "", {}, "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ=="], + "lodash.uniq": ["lodash.uniq@4.5.0", "", {}, "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ=="], "log-update": ["log-update@6.1.0", "", { "dependencies": { "ansi-escapes": "^7.0.0", "cli-cursor": "^5.0.0", "slice-ansi": "^7.1.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w=="], @@ -3635,8 +3381,6 @@ "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": "cli.js" }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], - "lop": ["lop@0.4.2", "", { "dependencies": { "duck": "^0.1.12", "option": "~0.2.1", "underscore": "^1.13.1" } }, "sha512-RefILVDQ4DKoRZsJ4Pj22TxE3omDO47yFpkIBoDKzkqPRISs5U1cnAdg/5583YPkWPaLIYHOKRMQSvjFsO26cw=="], - "lowlight": ["lowlight@2.9.0", "", { "dependencies": { "@types/hast": "^2.0.0", "fault": "^2.0.0", "highlight.js": "~11.8.0" } }, "sha512-OpcaUTCLmHuVuBcyNckKfH5B0oA4JUavb/M/8n9iAvanJYNQkrVm4pvyX0SUaqkBG4dnWHKt7p50B3ngAG2Rfw=="], "lru-cache": ["lru-cache@4.1.5", "", { "dependencies": { "pseudomap": "^1.0.2", "yallist": "^2.1.2" } }, "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g=="], @@ -3655,12 +3399,8 @@ "makeerror": ["makeerror@1.0.12", "", { "dependencies": { "tmpl": "1.0.5" } }, "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg=="], - "mammoth": ["mammoth@1.11.0", "", { "dependencies": { "@xmldom/xmldom": "^0.8.6", "argparse": "~1.0.3", "base64-js": "^1.5.1", "bluebird": "~3.4.0", "dingbat-to-unicode": "^1.0.1", "jszip": "^3.7.1", "lop": "^0.4.2", "path-is-absolute": "^1.0.0", "underscore": "^1.13.1", "xmlbuilder": "^10.0.0" }, "bin": { "mammoth": "bin/mammoth" } }, "sha512-BcEqqY/BOwIcI1iR5tqyVlqc3KIaMRa4egSoK83YAVrBf6+yqdAAbtUcFDCWX8Zef8/fgNZ6rl4VUv+vVX8ddQ=="], - "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], - "marked": ["marked@14.0.0", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ=="], - "match-sorter": ["match-sorter@8.1.0", "", { "dependencies": { "@babel/runtime": "^7.23.8", "remove-accents": "0.5.0" } }, "sha512-0HX3BHPixkbECX+Vt7nS1vJ6P2twPgGTU3PMXjWrl1eyVCL24tFHeyYN1FN5RKLzve0TyzNI9qntqQGbebnfPQ=="], "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], @@ -3719,8 +3459,6 @@ "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], - "mermaid": ["mermaid@11.13.0", "", { "dependencies": { "@braintree/sanitize-url": "^7.1.1", "@iconify/utils": "^3.0.2", "@mermaid-js/parser": "^1.0.1", "@types/d3": "^7.4.3", "@upsetjs/venn.js": "^2.0.0", "cytoscape": "^3.33.1", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.14", "dayjs": "^1.11.19", "dompurify": "^3.3.1", "katex": "^0.16.25", "khroma": "^2.1.0", "lodash-es": "^4.17.23", "marked": "^16.3.0", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", "uuid": "^11.1.0" } }, "sha512-fEnci+Immw6lKMFI8sqzjlATTyjLkRa6axrEgLV2yHTfv8r+h1wjFbV6xeRtd4rUV1cS4EpR9rwp3Rci7TRWDw=="], - "methods": ["methods@1.1.2", "", {}, "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="], "micromark": ["micromark@4.0.0", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ=="], @@ -3805,24 +3543,20 @@ "minimalistic-crypto-utils": ["minimalistic-crypto-utils@1.0.1", "", {}, "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg=="], - "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], - "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], "mkdirp": ["mkdirp@1.0.4", "", { "bin": "bin/cmd.js" }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], - "mlly": ["mlly@1.8.1", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-SnL6sNutTwRWWR/vcmCYHSADjiEesp5TGQQ0pXyLhW5IoeibRlF/CbSLailbB3CNqJUk9cVJ9dUDnbD7GrcHBQ=="], - "module-alias": ["module-alias@2.2.3", "", {}, "sha512-23g5BFj4zdQL/b6tor7Ji+QY4pEfNH784BMslY9Qb0UnJWRAt+lQGLYmRaM0KDBwIG23ffEBELhZDP2rhi9f/Q=="], "module-details-from-path": ["module-details-from-path@1.0.4", "", {}, "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w=="], "moment": ["moment@2.30.1", "", {}, "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how=="], - "monaco-editor": ["monaco-editor@0.55.1", "", { "dependencies": { "dompurify": "3.2.7", "marked": "14.0.0" } }, "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A=="], - "mongodb": ["mongodb@6.14.2", "", { "dependencies": { "@mongodb-js/saslprep": "^1.1.9", "bson": "^6.10.3", "mongodb-connection-string-url": "^3.0.0" }, "peerDependencies": { "@aws-sdk/credential-providers": "^3.188.0", "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", "gcp-metadata": "^5.2.0", "kerberos": "^2.0.1", "mongodb-client-encryption": ">=6.0.0 <7", "snappy": "^7.2.2", "socks": "^2.7.1" }, "optionalPeers": ["@mongodb-js/zstd", "kerberos", "mongodb-client-encryption", "snappy", "socks"] }, "sha512-kMEHNo0F3P6QKDq17zcDuPeaywK/YaJVCEQRzPF3TOM/Bl9MFg64YE5Tu7ifj37qZJMhwU1tl2Ioivws5gRG5Q=="], "mongodb-connection-string-url": ["mongodb-connection-string-url@3.0.2", "", { "dependencies": { "@types/whatwg-url": "^11.0.2", "whatwg-url": "^14.1.0 || ^13.0.0" } }, "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA=="], @@ -3845,13 +3579,13 @@ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - "multer": ["multer@2.1.1", "", { "dependencies": { "append-field": "^1.0.0", "busboy": "^1.6.0", "concat-stream": "^2.0.0", "type-is": "^1.6.18" } }, "sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A=="], + "multer": ["multer@2.0.2", "", { "dependencies": { "append-field": "^1.0.0", "busboy": "^1.6.0", "concat-stream": "^2.0.0", "mkdirp": "^0.5.6", "object-assign": "^4.1.1", "type-is": "^1.6.18", "xtend": "^4.0.2" } }, "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw=="], "mustache": ["mustache@4.2.0", "", { "bin": "bin/mustache" }, "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="], "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.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + "nanoid": ["nanoid@3.3.8", "", { "bin": "bin/nanoid.cjs" }, "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w=="], "napi-postinstall": ["napi-postinstall@0.3.4", "", { "bin": "lib/cli.js" }, "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ=="], @@ -3871,8 +3605,6 @@ "node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="], - "node-readable-to-web-readable-stream": ["node-readable-to-web-readable-stream@0.4.2", "", {}, "sha512-/cMZNI34v//jUTrI+UIo4ieHAB5EZRY/+7OmXZgBxaWBMcW2tGdceIw06RFxWxrKZ5Jp3sI2i5TsRo+CBhtVLQ=="], - "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], "node-stdlib-browser": ["node-stdlib-browser@1.3.1", "", { "dependencies": { "assert": "^2.0.0", "browser-resolve": "^2.0.0", "browserify-zlib": "^0.2.0", "buffer": "^5.7.1", "console-browserify": "^1.1.0", "constants-browserify": "^1.0.0", "create-require": "^1.1.1", "crypto-browserify": "^3.12.1", "domain-browser": "4.22.0", "events": "^3.0.0", "https-browserify": "^1.0.0", "isomorphic-timers-promises": "^1.0.1", "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", "pkg-dir": "^5.0.0", "process": "^0.11.10", "punycode": "^1.4.1", "querystring-es3": "^0.2.1", "readable-stream": "^3.6.0", "stream-browserify": "^3.0.0", "stream-http": "^3.2.0", "string_decoder": "^1.0.0", "timers-browserify": "^2.0.4", "tty-browserify": "0.0.1", "url": "^0.11.4", "util": "^0.12.4", "vm-browserify": "^1.0.1" } }, "sha512-X75ZN8DCLftGM5iKwoYLA3rjnrAEs97MkzvSd4q2746Tgpg8b8XWiBGiBG4ZpgcAqBgtgPHTiAc8ZMCvZuikDw=="], @@ -3885,6 +3617,8 @@ "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], + "normalize-range": ["normalize-range@0.1.2", "", {}, "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA=="], + "normalize-url": ["normalize-url@6.1.0", "", {}, "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A=="], "npm-run-path": ["npm-run-path@5.3.0", "", { "dependencies": { "path-key": "^4.0.0" } }, "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ=="], @@ -3917,8 +3651,6 @@ "object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="], - "okapibm25": ["okapibm25@1.4.1", "", {}, "sha512-UHmeH4MAtZXGFVncwbY7pfFvDVNxpsyM3W66aGPU0SHj1+ld59ty+9lJ0ifcrcnPUl1XdYoDgb06ObyCnpTs3g=="], - "ollama": ["ollama@0.5.18", "", { "dependencies": { "whatwg-fetch": "^3.6.20" } }, "sha512-lTFqTf9bo7Cd3hpF6CviBe/DEhewjoZYd9N/uCe7O20qYTvGqrNOFOBDj3lbZgFWHUgDv5EeyusYxsZSLS8nvg=="], "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], @@ -3939,8 +3671,6 @@ "openid-client": ["openid-client@6.5.0", "", { "dependencies": { "jose": "^6.0.10", "oauth4webapi": "^3.5.1" } }, "sha512-fAfYaTnOYE2kQCqEJGX9KDObW2aw7IQy4jWpU/+3D3WoCFLbix5Hg6qIPQ6Js9r7f8jDUmsnnguRNCSw4wU/IQ=="], - "option": ["option@0.2.4", "", {}, "sha512-pkEqbDyl8ou5cpq+VsnQbe/WlEy5qS7xPzMS1U55OCG9KPvwFD46zDbxQIj3egJSFc3D+XhYOPUzz49zQAVy7A=="], - "optionator": ["optionator@0.9.3", "", { "dependencies": { "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0" } }, "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg=="], "os-browserify": ["os-browserify@0.3.0", "", {}, "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A=="], @@ -3965,8 +3695,6 @@ "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], - "package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="], - "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], @@ -4009,29 +3737,25 @@ "path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="], - "path-data-parser": ["path-data-parser@0.1.0", "", {}, "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w=="], - "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], - "path-expression-matcher": ["path-expression-matcher@1.2.0", "", {}, "sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ=="], - "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], - "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="], + "path-scurry": ["path-scurry@2.0.1", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA=="], "path-to-regexp": ["path-to-regexp@8.2.0", "", {}, "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ=="], - "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], "pause": ["pause@0.0.1", "", {}, "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="], "pbkdf2": ["pbkdf2@3.1.3", "", { "dependencies": { "create-hash": "~1.1.3", "create-hmac": "^1.1.7", "ripemd160": "=2.0.1", "safe-buffer": "^5.2.1", "sha.js": "^2.4.11", "to-buffer": "^1.2.0" } }, "sha512-wfRLBZ0feWRhCIkoMB6ete7czJcnNnqRpcoWQBLqatqXXmelSRqfdDK4F3u9T2s2cXas/hQJcryI/4lAL+XTlA=="], - "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=="], @@ -4049,43 +3773,37 @@ "pkg-dir": ["pkg-dir@4.2.0", "", { "dependencies": { "find-up": "^4.0.0" } }, "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ=="], - "pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], - "playwright": ["playwright@1.56.1", "", { "dependencies": { "playwright-core": "1.56.1" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": "cli.js" }, "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw=="], "playwright-core": ["playwright-core@1.56.1", "", { "bin": "cli.js" }, "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ=="], - "points-on-curve": ["points-on-curve@0.2.0", "", {}, "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A=="], - - "points-on-path": ["points-on-path@0.2.1", "", { "dependencies": { "path-data-parser": "0.1.0", "points-on-curve": "0.2.0" } }, "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g=="], - "possible-typed-array-names": ["possible-typed-array-names@1.0.0", "", {}, "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q=="], - "postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], + "postcss": ["postcss@8.5.3", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A=="], - "postcss-attribute-case-insensitive": ["postcss-attribute-case-insensitive@8.0.0", "", { "dependencies": { "postcss-selector-parser": "^7.1.1" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-fovIPEV35c2JzVXdmP+sp2xirbBMt54J+upU8u6TSj410kUU5+axgEzvBBSAX8KCybze8CFCelzFAw/FfWg2TA=="], + "postcss-attribute-case-insensitive": ["postcss-attribute-case-insensitive@6.0.2", "", { "dependencies": { "postcss-selector-parser": "^6.0.10" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-IRuCwwAAQbgaLhxQdQcIIK0dCVXg3XDUnzgKD8iwdiYdwU4rMWRWyl/W9/0nA4ihVpq5pyALiHB2veBJ0292pw=="], "postcss-calc": ["postcss-calc@8.2.4", "", { "dependencies": { "postcss-selector-parser": "^6.0.9", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.2.2" } }, "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q=="], "postcss-clamp": ["postcss-clamp@4.1.0", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.6" } }, "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow=="], - "postcss-color-functional-notation": ["postcss-color-functional-notation@8.0.2", "", { "dependencies": { "@csstools/css-color-parser": "^4.0.2", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0", "@csstools/postcss-progressive-custom-properties": "^5.0.0", "@csstools/utilities": "^3.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-tbmkk6teYpJzFcGwPIhN1gkvxqGHvNx2PMb8Y3S5Ktyn7xOlvD98XzQ99MFY5mAyvXWclDG+BgoJKYJXFJOp5Q=="], + "postcss-color-functional-notation": ["postcss-color-functional-notation@5.1.0", "", { "dependencies": { "@csstools/postcss-progressive-custom-properties": "^2.3.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-w2R4py6zrVE1U7FwNaAc76tNQlG9GLkrBbcFw+VhUjyDDiV28vfZG+l4LyPmpoQpeSJVtu8VgNjE8Jv5SpC7dQ=="], - "postcss-color-hex-alpha": ["postcss-color-hex-alpha@11.0.0", "", { "dependencies": { "@csstools/utilities": "^3.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-NCGa6vjIyrjosz9GqRxVKbONBklz5TeipYqTJp3IqbnBWlBq5e5EMtG6MaX4vqk9LzocPfMQkuRK9tfk+OQuKg=="], + "postcss-color-hex-alpha": ["postcss-color-hex-alpha@9.0.3", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-7sEHU4tAS6htlxun8AB9LDrCXoljxaC34tFVRlYKcvO+18r5fvGiXgv5bQzN40+4gXLCyWSMRK5FK31244WcCA=="], - "postcss-color-rebeccapurple": ["postcss-color-rebeccapurple@11.0.0", "", { "dependencies": { "@csstools/utilities": "^3.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-g9561mx7cbdqx7XeO/L+lJzVlzu7bICyXr72efBVKZGxIhvBBJf9fGXn3Cb6U4Bwh3LbzQO2e9NWBLVYdX5Eag=="], + "postcss-color-rebeccapurple": ["postcss-color-rebeccapurple@8.0.2", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-xWf/JmAxVoB5bltHpXk+uGRoGFwu4WDAR7210el+iyvTdqiKpDhtcT8N3edXMoVJY0WHFMrKMUieql/wRNiXkw=="], "postcss-colormin": ["postcss-colormin@5.3.1", "", { "dependencies": { "browserslist": "^4.21.4", "caniuse-api": "^3.0.0", "colord": "^2.9.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.2.15" } }, "sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ=="], "postcss-convert-values": ["postcss-convert-values@5.1.3", "", { "dependencies": { "browserslist": "^4.21.4", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.2.15" } }, "sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA=="], - "postcss-custom-media": ["postcss-custom-media@12.0.1", "", { "dependencies": { "@csstools/cascade-layer-name-parser": "^3.0.0", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0", "@csstools/media-query-list-parser": "^5.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-66syE14+VeqkUf0rRX0bvbTCbNRJF132jD+ceo8th1dap2YJEAqpdh5uG98CE3IbgHT7m9XM0GIlOazNWqQdeA=="], + "postcss-custom-media": ["postcss-custom-media@9.1.5", "", { "dependencies": { "@csstools/cascade-layer-name-parser": "^1.0.2", "@csstools/css-parser-algorithms": "^2.2.0", "@csstools/css-tokenizer": "^2.1.1", "@csstools/media-query-list-parser": "^2.1.1" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-GStyWMz7Qbo/Gtw1xVspzVSX8eipgNg4lpsO3CAeY4/A1mzok+RV6MCv3fg62trWijh/lYEj6vps4o8JcBBpDA=="], - "postcss-custom-properties": ["postcss-custom-properties@15.0.1", "", { "dependencies": { "@csstools/cascade-layer-name-parser": "^3.0.0", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0", "@csstools/utilities": "^3.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-cuyq8sd8dLY0GLbelz1KB8IMIoDECo6RVXMeHeXY2Uw3Q05k/d1GVITdaKLsheqrHbnxlwxzSRZQQ5u+rNtbMg=="], + "postcss-custom-properties": ["postcss-custom-properties@13.3.4", "", { "dependencies": { "@csstools/cascade-layer-name-parser": "^1.0.7", "@csstools/css-parser-algorithms": "^2.5.0", "@csstools/css-tokenizer": "^2.2.3", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-9YN0gg9sG3OH+Z9xBrp2PWRb+O4msw+5Sbp3ZgqrblrwKspXVQe5zr5sVqi43gJGwW/Rv1A483PRQUzQOEewvA=="], - "postcss-custom-selectors": ["postcss-custom-selectors@9.0.1", "", { "dependencies": { "@csstools/cascade-layer-name-parser": "^3.0.0", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0", "postcss-selector-parser": "^7.1.1" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-2XBELy4DmdVKimChfaZ2id9u9CSGYQhiJ53SvlfBvMTzLMW2VxuMb9rHsMSQw9kRq/zSbhT5x13EaK8JSmK8KQ=="], + "postcss-custom-selectors": ["postcss-custom-selectors@7.1.6", "", { "dependencies": { "@csstools/cascade-layer-name-parser": "^1.0.5", "@csstools/css-parser-algorithms": "^2.3.2", "@csstools/css-tokenizer": "^2.2.1", "postcss-selector-parser": "^6.0.13" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-svsjWRaxqL3vAzv71dV0/65P24/FB8TbPX+lWyyf9SZ7aZm4S4NhCn7N3Bg+Z5sZunG3FS8xQ80LrCU9hb37cw=="], - "postcss-dir-pseudo-class": ["postcss-dir-pseudo-class@10.0.0", "", { "dependencies": { "postcss-selector-parser": "^7.1.1" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-DmtIzULpyC8XaH4b5AaUgt4Jic4QmrECqidNCdR7u7naQFdnxX80YI06u238a+ZVRXwURDxVzy0s/UQnWmpVeg=="], + "postcss-dir-pseudo-class": ["postcss-dir-pseudo-class@7.0.2", "", { "dependencies": { "postcss-selector-parser": "^6.0.10" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-cMnslilYxBf9k3qejnovrUONZx1rXeUZJw06fgIUBzABJe3D2LiLL5WAER7Imt3nrkaIgG05XZBztueLEf5P8w=="], "postcss-discard-comments": ["postcss-discard-comments@5.1.2", "", { "peerDependencies": { "postcss": "^8.2.15" } }, "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ=="], @@ -4095,27 +3813,31 @@ "postcss-discard-overridden": ["postcss-discard-overridden@5.1.0", "", { "peerDependencies": { "postcss": "^8.2.15" } }, "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw=="], - "postcss-double-position-gradients": ["postcss-double-position-gradients@7.0.0", "", { "dependencies": { "@csstools/postcss-progressive-custom-properties": "^5.0.0", "@csstools/utilities": "^3.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-Msr/dxj8Os7KLJE5Hdhvprwm3K5Zrh1KTY0eFN3ngPKNkej/Usy4BM9JQmqE6CLAkDpHoQVsi4snbL72CPt6qg=="], + "postcss-double-position-gradients": ["postcss-double-position-gradients@4.0.4", "", { "dependencies": { "@csstools/postcss-progressive-custom-properties": "^2.3.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-nUAbUXURemLXIrl4Xoia2tiu5z/n8sY+BVDZApoeT9BlpByyrp02P/lFCRrRvZ/zrGRE+MOGLhk8o7VcMCtPtQ=="], - "postcss-focus-visible": ["postcss-focus-visible@11.0.0", "", { "dependencies": { "postcss-selector-parser": "^7.1.1" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-VG1a9kBKizUBWS66t5xyB4uLONBnvZLCmZXxT40FALu8EF0QgVZBYy5ApC0KhmpHsv+pvHMJHB3agKHwmocWjw=="], + "postcss-focus-visible": ["postcss-focus-visible@8.0.2", "", { "dependencies": { "postcss-selector-parser": "^6.0.10" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-f/Vd+EC/GaKElknU59esVcRYr/Y3t1ZAQyL4u2xSOgkDy4bMCmG7VP5cGvj3+BTLNE9ETfEuz2nnt4qkZwTTeA=="], - "postcss-focus-within": ["postcss-focus-within@10.0.0", "", { "dependencies": { "postcss-selector-parser": "^7.1.1" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-dvql0fzUTG+gcJYp+KTbag5vAjuo94LDYZHkqDV1rnf5gPGer1v/SrmIZBdvKU8moep3HbcbujqGjzSb3DL53Q=="], + "postcss-focus-within": ["postcss-focus-within@7.0.2", "", { "dependencies": { "postcss-selector-parser": "^6.0.10" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-AHAJ89UQBcqBvFgQJE9XasGuwMNkKsGj4D/f9Uk60jFmEBHpAL14DrnSk3Rj+SwZTr/WUG+mh+Rvf8fid/346w=="], "postcss-font-variant": ["postcss-font-variant@5.0.0", "", { "peerDependencies": { "postcss": "^8.1.0" } }, "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA=="], - "postcss-gap-properties": ["postcss-gap-properties@7.0.0", "", { "peerDependencies": { "postcss": "^8.4" } }, "sha512-PSDF2QoZMRUbsINvXObQgxx4HExRP85QTT8qS/YN9fBsCPWCqUuwqAD6E6PNp0BqL/jU1eyWUBORaOK/J/9LDA=="], + "postcss-gap-properties": ["postcss-gap-properties@4.0.1", "", { "peerDependencies": { "postcss": "^8.4" } }, "sha512-V5OuQGw4lBumPlwHWk/PRfMKjaq/LTGR4WDTemIMCaMevArVfCCA9wBJiL1VjDAd+rzuCIlkRoRvDsSiAaZ4Fg=="], - "postcss-image-set-function": ["postcss-image-set-function@8.0.0", "", { "dependencies": { "@csstools/utilities": "^3.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-rEGNkOkNusf4+IuMmfEoIdLuVmvbExGbmG+MIsyV6jR5UaWSoyPcAYHV/PxzVDCmudyF+2Nh/o6Ub2saqUdnuA=="], + "postcss-image-set-function": ["postcss-image-set-function@5.0.2", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-Sszjwo0ubETX0Fi5MvpYzsONwrsjeabjMoc5YqHvURFItXgIu3HdCjcVuVKGMPGzKRhgaknmdM5uVWInWPJmeg=="], "postcss-import": ["postcss-import@15.1.0", "", { "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", "resolve": "^1.1.7" }, "peerDependencies": { "postcss": "^8.0.0" } }, "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew=="], + "postcss-initial": ["postcss-initial@4.0.1", "", { "peerDependencies": { "postcss": "^8.0.0" } }, "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ=="], + "postcss-js": ["postcss-js@4.0.1", "", { "dependencies": { "camelcase-css": "^2.0.1" }, "peerDependencies": { "postcss": "^8.4.21" } }, "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw=="], - "postcss-lab-function": ["postcss-lab-function@8.0.2", "", { "dependencies": { "@csstools/css-color-parser": "^4.0.2", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0", "@csstools/postcss-progressive-custom-properties": "^5.0.0", "@csstools/utilities": "^3.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-1ZIAh8ODhZdnAb09Aq2BTenePKS1G/kUR0FwvzkQDfFtSOV64Ycv27YvV11fDycEvhIcEmgYkLABXKRiWcXRuA=="], + "postcss-lab-function": ["postcss-lab-function@5.2.3", "", { "dependencies": { "@csstools/css-color-parser": "^1.2.0", "@csstools/css-parser-algorithms": "^2.1.1", "@csstools/css-tokenizer": "^2.1.1", "@csstools/postcss-progressive-custom-properties": "^2.3.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-fi32AYKzji5/rvgxo5zXHFvAYBw0u0OzELbeCNjEZVLUir18Oj+9RmNphtM8QdLUaUnrfx8zy8vVYLmFLkdmrQ=="], "postcss-load-config": ["postcss-load-config@3.1.4", "", { "dependencies": { "lilconfig": "^2.0.5", "yaml": "^1.10.2" }, "peerDependencies": { "postcss": ">=8.0.9", "ts-node": ">=9.0.0" } }, "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg=="], - "postcss-logical": ["postcss-logical@9.0.0", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-A4LNd9dk3q/juEUA9Gd8ALhBO3TeOeYurnyHLlf2aAToD94VHR8c5Uv7KNmf8YVRhTxvWsyug4c5fKtARzyIRQ=="], + "postcss-loader": ["postcss-loader@7.3.4", "", { "dependencies": { "cosmiconfig": "^8.3.5", "jiti": "^1.20.0", "semver": "^7.5.4" }, "peerDependencies": { "postcss": "^7.0.0 || ^8.0.1", "webpack": "^5.0.0" } }, "sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A=="], + + "postcss-logical": ["postcss-logical@6.2.0", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-aqlfKGaY0nnbgI9jwUikp4gJKBqcH5noU/EdnIVceghaaDPYhZuyJVxlvWNy55tlTG5tunRKCTAX9yljLiFgmw=="], "postcss-merge-longhand": ["postcss-merge-longhand@5.1.7", "", { "dependencies": { "postcss-value-parser": "^4.2.0", "stylehacks": "^5.1.1" }, "peerDependencies": { "postcss": "^8.2.15" } }, "sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ=="], @@ -4141,7 +3863,7 @@ "postcss-nested": ["postcss-nested@6.0.1", "", { "dependencies": { "postcss-selector-parser": "^6.0.11" }, "peerDependencies": { "postcss": "^8.2.14" } }, "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ=="], - "postcss-nesting": ["postcss-nesting@14.0.0", "", { "dependencies": { "@csstools/selector-resolve-nested": "^4.0.0", "@csstools/selector-specificity": "^6.0.0", "postcss-selector-parser": "^7.1.1" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-YGFOfVrjxYfeGTS5XctP1WCI5hu8Lr9SmntjfRC+iX5hCihEO+QZl9Ra+pkjqkgoVdDKvb2JccpElcowhZtzpw=="], + "postcss-nesting": ["postcss-nesting@11.3.0", "", { "dependencies": { "@csstools/selector-specificity": "^2.0.0", "postcss-selector-parser": "^6.0.10" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-JlS10AQm/RzyrUGgl5irVkAlZYTJ99mNueUl+Qab+TcHhVedLiylWVkKBhRale+rS9yWIJK48JVzQlq3LcSdeA=="], "postcss-normalize-charset": ["postcss-normalize-charset@5.1.0", "", { "peerDependencies": { "postcss": "^8.2.15" } }, "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg=="], @@ -4161,19 +3883,19 @@ "postcss-normalize-whitespace": ["postcss-normalize-whitespace@5.1.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.2.15" } }, "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA=="], - "postcss-opacity-percentage": ["postcss-opacity-percentage@3.0.0", "", { "peerDependencies": { "postcss": "^8.4" } }, "sha512-K6HGVzyxUxd/VgZdX04DCtdwWJ4NGLG212US4/LA1TLAbHgmAsTWVR86o+gGIbFtnTkfOpb9sCRBx8K7HO66qQ=="], + "postcss-opacity-percentage": ["postcss-opacity-percentage@2.0.0", "", { "peerDependencies": { "postcss": "^8.2" } }, "sha512-lyDrCOtntq5Y1JZpBFzIWm2wG9kbEdujpNt4NLannF+J9c8CgFIzPa80YQfdza+Y+yFfzbYj/rfoOsYsooUWTQ=="], "postcss-ordered-values": ["postcss-ordered-values@5.1.3", "", { "dependencies": { "cssnano-utils": "^3.1.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.2.15" } }, "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ=="], - "postcss-overflow-shorthand": ["postcss-overflow-shorthand@7.0.0", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-9SLpjoUdGRoRrzoOdX66HbUs0+uDwfIAiXsRa7piKGOqPd6F4ZlON9oaDSP5r1Qpgmzw5L9Ht0undIK6igJPMA=="], + "postcss-overflow-shorthand": ["postcss-overflow-shorthand@4.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-HQZ0qi/9iSYHW4w3ogNqVNr2J49DHJAl7r8O2p0Meip38jsdnRPgiDW7r/LlLrrMBMe3KHkvNtAV2UmRVxzLIg=="], "postcss-page-break": ["postcss-page-break@3.0.4", "", { "peerDependencies": { "postcss": "^8" } }, "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ=="], - "postcss-place": ["postcss-place@11.0.0", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-fAifpyjQ+fuDRp2nmF95WbotqbpjdazebedahXdfBxy5sHembOLpBQ1cHveZD9ZmjK26tYM8tikeNaUlp/KfHA=="], + "postcss-place": ["postcss-place@8.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-Ow2LedN8sL4pq8ubukO77phSVt4QyCm35ZGCYXKvRFayAwcpgB0sjNJglDoTuRdUL32q/ZC1VkPBo0AOEr4Uiw=="], - "postcss-preset-env": ["postcss-preset-env@11.2.0", "", { "dependencies": { "@csstools/postcss-alpha-function": "^2.0.3", "@csstools/postcss-cascade-layers": "^6.0.0", "@csstools/postcss-color-function": "^5.0.2", "@csstools/postcss-color-function-display-p3-linear": "^2.0.2", "@csstools/postcss-color-mix-function": "^4.0.2", "@csstools/postcss-color-mix-variadic-function-arguments": "^2.0.2", "@csstools/postcss-content-alt-text": "^3.0.0", "@csstools/postcss-contrast-color-function": "^3.0.2", "@csstools/postcss-exponential-functions": "^3.0.1", "@csstools/postcss-font-format-keywords": "^5.0.0", "@csstools/postcss-font-width-property": "^1.0.0", "@csstools/postcss-gamut-mapping": "^3.0.2", "@csstools/postcss-gradients-interpolation-method": "^6.0.2", "@csstools/postcss-hwb-function": "^5.0.2", "@csstools/postcss-ic-unit": "^5.0.0", "@csstools/postcss-initial": "^3.0.0", "@csstools/postcss-is-pseudo-class": "^6.0.0", "@csstools/postcss-light-dark-function": "^3.0.0", "@csstools/postcss-logical-float-and-clear": "^4.0.0", "@csstools/postcss-logical-overflow": "^3.0.0", "@csstools/postcss-logical-overscroll-behavior": "^3.0.0", "@csstools/postcss-logical-resize": "^4.0.0", "@csstools/postcss-logical-viewport-units": "^4.0.0", "@csstools/postcss-media-minmax": "^3.0.1", "@csstools/postcss-media-queries-aspect-ratio-number-values": "^4.0.0", "@csstools/postcss-mixins": "^1.0.0", "@csstools/postcss-nested-calc": "^5.0.0", "@csstools/postcss-normalize-display-values": "^5.0.1", "@csstools/postcss-oklab-function": "^5.0.2", "@csstools/postcss-position-area-property": "^2.0.0", "@csstools/postcss-progressive-custom-properties": "^5.0.0", "@csstools/postcss-property-rule-prelude-list": "^2.0.0", "@csstools/postcss-random-function": "^3.0.1", "@csstools/postcss-relative-color-syntax": "^4.0.2", "@csstools/postcss-scope-pseudo-class": "^5.0.0", "@csstools/postcss-sign-functions": "^2.0.1", "@csstools/postcss-stepped-value-functions": "^5.0.1", "@csstools/postcss-syntax-descriptor-syntax-production": "^2.0.0", "@csstools/postcss-system-ui-font-family": "^2.0.0", "@csstools/postcss-text-decoration-shorthand": "^5.0.3", "@csstools/postcss-trigonometric-functions": "^5.0.1", "@csstools/postcss-unset-value": "^5.0.0", "autoprefixer": "^10.4.24", "browserslist": "^4.28.1", "css-blank-pseudo": "^8.0.1", "css-has-pseudo": "^8.0.0", "css-prefers-color-scheme": "^11.0.0", "cssdb": "^8.8.0", "postcss-attribute-case-insensitive": "^8.0.0", "postcss-clamp": "^4.1.0", "postcss-color-functional-notation": "^8.0.2", "postcss-color-hex-alpha": "^11.0.0", "postcss-color-rebeccapurple": "^11.0.0", "postcss-custom-media": "^12.0.1", "postcss-custom-properties": "^15.0.1", "postcss-custom-selectors": "^9.0.1", "postcss-dir-pseudo-class": "^10.0.0", "postcss-double-position-gradients": "^7.0.0", "postcss-focus-visible": "^11.0.0", "postcss-focus-within": "^10.0.0", "postcss-font-variant": "^5.0.0", "postcss-gap-properties": "^7.0.0", "postcss-image-set-function": "^8.0.0", "postcss-lab-function": "^8.0.2", "postcss-logical": "^9.0.0", "postcss-nesting": "^14.0.0", "postcss-opacity-percentage": "^3.0.0", "postcss-overflow-shorthand": "^7.0.0", "postcss-page-break": "^3.0.4", "postcss-place": "^11.0.0", "postcss-pseudo-class-any-link": "^11.0.0", "postcss-replace-overflow-wrap": "^4.0.0", "postcss-selector-not": "^9.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-eNYpuj68cjGjvZMoSAbHilaCt3yIyzBL1cVuSGJfvJewsaBW/U6dI2bqCJl3iuZsL+yvBobcy4zJFA/3I68IHQ=="], + "postcss-preset-env": ["postcss-preset-env@8.5.1", "", { "dependencies": { "@csstools/postcss-cascade-layers": "^3.0.1", "@csstools/postcss-color-function": "^2.2.3", "@csstools/postcss-color-mix-function": "^1.0.3", "@csstools/postcss-font-format-keywords": "^2.0.2", "@csstools/postcss-gradients-interpolation-method": "^3.0.6", "@csstools/postcss-hwb-function": "^2.2.2", "@csstools/postcss-ic-unit": "^2.0.4", "@csstools/postcss-is-pseudo-class": "^3.2.1", "@csstools/postcss-logical-float-and-clear": "^1.0.1", "@csstools/postcss-logical-resize": "^1.0.1", "@csstools/postcss-logical-viewport-units": "^1.0.3", "@csstools/postcss-media-minmax": "^1.0.4", "@csstools/postcss-media-queries-aspect-ratio-number-values": "^1.0.4", "@csstools/postcss-nested-calc": "^2.0.2", "@csstools/postcss-normalize-display-values": "^2.0.1", "@csstools/postcss-oklab-function": "^2.2.3", "@csstools/postcss-progressive-custom-properties": "^2.3.0", "@csstools/postcss-relative-color-syntax": "^1.0.2", "@csstools/postcss-scope-pseudo-class": "^2.0.2", "@csstools/postcss-stepped-value-functions": "^2.1.1", "@csstools/postcss-text-decoration-shorthand": "^2.2.4", "@csstools/postcss-trigonometric-functions": "^2.1.1", "@csstools/postcss-unset-value": "^2.0.1", "autoprefixer": "^10.4.14", "browserslist": "^4.21.9", "css-blank-pseudo": "^5.0.2", "css-has-pseudo": "^5.0.2", "css-prefers-color-scheme": "^8.0.2", "cssdb": "^7.6.0", "postcss-attribute-case-insensitive": "^6.0.2", "postcss-clamp": "^4.1.0", "postcss-color-functional-notation": "^5.1.0", "postcss-color-hex-alpha": "^9.0.2", "postcss-color-rebeccapurple": "^8.0.2", "postcss-custom-media": "^9.1.5", "postcss-custom-properties": "^13.2.0", "postcss-custom-selectors": "^7.1.3", "postcss-dir-pseudo-class": "^7.0.2", "postcss-double-position-gradients": "^4.0.4", "postcss-focus-visible": "^8.0.2", "postcss-focus-within": "^7.0.2", "postcss-font-variant": "^5.0.0", "postcss-gap-properties": "^4.0.1", "postcss-image-set-function": "^5.0.2", "postcss-initial": "^4.0.1", "postcss-lab-function": "^5.2.3", "postcss-logical": "^6.2.0", "postcss-nesting": "^11.3.0", "postcss-opacity-percentage": "^2.0.0", "postcss-overflow-shorthand": "^4.0.1", "postcss-page-break": "^3.0.4", "postcss-place": "^8.0.1", "postcss-pseudo-class-any-link": "^8.0.2", "postcss-replace-overflow-wrap": "^4.0.0", "postcss-selector-not": "^7.0.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-qhWnJJjP6ArLUINWJ38t6Aftxnv9NW6cXK0NuwcLCcRilbuw72dSFLkCVUJeCfHGgJiKzX+pnhkGiki0PEynWg=="], - "postcss-pseudo-class-any-link": ["postcss-pseudo-class-any-link@11.0.0", "", { "dependencies": { "postcss-selector-parser": "^7.1.1" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-DNFZ4GMa3C3pU5dM+UCTG1CEeLtS1ZqV5DKSqCTJQMn1G5jnd/30fS8+A7H4o5bSD3MOcnx+VgI+xPE9Z5Wvig=="], + "postcss-pseudo-class-any-link": ["postcss-pseudo-class-any-link@8.0.2", "", { "dependencies": { "postcss-selector-parser": "^6.0.10" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-FYTIuRE07jZ2CW8POvctRgArQJ43yxhr5vLmImdKUvjFCkR09kh8pIdlCwdx/jbFm7MiW4QP58L4oOUv3grQYA=="], "postcss-reduce-initial": ["postcss-reduce-initial@5.1.2", "", { "dependencies": { "browserslist": "^4.21.4", "caniuse-api": "^3.0.0" }, "peerDependencies": { "postcss": "^8.2.15" } }, "sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg=="], @@ -4181,7 +3903,7 @@ "postcss-replace-overflow-wrap": ["postcss-replace-overflow-wrap@4.0.0", "", { "peerDependencies": { "postcss": "^8.0.3" } }, "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw=="], - "postcss-selector-not": ["postcss-selector-not@9.0.0", "", { "dependencies": { "postcss-selector-parser": "^7.1.1" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-xhAtTdHnVU2M/CrpYOPyRUvg3njhVlKmn2GNYXDaRJV9Ygx4d5OkSkc7NINzjUqnbDFtaKXlISOBeyMXU/zyFQ=="], + "postcss-selector-not": ["postcss-selector-not@7.0.1", "", { "dependencies": { "postcss-selector-parser": "^6.0.10" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-1zT5C27b/zeJhchN7fP0kBr16Cc61mu7Si9uWWLoA3Px/D9tIJPKchJCkUH3tPO5D0pCFmGeApAv8XpXBQJ8SQ=="], "postcss-selector-parser": ["postcss-selector-parser@6.0.15", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw=="], @@ -4215,7 +3937,7 @@ "property-information": ["property-information@6.4.1", "", {}, "sha512-OHYtXfu5aI2sS2LWFSN5rgJjrQ4pCy8i1jubJLe2QvMF8JJ++HXTUIVWFLfXJoaOfvYYjk2SN8J2wFUWIGXT4w=="], - "protobufjs": ["protobufjs@7.5.4", "", { "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-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], + "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=="], "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], @@ -4223,6 +3945,8 @@ "pseudomap": ["pseudomap@1.0.2", "", {}, "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ=="], + "psl": ["psl@1.9.0", "", {}, "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag=="], + "pstree.remy": ["pstree.remy@1.1.8", "", {}, "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w=="], "public-encrypt": ["public-encrypt@4.0.3", "", { "dependencies": { "bn.js": "^4.1.0", "browserify-rsa": "^4.0.0", "create-hash": "^1.1.0", "parse-asn1": "^5.0.0", "randombytes": "^2.0.1", "safe-buffer": "^5.1.2" } }, "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q=="], @@ -4237,6 +3961,8 @@ "querystring-es3": ["querystring-es3@0.2.1", "", {}, "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA=="], + "querystringify": ["querystringify@2.2.0", "", {}, "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="], + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], "random-bytes": ["random-bytes@1.0.0", "", {}, "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ=="], @@ -4277,21 +4003,23 @@ "react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="], + "react-lazy-load-image-component": ["react-lazy-load-image-component@1.6.0", "", { "dependencies": { "lodash.debounce": "^4.0.8", "lodash.throttle": "^4.1.1" }, "peerDependencies": { "react": "^15.x.x || ^16.x.x || ^17.x.x || ^18.x.x", "react-dom": "^15.x.x || ^16.x.x || ^17.x.x || ^18.x.x" } }, "sha512-8KFkDTgjh+0+PVbH+cx0AgxLGbdTsxWMnxXzU5HEUztqewk9ufQAu8cstjZhyvtMIPsdMcPZfA0WAa7HtjQbBQ=="], + "react-lifecycles-compat": ["react-lifecycles-compat@3.0.4", "", {}, "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="], "react-markdown": ["react-markdown@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "devlop": "^1.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "html-url-attributes": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" }, "peerDependencies": { "@types/react": ">=18", "react": ">=18" } }, "sha512-186Gw/vF1uRkydbsOIkcGXw7aHq0sZOCRFFjGrr7b9+nVZg4UfA4enXCaxm4fUzecU38sWfrNDitGhshuU7rdg=="], - "react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="], + "react-refresh": ["react-refresh@0.14.2", "", {}, "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA=="], - "react-remove-scroll": ["react-remove-scroll@2.5.5", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.3", "react-style-singleton": "^2.2.1", "tslib": "^2.1.0", "use-callback-ref": "^1.3.0", "use-sidecar": "^1.1.2" }, "peerDependencies": { "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw=="], + "react-remove-scroll": ["react-remove-scroll@2.7.1", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA=="], "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="], "react-resizable-panels": ["react-resizable-panels@3.0.6", "", { "peerDependencies": { "react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-b3qKHQ3MLqOgSS+FRYKapNkJZf5EQzuf6+RLiq1/IlTHw99YrZ2NJZLk4hQIzTnnIkRg2LUqyVinu6YWWpUYew=="], - "react-router": ["react-router@6.30.3", "", { "dependencies": { "@remix-run/router": "1.23.2" }, "peerDependencies": { "react": ">=16.8" } }, "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw=="], + "react-router": ["react-router@6.22.0", "", { "dependencies": { "@remix-run/router": "1.15.0" }, "peerDependencies": { "react": ">=16.8" } }, "sha512-q2yemJeg6gw/YixRlRnVx6IRJWZD6fonnfZhN1JIOhV2iJCPeRNSH3V1ISwHf+JWcESzLC3BOLD1T07tmO5dmg=="], - "react-router-dom": ["react-router-dom@6.30.3", "", { "dependencies": { "@remix-run/router": "1.23.2", "react-router": "6.30.3" }, "peerDependencies": { "react": ">=16.8", "react-dom": ">=16.8" } }, "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag=="], + "react-router-dom": ["react-router-dom@6.22.0", "", { "dependencies": { "@remix-run/router": "1.15.0", "react-router": "6.22.0" }, "peerDependencies": { "react": ">=16.8", "react-dom": ">=16.8" } }, "sha512-z2w+M4tH5wlcLmH3BMMOMdrtrJ9T3oJJNsAlBJbwk+8Syxd5WFJ7J5dxMEW0/GEXD1BBis4uXRrNIz3mORr0ag=="], "react-speech-recognition": ["react-speech-recognition@3.10.0", "", { "peerDependencies": { "react": ">=16.8.0" } }, "sha512-EVSr4Ik8l9urwdPiK2r0+ADrLyDDrjB0qBRdUWO+w2MfwEBrj6NuRmy1GD3x7BU/V6/hab0pl8Lupen0zwlJyw=="], @@ -4305,7 +4033,9 @@ "read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="], - "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=="], + "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=="], @@ -4327,6 +4057,8 @@ "regenerator-runtime": ["regenerator-runtime@0.14.1", "", {}, "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="], + "regenerator-transform": ["regenerator-transform@0.15.2", "", { "dependencies": { "@babel/runtime": "^7.8.4" } }, "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg=="], + "regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="], "regexpu-core": ["regexpu-core@6.2.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.0", "regjsgen": "^0.8.0", "regjsparser": "^0.12.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.1.0" } }, "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA=="], @@ -4365,6 +4097,8 @@ "requireindex": ["requireindex@1.1.0", "", {}, "sha512-LBnkqsDE7BZKvqylbmn7lTIVdpx4K/QCduRATpO5R+wtPmky/a8pN1bO2D6wXppn1497AJF9mNjqAXr6bdl9jg=="], + "requires-port": ["requires-port@1.0.0", "", {}, "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="], + "resolve": ["resolve@2.0.0-next.5", "", { "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": "bin/resolve" }, "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA=="], "resolve-cwd": ["resolve-cwd@3.0.0", "", { "dependencies": { "resolve-from": "^5.0.0" } }, "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg=="], @@ -4381,13 +4115,11 @@ "rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="], - "rimraf": ["rimraf@6.1.3", "", { "dependencies": { "glob": "^13.0.3", "package-json-from-dist": "^1.0.1" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA=="], + "rimraf": ["rimraf@6.1.2", "", { "dependencies": { "glob": "^13.0.0", "package-json-from-dist": "^1.0.1" }, "bin": "dist/esm/bin.mjs" }, "sha512-cFCkPslJv7BAXJsYlK1dZsbP8/ZNLkCAQ0bi1hf5EKX2QHegmDFEFA6QhuYJlk7UDdc+02JjO80YSOrWPpw06g=="], "ripemd160": ["ripemd160@2.0.2", "", { "dependencies": { "hash-base": "^3.0.0", "inherits": "^2.0.1" } }, "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA=="], - "robust-predicates": ["robust-predicates@3.0.2", "", {}, "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="], - - "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": ["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-plugin-peer-deps-external": ["rollup-plugin-peer-deps-external@2.2.4", "", { "peerDependencies": { "rollup": "*" } }, "sha512-AWdukIM1+k5JDdAqV/Cxd+nejvno2FVLVeZ74NKggm3Q5s9cbbcOgUPGdbxPi4BXu7xGaZ8HG12F+thImYu/0g=="], @@ -4397,8 +4129,6 @@ "rollup-pluginutils": ["rollup-pluginutils@2.8.2", "", { "dependencies": { "estree-walker": "^0.6.1" } }, "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ=="], - "roughjs": ["roughjs@4.6.6", "", { "dependencies": { "hachure-fill": "^0.5.2", "path-data-parser": "^0.1.0", "points-on-curve": "^0.2.0", "points-on-path": "^0.2.1" } }, "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ=="], - "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], "rrweb-cssom": ["rrweb-cssom@0.8.0", "", {}, "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw=="], @@ -4407,8 +4137,6 @@ "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], - "rw": ["rw@1.3.3", "", {}, "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="], - "safe-array-concat": ["safe-array-concat@1.1.3", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="], "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], @@ -4429,13 +4157,15 @@ "scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], + "schema-utils": ["schema-utils@3.3.0", "", { "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", "ajv-keywords": "^3.5.2" } }, "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg=="], + "seedrandom": ["seedrandom@3.0.5", "", {}, "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg=="], "semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "send": ["send@1.2.0", "", { "dependencies": { "debug": "^4.3.5", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.0", "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.1" } }, "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw=="], - "serialize-javascript": ["serialize-javascript@7.0.4", "", {}, "sha512-DuGdB+Po43Q5Jxwpzt1lhyFSYKryqoNjQSA9M92tyw0lyHIOur+XCalOUe0KTJpyqzT8+fQ5A0Jf7vCx/NKmIg=="], + "serialize-javascript": ["serialize-javascript@6.0.2", "", { "dependencies": { "randombytes": "^2.1.0" } }, "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g=="], "serve-static": ["serve-static@2.2.0", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ=="], @@ -4507,8 +4237,6 @@ "standard-as-callback": ["standard-as-callback@2.1.0", "", {}, "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="], - "state-local": ["state-local@1.0.7", "", {}, "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w=="], - "static-browser-server": ["static-browser-server@1.0.3", "", { "dependencies": { "@open-draft/deferred-promise": "^2.1.0", "dotenv": "^16.0.3", "mime-db": "^1.52.0", "outvariant": "^1.3.0" } }, "sha512-ZUyfgGDdFRbZGGJQ1YhiM930Yczz5VlbJObrQLlk24+qNHVQx4OlLcYswEUo3bIyNAbQUIUR9Yr5/Hqjzqb4zA=="], "statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="], @@ -4569,9 +4297,9 @@ "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], - "strnum": ["strnum@2.2.0", "", {}, "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg=="], + "strnum": ["strnum@1.0.5", "", {}, "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA=="], - "strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="], + "strtok3": ["strtok3@7.0.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^5.0.0" } }, "sha512-pQ+V+nYQdC5H3Q7qBZAz/MO6lwGhoC2gOAjuouGf/VO0m7vQRh8QNMl2Uf6SwAtzZ9bOw3UIeBukEGNJl5dtXQ=="], "style-inject": ["style-inject@0.3.0", "", {}, "sha512-IezA2qp+vcdlhJaVm5SOdPPTUu0FCEqfNSli2vRuSIBbu5Nq5UvygTk/VzeCqfLz2Atj3dVII5QBKGZRZ0edzw=="], @@ -4581,8 +4309,6 @@ "stylehacks": ["stylehacks@5.1.1", "", { "dependencies": { "browserslist": "^4.21.4", "postcss-selector-parser": "^6.0.4" }, "peerDependencies": { "postcss": "^8.2.15" } }, "sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw=="], - "stylis": ["stylis@4.3.6", "", {}, "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ=="], - "sucrase": ["sucrase@3.35.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA=="], "superagent": ["superagent@9.0.2", "", { "dependencies": { "component-emitter": "^1.3.0", "cookiejar": "^2.1.4", "debug": "^4.3.4", "fast-safe-stringify": "^2.1.1", "form-data": "^4.0.0", "formidable": "^3.5.1", "methods": "^1.1.2", "mime": "2.6.0", "qs": "^6.11.0" } }, "sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w=="], @@ -4595,9 +4321,7 @@ "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], - "svgo": ["svgo@2.8.2", "", { "dependencies": { "commander": "^7.2.0", "css-select": "^4.1.3", "css-tree": "^1.1.3", "csso": "^4.2.0", "picocolors": "^1.0.0", "sax": "^1.5.0", "stable": "^0.1.8" }, "bin": "./bin/svgo" }, "sha512-TyzE4NVGLUFy+H/Uy4N6c3G0HEeprsVfge6Lmq+0FdQQ/zqoVYB62IsBZORsiL+o96s6ff/V6/3UQo/C0cgCAA=="], - - "swr": ["swr@2.4.1", "", { "dependencies": { "dequal": "^2.0.3", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-2CC6CiKQtEwaEeNiqWTAw9PGykW8SR5zZX8MZk6TeAvEAnVS7Visz8WzphqgtQ8v2xz/4Q5K+j+SeMaKXeeQIA=="], + "svgo": ["svgo@2.8.0", "", { "dependencies": { "@trysound/sax": "0.2.0", "commander": "^7.2.0", "css-select": "^4.1.3", "css-tree": "^1.1.3", "csso": "^4.2.0", "picocolors": "^1.0.0", "stable": "^0.1.8" }, "bin": "bin/svgo" }, "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg=="], "symbol-tree": ["symbol-tree@3.2.4", "", {}, "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="], @@ -4625,6 +4349,8 @@ "terser": ["terser@5.27.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": "bin/terser" }, "sha512-bi1HRwVRskAjheeYl291n3JC4GgO/Ty4z1nVs5AAsmonJulGxpSektecnNedrwK9C7vpvVtcX3cw00VSLt7U2A=="], + "terser-webpack-plugin": ["terser-webpack-plugin@5.3.10", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.20", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", "serialize-javascript": "^6.0.1", "terser": "^5.26.0" }, "peerDependencies": { "webpack": "^5.1.0" } }, "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w=="], + "test-exclude": ["test-exclude@6.0.0", "", { "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", "minimatch": "^3.0.4" } }, "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w=="], "text-decoder": ["text-decoder@1.2.3", "", { "dependencies": { "b4a": "^1.6.4" } }, "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA=="], @@ -4635,13 +4361,13 @@ "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=="], - "tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], - - "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + "tinyglobby": ["tinyglobby@0.2.13", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw=="], "tldts": ["tldts@6.1.86", "", { "dependencies": { "tldts-core": "^6.1.86" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ=="], @@ -4657,7 +4383,7 @@ "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], - "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=="], + "token-types": ["token-types@5.0.1", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg=="], "touch": ["touch@3.1.0", "", { "dependencies": { "nopt": "~1.0.10" }, "bin": { "nodetouch": "bin/nodetouch.js" } }, "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA=="], @@ -4677,12 +4403,8 @@ "ts-api-utils": ["ts-api-utils@2.0.1", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w=="], - "ts-dedent": ["ts-dedent@2.2.0", "", {}, "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ=="], - "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], - "ts-md5": ["ts-md5@1.3.1", "", {}, "sha512-DiwiXfwvcTeZ5wCE0z+2A9EseZsztaiZtGrtSaY5JOD7ekPnR/GoIVD5gXZAlK9Na9Kvpo9Waz5rW64WKAWApg=="], - "ts-node": ["ts-node@10.9.2", "", { "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", "@tsconfig/node12": "^1.0.7", "@tsconfig/node14": "^1.0.0", "@tsconfig/node16": "^1.0.2", "acorn": "^8.4.1", "acorn-walk": "^8.1.1", "arg": "^4.1.0", "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", "v8-compile-cache-lib": "^3.0.1", "yn": "3.1.1" }, "peerDependencies": { "@swc/core": ">=1.2.50", "@swc/wasm": ">=1.2.50", "@types/node": "*", "typescript": ">=2.7" }, "optionalPeers": ["@swc/core", "@swc/wasm"], "bin": { "ts-node": "dist/bin.js", "ts-node-cwd": "dist/bin-cwd.js", "ts-node-esm": "dist/bin-esm.js", "ts-node-script": "dist/bin-script.js", "ts-node-transpile-only": "dist/bin-transpile.js", "ts-script": "dist/bin-script-deprecated.js" } }, "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ=="], "tsconfig-paths": ["tsconfig-paths@3.15.0", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg=="], @@ -4691,20 +4413,6 @@ "tty-browserify": ["tty-browserify@0.0.1", "", {}, "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw=="], - "turbo": ["turbo@2.8.14", "", { "optionalDependencies": { "turbo-darwin-64": "2.8.14", "turbo-darwin-arm64": "2.8.14", "turbo-linux-64": "2.8.14", "turbo-linux-arm64": "2.8.14", "turbo-windows-64": "2.8.14", "turbo-windows-arm64": "2.8.14" }, "bin": { "turbo": "bin/turbo" } }, "sha512-UCTxeMNYT1cKaHiIFdLCQ7ulI+jw5i5uOnJOrRXsgUD7G3+OjlUjwVd7JfeVt2McWSVGjYA3EVW/v1FSsJ5DtA=="], - - "turbo-darwin-64": ["turbo-darwin-64@2.8.14", "", { "os": "darwin", "cpu": "x64" }, "sha512-9sFi7n2lLfEsGWi5OEoA/eTtQU2BPKtzSYKqufMtDeRmqMT9vKjbv9gJCRkllSVE9BOXA0qXC3diyX8V8rKIKw=="], - - "turbo-darwin-arm64": ["turbo-darwin-arm64@2.8.14", "", { "os": "darwin", "cpu": "arm64" }, "sha512-aS4yJuy6A1PCLws+PJpZP0qCURG8Y5iVx13z/WAbKyeDTY6W6PiGgcEllSaeLGxyn++382ztN/EZH85n2zZ6VQ=="], - - "turbo-linux-64": ["turbo-linux-64@2.8.14", "", { "os": "linux", "cpu": "x64" }, "sha512-XC6wPUDJkakjhNLaS0NrHDMiujRVjH+naEAwvKLArgqRaFkNxjmyNDRM4eu3soMMFmjym6NTxYaF74rvET+Orw=="], - - "turbo-linux-arm64": ["turbo-linux-arm64@2.8.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-ChfE7isyVNjZrVSPDwcfqcHLG/FuIBbOFxnt1FM8vSuBGzHAs8AlTdwFNIxlEMJfZ8Ad9mdMxdmsCUPIWiQ6cg=="], - - "turbo-windows-64": ["turbo-windows-64@2.8.14", "", { "os": "win32", "cpu": "x64" }, "sha512-FTbIeQL1ycLFW2t9uQNMy+bRSzi3Xhwun/e7ZhFBdM+U0VZxxrtfYEBM9CHOejlfqomk6Jh7aRz0sJoqYn39Hg=="], - - "turbo-windows-arm64": ["turbo-windows-arm64@2.8.14", "", { "os": "win32", "cpu": "arm64" }, "sha512-KgZX12cTyhY030qS7ieT8zRkhZZE2VWJasDFVUSVVn17nR7IShpv68/7j5UqJNeRLIGF1XPK0phsP5V5yw3how=="], - "type": ["type@2.7.3", "", {}, "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ=="], "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], @@ -4733,25 +4441,19 @@ "ua-parser-js": ["ua-parser-js@1.0.37", "", {}, "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ=="], - "ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="], - "uglify-js": ["uglify-js@3.17.4", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g=="], "uid-safe": ["uid-safe@2.1.5", "", { "dependencies": { "random-bytes": "~1.0.0" } }, "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA=="], "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.16.0", "", {}, "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g=="], - "undici": ["undici@7.24.4", "", {}, "sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w=="], - - "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + "undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], "unicode-canonical-property-names-ecmascript": ["unicode-canonical-property-names-ecmascript@2.0.1", "", {}, "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg=="], @@ -4787,12 +4489,16 @@ "upath": ["upath@1.2.0", "", {}, "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg=="], - "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + "update-browserslist-db": ["update-browserslist-db@1.1.2", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": "cli.js" }, "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg=="], "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], "url": ["url@0.11.4", "", { "dependencies": { "punycode": "^1.4.1", "qs": "^6.12.3" } }, "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg=="], + "url-parse": ["url-parse@1.5.10", "", { "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" } }, "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ=="], + + "url-template": ["url-template@2.0.8", "", {}, "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw=="], + "use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="], "use-composed-ref": ["use-composed-ref@1.3.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, "sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ=="], @@ -4829,42 +4535,36 @@ "vfile-message": ["vfile-message@4.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw=="], - "vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="], + "vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "tsx"], "bin": "bin/vite.js" }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="], "vite-plugin-compression2": ["vite-plugin-compression2@2.2.1", "", { "dependencies": { "@rollup/pluginutils": "^5.1.0", "tar-mini": "^0.2.0" } }, "sha512-LMDkgheJaFBmb8cB8ymgUpXHXnd3m4kmjEInvp59fOZMSaT/9oDUtqpO0ihr4ExGsnWfYcRe13/TNN3BEk2t/g=="], - "vite-plugin-node-polyfills": ["vite-plugin-node-polyfills@0.25.0", "", { "dependencies": { "@rollup/plugin-inject": "^5.0.5", "node-stdlib-browser": "^1.3.1" }, "peerDependencies": { "vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-rHZ324W3LhfGPxWwQb2N048TThB6nVvnipsqBUJEzh3R9xeK9KI3si+GMQxCuAcpPJBVf0LpDtJ+beYzB3/chg=="], + "vite-plugin-node-polyfills": ["vite-plugin-node-polyfills@0.23.0", "", { "dependencies": { "@rollup/plugin-inject": "^5.0.5", "node-stdlib-browser": "^1.2.0" }, "peerDependencies": { "vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" } }, "sha512-4n+Ys+2bKHQohPBKigFlndwWQ5fFKwaGY6muNDMTb0fSQLyBzS+jjUNRZG9sKF0S/Go4ApG6LFnUGopjkILg3w=="], - "vite-plugin-pwa": ["vite-plugin-pwa@1.2.0", "", { "dependencies": { "debug": "^4.3.6", "pretty-bytes": "^6.1.1", "tinyglobby": "^0.2.10", "workbox-build": "^7.4.0", "workbox-window": "^7.4.0" }, "peerDependencies": { "@vite-pwa/assets-generator": "^1.0.0", "vite": "^3.1.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@vite-pwa/assets-generator"] }, "sha512-a2xld+SJshT9Lgcv8Ji4+srFJL4k/1bVbd1x06JIkvecpQkwkvCncD1+gSzcdm3s+owWLpMJerG3aN5jupJEVw=="], + "vite-plugin-pwa": ["vite-plugin-pwa@0.21.2", "", { "dependencies": { "debug": "^4.3.6", "pretty-bytes": "^6.1.1", "tinyglobby": "^0.2.10", "workbox-build": "^7.3.0", "workbox-window": "^7.3.0" }, "peerDependencies": { "@vite-pwa/assets-generator": "^0.2.6", "vite": "^3.1.0 || ^4.0.0 || ^5.0.0 || ^6.0.0", "workbox-build": "^7.3.0", "workbox-window": "^7.3.0" }, "optionalPeers": ["@vite-pwa/assets-generator"] }, "sha512-vFhH6Waw8itNu37hWUJxL50q+CBbNcMVzsKaYHQVrfxTt3ihk3PeLO22SbiP1UNWzcEPaTQv+YVxe4G0KOjAkg=="], "vm-browserify": ["vm-browserify@1.1.2", "", {}, "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ=="], "void-elements": ["void-elements@3.1.0", "", {}, "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w=="], - "vscode-jsonrpc": ["vscode-jsonrpc@8.2.0", "", {}, "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA=="], - - "vscode-languageserver": ["vscode-languageserver@9.0.1", "", { "dependencies": { "vscode-languageserver-protocol": "3.17.5" }, "bin": { "installServerIntoExtension": "bin/installServerIntoExtension" } }, "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g=="], - - "vscode-languageserver-protocol": ["vscode-languageserver-protocol@3.17.5", "", { "dependencies": { "vscode-jsonrpc": "8.2.0", "vscode-languageserver-types": "3.17.5" } }, "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg=="], - - "vscode-languageserver-textdocument": ["vscode-languageserver-textdocument@1.0.12", "", {}, "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA=="], - - "vscode-languageserver-types": ["vscode-languageserver-types@3.17.5", "", {}, "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg=="], - - "vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="], - "w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="], "w3c-xmlserializer": ["w3c-xmlserializer@5.0.0", "", { "dependencies": { "xml-name-validator": "^5.0.0" } }, "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA=="], "walker": ["walker@1.0.8", "", { "dependencies": { "makeerror": "1.0.12" } }, "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ=="], + "watchpack": ["watchpack@2.4.2", "", { "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" } }, "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw=="], + "web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="], "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + "webpack": ["webpack@5.94.0", "", { "dependencies": { "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", "@webassemblyjs/wasm-edit": "^1.12.1", "@webassemblyjs/wasm-parser": "^1.12.1", "acorn": "^8.7.1", "acorn-import-attributes": "^1.9.5", "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", "schema-utils": "^3.2.0", "tapable": "^2.1.1", "terser-webpack-plugin": "^5.3.10", "watchpack": "^2.4.1", "webpack-sources": "^3.2.3" }, "bin": "bin/webpack.js" }, "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg=="], + + "webpack-sources": ["webpack-sources@3.2.3", "", {}, "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w=="], + "websocket-driver": ["websocket-driver@0.7.4", "", { "dependencies": { "http-parser-js": ">=0.5.1", "safe-buffer": ">=5.1.0", "websocket-extensions": ">=0.1.1" } }, "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg=="], "websocket-extensions": ["websocket-extensions@0.1.4", "", {}, "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg=="], @@ -4895,37 +4595,37 @@ "wordwrap": ["wordwrap@1.0.0", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="], - "workbox-background-sync": ["workbox-background-sync@7.4.0", "", { "dependencies": { "idb": "^7.0.1", "workbox-core": "7.4.0" } }, "sha512-8CB9OxKAgKZKyNMwfGZ1XESx89GryWTfI+V5yEj8sHjFH8MFelUwYXEyldEK6M6oKMmn807GoJFUEA1sC4XS9w=="], + "workbox-background-sync": ["workbox-background-sync@7.3.0", "", { "dependencies": { "idb": "^7.0.1", "workbox-core": "7.3.0" } }, "sha512-PCSk3eK7Mxeuyatb22pcSx9dlgWNv3+M8PqPaYDokks8Y5/FX4soaOqj3yhAZr5k6Q5JWTOMYgaJBpbw11G9Eg=="], - "workbox-broadcast-update": ["workbox-broadcast-update@7.4.0", "", { "dependencies": { "workbox-core": "7.4.0" } }, "sha512-+eZQwoktlvo62cI0b+QBr40v5XjighxPq3Fzo9AWMiAosmpG5gxRHgTbGGhaJv/q/MFVxwFNGh/UwHZ/8K88lA=="], + "workbox-broadcast-update": ["workbox-broadcast-update@7.3.0", "", { "dependencies": { "workbox-core": "7.3.0" } }, "sha512-T9/F5VEdJVhwmrIAE+E/kq5at2OY6+OXXgOWQevnubal6sO92Gjo24v6dCVwQiclAF5NS3hlmsifRrpQzZCdUA=="], - "workbox-build": ["workbox-build@7.4.0", "", { "dependencies": { "@apideck/better-ajv-errors": "^0.3.1", "@babel/core": "^7.24.4", "@babel/preset-env": "^7.11.0", "@babel/runtime": "^7.11.2", "@rollup/plugin-babel": "^5.2.0", "@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-replace": "^2.4.1", "@rollup/plugin-terser": "^0.4.3", "@surma/rollup-plugin-off-main-thread": "^2.2.3", "ajv": "^8.6.0", "common-tags": "^1.8.0", "fast-json-stable-stringify": "^2.1.0", "fs-extra": "^9.0.1", "glob": "^11.0.1", "lodash": "^4.17.20", "pretty-bytes": "^5.3.0", "rollup": "^2.79.2", "source-map": "^0.8.0-beta.0", "stringify-object": "^3.3.0", "strip-comments": "^2.0.1", "tempy": "^0.6.0", "upath": "^1.2.0", "workbox-background-sync": "7.4.0", "workbox-broadcast-update": "7.4.0", "workbox-cacheable-response": "7.4.0", "workbox-core": "7.4.0", "workbox-expiration": "7.4.0", "workbox-google-analytics": "7.4.0", "workbox-navigation-preload": "7.4.0", "workbox-precaching": "7.4.0", "workbox-range-requests": "7.4.0", "workbox-recipes": "7.4.0", "workbox-routing": "7.4.0", "workbox-strategies": "7.4.0", "workbox-streams": "7.4.0", "workbox-sw": "7.4.0", "workbox-window": "7.4.0" } }, "sha512-Ntk1pWb0caOFIvwz/hfgrov/OJ45wPEhI5PbTywQcYjyZiVhT3UrwwUPl6TRYbTm4moaFYithYnl1lvZ8UjxcA=="], + "workbox-build": ["workbox-build@7.3.0", "", { "dependencies": { "@apideck/better-ajv-errors": "^0.3.1", "@babel/core": "^7.24.4", "@babel/preset-env": "^7.11.0", "@babel/runtime": "^7.11.2", "@rollup/plugin-babel": "^5.2.0", "@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-replace": "^2.4.1", "@rollup/plugin-terser": "^0.4.3", "@surma/rollup-plugin-off-main-thread": "^2.2.3", "ajv": "^8.6.0", "common-tags": "^1.8.0", "fast-json-stable-stringify": "^2.1.0", "fs-extra": "^9.0.1", "glob": "^7.1.6", "lodash": "^4.17.20", "pretty-bytes": "^5.3.0", "rollup": "^2.43.1", "source-map": "^0.8.0-beta.0", "stringify-object": "^3.3.0", "strip-comments": "^2.0.1", "tempy": "^0.6.0", "upath": "^1.2.0", "workbox-background-sync": "7.3.0", "workbox-broadcast-update": "7.3.0", "workbox-cacheable-response": "7.3.0", "workbox-core": "7.3.0", "workbox-expiration": "7.3.0", "workbox-google-analytics": "7.3.0", "workbox-navigation-preload": "7.3.0", "workbox-precaching": "7.3.0", "workbox-range-requests": "7.3.0", "workbox-recipes": "7.3.0", "workbox-routing": "7.3.0", "workbox-strategies": "7.3.0", "workbox-streams": "7.3.0", "workbox-sw": "7.3.0", "workbox-window": "7.3.0" } }, "sha512-JGL6vZTPlxnlqZRhR/K/msqg3wKP+m0wfEUVosK7gsYzSgeIxvZLi1ViJJzVL7CEeI8r7rGFV973RiEqkP3lWQ=="], - "workbox-cacheable-response": ["workbox-cacheable-response@7.4.0", "", { "dependencies": { "workbox-core": "7.4.0" } }, "sha512-0Fb8795zg/x23ISFkAc7lbWes6vbw34DGFIMw31cwuHPgDEC/5EYm6m/ZkylLX0EnEbbOyOCLjKgFS/Z5g0HeQ=="], + "workbox-cacheable-response": ["workbox-cacheable-response@7.3.0", "", { "dependencies": { "workbox-core": "7.3.0" } }, "sha512-eAFERIg6J2LuyELhLlmeRcJFa5e16Mj8kL2yCDbhWE+HUun9skRQrGIFVUagqWj4DMaaPSMWfAolM7XZZxNmxA=="], - "workbox-core": ["workbox-core@7.4.0", "", {}, "sha512-6BMfd8tYEnN4baG4emG9U0hdXM4gGuDU3ectXuVHnj71vwxTFI7WOpQJC4siTOlVtGqCUtj0ZQNsrvi6kZZTAQ=="], + "workbox-core": ["workbox-core@7.3.0", "", {}, "sha512-Z+mYrErfh4t3zi7NVTvOuACB0A/jA3bgxUN3PwtAVHvfEsZxV9Iju580VEETug3zYJRc0Dmii/aixI/Uxj8fmw=="], - "workbox-expiration": ["workbox-expiration@7.4.0", "", { "dependencies": { "idb": "^7.0.1", "workbox-core": "7.4.0" } }, "sha512-V50p4BxYhtA80eOvulu8xVfPBgZbkxJ1Jr8UUn0rvqjGhLDqKNtfrDfjJKnLz2U8fO2xGQJTx/SKXNTzHOjnHw=="], + "workbox-expiration": ["workbox-expiration@7.3.0", "", { "dependencies": { "idb": "^7.0.1", "workbox-core": "7.3.0" } }, "sha512-lpnSSLp2BM+K6bgFCWc5bS1LR5pAwDWbcKt1iL87/eTSJRdLdAwGQznZE+1czLgn/X05YChsrEegTNxjM067vQ=="], - "workbox-google-analytics": ["workbox-google-analytics@7.4.0", "", { "dependencies": { "workbox-background-sync": "7.4.0", "workbox-core": "7.4.0", "workbox-routing": "7.4.0", "workbox-strategies": "7.4.0" } }, "sha512-MVPXQslRF6YHkzGoFw1A4GIB8GrKym/A5+jYDUSL+AeJw4ytQGrozYdiZqUW1TPQHW8isBCBtyFJergUXyNoWQ=="], + "workbox-google-analytics": ["workbox-google-analytics@7.3.0", "", { "dependencies": { "workbox-background-sync": "7.3.0", "workbox-core": "7.3.0", "workbox-routing": "7.3.0", "workbox-strategies": "7.3.0" } }, "sha512-ii/tSfFdhjLHZ2BrYgFNTrb/yk04pw2hasgbM70jpZfLk0vdJAXgaiMAWsoE+wfJDNWoZmBYY0hMVI0v5wWDbg=="], - "workbox-navigation-preload": ["workbox-navigation-preload@7.4.0", "", { "dependencies": { "workbox-core": "7.4.0" } }, "sha512-etzftSgdQfjMcfPgbfaZCfM2QuR1P+4o8uCA2s4rf3chtKTq/Om7g/qvEOcZkG6v7JZOSOxVYQiOu6PbAZgU6w=="], + "workbox-navigation-preload": ["workbox-navigation-preload@7.3.0", "", { "dependencies": { "workbox-core": "7.3.0" } }, "sha512-fTJzogmFaTv4bShZ6aA7Bfj4Cewaq5rp30qcxl2iYM45YD79rKIhvzNHiFj1P+u5ZZldroqhASXwwoyusnr2cg=="], - "workbox-precaching": ["workbox-precaching@7.4.0", "", { "dependencies": { "workbox-core": "7.4.0", "workbox-routing": "7.4.0", "workbox-strategies": "7.4.0" } }, "sha512-VQs37T6jDqf1rTxUJZXRl3yjZMf5JX/vDPhmx2CPgDDKXATzEoqyRqhYnRoxl6Kr0rqaQlp32i9rtG5zTzIlNg=="], + "workbox-precaching": ["workbox-precaching@7.3.0", "", { "dependencies": { "workbox-core": "7.3.0", "workbox-routing": "7.3.0", "workbox-strategies": "7.3.0" } }, "sha512-ckp/3t0msgXclVAYaNndAGeAoWQUv7Rwc4fdhWL69CCAb2UHo3Cef0KIUctqfQj1p8h6aGyz3w8Cy3Ihq9OmIw=="], - "workbox-range-requests": ["workbox-range-requests@7.4.0", "", { "dependencies": { "workbox-core": "7.4.0" } }, "sha512-3Vq854ZNuP6Y0KZOQWLaLC9FfM7ZaE+iuQl4VhADXybwzr4z/sMmnLgTeUZLq5PaDlcJBxYXQ3U91V7dwAIfvw=="], + "workbox-range-requests": ["workbox-range-requests@7.3.0", "", { "dependencies": { "workbox-core": "7.3.0" } }, "sha512-EyFmM1KpDzzAouNF3+EWa15yDEenwxoeXu9bgxOEYnFfCxns7eAxA9WSSaVd8kujFFt3eIbShNqa4hLQNFvmVQ=="], - "workbox-recipes": ["workbox-recipes@7.4.0", "", { "dependencies": { "workbox-cacheable-response": "7.4.0", "workbox-core": "7.4.0", "workbox-expiration": "7.4.0", "workbox-precaching": "7.4.0", "workbox-routing": "7.4.0", "workbox-strategies": "7.4.0" } }, "sha512-kOkWvsAn4H8GvAkwfJTbwINdv4voFoiE9hbezgB1sb/0NLyTG4rE7l6LvS8lLk5QIRIto+DjXLuAuG3Vmt3cxQ=="], + "workbox-recipes": ["workbox-recipes@7.3.0", "", { "dependencies": { "workbox-cacheable-response": "7.3.0", "workbox-core": "7.3.0", "workbox-expiration": "7.3.0", "workbox-precaching": "7.3.0", "workbox-routing": "7.3.0", "workbox-strategies": "7.3.0" } }, "sha512-BJro/MpuW35I/zjZQBcoxsctgeB+kyb2JAP5EB3EYzePg8wDGoQuUdyYQS+CheTb+GhqJeWmVs3QxLI8EBP1sg=="], - "workbox-routing": ["workbox-routing@7.4.0", "", { "dependencies": { "workbox-core": "7.4.0" } }, "sha512-C/ooj5uBWYAhAqwmU8HYQJdOjjDKBp9MzTQ+otpMmd+q0eF59K+NuXUek34wbL0RFrIXe/KKT+tUWcZcBqxbHQ=="], + "workbox-routing": ["workbox-routing@7.3.0", "", { "dependencies": { "workbox-core": "7.3.0" } }, "sha512-ZUlysUVn5ZUzMOmQN3bqu+gK98vNfgX/gSTZ127izJg/pMMy4LryAthnYtjuqcjkN4HEAx1mdgxNiKJMZQM76A=="], - "workbox-strategies": ["workbox-strategies@7.4.0", "", { "dependencies": { "workbox-core": "7.4.0" } }, "sha512-T4hVqIi5A4mHi92+5EppMX3cLaVywDp8nsyUgJhOZxcfSV/eQofcOA6/EMo5rnTNmNTpw0rUgjAI6LaVullPpg=="], + "workbox-strategies": ["workbox-strategies@7.3.0", "", { "dependencies": { "workbox-core": "7.3.0" } }, "sha512-tmZydug+qzDFATwX7QiEL5Hdf7FrkhjaF9db1CbB39sDmEZJg3l9ayDvPxy8Y18C3Y66Nrr9kkN1f/RlkDgllg=="], - "workbox-streams": ["workbox-streams@7.4.0", "", { "dependencies": { "workbox-core": "7.4.0", "workbox-routing": "7.4.0" } }, "sha512-QHPBQrey7hQbnTs5GrEVoWz7RhHJXnPT+12qqWM378orDMo5VMJLCkCM1cnCk+8Eq92lccx/VgRZ7WAzZWbSLg=="], + "workbox-streams": ["workbox-streams@7.3.0", "", { "dependencies": { "workbox-core": "7.3.0", "workbox-routing": "7.3.0" } }, "sha512-SZnXucyg8x2Y61VGtDjKPO5EgPUG5NDn/v86WYHX+9ZqvAsGOytP0Jxp1bl663YUuMoXSAtsGLL+byHzEuMRpw=="], - "workbox-sw": ["workbox-sw@7.4.0", "", {}, "sha512-ltU+Kr3qWR6BtbdlMnCjobZKzeV1hN+S6UvDywBrwM19TTyqA03X66dzw1tEIdJvQ4lYKkBFox6IAEhoSEZ8Xw=="], + "workbox-sw": ["workbox-sw@7.3.0", "", {}, "sha512-aCUyoAZU9IZtH05mn0ACUpyHzPs0lMeJimAYkQkBsOWiqaJLgusfDCR+yllkPkFRxWpZKF8vSvgHYeG7LwhlmA=="], - "workbox-window": ["workbox-window@7.4.0", "", { "dependencies": { "@types/trusted-types": "^2.0.2", "workbox-core": "7.4.0" } }, "sha512-/bIYdBLAVsNR3v7gYGaV4pQW3M3kEPx5E8vDxGvxo6khTrGtSSCS7QiFKv9ogzBgZiy0OXLP9zO28U/1nF1mfw=="], + "workbox-window": ["workbox-window@7.3.0", "", { "dependencies": { "@types/trusted-types": "^2.0.2", "workbox-core": "7.3.0" } }, "sha512-qW8PDy16OV1UBaUNGlTVcepzrlzyzNW/ZJvFQQs2j2TzGsg6IKjcpZC1RSquqQnTOafl5pCj5bGfAHlCjOOjdA=="], "wrap-ansi": ["wrap-ansi@9.0.0", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q=="], @@ -4937,8 +4637,6 @@ "ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="], - "xlsx": ["xlsx@https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", { "bin": { "xlsx": "./bin/xlsx.njs" } }], - "xml": ["xml@1.0.1", "", {}, "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw=="], "xml-crypto": ["xml-crypto@6.1.2", "", { "dependencies": { "@xmldom/is-dom-node": "^1.0.1", "@xmldom/xmldom": "^0.8.10", "xpath": "^0.0.33" } }, "sha512-leBOVQdVi8FvPJrMYoum7Ici9qyxfE4kVi+AkpUoYCSXaQF4IlBm1cneTK9oAxR61LpYxTx7lNcsnBIeRpGW2w=="], @@ -4949,7 +4647,7 @@ "xml2js": ["xml2js@0.6.2", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA=="], - "xmlbuilder": ["xmlbuilder@10.1.1", "", {}, "sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg=="], + "xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="], "xmlchars": ["xmlchars@2.2.0", "", {}, "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="], @@ -4967,15 +4665,17 @@ "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], - "yauzl": ["yauzl@3.2.1", "", { "dependencies": { "buffer-crc32": "~0.2.3", "pend": "~1.2.0" } }, "sha512-k1isifdbpNSFEHFJ1ZY4YDewv0IH9FR61lDetaRMD3j2ae3bIXGV+7c+LHCqtQGofSd8PIyV4X6+dHMAnSr60A=="], + "yauzl": ["yauzl@3.2.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "pend": "~1.2.0" } }, "sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w=="], "yn": ["yn@3.1.1", "", {}, "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q=="], "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + "youtube-transcript": ["youtube-transcript@1.2.1", "", {}, "sha512-TvEGkBaajKw+B6y91ziLuBLsa5cawgowou+Bk0ciGpjELDfAzSzTGXaZmeSSkUeknCPpEr/WGApOHDwV7V+Y9Q=="], + "zod": ["zod@3.25.67", "", {}, "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw=="], - "zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="], + "zod-to-json-schema": ["zod-to-json-schema@3.24.3", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-HIAfWdYIt1sssHfYZFCXp4rU1w2r8hVVXYIlmoa0r0gABLs5di3RCqPU5DDROogVz1pAdYBaz7HK5n9pSUNs3A=="], "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], @@ -5035,12 +4735,6 @@ "@aws-sdk/client-bedrock-agent-runtime/@smithy/core": ["@smithy/core@3.17.2", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.4", "@smithy/util-stream": "^4.5.5", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-n3g4Nl1Te+qGPDbNFAYf+smkRVB+JhFsGy9uJXXZQEufoP4u0r+WLh6KvTDolCswaagysDc/afS1yvb2jnj1gQ=="], - "@aws-sdk/client-bedrock-agent-runtime/@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.4", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-d5T7ZS3J/r8P/PDjgmCcutmNxnSRvPH1U6iHeXjzI50sMr78GLmFcrczLw33Ap92oEKqa4CLrkAPeSSOqvGdUA=="], - - "@aws-sdk/client-bedrock-agent-runtime/@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-lxfDT0UuSc1HqltOGsTEAlZ6H29gpfDSdEPTapD5G63RbnYToZ+ezjzdonCCH90j5tRRCw3aLXVbiZaBW3VRVg=="], - - "@aws-sdk/client-bedrock-agent-runtime/@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.4", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-TPhiGByWnYyzcpU/K3pO5V7QgtXYpE0NaJPEZBCa1Y5jlw5SjqzMSbFiLb+ZkJhqoQc0ImGyVINqnq1ze0ZRcQ=="], - "@aws-sdk/client-bedrock-agent-runtime/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.5", "", { "dependencies": { "@smithy/protocol-http": "^5.3.4", "@smithy/querystring-builder": "^4.2.4", "@smithy/types": "^4.8.1", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-mg83SM3FLI8Sa2ooTJbsh5MFfyMTyNRwxqpKHmE0ICRIa66Aodv80DMsTQI02xBLVJ0hckwqTRr5IGAbbWuFLQ=="], "@aws-sdk/client-bedrock-agent-runtime/@smithy/hash-node": ["@smithy/hash-node@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kKU0gVhx/ppVMntvUOZE7WRMFW86HuaxLwvqileBEjL7PoILI8/djoILw3gPQloGVE6O0oOzqafxeNi2KbnUJw=="], @@ -5061,12 +4755,8 @@ "@aws-sdk/client-bedrock-agent-runtime/@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.4", "", { "dependencies": { "@smithy/abort-controller": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/querystring-builder": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-VXHGfzCXLZeKnFp6QXjAdy+U8JF9etfpUXD1FAbzY1GzsFJiDQRQIt2CnMUvUdz3/YaHNqT3RphVWMUpXTIODA=="], - "@aws-sdk/client-bedrock-agent-runtime/@smithy/protocol-http": ["@smithy/protocol-http@5.3.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-3sfFd2MAzVt0Q/klOmjFi3oIkxczHs0avbwrfn1aBqtc23WqQSmjvk77MBw9WkEQcwbOYIX5/2z4ULj8DuxSsw=="], - "@aws-sdk/client-bedrock-agent-runtime/@smithy/smithy-client": ["@smithy/smithy-client@4.9.2", "", { "dependencies": { "@smithy/core": "^3.17.2", "@smithy/middleware-endpoint": "^4.3.6", "@smithy/middleware-stack": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-stream": "^4.5.5", "tslib": "^2.6.2" } }, "sha512-gZU4uAFcdrSi3io8U99Qs/FvVdRxPvIMToi+MFfsy/DN9UqtknJ1ais+2M9yR8e0ASQpNmFYEKeIKVcMjQg3rg=="], - "@aws-sdk/client-bedrock-agent-runtime/@smithy/types": ["@smithy/types@4.8.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-N0Zn0OT1zc+NA+UVfkYqQzviRh5ucWwO7mBV3TmHHprMnfcJNfhlPicDkBHi0ewbh+y3evR6cNAW0Raxvb01NA=="], - "@aws-sdk/client-bedrock-agent-runtime/@smithy/url-parser": ["@smithy/url-parser@4.2.4", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-w/N/Iw0/PTwJ36PDqU9PzAwVElo4qXxCC0eCTlUtIz/Z5V/2j/cViMHi0hPukSBHp4DVwvUlUhLgCzqSJ6plrg=="], "@aws-sdk/client-bedrock-agent-runtime/@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="], @@ -5081,12 +4771,88 @@ "@aws-sdk/client-bedrock-agent-runtime/@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.4", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-f+nBDhgYRCmUEDKEQb6q0aCcOTXRDqH5wWaFHJxt4anB4pKHlgGoYP3xtioKXH64e37ANUkzWf6p4Mnv1M5/Vg=="], - "@aws-sdk/client-bedrock-agent-runtime/@smithy/util-middleware": ["@smithy/util-middleware@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-fKGQAPAn8sgV0plRikRVo6g6aR0KyKvgzNrPuM74RZKy/wWVzx3BMk+ZWEueyN3L5v5EDg+P582mKU+sH5OAsg=="], - "@aws-sdk/client-bedrock-agent-runtime/@smithy/util-retry": ["@smithy/util-retry@4.2.4", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-yQncJmj4dtv/isTXxRb4AamZHy4QFr4ew8GxS6XLWt7sCIxkPxPzINWd7WLISEFPsIan14zrKgvyAF+/yzfwoA=="], "@aws-sdk/client-bedrock-agent-runtime/@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], + "@aws-sdk/client-bedrock-runtime/@aws-sdk/core": ["@aws-sdk/core@3.947.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@aws-sdk/xml-builder": "3.930.0", "@smithy/core": "^3.18.7", "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Khq4zHhuAkvCFuFbgcy3GrZTzfSX7ZIjIcW1zRDxXRLZKRtuhnZdonqTUfaWi5K42/4OmxkYNpsO7X7trQOeHw=="], + + "@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.952.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.947.0", "@aws-sdk/credential-provider-http": "3.947.0", "@aws-sdk/credential-provider-ini": "3.952.0", "@aws-sdk/credential-provider-process": "3.947.0", "@aws-sdk/credential-provider-sso": "3.952.0", "@aws-sdk/credential-provider-web-identity": "3.952.0", "@aws-sdk/types": "3.936.0", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-pj7nidLrb3Dz9llcUPh6N0Yv1dBYTS9xJqi8u0kI8D5sn72HJMB+fIOhcDQVXXAw/dpVolOAH9FOAbog5JDAMg=="], + + "@aws-sdk/client-bedrock-runtime/@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw=="], + + "@aws-sdk/client-bedrock-runtime/@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw=="], + + "@aws-sdk/client-bedrock-runtime/@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.948.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Qa8Zj+EAqA0VlAVvxpRnpBpIWJI9KUwaioY1vkeNVwXPlNaz9y9zCKVM9iU9OZ5HXpoUg6TnhATAHXHAE8+QsQ=="], + + "@aws-sdk/client-bedrock-runtime/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.947.0", "", { "dependencies": { "@aws-sdk/core": "3.947.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@smithy/core": "^3.18.7", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-7rpKV8YNgCP2R4F9RjWZFcD2R+SO/0R4VHIbY9iZJdH2MzzJ8ZG7h8dZ2m8QkQd1fjx4wrFJGGPJUTYXPV3baA=="], + + "@aws-sdk/client-bedrock-runtime/@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/config-resolver": "^4.4.3", "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw=="], + + "@aws-sdk/client-bedrock-runtime/@aws-sdk/types": ["@aws-sdk/types@3.936.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg=="], + + "@aws-sdk/client-bedrock-runtime/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-endpoints": "^3.2.5", "tslib": "^2.6.2" } }, "sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w=="], + + "@aws-sdk/client-bedrock-runtime/@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw=="], + + "@aws-sdk/client-bedrock-runtime/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.947.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.947.0", "@aws-sdk/types": "3.936.0", "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-+vhHoDrdbb+zerV4noQk1DHaUMNzWFWPpPYjVTwW2186k5BEJIecAMChYkghRrBVJ3KPWP1+JnZwOd72F3d4rQ=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/config-resolver": ["@smithy/config-resolver@4.4.4", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.6", "@smithy/types": "^4.10.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-endpoints": "^3.2.6", "@smithy/util-middleware": "^4.2.6", "tslib": "^2.6.2" } }, "sha512-s3U5ChS21DwU54kMmZ0UJumoS5cg0+rGVZvN6f5Lp6EbAVi0ZyP+qDSHdewfmXKUgNK1j3z45JyzulkDukrjAA=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/core": ["@smithy/core@3.19.0", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.7", "@smithy/protocol-http": "^5.3.6", "@smithy/types": "^4.10.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.6", "@smithy/util-stream": "^4.5.7", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-Y9oHXpBcXQgYHOcAEmxjkDilUbSTkgKjoHYed3WaYUH8jngq8lPWDBSpjHblJ9uOgBdy5mh3pzebrScDdYr29w=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.6", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.6", "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-6OiaAaEbLB6dEkRbQyNzFSJv5HDvly3Mc6q/qcPd2uS/g3szR8wAIkh7UndAFKfMypNSTuZ6eCBmgCLR5LacTg=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-xP5YXbOVRVN8A4pDnSUkEUsL9fYFU6VNhxo8tgr13YnMbf3Pn4xVr+hSyLVjS1Frfi1Uk03ET5Bwml4+0CeYEw=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.6", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.6", "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-jhH7nJuaOpnTFcuZpWK9dqb6Ge2yGi1okTo0W6wkJrfwAm2vwmO74tF1v07JmrSyHBcKLQATEexclJw9K1Vj7w=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.7", "", { "dependencies": { "@smithy/protocol-http": "^5.3.6", "@smithy/querystring-builder": "^4.2.6", "@smithy/types": "^4.10.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-fcVap4QwqmzQwQK9QU3keeEpCzTjnP9NJ171vI7GnD7nbkAIcP9biZhDUx88uRH9BabSsQDS0unUps88uZvFIQ=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/hash-node": ["@smithy/hash-node@4.2.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-k3Dy9VNR37wfMh2/1RHkFf/e0rMyN0pjY0FdyY6ItJRjENYyVPRMwad6ZR1S9HFm6tTuIOd9pqKBmtJ4VHxvxg=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-E4t/V/q2T46RY21fpfznd1iSLTvCXKNKo4zJ1QuEFN4SE9gKfu2vb6bgq35LpufkQ+SETWIC7ZAf2GGvTlBaMQ=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.6", "", { "dependencies": { "@smithy/protocol-http": "^5.3.6", "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-0cjqjyfj+Gls30ntq45SsBtqF3dfJQCeqQPyGz58Pk8OgrAr5YiB7ZvDzjCA94p4r6DCI4qLm7FKobqBjf515w=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.0", "", { "dependencies": { "@smithy/core": "^3.19.0", "@smithy/middleware-serde": "^4.2.7", "@smithy/node-config-provider": "^4.3.6", "@smithy/shared-ini-file-loader": "^4.4.1", "@smithy/types": "^4.10.0", "@smithy/url-parser": "^4.2.6", "@smithy/util-middleware": "^4.2.6", "tslib": "^2.6.2" } }, "sha512-M6qWfUNny6NFNy8amrCGIb9TfOMUkHVtg9bHtEFGRgfH7A7AtPpn/fcrToGPjVDK1ECuMVvqGQOXcZxmu9K+7A=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.16", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.6", "@smithy/protocol-http": "^5.3.6", "@smithy/service-error-classification": "^4.2.6", "@smithy/smithy-client": "^4.10.1", "@smithy/types": "^4.10.0", "@smithy/util-middleware": "^4.2.6", "@smithy/util-retry": "^4.2.6", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-XPpNhNRzm3vhYm7YCsyw3AtmWggJbg1wNGAoqb7NBYr5XA5isMRv14jgbYyUV6IvbTBFZQdf2QpeW43LrRdStQ=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.7", "", { "dependencies": { "@smithy/protocol-http": "^5.3.6", "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-PFMVHVPgtFECeu4iZ+4SX6VOQT0+dIpm4jSPLLL6JLSkp9RohGqKBKD0cbiXdeIFS08Forp0UHI6kc0gIHenSA=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-JSbALU3G+JS4kyBZPqnJ3hxIYwOVRV7r9GNQMS6j5VsQDo5+Es5nddLfr9TQlxZLNHPvKSh+XSB0OuWGfSWFcA=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.6", "", { "dependencies": { "@smithy/property-provider": "^4.2.6", "@smithy/shared-ini-file-loader": "^4.4.1", "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-fYEyL59Qe82Ha1p97YQTMEQPJYmBS+ux76foqluaTVWoG9Px5J53w6NvXZNE3wP7lIicLDF7Vj1Em18XTX7fsA=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/protocol-http": ["@smithy/protocol-http@5.3.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-qLRZzP2+PqhE3OSwvY2jpBbP0WKTZ9opTsn+6IWYI0SKVpbG+imcfNxXPq9fj5XeaUTr7odpsNpK6dmoiM1gJQ=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/smithy-client": ["@smithy/smithy-client@4.10.1", "", { "dependencies": { "@smithy/core": "^3.19.0", "@smithy/middleware-endpoint": "^4.4.0", "@smithy/middleware-stack": "^4.2.6", "@smithy/protocol-http": "^5.3.6", "@smithy/types": "^4.10.0", "@smithy/util-stream": "^4.5.7", "tslib": "^2.6.2" } }, "sha512-1ovWdxzYprhq+mWqiGZlt3kF69LJthuQcfY9BIyHx9MywTFKzFapluku1QXoaBB43GCsLDxNqS+1v30ure69AA=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/types": ["@smithy/types@4.10.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-K9mY7V/f3Ul+/Gz4LJANZ3vJ/yiBIwCyxe0sPT4vNJK63Srvd+Yk1IzP0t+nE7XFSpIGtzR71yljtnqpUTYFlQ=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/url-parser": ["@smithy/url-parser@4.2.6", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.6", "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-tVoyzJ2vXp4R3/aeV4EQjBDmCuWxRa8eo3KybL7Xv4wEM16nObYh7H1sNfcuLWHAAAzb0RVyxUz1S3sGj4X+Tg=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.15", "", { "dependencies": { "@smithy/property-provider": "^4.2.6", "@smithy/smithy-client": "^4.10.1", "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-LiZQVAg/oO8kueX4c+oMls5njaD2cRLXRfcjlTYjhIqmwHnCwkQO5B3dMQH0c5PACILxGAQf6Mxsq7CjlDc76A=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.18", "", { "dependencies": { "@smithy/config-resolver": "^4.4.4", "@smithy/credential-provider-imds": "^4.2.6", "@smithy/node-config-provider": "^4.3.6", "@smithy/property-provider": "^4.2.6", "@smithy/smithy-client": "^4.10.1", "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-Kw2J+KzYm9C9Z9nY6+W0tEnoZOofstVCMTshli9jhQbQCy64rueGfKzPfuFBnVUqZD9JobxTh2DzHmPkp/Va/Q=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.6", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.6", "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-v60VNM2+mPvgHCBXEfMCYrQ0RepP6u6xvbAkMenfe4Mi872CqNkJzgcnQL837e8NdeDxBgrWQRTluKq5Lqdhfg=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/util-middleware": ["@smithy/util-middleware@4.2.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-qrvXUkxBSAFomM3/OEMuDVwjh4wtqK8D2uDZPShzIqOylPst6gor2Cdp6+XrH4dyksAWq/bE2aSDYBTTnj0Rxg=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/util-retry": ["@smithy/util-retry@4.2.6", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.6", "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-x7CeDQLPQ9cb6xN7fRJEjlP9NyGW/YeXWc4j/RUhg4I+H60F0PEeRc2c/z3rm9zmsdiMFzpV/rT+4UHW6KM1SA=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/util-stream": ["@smithy/util-stream@4.5.7", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.7", "@smithy/node-http-handler": "^4.4.6", "@smithy/types": "^4.10.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", "tslib": "^2.6.2" } }, "sha512-Uuy4S5Aj4oF6k1z+i2OtIBJUns4mlg29Ph4S+CqjR+f4XXpSFVgTCYLzMszHJTicYDBxKFtwq2/QSEDSS5l02A=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], + "@aws-sdk/client-cognito-identity/@aws-sdk/core": ["@aws-sdk/core@3.623.0", "", { "dependencies": { "@smithy/core": "^2.3.2", "@smithy/node-config-provider": "^3.1.4", "@smithy/protocol-http": "^4.1.0", "@smithy/signature-v4": "^4.1.0", "@smithy/smithy-client": "^3.1.12", "@smithy/types": "^3.3.0", "@smithy/util-middleware": "^3.0.3", "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" } }, "sha512-8Toq3X6trX/67obSdh4K0MFQY4f132bEbr1i0YPDWk/O3KdBt12mLC/sW3aVRnlIs110XMuX9yrWWqJ8fDW10g=="], "@aws-sdk/client-cognito-identity/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.623.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.620.1", "@aws-sdk/credential-provider-http": "3.622.0", "@aws-sdk/credential-provider-ini": "3.623.0", "@aws-sdk/credential-provider-process": "3.620.1", "@aws-sdk/credential-provider-sso": "3.623.0", "@aws-sdk/credential-provider-web-identity": "3.621.0", "@aws-sdk/types": "3.609.0", "@smithy/credential-provider-imds": "^3.2.0", "@smithy/property-provider": "^3.1.3", "@smithy/shared-ini-file-loader": "^3.1.4", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" } }, "sha512-qDwCOkhbu5PfaQHyuQ+h57HEx3+eFhKdtIw7aISziWkGdFrMe07yIBd7TJqGe4nxXnRF1pfkg05xeOlMId997g=="], @@ -5205,12 +4971,8 @@ "@aws-sdk/client-kendra/@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.4", "", { "dependencies": { "@smithy/abort-controller": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/querystring-builder": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-VXHGfzCXLZeKnFp6QXjAdy+U8JF9etfpUXD1FAbzY1GzsFJiDQRQIt2CnMUvUdz3/YaHNqT3RphVWMUpXTIODA=="], - "@aws-sdk/client-kendra/@smithy/protocol-http": ["@smithy/protocol-http@5.3.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-3sfFd2MAzVt0Q/klOmjFi3oIkxczHs0avbwrfn1aBqtc23WqQSmjvk77MBw9WkEQcwbOYIX5/2z4ULj8DuxSsw=="], - "@aws-sdk/client-kendra/@smithy/smithy-client": ["@smithy/smithy-client@4.9.2", "", { "dependencies": { "@smithy/core": "^3.17.2", "@smithy/middleware-endpoint": "^4.3.6", "@smithy/middleware-stack": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-stream": "^4.5.5", "tslib": "^2.6.2" } }, "sha512-gZU4uAFcdrSi3io8U99Qs/FvVdRxPvIMToi+MFfsy/DN9UqtknJ1ais+2M9yR8e0ASQpNmFYEKeIKVcMjQg3rg=="], - "@aws-sdk/client-kendra/@smithy/types": ["@smithy/types@4.8.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-N0Zn0OT1zc+NA+UVfkYqQzviRh5ucWwO7mBV3TmHHprMnfcJNfhlPicDkBHi0ewbh+y3evR6cNAW0Raxvb01NA=="], - "@aws-sdk/client-kendra/@smithy/url-parser": ["@smithy/url-parser@4.2.4", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-w/N/Iw0/PTwJ36PDqU9PzAwVElo4qXxCC0eCTlUtIz/Z5V/2j/cViMHi0hPukSBHp4DVwvUlUhLgCzqSJ6plrg=="], "@aws-sdk/client-kendra/@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="], @@ -5225,85 +4987,11 @@ "@aws-sdk/client-kendra/@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.4", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-f+nBDhgYRCmUEDKEQb6q0aCcOTXRDqH5wWaFHJxt4anB4pKHlgGoYP3xtioKXH64e37ANUkzWf6p4Mnv1M5/Vg=="], - "@aws-sdk/client-kendra/@smithy/util-middleware": ["@smithy/util-middleware@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-fKGQAPAn8sgV0plRikRVo6g6aR0KyKvgzNrPuM74RZKy/wWVzx3BMk+ZWEueyN3L5v5EDg+P582mKU+sH5OAsg=="], - "@aws-sdk/client-kendra/@smithy/util-retry": ["@smithy/util-retry@4.2.4", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-yQncJmj4dtv/isTXxRb4AamZHy4QFr4ew8GxS6XLWt7sCIxkPxPzINWd7WLISEFPsIan14zrKgvyAF+/yzfwoA=="], "@aws-sdk/client-kendra/@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], - "@aws-sdk/client-kendra/@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], - - "@aws-sdk/client-s3/@aws-sdk/core": ["@aws-sdk/core@3.973.18", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@aws-sdk/xml-builder": "^3.972.10", "@smithy/core": "^3.23.8", "@smithy/node-config-provider": "^4.3.11", "@smithy/property-provider": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/signature-v4": "^5.3.11", "@smithy/smithy-client": "^4.12.2", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-GUIlegfcK2LO1J2Y98sCJy63rQSiLiDOgVw7HiHPRqfI2vb3XozTVqemwO0VSGXp54ngCnAQz0Lf0YPCBINNxA=="], - - "@aws-sdk/client-s3/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.18", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.16", "@aws-sdk/credential-provider-http": "^3.972.18", "@aws-sdk/credential-provider-ini": "^3.972.17", "@aws-sdk/credential-provider-process": "^3.972.16", "@aws-sdk/credential-provider-sso": "^3.972.17", "@aws-sdk/credential-provider-web-identity": "^3.972.17", "@aws-sdk/types": "^3.973.5", "@smithy/credential-provider-imds": "^4.2.11", "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-ZDJa2gd1xiPg/nBDGhUlat02O8obaDEnICBAVS8qieZ0+nDfaB0Z3ec6gjZj27OqFTjnB/Q5a0GwQwb7rMVViw=="], - - "@aws-sdk/client-s3/@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-aHQZgztBFEpDU1BB00VWCIIm85JjGjQW1OG9+98BdmaOpguJvzmXBGbnAiYcciCd+IS4e9BEq664lhzGnWJHgQ=="], - - "@aws-sdk/client-s3/@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-LXhiWlWb26txCU1vcI9PneESSeRp/RYY/McuM4SpdrimQR5NgwaPb4VJCadVeuGWgh6QmqZ6rAKSoL1ob16W6w=="], - - "@aws-sdk/client-s3/@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-l2VQdcBcYLzIzykCHtXlbpiVCZ94/xniLIkAj0jpnpjY4xlgZx7f56Ypn+uV1y3gG0tNVytJqo3K9bfMFee7SQ=="], - - "@aws-sdk/client-s3/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.19", "", { "dependencies": { "@aws-sdk/core": "^3.973.18", "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-endpoints": "^3.996.4", "@smithy/core": "^3.23.8", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "@smithy/util-retry": "^4.2.11", "tslib": "^2.6.2" } }, "sha512-Km90fcXt3W/iqujHzuM6IaDkYCj73gsYufcuWXApWdzoTy6KGk8fnchAjePMARU0xegIR3K4N3yIo1vy7OVe8A=="], - - "@aws-sdk/client-s3/@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/config-resolver": "^4.4.10", "@smithy/node-config-provider": "^4.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-/Ev/6AI8bvt4HAAptzSjThGUMjcWaX3GX8oERkB0F0F9x2dLSBdgFDiyrRz3i0u0ZFZFQ1b28is4QhyqXTUsVA=="], - - "@aws-sdk/client-s3/@aws-sdk/types": ["@aws-sdk/types@3.973.5", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ=="], - - "@aws-sdk/client-s3/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.4", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@smithy/util-endpoints": "^3.3.2", "tslib": "^2.6.2" } }, "sha512-Hek90FBmd4joCFj+Vc98KLJh73Zqj3s2W56gjAcTkrNLMDI5nIFkG9YpfcJiVI1YlE2Ne1uOQNe+IgQ/Vz2XRA=="], - - "@aws-sdk/client-s3/@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-7SJVuvhKhMF/BkNS1n0QAJYgvEwYbK2QLKBrzDiwQGiTRU6Yf1f3nehTzm/l21xdAOtWSfp2uWSddPnP2ZtsVw=="], - - "@aws-sdk/client-s3/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.4", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.19", "@aws-sdk/types": "^3.973.5", "@smithy/node-config-provider": "^4.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-uqKeLqZ9D3nQjH7HGIERNXK9qnSpUK08l4MlJ5/NZqSSdeJsVANYp437EM9sEzwU28c2xfj2V6qlkqzsgtKs6Q=="], - - "@aws-sdk/client-s3/@smithy/config-resolver": ["@smithy/config-resolver@4.4.10", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.11", "@smithy/types": "^4.13.0", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-endpoints": "^3.3.2", "@smithy/util-middleware": "^4.2.11", "tslib": "^2.6.2" } }, "sha512-IRTkd6ps0ru+lTWnfnsbXzW80A8Od8p3pYiZnW98K2Hb20rqfsX7VTlfUwhrcOeSSy68Gn9WBofwPuw3e5CCsg=="], - - "@aws-sdk/client-s3/@smithy/core": ["@smithy/core@3.23.9", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.12", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-stream": "^4.5.17", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-1Vcut4LEL9HZsdpI0vFiRYIsaoPwZLjAxnVQDUMQK8beMS+EYPLDQCXtbzfxmM5GzSgjfe2Q9M7WaXwIMQllyQ=="], - - "@aws-sdk/client-s3/@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.11", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-3rEpo3G6f/nRS7fQDsZmxw/ius6rnlIpz4UX6FlALEzz8JoSxFmdBt0SZnthis+km7sQo6q5/3e+UJcuQivoXA=="], - - "@aws-sdk/client-s3/@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-XeNIA8tcP/GDWnnKkO7qEm/bg0B/bP9lvIXZBXcGZwZ+VYM8h8k9wuDvUODtdQ2Wcp2RcBkPTCSMmaniVHrMlA=="], - - "@aws-sdk/client-s3/@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.11", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-fzbCh18rscBDTQSCrsp1fGcclLNF//nJyhjldsEl/5wCYmgpHblv5JSppQAyQI24lClsFT0wV06N1Porn0IsEw=="], - - "@aws-sdk/client-s3/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.13", "", { "dependencies": { "@smithy/protocol-http": "^5.3.11", "@smithy/querystring-builder": "^4.2.11", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-U2Hcfl2s3XaYjikN9cT4mPu8ybDbImV3baXR0PkVlC0TTx808bRP3FaPGAzPtB8OByI+JqJ1kyS+7GEgae7+qQ=="], - - "@aws-sdk/client-s3/@smithy/hash-node": ["@smithy/hash-node@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-T+p1pNynRkydpdL015ruIoyPSRw9e/SQOWmSAMmmprfswMrd5Ow5igOWNVlvyVFZlxXqGmyH3NQwfwy8r5Jx0A=="], - - "@aws-sdk/client-s3/@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-cGNMrgykRmddrNhYy1yBdrp5GwIgEkniS7k9O1VLB38yxQtlvrxpZtUVvo6T4cKpeZsriukBuuxfJcdZQc/f/g=="], - - "@aws-sdk/client-s3/@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.11", "", { "dependencies": { "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-UvIfKYAKhCzr4p6jFevPlKhQwyQwlJ6IeKLDhmV1PlYfcW3RL4ROjNEDtSik4NYMi9kDkH7eSwyTP3vNJ/u/Dw=="], - - "@aws-sdk/client-s3/@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.23", "", { "dependencies": { "@smithy/core": "^3.23.9", "@smithy/middleware-serde": "^4.2.12", "@smithy/node-config-provider": "^4.3.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@smithy/util-middleware": "^4.2.11", "tslib": "^2.6.2" } }, "sha512-UEFIejZy54T1EJn2aWJ45voB7RP2T+IRzUqocIdM6GFFa5ClZncakYJfcYnoXt3UsQrZZ9ZRauGm77l9UCbBLw=="], - - "@aws-sdk/client-s3/@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.40", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.11", "@smithy/protocol-http": "^5.3.11", "@smithy/service-error-classification": "^4.2.11", "@smithy/smithy-client": "^4.12.3", "@smithy/types": "^4.13.0", "@smithy/util-middleware": "^4.2.11", "@smithy/util-retry": "^4.2.11", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-YhEMakG1Ae57FajERdHNZ4ShOPIY7DsgV+ZoAxo/5BT0KIe+f6DDU2rtIymNNFIj22NJfeeI6LWIifrwM0f+rA=="], - - "@aws-sdk/client-s3/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-W9g1bOLui7Xn5FABRVS0o3rXL0gfN37d/8I/W7i0N7oxjx9QecUmXEMSUMADTODwdtka9cN43t5BI2CodLJpng=="], - - "@aws-sdk/client-s3/@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-s+eenEPW6RgliDk2IhjD2hWOxIx1NKrOHxEwNUaUXxYBxIyCcDfNULZ2Mu15E3kwcJWBedTET/kEASPV1A1Akg=="], - - "@aws-sdk/client-s3/@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.11", "", { "dependencies": { "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-xD17eE7kaLgBBGf5CZQ58hh2YmwK1Z0O8YhffwB/De2jsL0U3JklmhVYJ9Uf37OtUDLF2gsW40Xwwag9U869Gg=="], - - "@aws-sdk/client-s3/@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.14", "", { "dependencies": { "@smithy/abort-controller": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/querystring-builder": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-DamSqaU8nuk0xTJDrYnRzZndHwwRnyj/n/+RqGGCcBKB4qrQem0mSDiWdupaNWdwxzyMU91qxDmHOCazfhtO3A=="], - - "@aws-sdk/client-s3/@smithy/protocol-http": ["@smithy/protocol-http@5.3.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hI+barOVDJBkNt4y0L2mu3Ugc0w7+BpJ2CZuLwXtSltGAAwCb3IvnalGlbDV/UCS6a9ZuT3+exd1WxNdLb5IlQ=="], - - "@aws-sdk/client-s3/@smithy/smithy-client": ["@smithy/smithy-client@4.12.3", "", { "dependencies": { "@smithy/core": "^3.23.9", "@smithy/middleware-endpoint": "^4.4.23", "@smithy/middleware-stack": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "@smithy/util-stream": "^4.5.17", "tslib": "^2.6.2" } }, "sha512-7k4UxjSpHmPN2AxVhvIazRSzFQjWnud3sOsXcFStzagww17j1cFQYqTSiQ8xuYK3vKLR1Ni8FzuT3VlKr3xCNw=="], - - "@aws-sdk/client-s3/@smithy/types": ["@smithy/types@4.13.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw=="], - - "@aws-sdk/client-s3/@smithy/url-parser": ["@smithy/url-parser@4.2.11", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-oTAGGHo8ZYc5VZsBREzuf5lf2pAurJQsccMusVZ85wDkX66ojEc/XauiGjzCj50A61ObFTPe6d7Pyt6UBYaing=="], - - "@aws-sdk/client-s3/@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.39", "", { "dependencies": { "@smithy/property-provider": "^4.2.11", "@smithy/smithy-client": "^4.12.3", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-ui7/Ho/+VHqS7Km2wBw4/Ab4RktoiSshgcgpJzC4keFPs6tLJS4IQwbeahxQS3E/w98uq6E1mirCH/id9xIXeQ=="], - - "@aws-sdk/client-s3/@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.42", "", { "dependencies": { "@smithy/config-resolver": "^4.4.10", "@smithy/credential-provider-imds": "^4.2.11", "@smithy/node-config-provider": "^4.3.11", "@smithy/property-provider": "^4.2.11", "@smithy/smithy-client": "^4.12.3", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-QDA84CWNe8Akpj15ofLO+1N3Rfg8qa2K5uX0y6HnOp4AnRYRgWrKx/xzbYNbVF9ZsyJUYOfcoaN3y93wA/QJ2A=="], - - "@aws-sdk/client-s3/@smithy/util-endpoints": ["@smithy/util-endpoints@3.3.2", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-+4HFLpE5u29AbFlTdlKIT7jfOzZ8PDYZKTb3e+AgLz986OYwqTourQ5H+jg79/66DB69Un1+qKecLnkZdAsYcA=="], - - "@aws-sdk/client-s3/@smithy/util-middleware": ["@smithy/util-middleware@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-r3dtF9F+TpSZUxpOVVtPfk09Rlo4lT6ORBqEvX3IBT6SkQAdDSVKR5GcfmZbtl7WKhKnmb3wbDTQ6ibR2XHClw=="], - - "@aws-sdk/client-s3/@smithy/util-retry": ["@smithy/util-retry@4.2.11", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-XSZULmL5x6aCTTii59wJqKsY1l3eMIAomRAccW7Tzh9r8s7T/7rdo03oektuH5jeYRlJMPcNP92EuRDvk9aXbw=="], - - "@aws-sdk/client-s3/@smithy/util-stream": ["@smithy/util-stream@4.5.17", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.13", "@smithy/node-http-handler": "^4.4.14", "@smithy/types": "^4.13.0", "@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" } }, "sha512-793BYZ4h2JAQkNHcEnyFxDTcZbm9bVybD0UV/LEWmZ5bkTms7JqjfrLMi2Qy0E5WFcCzLwCAPgcvcvxoeALbAQ=="], + "@aws-sdk/client-s3/@smithy/node-http-handler": ["@smithy/node-http-handler@4.0.3", "", { "dependencies": { "@smithy/abort-controller": "^4.0.1", "@smithy/protocol-http": "^5.0.1", "@smithy/querystring-builder": "^4.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-dYCLeINNbYdvmMLtW0VdhW1biXt+PPCGazzT5ZjKw46mOtdgToQEwjqZSS9/EN8+tNs/RO0cEWG044+YZs97aA=="], "@aws-sdk/client-sso/@aws-sdk/core": ["@aws-sdk/core@3.623.0", "", { "dependencies": { "@smithy/core": "^2.3.2", "@smithy/node-config-provider": "^3.1.4", "@smithy/protocol-http": "^4.1.0", "@smithy/signature-v4": "^4.1.0", "@smithy/smithy-client": "^3.1.12", "@smithy/types": "^3.3.0", "@smithy/util-middleware": "^3.0.3", "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" } }, "sha512-8Toq3X6trX/67obSdh4K0MFQY4f132bEbr1i0YPDWk/O3KdBt12mLC/sW3aVRnlIs110XMuX9yrWWqJ8fDW10g=="], @@ -5447,9 +5135,7 @@ "@aws-sdk/client-sso-oidc/@smithy/util-utf8": ["@smithy/util-utf8@3.0.0", "", { "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" } }, "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA=="], - "@aws-sdk/core/@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="], - - "@aws-sdk/crc64-nvme/@smithy/types": ["@smithy/types@4.13.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw=="], + "@aws-sdk/core/@smithy/property-provider": ["@smithy/property-provider@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-o+VRiwC2cgmk/WFV0jaETGOtX16VNPp2bSQEzu0whbReqE1BMqsP2ami2Vi3cbGVdKu1kq9gQkDAGKbt0WOHAQ=="], "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/types": ["@aws-sdk/types@3.609.0", "", { "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" } }, "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q=="], @@ -5479,23 +5165,33 @@ "@aws-sdk/credential-provider-ini/@smithy/types": ["@smithy/types@3.3.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA=="], - "@aws-sdk/credential-provider-login/@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="], + "@aws-sdk/credential-provider-login/@aws-sdk/core": ["@aws-sdk/core@3.947.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@aws-sdk/xml-builder": "3.930.0", "@smithy/core": "^3.18.7", "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Khq4zHhuAkvCFuFbgcy3GrZTzfSX7ZIjIcW1zRDxXRLZKRtuhnZdonqTUfaWi5K42/4OmxkYNpsO7X7trQOeHw=="], - "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.20", "", { "dependencies": { "@aws-sdk/core": "^3.973.22", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-vI0QN96DFx3g9AunfOWF3CS4cMkqFiR/WM/FyP9QHr5rZ2dKPkYwP3tCgAOvGuu9CXI7dC1vU2FVUuZ+tfpNvQ=="], + "@aws-sdk/credential-provider-login/@aws-sdk/types": ["@aws-sdk/types@3.936.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg=="], - "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.22", "", { "dependencies": { "@aws-sdk/core": "^3.973.22", "@aws-sdk/types": "^3.973.6", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/node-http-handler": "^4.5.0", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.6", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.20", "tslib": "^2.6.2" } }, "sha512-aS/81smalpe7XDnuQfOq4LIPuaV2PRKU2aMTrHcqO5BD4HwO5kESOHNcec2AYfBtLtIDqgF6RXisgBnfK/jt0w=="], + "@aws-sdk/credential-provider-login/@smithy/property-provider": ["@smithy/property-provider@4.2.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-a/tGSLPtaia2krbRdwR4xbZKO8lU67DjMk/jfY4QKt4PRlKML+2tL/gmAuhNdFDioO6wOq0sXkfnddNFH9mNUA=="], - "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.22", "", { "dependencies": { "@aws-sdk/core": "^3.973.22", "@aws-sdk/credential-provider-env": "^3.972.20", "@aws-sdk/credential-provider-http": "^3.972.22", "@aws-sdk/credential-provider-login": "^3.972.22", "@aws-sdk/credential-provider-process": "^3.972.20", "@aws-sdk/credential-provider-sso": "^3.972.22", "@aws-sdk/credential-provider-web-identity": "^3.972.22", "@aws-sdk/nested-clients": "^3.996.12", "@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" } }, "sha512-rpF8fBT0LllMDp78s62aL2A/8MaccjyJ0ORzqu+ZADeECLSrrCWIeeXsuRam+pxiAMkI1uIyDZJmgLGdadkPXw=="], + "@aws-sdk/credential-provider-login/@smithy/protocol-http": ["@smithy/protocol-http@5.3.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-qLRZzP2+PqhE3OSwvY2jpBbP0WKTZ9opTsn+6IWYI0SKVpbG+imcfNxXPq9fj5XeaUTr7odpsNpK6dmoiM1gJQ=="], - "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.20", "", { "dependencies": { "@aws-sdk/core": "^3.973.22", "@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" } }, "sha512-QRfk7GbA4/HDRjhP3QYR6QBr/QKreVoOzvvlRHnOuGgYJkeoPgPY3LAI1kK1ZMgZ4hH9KiGp757/ntol+INAig=="], + "@aws-sdk/credential-provider-login/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.1", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-tph+oQYPbpN6NamF030hx1gb5YN2Plog+GLaRHpoEDwp8+ZPG26rIJvStG9hkWzN2HBn3HcWg0sHeB0tmkYzqA=="], - "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.22", "", { "dependencies": { "@aws-sdk/core": "^3.973.22", "@aws-sdk/nested-clients": "^3.996.12", "@aws-sdk/token-providers": "3.1013.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" } }, "sha512-4vqlSaUbBj4aNPVKfB6yXuIQ2Z2mvLfIGba2OzzF6zUkN437/PGWsxBU2F8QPSFHti6seckvyCXidU3H+R8NvQ=="], + "@aws-sdk/credential-provider-login/@smithy/types": ["@smithy/types@4.10.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-K9mY7V/f3Ul+/Gz4LJANZ3vJ/yiBIwCyxe0sPT4vNJK63Srvd+Yk1IzP0t+nE7XFSpIGtzR71yljtnqpUTYFlQ=="], - "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.22", "", { "dependencies": { "@aws-sdk/core": "^3.973.22", "@aws-sdk/nested-clients": "^3.996.12", "@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" } }, "sha512-/wN1CYg2rVLhW8/jLxMWacQrkpaynnL+4j/Z+e6X1PfoE6NiC0BeOw3i0JmtZrKun85wNV5GmspvuWJihfeeUw=="], + "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.758.0", "", { "dependencies": { "@aws-sdk/core": "3.758.0", "@aws-sdk/types": "3.734.0", "@smithy/property-provider": "^4.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-N27eFoRrO6MeUNumtNHDW9WOiwfd59LPXPqDrIa3kWL/s+fOKFHb9xIcF++bAwtcZnAxKkgpDCUP+INNZskE+w=="], - "@aws-sdk/credential-provider-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.12", "", { "dependencies": { "@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" } }, "sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg=="], + "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.758.0", "", { "dependencies": { "@aws-sdk/core": "3.758.0", "@aws-sdk/types": "3.734.0", "@smithy/fetch-http-handler": "^5.0.1", "@smithy/node-http-handler": "^4.0.3", "@smithy/property-provider": "^4.0.1", "@smithy/protocol-http": "^5.0.1", "@smithy/smithy-client": "^4.1.6", "@smithy/types": "^4.1.0", "@smithy/util-stream": "^4.1.2", "tslib": "^2.6.2" } }, "sha512-Xt9/U8qUCiw1hihztWkNeIR+arg6P+yda10OuCHX6kFVx3auTlU7+hCqs3UxqniGU4dguHuftf3mRpi5/GJ33Q=="], - "@aws-sdk/credential-provider-node/@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="], + "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.758.0", "", { "dependencies": { "@aws-sdk/core": "3.758.0", "@aws-sdk/credential-provider-env": "3.758.0", "@aws-sdk/credential-provider-http": "3.758.0", "@aws-sdk/credential-provider-process": "3.758.0", "@aws-sdk/credential-provider-sso": "3.758.0", "@aws-sdk/credential-provider-web-identity": "3.758.0", "@aws-sdk/nested-clients": "3.758.0", "@aws-sdk/types": "3.734.0", "@smithy/credential-provider-imds": "^4.0.1", "@smithy/property-provider": "^4.0.1", "@smithy/shared-ini-file-loader": "^4.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-cymSKMcP5d+OsgetoIZ5QCe1wnp2Q/tq+uIxVdh9MbfdBBEnl9Ecq6dH6VlYS89sp4QKuxHxkWXVnbXU3Q19Aw=="], + + "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.758.0", "", { "dependencies": { "@aws-sdk/core": "3.758.0", "@aws-sdk/types": "3.734.0", "@smithy/property-provider": "^4.0.1", "@smithy/shared-ini-file-loader": "^4.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-AzcY74QTPqcbXWVgjpPZ3HOmxQZYPROIBz2YINF0OQk0MhezDWV/O7Xec+K1+MPGQO3qS6EDrUUlnPLjsqieHA=="], + + "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.758.0", "", { "dependencies": { "@aws-sdk/client-sso": "3.758.0", "@aws-sdk/core": "3.758.0", "@aws-sdk/token-providers": "3.758.0", "@aws-sdk/types": "3.734.0", "@smithy/property-provider": "^4.0.1", "@smithy/shared-ini-file-loader": "^4.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-x0FYJqcOLUCv8GLLFDYMXRAQKGjoM+L0BG4BiHYZRDf24yQWFCAZsCQAYKo6XZYh2qznbsW6f//qpyJ5b0QVKQ=="], + + "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.758.0", "", { "dependencies": { "@aws-sdk/core": "3.758.0", "@aws-sdk/nested-clients": "3.758.0", "@aws-sdk/types": "3.734.0", "@smithy/property-provider": "^4.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-XGguXhBqiCXMXRxcfCAVPlMbm3VyJTou79r/3mxWddHWF0XbhaQiBIbUz6vobVTD25YQRbWSmSch7VA8kI5Lrw=="], + + "@aws-sdk/credential-provider-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.0.1", "", { "dependencies": { "@smithy/node-config-provider": "^4.0.1", "@smithy/property-provider": "^4.0.1", "@smithy/types": "^4.1.0", "@smithy/url-parser": "^4.0.1", "tslib": "^2.6.2" } }, "sha512-l/qdInaDq1Zpznpmev/+52QomsJNZ3JkTl5yrTl02V6NBgJOQ4LY0SFw/8zsMwj3tLe8vqiIuwF6nxaEwgf6mg=="], + + "@aws-sdk/credential-provider-node/@smithy/property-provider": ["@smithy/property-provider@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-o+VRiwC2cgmk/WFV0jaETGOtX16VNPp2bSQEzu0whbReqE1BMqsP2ami2Vi3cbGVdKu1kq9gQkDAGKbt0WOHAQ=="], "@aws-sdk/credential-provider-process/@aws-sdk/types": ["@aws-sdk/types@3.609.0", "", { "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" } }, "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q=="], @@ -5521,181 +5217,125 @@ "@aws-sdk/credential-providers/@smithy/types": ["@smithy/types@3.3.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA=="], - "@aws-sdk/middleware-bucket-endpoint/@aws-sdk/types": ["@aws-sdk/types@3.973.5", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ=="], + "@aws-sdk/eventstream-handler-node/@aws-sdk/types": ["@aws-sdk/types@3.936.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg=="], - "@aws-sdk/middleware-bucket-endpoint/@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.11", "", { "dependencies": { "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-xD17eE7kaLgBBGf5CZQ58hh2YmwK1Z0O8YhffwB/De2jsL0U3JklmhVYJ9Uf37OtUDLF2gsW40Xwwag9U869Gg=="], + "@aws-sdk/eventstream-handler-node/@smithy/types": ["@smithy/types@4.10.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-K9mY7V/f3Ul+/Gz4LJANZ3vJ/yiBIwCyxe0sPT4vNJK63Srvd+Yk1IzP0t+nE7XFSpIGtzR71yljtnqpUTYFlQ=="], - "@aws-sdk/middleware-bucket-endpoint/@smithy/protocol-http": ["@smithy/protocol-http@5.3.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hI+barOVDJBkNt4y0L2mu3Ugc0w7+BpJ2CZuLwXtSltGAAwCb3IvnalGlbDV/UCS6a9ZuT3+exd1WxNdLb5IlQ=="], + "@aws-sdk/middleware-eventstream/@aws-sdk/types": ["@aws-sdk/types@3.936.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg=="], - "@aws-sdk/middleware-bucket-endpoint/@smithy/types": ["@smithy/types@4.13.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw=="], + "@aws-sdk/middleware-eventstream/@smithy/protocol-http": ["@smithy/protocol-http@5.3.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-qLRZzP2+PqhE3OSwvY2jpBbP0WKTZ9opTsn+6IWYI0SKVpbG+imcfNxXPq9fj5XeaUTr7odpsNpK6dmoiM1gJQ=="], - "@aws-sdk/middleware-expect-continue/@aws-sdk/types": ["@aws-sdk/types@3.973.5", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ=="], + "@aws-sdk/middleware-eventstream/@smithy/types": ["@smithy/types@4.10.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-K9mY7V/f3Ul+/Gz4LJANZ3vJ/yiBIwCyxe0sPT4vNJK63Srvd+Yk1IzP0t+nE7XFSpIGtzR71yljtnqpUTYFlQ=="], - "@aws-sdk/middleware-expect-continue/@smithy/protocol-http": ["@smithy/protocol-http@5.3.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hI+barOVDJBkNt4y0L2mu3Ugc0w7+BpJ2CZuLwXtSltGAAwCb3IvnalGlbDV/UCS6a9ZuT3+exd1WxNdLb5IlQ=="], + "@aws-sdk/middleware-websocket/@aws-sdk/types": ["@aws-sdk/types@3.936.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg=="], - "@aws-sdk/middleware-expect-continue/@smithy/types": ["@smithy/types@4.13.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw=="], + "@aws-sdk/middleware-websocket/@aws-sdk/util-format-url": ["@aws-sdk/util-format-url@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/querystring-builder": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-MS5eSEtDUFIAMHrJaMERiHAvDPdfxc/T869ZjDNFAIiZhyc037REw0aoTNeimNXDNy2txRNZJaAUn/kE4RwN+g=="], - "@aws-sdk/middleware-flexible-checksums/@aws-sdk/core": ["@aws-sdk/core@3.973.18", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@aws-sdk/xml-builder": "^3.972.10", "@smithy/core": "^3.23.8", "@smithy/node-config-provider": "^4.3.11", "@smithy/property-provider": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/signature-v4": "^5.3.11", "@smithy/smithy-client": "^4.12.2", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-GUIlegfcK2LO1J2Y98sCJy63rQSiLiDOgVw7HiHPRqfI2vb3XozTVqemwO0VSGXp54ngCnAQz0Lf0YPCBINNxA=="], + "@aws-sdk/middleware-websocket/@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.6", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.6", "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-6OiaAaEbLB6dEkRbQyNzFSJv5HDvly3Mc6q/qcPd2uS/g3szR8wAIkh7UndAFKfMypNSTuZ6eCBmgCLR5LacTg=="], - "@aws-sdk/middleware-flexible-checksums/@aws-sdk/types": ["@aws-sdk/types@3.973.5", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ=="], + "@aws-sdk/middleware-websocket/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.7", "", { "dependencies": { "@smithy/protocol-http": "^5.3.6", "@smithy/querystring-builder": "^4.2.6", "@smithy/types": "^4.10.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-fcVap4QwqmzQwQK9QU3keeEpCzTjnP9NJ171vI7GnD7nbkAIcP9biZhDUx88uRH9BabSsQDS0unUps88uZvFIQ=="], - "@aws-sdk/middleware-flexible-checksums/@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.11", "", { "dependencies": { "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-xD17eE7kaLgBBGf5CZQ58hh2YmwK1Z0O8YhffwB/De2jsL0U3JklmhVYJ9Uf37OtUDLF2gsW40Xwwag9U869Gg=="], + "@aws-sdk/middleware-websocket/@smithy/protocol-http": ["@smithy/protocol-http@5.3.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-qLRZzP2+PqhE3OSwvY2jpBbP0WKTZ9opTsn+6IWYI0SKVpbG+imcfNxXPq9fj5XeaUTr7odpsNpK6dmoiM1gJQ=="], - "@aws-sdk/middleware-flexible-checksums/@smithy/protocol-http": ["@smithy/protocol-http@5.3.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hI+barOVDJBkNt4y0L2mu3Ugc0w7+BpJ2CZuLwXtSltGAAwCb3IvnalGlbDV/UCS6a9ZuT3+exd1WxNdLb5IlQ=="], + "@aws-sdk/middleware-websocket/@smithy/signature-v4": ["@smithy/signature-v4@5.3.6", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.6", "@smithy/types": "^4.10.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.6", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-P1TXDHuQMadTMTOBv4oElZMURU4uyEhxhHfn+qOc2iofW9Rd4sZtBGx58Lzk112rIGVEYZT8eUMK4NftpewpRA=="], - "@aws-sdk/middleware-flexible-checksums/@smithy/types": ["@smithy/types@4.13.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw=="], + "@aws-sdk/middleware-websocket/@smithy/types": ["@smithy/types@4.10.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-K9mY7V/f3Ul+/Gz4LJANZ3vJ/yiBIwCyxe0sPT4vNJK63Srvd+Yk1IzP0t+nE7XFSpIGtzR71yljtnqpUTYFlQ=="], - "@aws-sdk/middleware-flexible-checksums/@smithy/util-middleware": ["@smithy/util-middleware@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-r3dtF9F+TpSZUxpOVVtPfk09Rlo4lT6ORBqEvX3IBT6SkQAdDSVKR5GcfmZbtl7WKhKnmb3wbDTQ6ibR2XHClw=="], + "@aws-sdk/nested-clients/@aws-sdk/core": ["@aws-sdk/core@3.947.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@aws-sdk/xml-builder": "3.930.0", "@smithy/core": "^3.18.7", "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Khq4zHhuAkvCFuFbgcy3GrZTzfSX7ZIjIcW1zRDxXRLZKRtuhnZdonqTUfaWi5K42/4OmxkYNpsO7X7trQOeHw=="], - "@aws-sdk/middleware-flexible-checksums/@smithy/util-stream": ["@smithy/util-stream@4.5.17", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.13", "@smithy/node-http-handler": "^4.4.14", "@smithy/types": "^4.13.0", "@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" } }, "sha512-793BYZ4h2JAQkNHcEnyFxDTcZbm9bVybD0UV/LEWmZ5bkTms7JqjfrLMi2Qy0E5WFcCzLwCAPgcvcvxoeALbAQ=="], + "@aws-sdk/nested-clients/@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw=="], - "@aws-sdk/middleware-location-constraint/@aws-sdk/types": ["@aws-sdk/types@3.973.5", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ=="], + "@aws-sdk/nested-clients/@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw=="], - "@aws-sdk/middleware-location-constraint/@smithy/types": ["@smithy/types@4.13.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw=="], + "@aws-sdk/nested-clients/@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.948.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Qa8Zj+EAqA0VlAVvxpRnpBpIWJI9KUwaioY1vkeNVwXPlNaz9y9zCKVM9iU9OZ5HXpoUg6TnhATAHXHAE8+QsQ=="], - "@aws-sdk/middleware-sdk-s3/@aws-sdk/core": ["@aws-sdk/core@3.973.18", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@aws-sdk/xml-builder": "^3.972.10", "@smithy/core": "^3.23.8", "@smithy/node-config-provider": "^4.3.11", "@smithy/property-provider": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/signature-v4": "^5.3.11", "@smithy/smithy-client": "^4.12.2", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-GUIlegfcK2LO1J2Y98sCJy63rQSiLiDOgVw7HiHPRqfI2vb3XozTVqemwO0VSGXp54ngCnAQz0Lf0YPCBINNxA=="], + "@aws-sdk/nested-clients/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.947.0", "", { "dependencies": { "@aws-sdk/core": "3.947.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@smithy/core": "^3.18.7", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-7rpKV8YNgCP2R4F9RjWZFcD2R+SO/0R4VHIbY9iZJdH2MzzJ8ZG7h8dZ2m8QkQd1fjx4wrFJGGPJUTYXPV3baA=="], - "@aws-sdk/middleware-sdk-s3/@aws-sdk/types": ["@aws-sdk/types@3.973.5", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ=="], + "@aws-sdk/nested-clients/@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/config-resolver": "^4.4.3", "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw=="], - "@aws-sdk/middleware-sdk-s3/@smithy/core": ["@smithy/core@3.23.9", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.12", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-stream": "^4.5.17", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-1Vcut4LEL9HZsdpI0vFiRYIsaoPwZLjAxnVQDUMQK8beMS+EYPLDQCXtbzfxmM5GzSgjfe2Q9M7WaXwIMQllyQ=="], + "@aws-sdk/nested-clients/@aws-sdk/types": ["@aws-sdk/types@3.936.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg=="], - "@aws-sdk/middleware-sdk-s3/@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.11", "", { "dependencies": { "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-xD17eE7kaLgBBGf5CZQ58hh2YmwK1Z0O8YhffwB/De2jsL0U3JklmhVYJ9Uf37OtUDLF2gsW40Xwwag9U869Gg=="], + "@aws-sdk/nested-clients/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-endpoints": "^3.2.5", "tslib": "^2.6.2" } }, "sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w=="], - "@aws-sdk/middleware-sdk-s3/@smithy/protocol-http": ["@smithy/protocol-http@5.3.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hI+barOVDJBkNt4y0L2mu3Ugc0w7+BpJ2CZuLwXtSltGAAwCb3IvnalGlbDV/UCS6a9ZuT3+exd1WxNdLb5IlQ=="], + "@aws-sdk/nested-clients/@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw=="], - "@aws-sdk/middleware-sdk-s3/@smithy/signature-v4": ["@smithy/signature-v4@5.3.11", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-V1L6N9aKOBAN4wEHLyqjLBnAz13mtILU0SeDrjOaIZEeN6IFa6DxwRt1NNpOdmSpQUfkBj0qeD3m6P77uzMhgQ=="], + "@aws-sdk/nested-clients/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.947.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.947.0", "@aws-sdk/types": "3.936.0", "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-+vhHoDrdbb+zerV4noQk1DHaUMNzWFWPpPYjVTwW2186k5BEJIecAMChYkghRrBVJ3KPWP1+JnZwOd72F3d4rQ=="], - "@aws-sdk/middleware-sdk-s3/@smithy/smithy-client": ["@smithy/smithy-client@4.12.3", "", { "dependencies": { "@smithy/core": "^3.23.9", "@smithy/middleware-endpoint": "^4.4.23", "@smithy/middleware-stack": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "@smithy/util-stream": "^4.5.17", "tslib": "^2.6.2" } }, "sha512-7k4UxjSpHmPN2AxVhvIazRSzFQjWnud3sOsXcFStzagww17j1cFQYqTSiQ8xuYK3vKLR1Ni8FzuT3VlKr3xCNw=="], + "@aws-sdk/nested-clients/@smithy/config-resolver": ["@smithy/config-resolver@4.4.4", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.6", "@smithy/types": "^4.10.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-endpoints": "^3.2.6", "@smithy/util-middleware": "^4.2.6", "tslib": "^2.6.2" } }, "sha512-s3U5ChS21DwU54kMmZ0UJumoS5cg0+rGVZvN6f5Lp6EbAVi0ZyP+qDSHdewfmXKUgNK1j3z45JyzulkDukrjAA=="], - "@aws-sdk/middleware-sdk-s3/@smithy/types": ["@smithy/types@4.13.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw=="], + "@aws-sdk/nested-clients/@smithy/core": ["@smithy/core@3.19.0", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.7", "@smithy/protocol-http": "^5.3.6", "@smithy/types": "^4.10.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.6", "@smithy/util-stream": "^4.5.7", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-Y9oHXpBcXQgYHOcAEmxjkDilUbSTkgKjoHYed3WaYUH8jngq8lPWDBSpjHblJ9uOgBdy5mh3pzebrScDdYr29w=="], - "@aws-sdk/middleware-sdk-s3/@smithy/util-middleware": ["@smithy/util-middleware@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-r3dtF9F+TpSZUxpOVVtPfk09Rlo4lT6ORBqEvX3IBT6SkQAdDSVKR5GcfmZbtl7WKhKnmb3wbDTQ6ibR2XHClw=="], + "@aws-sdk/nested-clients/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.7", "", { "dependencies": { "@smithy/protocol-http": "^5.3.6", "@smithy/querystring-builder": "^4.2.6", "@smithy/types": "^4.10.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-fcVap4QwqmzQwQK9QU3keeEpCzTjnP9NJ171vI7GnD7nbkAIcP9biZhDUx88uRH9BabSsQDS0unUps88uZvFIQ=="], - "@aws-sdk/middleware-sdk-s3/@smithy/util-stream": ["@smithy/util-stream@4.5.17", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.13", "@smithy/node-http-handler": "^4.4.14", "@smithy/types": "^4.13.0", "@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" } }, "sha512-793BYZ4h2JAQkNHcEnyFxDTcZbm9bVybD0UV/LEWmZ5bkTms7JqjfrLMi2Qy0E5WFcCzLwCAPgcvcvxoeALbAQ=="], + "@aws-sdk/nested-clients/@smithy/hash-node": ["@smithy/hash-node@4.2.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-k3Dy9VNR37wfMh2/1RHkFf/e0rMyN0pjY0FdyY6ItJRjENYyVPRMwad6ZR1S9HFm6tTuIOd9pqKBmtJ4VHxvxg=="], - "@aws-sdk/middleware-ssec/@aws-sdk/types": ["@aws-sdk/types@3.973.5", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ=="], + "@aws-sdk/nested-clients/@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-E4t/V/q2T46RY21fpfznd1iSLTvCXKNKo4zJ1QuEFN4SE9gKfu2vb6bgq35LpufkQ+SETWIC7ZAf2GGvTlBaMQ=="], - "@aws-sdk/middleware-ssec/@smithy/types": ["@smithy/types@4.13.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw=="], + "@aws-sdk/nested-clients/@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.6", "", { "dependencies": { "@smithy/protocol-http": "^5.3.6", "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-0cjqjyfj+Gls30ntq45SsBtqF3dfJQCeqQPyGz58Pk8OgrAr5YiB7ZvDzjCA94p4r6DCI4qLm7FKobqBjf515w=="], - "@aws-sdk/middleware-websocket/@aws-sdk/util-format-url": ["@aws-sdk/util-format-url@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-J6DS9oocrgxM8xlUTTmQOuwRF6rnAGEujAN9SAzllcrQmwn5iJ58ogxy3SEhD0Q7JZvlA5jvIXBkpQRqEqlE9A=="], + "@aws-sdk/nested-clients/@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.0", "", { "dependencies": { "@smithy/core": "^3.19.0", "@smithy/middleware-serde": "^4.2.7", "@smithy/node-config-provider": "^4.3.6", "@smithy/shared-ini-file-loader": "^4.4.1", "@smithy/types": "^4.10.0", "@smithy/url-parser": "^4.2.6", "@smithy/util-middleware": "^4.2.6", "tslib": "^2.6.2" } }, "sha512-M6qWfUNny6NFNy8amrCGIb9TfOMUkHVtg9bHtEFGRgfH7A7AtPpn/fcrToGPjVDK1ECuMVvqGQOXcZxmu9K+7A=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.758.0", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "3.758.0", "@aws-sdk/types": "3.734.0", "@smithy/protocol-http": "^5.0.1", "@smithy/signature-v4": "^5.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-0RPCo8fYJcrenJ6bRtiUbFOSgQ1CX/GpvwtLU2Fam1tS9h2klKK8d74caeV6A1mIUvBU7bhyQ0wMGlwMtn3EYw=="], + "@aws-sdk/nested-clients/@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.16", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.6", "@smithy/protocol-http": "^5.3.6", "@smithy/service-error-classification": "^4.2.6", "@smithy/smithy-client": "^4.10.1", "@smithy/types": "^4.10.0", "@smithy/util-middleware": "^4.2.6", "@smithy/util-retry": "^4.2.6", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-XPpNhNRzm3vhYm7YCsyw3AtmWggJbg1wNGAoqb7NBYr5XA5isMRv14jgbYyUV6IvbTBFZQdf2QpeW43LrRdStQ=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/types": ["@aws-sdk/types@3.734.0", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-o11tSPTT70nAkGV1fN9wm/hAIiLPyWX6SuGf+9JyTp7S/rC2cFWhR26MvA69nplcjNaXVzB0f+QFrLXXjOqCrg=="], + "@aws-sdk/nested-clients/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.7", "", { "dependencies": { "@smithy/protocol-http": "^5.3.6", "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-PFMVHVPgtFECeu4iZ+4SX6VOQT0+dIpm4jSPLLL6JLSkp9RohGqKBKD0cbiXdeIFS08Forp0UHI6kc0gIHenSA=="], - "@aws-sdk/s3-request-presigner/@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.0.6", "", { "dependencies": { "@smithy/core": "^3.1.5", "@smithy/middleware-serde": "^4.0.2", "@smithy/node-config-provider": "^4.0.1", "@smithy/shared-ini-file-loader": "^4.0.1", "@smithy/types": "^4.1.0", "@smithy/url-parser": "^4.0.1", "@smithy/util-middleware": "^4.0.1", "tslib": "^2.6.2" } }, "sha512-ftpmkTHIFqgaFugcjzLZv3kzPEFsBFSnq1JsIkr2mwFzCraZVhQk2gqN51OOeRxqhbPTkRFj39Qd2V91E/mQxg=="], + "@aws-sdk/nested-clients/@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-JSbALU3G+JS4kyBZPqnJ3hxIYwOVRV7r9GNQMS6j5VsQDo5+Es5nddLfr9TQlxZLNHPvKSh+XSB0OuWGfSWFcA=="], - "@aws-sdk/s3-request-presigner/@smithy/protocol-http": ["@smithy/protocol-http@5.3.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-3sfFd2MAzVt0Q/klOmjFi3oIkxczHs0avbwrfn1aBqtc23WqQSmjvk77MBw9WkEQcwbOYIX5/2z4ULj8DuxSsw=="], + "@aws-sdk/nested-clients/@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.6", "", { "dependencies": { "@smithy/property-provider": "^4.2.6", "@smithy/shared-ini-file-loader": "^4.4.1", "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-fYEyL59Qe82Ha1p97YQTMEQPJYmBS+ux76foqluaTVWoG9Px5J53w6NvXZNE3wP7lIicLDF7Vj1Em18XTX7fsA=="], - "@aws-sdk/s3-request-presigner/@smithy/smithy-client": ["@smithy/smithy-client@4.1.6", "", { "dependencies": { "@smithy/core": "^3.1.5", "@smithy/middleware-endpoint": "^4.0.6", "@smithy/middleware-stack": "^4.0.1", "@smithy/protocol-http": "^5.0.1", "@smithy/types": "^4.1.0", "@smithy/util-stream": "^4.1.2", "tslib": "^2.6.2" } }, "sha512-UYDolNg6h2O0L+cJjtgSyKKvEKCOa/8FHYJnBobyeoeWDmNpXjwOAtw16ezyeu1ETuuLEOZbrynK0ZY1Lx9Jbw=="], + "@aws-sdk/nested-clients/@smithy/protocol-http": ["@smithy/protocol-http@5.3.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-qLRZzP2+PqhE3OSwvY2jpBbP0WKTZ9opTsn+6IWYI0SKVpbG+imcfNxXPq9fj5XeaUTr7odpsNpK6dmoiM1gJQ=="], - "@aws-sdk/s3-request-presigner/@smithy/types": ["@smithy/types@4.8.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-N0Zn0OT1zc+NA+UVfkYqQzviRh5ucWwO7mBV3TmHHprMnfcJNfhlPicDkBHi0ewbh+y3evR6cNAW0Raxvb01NA=="], + "@aws-sdk/nested-clients/@smithy/smithy-client": ["@smithy/smithy-client@4.10.1", "", { "dependencies": { "@smithy/core": "^3.19.0", "@smithy/middleware-endpoint": "^4.4.0", "@smithy/middleware-stack": "^4.2.6", "@smithy/protocol-http": "^5.3.6", "@smithy/types": "^4.10.0", "@smithy/util-stream": "^4.5.7", "tslib": "^2.6.2" } }, "sha512-1ovWdxzYprhq+mWqiGZlt3kF69LJthuQcfY9BIyHx9MywTFKzFapluku1QXoaBB43GCsLDxNqS+1v30ure69AA=="], - "@aws-sdk/signature-v4-multi-region/@aws-sdk/types": ["@aws-sdk/types@3.973.5", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ=="], + "@aws-sdk/nested-clients/@smithy/types": ["@smithy/types@4.10.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-K9mY7V/f3Ul+/Gz4LJANZ3vJ/yiBIwCyxe0sPT4vNJK63Srvd+Yk1IzP0t+nE7XFSpIGtzR71yljtnqpUTYFlQ=="], - "@aws-sdk/signature-v4-multi-region/@smithy/protocol-http": ["@smithy/protocol-http@5.3.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hI+barOVDJBkNt4y0L2mu3Ugc0w7+BpJ2CZuLwXtSltGAAwCb3IvnalGlbDV/UCS6a9ZuT3+exd1WxNdLb5IlQ=="], + "@aws-sdk/nested-clients/@smithy/url-parser": ["@smithy/url-parser@4.2.6", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.6", "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-tVoyzJ2vXp4R3/aeV4EQjBDmCuWxRa8eo3KybL7Xv4wEM16nObYh7H1sNfcuLWHAAAzb0RVyxUz1S3sGj4X+Tg=="], - "@aws-sdk/signature-v4-multi-region/@smithy/signature-v4": ["@smithy/signature-v4@5.3.11", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-V1L6N9aKOBAN4wEHLyqjLBnAz13mtILU0SeDrjOaIZEeN6IFa6DxwRt1NNpOdmSpQUfkBj0qeD3m6P77uzMhgQ=="], + "@aws-sdk/nested-clients/@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="], - "@aws-sdk/signature-v4-multi-region/@smithy/types": ["@smithy/types@4.13.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw=="], + "@aws-sdk/nested-clients/@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg=="], - "@aws-sdk/token-providers/@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="], + "@aws-sdk/nested-clients/@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA=="], - "@aws-sdk/util-format-url/@aws-sdk/types": ["@aws-sdk/types@3.734.0", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-o11tSPTT70nAkGV1fN9wm/hAIiLPyWX6SuGf+9JyTp7S/rC2cFWhR26MvA69nplcjNaXVzB0f+QFrLXXjOqCrg=="], + "@aws-sdk/nested-clients/@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.15", "", { "dependencies": { "@smithy/property-provider": "^4.2.6", "@smithy/smithy-client": "^4.10.1", "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-LiZQVAg/oO8kueX4c+oMls5njaD2cRLXRfcjlTYjhIqmwHnCwkQO5B3dMQH0c5PACILxGAQf6Mxsq7CjlDc76A=="], + + "@aws-sdk/nested-clients/@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.18", "", { "dependencies": { "@smithy/config-resolver": "^4.4.4", "@smithy/credential-provider-imds": "^4.2.6", "@smithy/node-config-provider": "^4.3.6", "@smithy/property-provider": "^4.2.6", "@smithy/smithy-client": "^4.10.1", "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-Kw2J+KzYm9C9Z9nY6+W0tEnoZOofstVCMTshli9jhQbQCy64rueGfKzPfuFBnVUqZD9JobxTh2DzHmPkp/Va/Q=="], + + "@aws-sdk/nested-clients/@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.6", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.6", "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-v60VNM2+mPvgHCBXEfMCYrQ0RepP6u6xvbAkMenfe4Mi872CqNkJzgcnQL837e8NdeDxBgrWQRTluKq5Lqdhfg=="], + + "@aws-sdk/nested-clients/@smithy/util-middleware": ["@smithy/util-middleware@4.2.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-qrvXUkxBSAFomM3/OEMuDVwjh4wtqK8D2uDZPShzIqOylPst6gor2Cdp6+XrH4dyksAWq/bE2aSDYBTTnj0Rxg=="], + + "@aws-sdk/nested-clients/@smithy/util-retry": ["@smithy/util-retry@4.2.6", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.6", "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-x7CeDQLPQ9cb6xN7fRJEjlP9NyGW/YeXWc4j/RUhg4I+H60F0PEeRc2c/z3rm9zmsdiMFzpV/rT+4UHW6KM1SA=="], + + "@aws-sdk/nested-clients/@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], + + "@aws-sdk/token-providers/@aws-sdk/core": ["@aws-sdk/core@3.947.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@aws-sdk/xml-builder": "3.930.0", "@smithy/core": "^3.18.7", "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Khq4zHhuAkvCFuFbgcy3GrZTzfSX7ZIjIcW1zRDxXRLZKRtuhnZdonqTUfaWi5K42/4OmxkYNpsO7X7trQOeHw=="], + + "@aws-sdk/token-providers/@aws-sdk/types": ["@aws-sdk/types@3.936.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg=="], + + "@aws-sdk/token-providers/@smithy/property-provider": ["@smithy/property-provider@4.2.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-a/tGSLPtaia2krbRdwR4xbZKO8lU67DjMk/jfY4QKt4PRlKML+2tL/gmAuhNdFDioO6wOq0sXkfnddNFH9mNUA=="], + + "@aws-sdk/token-providers/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.1", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-tph+oQYPbpN6NamF030hx1gb5YN2Plog+GLaRHpoEDwp8+ZPG26rIJvStG9hkWzN2HBn3HcWg0sHeB0tmkYzqA=="], + + "@aws-sdk/token-providers/@smithy/types": ["@smithy/types@4.10.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-K9mY7V/f3Ul+/Gz4LJANZ3vJ/yiBIwCyxe0sPT4vNJK63Srvd+Yk1IzP0t+nE7XFSpIGtzR71yljtnqpUTYFlQ=="], "@aws-sdk/util-format-url/@smithy/querystring-builder": ["@smithy/querystring-builder@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "@smithy/util-uri-escape": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-wU87iWZoCbcqrwszsOewEIuq+SU2mSoBE2CcsLwE0I19m0B2gOJr1MVjxWcDQYOzHbR1xCk7AcOBbGFUYOKvdg=="], - "@aws-sdk/util-format-url/@smithy/types": ["@smithy/types@4.8.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-N0Zn0OT1zc+NA+UVfkYqQzviRh5ucWwO7mBV3TmHHprMnfcJNfhlPicDkBHi0ewbh+y3evR6cNAW0Raxvb01NA=="], - "@azure/core-http-compat/@azure/abort-controller": ["@azure/abort-controller@1.1.0", "", { "dependencies": { "tslib": "^2.2.0" } }, "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw=="], - "@azure/storage-blob/@azure/core-http-compat": ["@azure/core-http-compat@2.3.2", "", { "dependencies": { "@azure/abort-controller": "^2.1.2" }, "peerDependencies": { "@azure/core-client": "^1.10.0", "@azure/core-rest-pipeline": "^1.22.0" } }, "sha512-Tf6ltdKzOJEgxZeWLCjMxrxbodB/ZeCbzzA1A2qHbhzAjzjHoBVSUeSl/baT/oHAxhc4qdqVaDKnc2+iE932gw=="], - - "@azure/storage-blob/@azure/core-paging": ["@azure/core-paging@1.6.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA=="], - - "@azure/storage-blob/@azure/logger": ["@azure/logger@1.3.0", "", { "dependencies": { "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA=="], - - "@azure/storage-common/@azure/core-http-compat": ["@azure/core-http-compat@2.3.2", "", { "dependencies": { "@azure/abort-controller": "^2.1.2" }, "peerDependencies": { "@azure/core-client": "^1.10.0", "@azure/core-rest-pipeline": "^1.22.0" } }, "sha512-Tf6ltdKzOJEgxZeWLCjMxrxbodB/ZeCbzzA1A2qHbhzAjzjHoBVSUeSl/baT/oHAxhc4qdqVaDKnc2+iE932gw=="], - - "@azure/storage-common/@azure/logger": ["@azure/logger@1.3.0", "", { "dependencies": { "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA=="], - - "@babel/core/@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="], + "@azure/core-xml/fast-xml-parser": ["fast-xml-parser@5.0.9", "", { "dependencies": { "strnum": "^2.0.5" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-2mBwCiuW3ycKQQ6SOesSB8WeF+fIGb6I/GG5vU5/XEptwFFhp9PE8b9O7fbs2dpq9fXn4ULR3UsfydNUCntf5A=="], "@babel/generator/@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=="], - "@babel/helper-annotate-as-pure/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - "@babel/helper-compilation-targets/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=="], "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], - "@babel/helper-create-class-features-plugin/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], + "@babel/helper-define-polyfill-provider/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=="], - "@babel/helper-create-regexp-features-plugin/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.25.9", "", { "dependencies": { "@babel/types": "^7.25.9" } }, "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g=="], + "@babel/plugin-transform-classes/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="], - "@babel/helper-define-polyfill-provider/resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], - - "@babel/helper-member-expression-to-functions/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], - - "@babel/helper-member-expression-to-functions/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - - "@babel/helper-module-imports/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], - - "@babel/helper-module-imports/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - - "@babel/helper-module-transforms/@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="], - - "@babel/helper-optimise-call-expression/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - - "@babel/helper-remap-async-to-generator/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], - - "@babel/helper-replace-supers/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], - - "@babel/helper-skip-transparent-expression-wrappers/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], - - "@babel/helper-skip-transparent-expression-wrappers/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - - "@babel/helper-wrap-function/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], - - "@babel/helper-wrap-function/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], - - "@babel/helper-wrap-function/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - - "@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], - - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], - - "@babel/plugin-transform-async-generator-functions/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], - - "@babel/plugin-transform-classes/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], - - "@babel/plugin-transform-computed-properties/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], - - "@babel/plugin-transform-destructuring/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], - - "@babel/plugin-transform-dotall-regex/@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="], - - "@babel/plugin-transform-duplicate-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="], - - "@babel/plugin-transform-function-name/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], - - "@babel/plugin-transform-modules-amd/@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="], - - "@babel/plugin-transform-modules-commonjs/@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="], - - "@babel/plugin-transform-modules-systemjs/@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="], - - "@babel/plugin-transform-modules-systemjs/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], - - "@babel/plugin-transform-modules-umd/@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="], - - "@babel/plugin-transform-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="], - - "@babel/plugin-transform-object-rest-spread/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], - - "@babel/plugin-transform-react-jsx/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - - "@babel/plugin-transform-regexp-modifiers/@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="], + "@babel/plugin-transform-explicit-resource-management/@babel/plugin-transform-destructuring": ["@babel/plugin-transform-destructuring@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw=="], "@babel/plugin-transform-runtime/babel-plugin-polyfill-corejs2": ["babel-plugin-polyfill-corejs2@0.4.8", "", { "dependencies": { "@babel/compat-data": "^7.22.6", "@babel/helper-define-polyfill-provider": "^0.5.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-OtIuQfafSzpo/LhnJaykc0R/MMnuLSSVjVYy9mHArIZ9qTCSZ6TpWCuEKZYVoN//t8HqBNScHrOtCrIK5IaGLg=="], @@ -5703,40 +5343,18 @@ "@babel/plugin-transform-runtime/babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.5.5", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.5.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-OJGYZlhLqBh2DDHeqAxWB1XIvr49CxiJ2gIt61/PU55CQK4Z58OzMqjDe1zwQdQk+rBYsRc+1rJmdajM3gimHg=="], - "@babel/plugin-transform-unicode-property-regex/@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="], + "@babel/plugin-transform-typescript/@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.23.10", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-member-expression-to-functions": "^7.23.0", "@babel/helper-optimise-call-expression": "^7.22.5", "@babel/helper-replace-supers": "^7.22.20", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-2XpP2XhkXzgxecPNEEK8Vz8Asj9aRxt08oKOqtiZoqV2UGZ5T+EkyP9sXQ9nwMxBIG34a7jmasVqoMop7VdPUw=="], - "@babel/plugin-transform-unicode-regex/@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="], - - "@babel/plugin-transform-unicode-sets-regex/@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="], - - "@babel/preset-modules/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], + "@babel/preset-typescript/@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.23.3", "", { "dependencies": { "@babel/helper-module-transforms": "^7.23.3", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-simple-access": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA=="], "@codesandbox/sandpack-client/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], - "@csstools/postcss-cascade-layers/postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="], - - "@csstools/postcss-is-pseudo-class/postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="], - - "@csstools/postcss-scope-pseudo-class/postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="], - - "@csstools/selector-resolve-nested/postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="], - - "@csstools/selector-specificity/postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="], + "@csstools/css-color-parser/@csstools/color-helpers": ["@csstools/color-helpers@4.0.0", "", {}, "sha512-wjyXB22/h2OvxAr3jldPB7R7kjTUEzopvjitS8jWtyd8fN6xJ8vy1HnHu0ZNfEkqpBJgQ76Q+sBDshWcMvTa/w=="], "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], - "@eslint/config-array/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "@eslint/config-array/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], - "@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=="], "@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="], @@ -5755,45 +5373,51 @@ "@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/jest-util": ["jest-util@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", "graceful-fs": "^4.2.11", "picomatch": "^4.0.2" } }, "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA=="], "@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/jest-util": ["jest-util@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", "graceful-fs": "^4.2.11", "picomatch": "^4.0.2" } }, "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA=="], "@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/@jest/fake-timers": ["@jest/fake-timers@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@sinonjs/fake-timers": "^13.0.0", "@types/node": "*", "jest-message-util": "30.2.0", "jest-mock": "30.2.0", "jest-util": "30.2.0" } }, "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw=="], - "@jest/environment-jsdom-abstract/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="], + "@jest/environment/jest-mock": ["jest-mock@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "jest-util": "30.2.0" } }, "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw=="], + + "@jest/environment-jsdom-abstract/@jest/fake-timers": ["@jest/fake-timers@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@sinonjs/fake-timers": "^13.0.0", "@types/node": "*", "jest-message-util": "30.2.0", "jest-mock": "30.2.0", "jest-util": "30.2.0" } }, "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw=="], + + "@jest/environment-jsdom-abstract/jest-mock": ["jest-mock@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "jest-util": "30.2.0" } }, "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw=="], + + "@jest/environment-jsdom-abstract/jest-util": ["jest-util@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", "graceful-fs": "^4.2.11", "picomatch": "^4.0.2" } }, "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA=="], "@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/fake-timers/@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=="], - "@jest/pattern/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="], + "@jest/fake-timers/jest-message-util": ["jest-message-util@29.7.0", "", { "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w=="], + + "@jest/globals/jest-mock": ["jest-mock@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "jest-util": "30.2.0" } }, "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw=="], "@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/jest-util": ["jest-util@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", "graceful-fs": "^4.2.11", "picomatch": "^4.0.2" } }, "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA=="], + "@jest/reporters/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], "@jest/source-map/@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/test-sequencer/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], - "@jest/transform/@babel/core": ["@babel/core@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@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-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw=="], - "@jest/transform/@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/transform/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], + "@jest/transform/jest-util": ["jest-util@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", "graceful-fs": "^4.2.11", "picomatch": "^4.0.2" } }, "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA=="], - "@jest/types/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="], + "@jest/transform/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], "@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=="], @@ -5821,11 +5445,17 @@ "@langchain/mistralai/uuid": ["uuid@10.0.0", "", { "bin": "dist/bin/uuid" }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime": ["@aws-sdk/client-bedrock-runtime@3.1004.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.18", "@aws-sdk/credential-provider-node": "^3.972.18", "@aws-sdk/eventstream-handler-node": "^3.972.10", "@aws-sdk/middleware-eventstream": "^3.972.7", "@aws-sdk/middleware-host-header": "^3.972.7", "@aws-sdk/middleware-logger": "^3.972.7", "@aws-sdk/middleware-recursion-detection": "^3.972.7", "@aws-sdk/middleware-user-agent": "^3.972.19", "@aws-sdk/middleware-websocket": "^3.972.12", "@aws-sdk/region-config-resolver": "^3.972.7", "@aws-sdk/token-providers": "3.1004.0", "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-endpoints": "^3.996.4", "@aws-sdk/util-user-agent-browser": "^3.972.7", "@aws-sdk/util-user-agent-node": "^3.973.4", "@smithy/config-resolver": "^4.4.10", "@smithy/core": "^3.23.8", "@smithy/eventstream-serde-browser": "^4.2.11", "@smithy/eventstream-serde-config-resolver": "^4.3.11", "@smithy/eventstream-serde-node": "^4.2.11", "@smithy/fetch-http-handler": "^5.3.13", "@smithy/hash-node": "^4.2.11", "@smithy/invalid-dependency": "^4.2.11", "@smithy/middleware-content-length": "^4.2.11", "@smithy/middleware-endpoint": "^4.4.22", "@smithy/middleware-retry": "^4.4.39", "@smithy/middleware-serde": "^4.2.12", "@smithy/middleware-stack": "^4.2.11", "@smithy/node-config-provider": "^4.3.11", "@smithy/node-http-handler": "^4.4.14", "@smithy/protocol-http": "^5.3.11", "@smithy/smithy-client": "^4.12.2", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@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.38", "@smithy/util-defaults-mode-node": "^4.2.41", "@smithy/util-endpoints": "^3.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-retry": "^4.2.11", "@smithy/util-stream": "^4.5.17", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-t8cl+bPLlHZQD2Sw1a4hSLUybqJZU71+m8znkyeU8CHntFqEp2mMbuLKdHKaAYQ1fAApXMsvzenCAkDzNeeJlw=="], + "@librechat/client/@babel/preset-env": ["@babel/preset-env@7.28.5", "", { "dependencies": { "@babel/compat-data": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-import-assertions": "^7.27.1", "@babel/plugin-syntax-import-attributes": "^7.27.1", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.27.1", "@babel/plugin-transform-async-generator-functions": "^7.28.0", "@babel/plugin-transform-async-to-generator": "^7.27.1", "@babel/plugin-transform-block-scoped-functions": "^7.27.1", "@babel/plugin-transform-block-scoping": "^7.28.5", "@babel/plugin-transform-class-properties": "^7.27.1", "@babel/plugin-transform-class-static-block": "^7.28.3", "@babel/plugin-transform-classes": "^7.28.4", "@babel/plugin-transform-computed-properties": "^7.27.1", "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-dotall-regex": "^7.27.1", "@babel/plugin-transform-duplicate-keys": "^7.27.1", "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", "@babel/plugin-transform-dynamic-import": "^7.27.1", "@babel/plugin-transform-explicit-resource-management": "^7.28.0", "@babel/plugin-transform-exponentiation-operator": "^7.28.5", "@babel/plugin-transform-export-namespace-from": "^7.27.1", "@babel/plugin-transform-for-of": "^7.27.1", "@babel/plugin-transform-function-name": "^7.27.1", "@babel/plugin-transform-json-strings": "^7.27.1", "@babel/plugin-transform-literals": "^7.27.1", "@babel/plugin-transform-logical-assignment-operators": "^7.28.5", "@babel/plugin-transform-member-expression-literals": "^7.27.1", "@babel/plugin-transform-modules-amd": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", "@babel/plugin-transform-modules-systemjs": "^7.28.5", "@babel/plugin-transform-modules-umd": "^7.27.1", "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", "@babel/plugin-transform-new-target": "^7.27.1", "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", "@babel/plugin-transform-numeric-separator": "^7.27.1", "@babel/plugin-transform-object-rest-spread": "^7.28.4", "@babel/plugin-transform-object-super": "^7.27.1", "@babel/plugin-transform-optional-catch-binding": "^7.27.1", "@babel/plugin-transform-optional-chaining": "^7.28.5", "@babel/plugin-transform-parameters": "^7.27.7", "@babel/plugin-transform-private-methods": "^7.27.1", "@babel/plugin-transform-private-property-in-object": "^7.27.1", "@babel/plugin-transform-property-literals": "^7.27.1", "@babel/plugin-transform-regenerator": "^7.28.4", "@babel/plugin-transform-regexp-modifiers": "^7.27.1", "@babel/plugin-transform-reserved-words": "^7.27.1", "@babel/plugin-transform-shorthand-properties": "^7.27.1", "@babel/plugin-transform-spread": "^7.27.1", "@babel/plugin-transform-sticky-regex": "^7.27.1", "@babel/plugin-transform-template-literals": "^7.27.1", "@babel/plugin-transform-typeof-symbol": "^7.27.1", "@babel/plugin-transform-unicode-escapes": "^7.27.1", "@babel/plugin-transform-unicode-property-regex": "^7.27.1", "@babel/plugin-transform-unicode-regex": "^7.27.1", "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.14", "babel-plugin-polyfill-corejs3": "^0.13.0", "babel-plugin-polyfill-regenerator": "^0.6.5", "core-js-compat": "^3.43.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg=="], - "@librechat/backend/@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.14", "", { "dependencies": { "@smithy/abort-controller": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/querystring-builder": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-DamSqaU8nuk0xTJDrYnRzZndHwwRnyj/n/+RqGGCcBKB4qrQem0mSDiWdupaNWdwxzyMU91qxDmHOCazfhtO3A=="], + "@librechat/client/@babel/preset-react": ["@babel/preset-react@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-transform-react-display-name": "^7.28.0", "@babel/plugin-transform-react-jsx": "^7.27.1", "@babel/plugin-transform-react-jsx-development": "^7.27.1", "@babel/plugin-transform-react-pure-annotations": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ=="], - "@librechat/client/caniuse-lite": ["caniuse-lite@1.0.30001777", "", {}, "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ=="], + "@librechat/client/@babel/preset-typescript": ["@babel/preset-typescript@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", "@babel/plugin-transform-typescript": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g=="], + + "@librechat/frontend/@babel/preset-env": ["@babel/preset-env@7.28.5", "", { "dependencies": { "@babel/compat-data": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-import-assertions": "^7.27.1", "@babel/plugin-syntax-import-attributes": "^7.27.1", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.27.1", "@babel/plugin-transform-async-generator-functions": "^7.28.0", "@babel/plugin-transform-async-to-generator": "^7.27.1", "@babel/plugin-transform-block-scoped-functions": "^7.27.1", "@babel/plugin-transform-block-scoping": "^7.28.5", "@babel/plugin-transform-class-properties": "^7.27.1", "@babel/plugin-transform-class-static-block": "^7.28.3", "@babel/plugin-transform-classes": "^7.28.4", "@babel/plugin-transform-computed-properties": "^7.27.1", "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-dotall-regex": "^7.27.1", "@babel/plugin-transform-duplicate-keys": "^7.27.1", "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", "@babel/plugin-transform-dynamic-import": "^7.27.1", "@babel/plugin-transform-explicit-resource-management": "^7.28.0", "@babel/plugin-transform-exponentiation-operator": "^7.28.5", "@babel/plugin-transform-export-namespace-from": "^7.27.1", "@babel/plugin-transform-for-of": "^7.27.1", "@babel/plugin-transform-function-name": "^7.27.1", "@babel/plugin-transform-json-strings": "^7.27.1", "@babel/plugin-transform-literals": "^7.27.1", "@babel/plugin-transform-logical-assignment-operators": "^7.28.5", "@babel/plugin-transform-member-expression-literals": "^7.27.1", "@babel/plugin-transform-modules-amd": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", "@babel/plugin-transform-modules-systemjs": "^7.28.5", "@babel/plugin-transform-modules-umd": "^7.27.1", "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", "@babel/plugin-transform-new-target": "^7.27.1", "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", "@babel/plugin-transform-numeric-separator": "^7.27.1", "@babel/plugin-transform-object-rest-spread": "^7.28.4", "@babel/plugin-transform-object-super": "^7.27.1", "@babel/plugin-transform-optional-catch-binding": "^7.27.1", "@babel/plugin-transform-optional-chaining": "^7.28.5", "@babel/plugin-transform-parameters": "^7.27.7", "@babel/plugin-transform-private-methods": "^7.27.1", "@babel/plugin-transform-private-property-in-object": "^7.27.1", "@babel/plugin-transform-property-literals": "^7.27.1", "@babel/plugin-transform-regenerator": "^7.28.4", "@babel/plugin-transform-regexp-modifiers": "^7.27.1", "@babel/plugin-transform-reserved-words": "^7.27.1", "@babel/plugin-transform-shorthand-properties": "^7.27.1", "@babel/plugin-transform-spread": "^7.27.1", "@babel/plugin-transform-sticky-regex": "^7.27.1", "@babel/plugin-transform-template-literals": "^7.27.1", "@babel/plugin-transform-typeof-symbol": "^7.27.1", "@babel/plugin-transform-unicode-escapes": "^7.27.1", "@babel/plugin-transform-unicode-property-regex": "^7.27.1", "@babel/plugin-transform-unicode-regex": "^7.27.1", "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.14", "babel-plugin-polyfill-corejs3": "^0.13.0", "babel-plugin-polyfill-regenerator": "^0.6.5", "core-js-compat": "^3.43.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg=="], + + "@librechat/frontend/@babel/preset-react": ["@babel/preset-react@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-transform-react-display-name": "^7.28.0", "@babel/plugin-transform-react-jsx": "^7.27.1", "@babel/plugin-transform-react-jsx-development": "^7.27.1", "@babel/plugin-transform-react-pure-annotations": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ=="], + + "@librechat/frontend/@babel/preset-typescript": ["@babel/preset-typescript@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", "@babel/plugin-transform-typescript": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g=="], "@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=="], @@ -5833,20 +5463,20 @@ "@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=="], + "@librechat/frontend/jest-environment-jsdom": ["jest-environment-jsdom@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/jsdom": "^20.0.0", "@types/node": "*", "jest-mock": "^29.7.0", "jest-util": "^29.7.0", "jsdom": "^20.0.0" }, "peerDependencies": { "canvas": "^2.5.0" }, "optionalPeers": ["canvas"] }, "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA=="], + "@librechat/frontend/lucide-react": ["lucide-react@0.394.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0" } }, "sha512-PzTbJ0bsyXRhH59k5qe7MpTd5MxlpYZUcM9kGSwvPGAfnn0J6FElDwu2EX6Vuh//F7y60rcVJiFQ7EK9DCMgfw=="], "@mcp-ui/client/@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.21.0", "", { "dependencies": { "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" } }, "sha512-YFBsXJMFCyI1zP98u7gezMFKX4lgu/XpoZJk7ufI6UlFKXLj2hAMUuRlQX/nrmIPOmhRrG6tw2OQ2ZA/ZlXYpQ=="], - "@mistralai/mistralai/zod-to-json-schema": ["zod-to-json-schema@3.24.3", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-HIAfWdYIt1sssHfYZFCXp4rU1w2r8hVVXYIlmoa0r0gABLs5di3RCqPU5DDROogVz1pAdYBaz7HK5n9pSUNs3A=="], - "@modelcontextprotocol/sdk/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], + "@modelcontextprotocol/sdk/express-rate-limit": ["express-rate-limit@7.5.0", "", { "peerDependencies": { "express": "^4.11 || 5 || ^5.0.0-beta.1" } }, "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg=="], + + "@modelcontextprotocol/sdk/zod-to-json-schema": ["zod-to-json-schema@3.25.0", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ=="], + "@node-saml/node-saml/@types/qs": ["@types/qs@6.14.0", "", {}, "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ=="], - "@node-saml/node-saml/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "@node-saml/node-saml/xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="], - "@node-saml/passport-saml/@types/express": ["@types/express@4.17.23", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", "@types/serve-static": "*" } }, "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ=="], "@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=="], @@ -5855,19 +5485,9 @@ "@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=="], - "@opentelemetry/sdk-node/@opentelemetry/exporter-trace-otlp-http": ["@opentelemetry/exporter-trace-otlp-http@0.207.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-exporter-base": "0.207.0", "@opentelemetry/otlp-transformer": "0.207.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/sdk-trace-base": "2.2.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-HSRBzXHIC7C8UfPQdu15zEEoBGv0yWkhEwxqgPCHVUKUQ9NLHVGXkVrf65Uaj7UwmAkC1gQfkuVYvLlD//AnUQ=="], - "@radix-ui/react-alert-dialog/@radix-ui/primitive": ["@radix-ui/primitive@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10" } }, "sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA=="], - - "@radix-ui/react-alert-dialog/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA=="], - - "@radix-ui/react-alert-dialog/@radix-ui/react-context": ["@radix-ui/react-context@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg=="], - - "@radix-ui/react-alert-dialog/@radix-ui/react-primitive": ["@radix-ui/react-primitive@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-slot": "1.0.1" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" } }, "sha512-fHbmislWVkZaIdeF6GZxF0A/NH/3BjrGIYj+Ae6eTmTCr7EB0RQAAVEiqsXK6p3/JcRqVSBQoceZroj30Jj3XA=="], - - "@radix-ui/react-alert-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-compose-refs": "1.0.0" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw=="], + "@radix-ui/react-alert-dialog/@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], "@radix-ui/react-arrow/@radix-ui/react-primitive": ["@radix-ui/react-primitive@1.0.3", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-slot": "1.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" } }, "sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g=="], @@ -5883,29 +5503,11 @@ "@radix-ui/react-collapsible/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA=="], - "@radix-ui/react-dialog/@radix-ui/primitive": ["@radix-ui/primitive@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10" } }, "sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA=="], + "@radix-ui/react-dialog/@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], - "@radix-ui/react-dialog/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA=="], + "@radix-ui/react-dialog/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="], - "@radix-ui/react-dialog/@radix-ui/react-context": ["@radix-ui/react-context@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg=="], - - "@radix-ui/react-dialog/@radix-ui/react-id": ["@radix-ui/react-id@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-use-layout-effect": "1.0.0" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw=="], - - "@radix-ui/react-dialog/@radix-ui/react-presence": ["@radix-ui/react-presence@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-compose-refs": "1.0.0", "@radix-ui/react-use-layout-effect": "1.0.0" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" } }, "sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w=="], - - "@radix-ui/react-dialog/@radix-ui/react-primitive": ["@radix-ui/react-primitive@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-slot": "1.0.1" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" } }, "sha512-fHbmislWVkZaIdeF6GZxF0A/NH/3BjrGIYj+Ae6eTmTCr7EB0RQAAVEiqsXK6p3/JcRqVSBQoceZroj30Jj3XA=="], - - "@radix-ui/react-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-compose-refs": "1.0.0" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw=="], - - "@radix-ui/react-dialog/@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-use-callback-ref": "1.0.0" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-FohDoZvk3mEXh9AWAVyRTYR4Sq7/gavuofglmiXB2g1aKyboUD4YtgWxKj8O5n+Uak52gXQ4wKz5IFST4vtJHg=="], - - "@radix-ui/react-dismissable-layer/@radix-ui/primitive": ["@radix-ui/primitive@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10" } }, "sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA=="], - - "@radix-ui/react-dismissable-layer/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA=="], - - "@radix-ui/react-dismissable-layer/@radix-ui/react-primitive": ["@radix-ui/react-primitive@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-slot": "1.0.1" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" } }, "sha512-fHbmislWVkZaIdeF6GZxF0A/NH/3BjrGIYj+Ae6eTmTCr7EB0RQAAVEiqsXK6p3/JcRqVSBQoceZroj30Jj3XA=="], - - "@radix-ui/react-dismissable-layer/@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg=="], + "@radix-ui/react-dismissable-layer/@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], "@radix-ui/react-dropdown-menu/@radix-ui/primitive": ["@radix-ui/primitive@1.1.0", "", {}, "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA=="], @@ -5919,12 +5521,6 @@ "@radix-ui/react-dropdown-menu/@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.1.0", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw=="], - "@radix-ui/react-focus-scope/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA=="], - - "@radix-ui/react-focus-scope/@radix-ui/react-primitive": ["@radix-ui/react-primitive@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-slot": "1.0.1" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" } }, "sha512-fHbmislWVkZaIdeF6GZxF0A/NH/3BjrGIYj+Ae6eTmTCr7EB0RQAAVEiqsXK6p3/JcRqVSBQoceZroj30Jj3XA=="], - - "@radix-ui/react-focus-scope/@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg=="], - "@radix-ui/react-hover-card/@radix-ui/primitive": ["@radix-ui/primitive@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10" } }, "sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw=="], "@radix-ui/react-hover-card/@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=="], @@ -5971,6 +5567,8 @@ "@radix-ui/react-menu/@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw=="], + "@radix-ui/react-menu/react-remove-scroll": ["react-remove-scroll@2.5.5", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.3", "react-style-singleton": "^2.2.1", "tslib": "^2.1.0", "use-callback-ref": "^1.3.0", "use-sidecar": "^1.1.2" }, "peerDependencies": { "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw=="], + "@radix-ui/react-popover/@radix-ui/primitive": ["@radix-ui/primitive@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10" } }, "sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw=="], "@radix-ui/react-popover/@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=="], @@ -5993,6 +5591,8 @@ "@radix-ui/react-popover/@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-use-callback-ref": "1.0.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA=="], + "@radix-ui/react-popover/react-remove-scroll": ["react-remove-scroll@2.5.5", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.3", "react-style-singleton": "^2.2.1", "tslib": "^2.1.0", "use-callback-ref": "^1.3.0", "use-sidecar": "^1.1.2" }, "peerDependencies": { "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw=="], + "@radix-ui/react-popper/@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=="], "@radix-ui/react-popper/@radix-ui/react-context": ["@radix-ui/react-context@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg=="], @@ -6003,8 +5603,6 @@ "@radix-ui/react-popper/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ=="], - "@radix-ui/react-portal/@radix-ui/react-primitive": ["@radix-ui/react-primitive@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-slot": "1.0.1" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" } }, "sha512-fHbmislWVkZaIdeF6GZxF0A/NH/3BjrGIYj+Ae6eTmTCr7EB0RQAAVEiqsXK6p3/JcRqVSBQoceZroj30Jj3XA=="], - "@radix-ui/react-presence/@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=="], "@radix-ui/react-presence/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ=="], @@ -6023,16 +5621,10 @@ "@radix-ui/react-select/@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA=="], - "@radix-ui/react-select/@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw=="], - "@radix-ui/react-select/@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.7", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ=="], - "@radix-ui/react-select/@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.9", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ=="], - "@radix-ui/react-select/@radix-ui/react-use-previous": ["@radix-ui/react-use-previous@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ=="], - "@radix-ui/react-select/react-remove-scroll": ["react-remove-scroll@2.7.1", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA=="], - "@radix-ui/react-slider/@radix-ui/react-use-previous": ["@radix-ui/react-use-previous@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ=="], "@radix-ui/react-slider/@radix-ui/react-use-size": ["@radix-ui/react-use-size@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ=="], @@ -6077,8 +5669,6 @@ "@radix-ui/react-toast/@radix-ui/react-visually-hidden": ["@radix-ui/react-visually-hidden@1.0.3", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-primitive": "1.0.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" } }, "sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA=="], - "@radix-ui/react-use-escape-keydown/@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg=="], - "@radix-ui/react-use-size/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ=="], "@rollup/plugin-babel/@rollup/pluginutils": ["@rollup/pluginutils@3.1.0", "", { "dependencies": { "@types/estree": "0.0.39", "estree-walker": "^1.0.1", "picomatch": "^2.2.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0" } }, "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg=="], @@ -6091,90 +5681,62 @@ "@rollup/pluginutils/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "@smithy/abort-controller/@smithy/types": ["@smithy/types@4.10.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-K9mY7V/f3Ul+/Gz4LJANZ3vJ/yiBIwCyxe0sPT4vNJK63Srvd+Yk1IzP0t+nE7XFSpIGtzR71yljtnqpUTYFlQ=="], + "@smithy/credential-provider-imds/@smithy/node-config-provider": ["@smithy/node-config-provider@3.1.4", "", { "dependencies": { "@smithy/property-provider": "^3.1.3", "@smithy/shared-ini-file-loader": "^3.1.4", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" } }, "sha512-YvnElQy8HR4vDcAjoy7Xkx9YT8xZP4cBXcbJSgm/kxmiQu08DwUwj8rkGnyoJTpfl/3xYHH+d8zE+eHqoDCSdQ=="], "@smithy/credential-provider-imds/@smithy/types": ["@smithy/types@3.3.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA=="], "@smithy/credential-provider-imds/@smithy/url-parser": ["@smithy/url-parser@3.0.3", "", { "dependencies": { "@smithy/querystring-parser": "^3.0.3", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" } }, "sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A=="], - "@smithy/hash-blob-browser/@smithy/types": ["@smithy/types@4.13.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw=="], + "@smithy/eventstream-codec/@smithy/types": ["@smithy/types@4.10.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-K9mY7V/f3Ul+/Gz4LJANZ3vJ/yiBIwCyxe0sPT4vNJK63Srvd+Yk1IzP0t+nE7XFSpIGtzR71yljtnqpUTYFlQ=="], - "@smithy/hash-stream-node/@smithy/types": ["@smithy/types@4.13.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw=="], + "@smithy/eventstream-serde-universal/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.4", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.8.1", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-aV8blR9RBDKrOlZVgjOdmOibTC2sBXNiT7WA558b4MPdsLTV6sbyc1WIE9QiIuYMJjYtnPLciefoqSW8Gi+MZQ=="], - "@smithy/md5-js/@smithy/types": ["@smithy/types@4.13.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw=="], + "@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "@smithy/util-uri-escape": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-wU87iWZoCbcqrwszsOewEIuq+SU2mSoBE2CcsLwE0I19m0B2gOJr1MVjxWcDQYOzHbR1xCk7AcOBbGFUYOKvdg=="], - "@smithy/node-config-provider/@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="], + "@smithy/middleware-retry/uuid": ["uuid@9.0.1", "", { "bin": "dist/bin/uuid" }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], + + "@smithy/node-config-provider/@smithy/property-provider": ["@smithy/property-provider@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-o+VRiwC2cgmk/WFV0jaETGOtX16VNPp2bSQEzu0whbReqE1BMqsP2ami2Vi3cbGVdKu1kq9gQkDAGKbt0WOHAQ=="], + + "@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=="], + + "@smithy/node-http-handler/@smithy/types": ["@smithy/types@4.10.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-K9mY7V/f3Ul+/Gz4LJANZ3vJ/yiBIwCyxe0sPT4vNJK63Srvd+Yk1IzP0t+nE7XFSpIGtzR71yljtnqpUTYFlQ=="], "@smithy/property-provider/@smithy/types": ["@smithy/types@3.3.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA=="], - "@smithy/util-defaults-mode-browser/@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="], + "@smithy/querystring-builder/@smithy/types": ["@smithy/types@4.10.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-K9mY7V/f3Ul+/Gz4LJANZ3vJ/yiBIwCyxe0sPT4vNJK63Srvd+Yk1IzP0t+nE7XFSpIGtzR71yljtnqpUTYFlQ=="], - "@smithy/util-defaults-mode-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.12", "", { "dependencies": { "@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" } }, "sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg=="], + "@smithy/signature-v4/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - "@smithy/util-defaults-mode-node/@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="], + "@smithy/signature-v4/@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], - "@smithy/util-waiter/@smithy/abort-controller": ["@smithy/abort-controller@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-Hj4WoYWMJnSpM6/kchsm4bUNTL9XiSyhvoMb2KIq4VJzyDt7JpGHUZHkVNPZVC7YE1tf8tPeVauxpFBKGW4/KQ=="], + "@smithy/util-defaults-mode-browser/@smithy/property-provider": ["@smithy/property-provider@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-o+VRiwC2cgmk/WFV0jaETGOtX16VNPp2bSQEzu0whbReqE1BMqsP2ami2Vi3cbGVdKu1kq9gQkDAGKbt0WOHAQ=="], - "@smithy/util-waiter/@smithy/types": ["@smithy/types@4.13.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw=="], + "@smithy/util-defaults-mode-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.0.1", "", { "dependencies": { "@smithy/node-config-provider": "^4.0.1", "@smithy/property-provider": "^4.0.1", "@smithy/types": "^4.1.0", "@smithy/url-parser": "^4.0.1", "tslib": "^2.6.2" } }, "sha512-l/qdInaDq1Zpznpmev/+52QomsJNZ3JkTl5yrTl02V6NBgJOQ4LY0SFw/8zsMwj3tLe8vqiIuwF6nxaEwgf6mg=="], + + "@smithy/util-defaults-mode-node/@smithy/property-provider": ["@smithy/property-provider@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-o+VRiwC2cgmk/WFV0jaETGOtX16VNPp2bSQEzu0whbReqE1BMqsP2ami2Vi3cbGVdKu1kq9gQkDAGKbt0WOHAQ=="], + + "@smithy/util-stream/@smithy/node-http-handler": ["@smithy/node-http-handler@4.0.3", "", { "dependencies": { "@smithy/abort-controller": "^4.0.1", "@smithy/protocol-http": "^5.0.1", "@smithy/querystring-builder": "^4.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-dYCLeINNbYdvmMLtW0VdhW1biXt+PPCGazzT5ZjKw46mOtdgToQEwjqZSS9/EN8+tNs/RO0cEWG044+YZs97aA=="], + + "@smithy/util-waiter/@smithy/abort-controller": ["@smithy/abort-controller@4.0.4", "", { "dependencies": { "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA=="], "@surma/rollup-plugin-off-main-thread/magic-string": ["magic-string@0.25.9", "", { "dependencies": { "sourcemap-codec": "^1.4.8" } }, "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ=="], "@tanstack/match-sorter-utils/remove-accents": ["remove-accents@0.4.2", "", {}, "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA=="], - "@testing-library/dom/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], - "@testing-library/dom/aria-query": ["aria-query@5.1.3", "", { "dependencies": { "deep-equal": "^2.0.5" } }, "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ=="], "@testing-library/dom/dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="], "@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/babel__core/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], - - "@types/babel__core/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - - "@types/babel__generator/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - - "@types/babel__template/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], - - "@types/babel__template/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - - "@types/babel__traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - - "@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/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=="], - - "@typescript-eslint/type-utils/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "@typescript-eslint/typescript-estree/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], "@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": "bin/semver.js" }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], @@ -6189,29 +5751,17 @@ "asn1.js/bn.js": ["bn.js@4.12.2", "", {}, "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw=="], - "autoprefixer/caniuse-lite": ["caniuse-lite@1.0.30001777", "", {}, "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ=="], - "babel-jest/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], "babel-plugin-root-import/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], - "babel-plugin-transform-import-meta/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], - - "body-parser/iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], - - "body-parser/qs": ["qs@6.15.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ=="], - - "body-parser/raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="], - "browser-resolve/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=="], "browserify-rsa/bn.js": ["bn.js@5.2.2", "", {}, "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw=="], "browserify-sign/bn.js": ["bn.js@5.2.2", "", {}, "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw=="], - "browserslist/caniuse-lite": ["caniuse-lite@1.0.30001777", "", {}, "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ=="], - - "bun-types/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="], + "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=="], "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=="], @@ -6219,8 +5769,6 @@ "chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], - "cheerio/undici": ["undici@7.16.0", "", {}, "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g=="], - "chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "cli-truncate/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], @@ -6233,74 +5781,34 @@ "compression/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], - "concat-stream/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=="], - "cookie-parser/cookie-signature": ["cookie-signature@1.0.6", "", {}, "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="], - "core-js-compat/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=="], - "create-ecdh/bn.js": ["bn.js@4.12.2", "", {}, "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw=="], - "css-blank-pseudo/postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="], - - "css-has-pseudo/postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="], - "cssnano/lilconfig": ["lilconfig@2.1.0", "", {}, "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ=="], "cssnano/yaml": ["yaml@1.10.2", "", {}, "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="], - "cytoscape-fcose/cose-base": ["cose-base@2.2.0", "", { "dependencies": { "layout-base": "^2.0.0" } }, "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g=="], - - "d3-dsv/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], - - "d3-sankey/d3-array": ["d3-array@2.12.1", "", { "dependencies": { "internmap": "^1.0.0" } }, "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ=="], - - "d3-sankey/d3-shape": ["d3-shape@1.3.7", "", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="], - "data-urls/whatwg-url": ["whatwg-url@14.2.0", "", { "dependencies": { "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" } }, "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw=="], - "deep-equal/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], - "diffie-hellman/bn.js": ["bn.js@4.12.2", "", {}, "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw=="], - "dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + "domexception/webidl-conversions": ["webidl-conversions@7.0.0", "", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="], "error-ex/is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], - "es-get-iterator/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], - - "eslint/@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="], - - "eslint/ajv": ["ajv@6.12.6", "", { "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-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], - - "eslint/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "eslint/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - "eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], "eslint-import-resolver-node/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=="], - "eslint-import-resolver-typescript/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - "eslint-module-utils/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], - "eslint-plugin-i18next/lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], - "eslint-plugin-import/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], - "eslint-plugin-import/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - - "eslint-plugin-jsx-a11y/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - - "eslint-plugin-react/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - "execa/is-stream": ["is-stream@3.0.0", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="], "expect/jest-message-util": ["jest-message-util@29.7.0", "", { "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w=="], - "expect/jest-util": ["jest-util@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", "picomatch": "^2.2.3" } }, "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA=="], - "express-session/cookie-signature": ["cookie-signature@1.0.7", "", {}, "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="], "express-session/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], @@ -6311,23 +5819,25 @@ "filelist/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="], - "finalhandler/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - "flat-cache/keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], "form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], - "gaxios/https-proxy-agent": ["https-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.0.2", "debug": "4" } }, "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA=="], + "gaxios/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], - "gcp-metadata/gaxios": ["gaxios@5.1.3", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^5.0.0", "is-stream": "^2.0.0", "node-fetch": "^2.6.9" } }, "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA=="], + "glob/minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="], - "glob/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="], + "google-auth-library/gaxios": ["gaxios@6.2.0", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "is-stream": "^2.0.0", "node-fetch": "^2.6.9" } }, "sha512-H6+bHeoEAU5D6XNc6mPKeN5dLZqEDs9Gpk6I+SZBEzK5So58JVrHPmevNi35fRl1J9Y5TaeLW0kYx3pCJ1U2mQ=="], "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/whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="], + "googleapis-common/gaxios": ["gaxios@6.2.0", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "is-stream": "^2.0.0", "node-fetch": "^2.6.9" } }, "sha512-H6+bHeoEAU5D6XNc6mPKeN5dLZqEDs9Gpk6I+SZBEzK5So58JVrHPmevNi35fRl1J9Y5TaeLW0kYx3pCJ1U2mQ=="], - "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=="], + "googleapis-common/qs": ["qs@6.13.0", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="], + + "googleapis-common/uuid": ["uuid@9.0.1", "", { "bin": "dist/bin/uuid" }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], + + "gtoken/gaxios": ["gaxios@6.2.0", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "is-stream": "^2.0.0", "node-fetch": "^2.6.9" } }, "sha512-H6+bHeoEAU5D6XNc6mPKeN5dLZqEDs9Gpk6I+SZBEzK5So58JVrHPmevNi35fRl1J9Y5TaeLW0kYx3pCJ1U2mQ=="], "hast-util-from-html/@types/hast": ["@types/hast@2.3.10", "", { "dependencies": { "@types/unist": "^2" } }, "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw=="], @@ -6361,22 +5871,12 @@ "http-proxy-agent/agent-base": ["agent-base@7.1.3", "", {}, "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw=="], - "http-proxy-agent/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "https-proxy-agent/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - "import-from/resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], "import-in-the-middle/cjs-module-lexer": ["cjs-module-lexer@1.2.3", "", {}, "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ=="], - "ioredis/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - "is-bun-module/semver": ["semver@7.7.3", "", { "bin": "bin/semver.js" }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], - "istanbul-lib-instrument/@babel/core": ["@babel/core@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@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-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw=="], - - "istanbul-lib-instrument/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], - "istanbul-lib-instrument/semver": ["semver@7.7.3", "", { "bin": "bin/semver.js" }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], "istanbul-lib-report/make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="], @@ -6385,96 +5885,102 @@ "istanbul-lib-source-maps/@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=="], - "istanbul-lib-source-maps/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "jake/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - "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-changed-files/jest-util": ["jest-util@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", "graceful-fs": "^4.2.11", "picomatch": "^4.0.2" } }, "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA=="], "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/jest-util": ["jest-util@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", "graceful-fs": "^4.2.11", "picomatch": "^4.0.2" } }, "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA=="], + "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=="], "jest-circus/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], - "jest-config/@babel/core": ["@babel/core@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@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-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw=="], + "jest-cli/jest-util": ["jest-util@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", "graceful-fs": "^4.2.11", "picomatch": "^4.0.2" } }, "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA=="], "jest-config/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-config/jest-util": ["jest-util@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", "graceful-fs": "^4.2.11", "picomatch": "^4.0.2" } }, "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA=="], + "jest-config/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-config/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], "jest-diff/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-each/jest-util": ["jest-util@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", "graceful-fs": "^4.2.11", "picomatch": "^4.0.2" } }, "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA=="], + "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/@jest/fake-timers": ["@jest/fake-timers@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@sinonjs/fake-timers": "^13.0.0", "@types/node": "*", "jest-message-util": "30.2.0", "jest-mock": "30.2.0", "jest-util": "30.2.0" } }, "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw=="], - "jest-environment-node/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="], + "jest-environment-node/jest-mock": ["jest-mock@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "jest-util": "30.2.0" } }, "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw=="], + + "jest-environment-node/jest-util": ["jest-util@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", "graceful-fs": "^4.2.11", "picomatch": "^4.0.2" } }, "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA=="], "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-haste-map/jest-util": ["jest-util@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", "graceful-fs": "^4.2.11", "picomatch": "^4.0.2" } }, "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA=="], "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=="], - "jest-message-util/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], - "jest-message-util/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-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-mock/@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=="], + + "jest-resolve/jest-util": ["jest-util@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", "graceful-fs": "^4.2.11", "picomatch": "^4.0.2" } }, "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA=="], "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/jest-util": ["jest-util@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", "graceful-fs": "^4.2.11", "picomatch": "^4.0.2" } }, "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA=="], "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/@jest/fake-timers": ["@jest/fake-timers@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@sinonjs/fake-timers": "^13.0.0", "@types/node": "*", "jest-message-util": "30.2.0", "jest-mock": "30.2.0", "jest-util": "30.2.0" } }, "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw=="], "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/jest-mock": ["jest-mock@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "jest-util": "30.2.0" } }, "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw=="], + + "jest-runtime/jest-util": ["jest-util@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", "graceful-fs": "^4.2.11", "picomatch": "^4.0.2" } }, "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA=="], + "jest-runtime/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], "jest-runtime/strip-bom": ["strip-bom@4.0.0", "", {}, "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w=="], - "jest-snapshot/@babel/core": ["@babel/core@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@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-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw=="], - - "jest-snapshot/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], - - "jest-snapshot/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - "jest-snapshot/@jest/expect-utils": ["@jest/expect-utils@30.2.0", "", { "dependencies": { "@jest/get-type": "30.1.0" } }, "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA=="], "jest-snapshot/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-snapshot/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-snapshot/jest-util": ["jest-util@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", "graceful-fs": "^4.2.11", "picomatch": "^4.0.2" } }, "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA=="], + "jest-snapshot/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-snapshot/semver": ["semver@7.7.3", "", { "bin": "bin/semver.js" }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], "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-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=="], + + "jest-util/ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="], + + "jest-util/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "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-watcher/jest-util": ["jest-util@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", "graceful-fs": "^4.2.11", "picomatch": "^4.0.2" } }, "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA=="], - "jest-worker/@types/node": ["@types/node@20.11.16", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ=="], + "jest-worker/jest-util": ["jest-util@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", "graceful-fs": "^4.2.11", "picomatch": "^4.0.2" } }, "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA=="], "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=="], - "jsdom/decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="], "jsdom/webidl-conversions": ["webidl-conversions@7.0.0", "", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="], @@ -6487,23 +5993,27 @@ "jwks-rsa/@types/express": ["@types/express@4.17.21", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", "@types/serve-static": "*" } }, "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ=="], - "jwks-rsa/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - "jwks-rsa/jose": ["jose@4.15.5", "", {}, "sha512-jc7BFxgKPKi94uOvEmzlSWFFe2+vASyXaKUpdQKatWAESU2MWjDfFf0fdfc83CDKcA5QecabZeNLyfhe3yKNkg=="], "katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], "keyv-file/@keyv/serialize": ["@keyv/serialize@1.0.3", "", { "dependencies": { "buffer": "^6.0.3" } }, "sha512-qnEovoOp5Np2JDGonIDL6Ayihw0RhnRh6vxPuHo4RDn1UOzwEo4AeIfpL6UGIrsceWrCMiVPgwRjbHu4vYFc3g=="], + "keyv-file/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], + "langsmith/semver": ["semver@7.7.3", "", { "bin": "bin/semver.js" }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], "langsmith/uuid": ["uuid@10.0.0", "", { "bin": "dist/bin/uuid" }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="], "ldapauth-fork/lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], - "lint-staged/chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="], + "librechat-data-provider/@babel/preset-env": ["@babel/preset-env@7.28.5", "", { "dependencies": { "@babel/compat-data": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-import-assertions": "^7.27.1", "@babel/plugin-syntax-import-attributes": "^7.27.1", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.27.1", "@babel/plugin-transform-async-generator-functions": "^7.28.0", "@babel/plugin-transform-async-to-generator": "^7.27.1", "@babel/plugin-transform-block-scoped-functions": "^7.27.1", "@babel/plugin-transform-block-scoping": "^7.28.5", "@babel/plugin-transform-class-properties": "^7.27.1", "@babel/plugin-transform-class-static-block": "^7.28.3", "@babel/plugin-transform-classes": "^7.28.4", "@babel/plugin-transform-computed-properties": "^7.27.1", "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-dotall-regex": "^7.27.1", "@babel/plugin-transform-duplicate-keys": "^7.27.1", "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", "@babel/plugin-transform-dynamic-import": "^7.27.1", "@babel/plugin-transform-explicit-resource-management": "^7.28.0", "@babel/plugin-transform-exponentiation-operator": "^7.28.5", "@babel/plugin-transform-export-namespace-from": "^7.27.1", "@babel/plugin-transform-for-of": "^7.27.1", "@babel/plugin-transform-function-name": "^7.27.1", "@babel/plugin-transform-json-strings": "^7.27.1", "@babel/plugin-transform-literals": "^7.27.1", "@babel/plugin-transform-logical-assignment-operators": "^7.28.5", "@babel/plugin-transform-member-expression-literals": "^7.27.1", "@babel/plugin-transform-modules-amd": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", "@babel/plugin-transform-modules-systemjs": "^7.28.5", "@babel/plugin-transform-modules-umd": "^7.27.1", "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", "@babel/plugin-transform-new-target": "^7.27.1", "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", "@babel/plugin-transform-numeric-separator": "^7.27.1", "@babel/plugin-transform-object-rest-spread": "^7.28.4", "@babel/plugin-transform-object-super": "^7.27.1", "@babel/plugin-transform-optional-catch-binding": "^7.27.1", "@babel/plugin-transform-optional-chaining": "^7.28.5", "@babel/plugin-transform-parameters": "^7.27.7", "@babel/plugin-transform-private-methods": "^7.27.1", "@babel/plugin-transform-private-property-in-object": "^7.27.1", "@babel/plugin-transform-property-literals": "^7.27.1", "@babel/plugin-transform-regenerator": "^7.28.4", "@babel/plugin-transform-regexp-modifiers": "^7.27.1", "@babel/plugin-transform-reserved-words": "^7.27.1", "@babel/plugin-transform-shorthand-properties": "^7.27.1", "@babel/plugin-transform-spread": "^7.27.1", "@babel/plugin-transform-sticky-regex": "^7.27.1", "@babel/plugin-transform-template-literals": "^7.27.1", "@babel/plugin-transform-typeof-symbol": "^7.27.1", "@babel/plugin-transform-unicode-escapes": "^7.27.1", "@babel/plugin-transform-unicode-property-regex": "^7.27.1", "@babel/plugin-transform-unicode-regex": "^7.27.1", "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.14", "babel-plugin-polyfill-corejs3": "^0.13.0", "babel-plugin-polyfill-regenerator": "^0.6.5", "core-js-compat": "^3.43.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg=="], - "lint-staged/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], + "librechat-data-provider/@babel/preset-react": ["@babel/preset-react@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-transform-react-display-name": "^7.28.0", "@babel/plugin-transform-react-jsx": "^7.27.1", "@babel/plugin-transform-react-jsx-development": "^7.27.1", "@babel/plugin-transform-react-pure-annotations": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ=="], + + "librechat-data-provider/@babel/preset-typescript": ["@babel/preset-typescript@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", "@babel/plugin-transform-typescript": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g=="], + + "lint-staged/chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="], "log-update/ansi-escapes": ["ansi-escapes@7.0.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw=="], @@ -6517,54 +6027,30 @@ "lru-memoizer/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "mathjs/fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="], + "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], "mdast-util-math/unist-util-remove-position": ["unist-util-remove-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q=="], "mdast-util-mdx-jsx/unist-util-remove-position": ["unist-util-remove-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q=="], - "memorystore/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "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=="], - - "micromark/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "miller-rabin/bn.js": ["bn.js@4.12.2", "", {}, "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw=="], - "mlly/acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], - - "monaco-editor/dompurify": ["dompurify@3.2.7", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw=="], - "mongodb-connection-string-url/whatwg-url": ["whatwg-url@14.2.0", "", { "dependencies": { "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" } }, "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw=="], - "mongodb-memory-server-core/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "mongodb-memory-server-core/follow-redirects": ["follow-redirects@1.15.9", "", {}, "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="], - "mongodb-memory-server-core/semver": ["semver@7.7.3", "", { "bin": "bin/semver.js" }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], - "mongodb-memory-server-core/yauzl": ["yauzl@3.2.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "pend": "~1.2.0" } }, "sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w=="], - - "mquery/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], + "multer/mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": "bin/cmd.js" }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="], "multer/type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="], - "new-find-package-json/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - "node-stdlib-browser/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], "node-stdlib-browser/pkg-dir": ["pkg-dir@5.0.0", "", { "dependencies": { "find-up": "^5.0.0" } }, "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA=="], - "node-stdlib-browser/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=="], - - "nodemon/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "nodemon/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - "nodemon/semver": ["semver@7.7.3", "", { "bin": "bin/semver.js" }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], @@ -6575,8 +6061,6 @@ "parse-entities/@types/unist": ["@types/unist@2.0.10", "", {}, "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA=="], - "parse-json/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], - "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], "path-scurry/lru-cache": ["lru-cache@11.2.2", "", {}, "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg=="], @@ -6589,26 +6073,18 @@ "playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], - "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=="], "postcss-convert-values/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=="], - "postcss-custom-selectors/postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="], - - "postcss-dir-pseudo-class/postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="], - - "postcss-focus-visible/postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="], - - "postcss-focus-within/postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="], - "postcss-import/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=="], "postcss-load-config/lilconfig": ["lilconfig@2.1.0", "", {}, "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ=="], "postcss-load-config/yaml": ["yaml@1.10.2", "", {}, "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="], + "postcss-loader/semver": ["semver@7.7.3", "", { "bin": "bin/semver.js" }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "postcss-merge-rules/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=="], "postcss-minify-params/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=="], @@ -6617,16 +6093,14 @@ "postcss-modules-scope/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="], - "postcss-nesting/postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="], - "postcss-normalize-unicode/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=="], - "postcss-pseudo-class-any-link/postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="], + "postcss-preset-env/autoprefixer": ["autoprefixer@10.4.17", "", { "dependencies": { "browserslist": "^4.22.2", "caniuse-lite": "^1.0.30001578", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": "bin/autoprefixer" }, "sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg=="], + + "postcss-preset-env/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=="], "postcss-reduce-initial/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=="], - "postcss-selector-not/postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="], - "pretty-format/@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="], "pretty-format/react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], @@ -6641,8 +6115,6 @@ "read-cache/pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="], - "readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], - "readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "regjsparser/jsesc": ["jsesc@3.0.2", "", { "bin": "bin/jsesc" }, "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g=="], @@ -6661,14 +6133,10 @@ "remark-supersub/unist-util-visit": ["unist-util-visit@4.1.2", "", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0", "unist-util-visit-parents": "^5.1.1" } }, "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg=="], - "require-in-the-middle/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - "resolve-cwd/resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], "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=="], @@ -6679,13 +6147,7 @@ "rollup-pluginutils/estree-walker": ["estree-walker@0.6.1", "", {}, "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w=="], - "router/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "safe-array-concat/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], - - "safe-push-apply/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], - - "send/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], + "schema-utils/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], "sharp/semver": ["semver@7.7.3", "", { "bin": "bin/semver.js" }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], @@ -6699,10 +6161,6 @@ "static-browser-server/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], - "stream-browserify/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=="], - - "stream-http/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=="], - "string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], @@ -6715,8 +6173,6 @@ "sucrase/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=="], - "superagent/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - "superagent/mime": ["mime@2.6.0", "", { "bin": "cli.js" }, "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg=="], "superagent/qs": ["qs@6.13.0", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="], @@ -6725,16 +6181,10 @@ "svgo/css-select": ["css-select@4.3.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.0.1", "domhandler": "^4.3.1", "domutils": "^2.8.0", "nth-check": "^2.0.1" } }, "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ=="], - "svgo/sax": ["sax@1.5.0", "", {}, "sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA=="], - - "swr/use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], - "tailwindcss/arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="], "tailwindcss/lilconfig": ["lilconfig@2.1.0", "", {}, "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ=="], - "tailwindcss/postcss": ["postcss@8.5.3", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A=="], - "tailwindcss/postcss-load-config": ["postcss-load-config@4.0.2", "", { "dependencies": { "lilconfig": "^3.0.0", "yaml": "^2.3.4" }, "peerDependencies": { "postcss": ">=8.0.9", "ts-node": ">=9.0.0" } }, "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ=="], "tailwindcss/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=="], @@ -6743,11 +6193,17 @@ "terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], + "terser-webpack-plugin/@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=="], + + "terser-webpack-plugin/jest-worker": ["jest-worker@27.5.1", "", { "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" } }, "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg=="], + "test-exclude/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], - "test-exclude/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + "tinyglobby/fdir": ["fdir@6.4.4", "", { "peerDependencies": { "picomatch": "^3 || ^4" } }, "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg=="], - "to-buffer/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], + "tinyglobby/picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="], + + "ts-node/diff": ["diff@4.0.2", "", {}, "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A=="], "tsconfig-paths/json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": "lib/cli.js" }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="], @@ -6769,19 +6225,19 @@ "vasync/verror": ["verror@1.10.0", "", { "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } }, "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw=="], - "verror/core-util-is": ["core-util-is@1.0.2", "", {}, "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="], - "vfile-location/@types/unist": ["@types/unist@2.0.10", "", {}, "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA=="], "vfile-location/vfile": ["vfile@5.3.7", "", { "dependencies": { "@types/unist": "^2.0.0", "is-buffer": "^2.0.0", "unist-util-stringify-position": "^3.0.0", "vfile-message": "^3.0.0" } }, "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g=="], - "which-builtin-type/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], + "webpack/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=="], - "winston/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=="], + "webpack/eslint-scope": ["eslint-scope@5.1.1", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw=="], + + "webpack/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], "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=="], - "winston-transport/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=="], + "workbox-build/@babel/preset-env": ["@babel/preset-env@7.23.9", "", { "dependencies": { "@babel/compat-data": "^7.23.5", "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-validator-option": "^7.23.5", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.7", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-namespace-from": "^7.8.3", "@babel/plugin-syntax-import-assertions": "^7.23.3", "@babel/plugin-syntax-import-attributes": "^7.23.3", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-numeric-separator": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.23.3", "@babel/plugin-transform-async-generator-functions": "^7.23.9", "@babel/plugin-transform-async-to-generator": "^7.23.3", "@babel/plugin-transform-block-scoped-functions": "^7.23.3", "@babel/plugin-transform-block-scoping": "^7.23.4", "@babel/plugin-transform-class-properties": "^7.23.3", "@babel/plugin-transform-class-static-block": "^7.23.4", "@babel/plugin-transform-classes": "^7.23.8", "@babel/plugin-transform-computed-properties": "^7.23.3", "@babel/plugin-transform-destructuring": "^7.23.3", "@babel/plugin-transform-dotall-regex": "^7.23.3", "@babel/plugin-transform-duplicate-keys": "^7.23.3", "@babel/plugin-transform-dynamic-import": "^7.23.4", "@babel/plugin-transform-exponentiation-operator": "^7.23.3", "@babel/plugin-transform-export-namespace-from": "^7.23.4", "@babel/plugin-transform-for-of": "^7.23.6", "@babel/plugin-transform-function-name": "^7.23.3", "@babel/plugin-transform-json-strings": "^7.23.4", "@babel/plugin-transform-literals": "^7.23.3", "@babel/plugin-transform-logical-assignment-operators": "^7.23.4", "@babel/plugin-transform-member-expression-literals": "^7.23.3", "@babel/plugin-transform-modules-amd": "^7.23.3", "@babel/plugin-transform-modules-commonjs": "^7.23.3", "@babel/plugin-transform-modules-systemjs": "^7.23.9", "@babel/plugin-transform-modules-umd": "^7.23.3", "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", "@babel/plugin-transform-new-target": "^7.23.3", "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", "@babel/plugin-transform-numeric-separator": "^7.23.4", "@babel/plugin-transform-object-rest-spread": "^7.23.4", "@babel/plugin-transform-object-super": "^7.23.3", "@babel/plugin-transform-optional-catch-binding": "^7.23.4", "@babel/plugin-transform-optional-chaining": "^7.23.4", "@babel/plugin-transform-parameters": "^7.23.3", "@babel/plugin-transform-private-methods": "^7.23.3", "@babel/plugin-transform-private-property-in-object": "^7.23.4", "@babel/plugin-transform-property-literals": "^7.23.3", "@babel/plugin-transform-regenerator": "^7.23.3", "@babel/plugin-transform-reserved-words": "^7.23.3", "@babel/plugin-transform-shorthand-properties": "^7.23.3", "@babel/plugin-transform-spread": "^7.23.3", "@babel/plugin-transform-sticky-regex": "^7.23.3", "@babel/plugin-transform-template-literals": "^7.23.3", "@babel/plugin-transform-typeof-symbol": "^7.23.3", "@babel/plugin-transform-unicode-escapes": "^7.23.3", "@babel/plugin-transform-unicode-property-regex": "^7.23.3", "@babel/plugin-transform-unicode-regex": "^7.23.3", "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.8", "babel-plugin-polyfill-corejs3": "^0.9.0", "babel-plugin-polyfill-regenerator": "^0.5.5", "core-js-compat": "^3.31.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-3kBGTNBBk9DQiPoXYS0g0BYlwTQYUTifqgKTjxUwEUkduRT2QOa0FPGBJ+NROQhGyYO5BuTJwGvBnqKDykac6A=="], "workbox-build/@rollup/plugin-replace": ["@rollup/plugin-replace@2.4.2", "", { "dependencies": { "@rollup/pluginutils": "^3.1.0", "magic-string": "^0.25.7" }, "peerDependencies": { "rollup": "^1.20.0 || ^2.0.0" } }, "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg=="], @@ -6789,7 +6245,7 @@ "workbox-build/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], - "workbox-build/glob": ["glob@11.1.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="], + "workbox-build/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], "workbox-build/pretty-bytes": ["pretty-bytes@5.6.0", "", {}, "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg=="], @@ -6837,8 +6293,6 @@ "@aws-sdk/client-bedrock-agent-runtime/@aws-sdk/core/@smithy/property-provider": ["@smithy/property-provider@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-g2DHo08IhxV5GdY3Cpt/jr0mkTlAD39EJKN27Jb5N8Fb5qt8KG39wVKTXiTRCmHHou7lbXR8nKVU14/aRUf86w=="], - "@aws-sdk/client-bedrock-agent-runtime/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.3.4", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.4", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-ScDCpasxH7w1HXHYbtk3jcivjvdA1VICyAdgvVqKhKKwxi+MTwZEqFw0minE+oZ7F07oF25xh4FGJxgqgShz0A=="], - "@aws-sdk/client-bedrock-agent-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.927.0", "", { "dependencies": { "@aws-sdk/core": "3.927.0", "@aws-sdk/types": "3.922.0", "@smithy/property-provider": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-bAllBpmaWINpf0brXQWh/hjkBctapknZPYb3FJRlBHytEGHi7TpgqBXi8riT0tc6RVWChhnw58rQz22acOmBuw=="], "@aws-sdk/client-bedrock-agent-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.927.0", "", { "dependencies": { "@aws-sdk/core": "3.927.0", "@aws-sdk/types": "3.922.0", "@smithy/fetch-http-handler": "^5.3.5", "@smithy/node-http-handler": "^4.4.4", "@smithy/property-provider": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/smithy-client": "^4.9.2", "@smithy/types": "^4.8.1", "@smithy/util-stream": "^4.5.5", "tslib": "^2.6.2" } }, "sha512-jEvb8C7tuRBFhe8vZY9vm9z6UQnbP85IMEt3Qiz0dxAd341Hgu0lOzMv5mSKQ5yBnTLq+t3FPKgD9tIiHLqxSQ=="], @@ -6863,12 +6317,6 @@ "@aws-sdk/client-bedrock-agent-runtime/@smithy/core/@smithy/util-stream": ["@smithy/util-stream@4.5.5", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.5", "@smithy/node-http-handler": "^4.4.4", "@smithy/types": "^4.8.1", "@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", "tslib": "^2.6.2" } }, "sha512-7M5aVFjT+HPilPOKbOmQfCIPchZe4DSBc1wf1+NvHvSoFTiFtauZzT+onZvCj70xhXd0AEmYnZYmdJIuwxOo4w=="], - "@aws-sdk/client-bedrock-agent-runtime/@smithy/core/@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], - - "@aws-sdk/client-bedrock-agent-runtime/@smithy/eventstream-serde-browser/@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.4", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-GNI/IXaY/XBB1SkGBFmbW033uWA0tj085eCxYih0eccUe/PFR7+UBQv9HNDk2fD9TJu7UVsCWsH99TkpEPSOzQ=="], - - "@aws-sdk/client-bedrock-agent-runtime/@smithy/eventstream-serde-node/@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.4", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-GNI/IXaY/XBB1SkGBFmbW033uWA0tj085eCxYih0eccUe/PFR7+UBQv9HNDk2fD9TJu7UVsCWsH99TkpEPSOzQ=="], - "@aws-sdk/client-bedrock-agent-runtime/@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-KQ1gFXXC+WsbPFnk7pzskzOpn4s+KheWgO3dzkIEmnb6NskAIGp/dGdbKisTPJdtov28qNDohQrgDUKzXZBLig=="], "@aws-sdk/client-bedrock-agent-runtime/@smithy/hash-node/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], @@ -6877,8 +6325,6 @@ "@aws-sdk/client-bedrock-agent-runtime/@smithy/middleware-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1" } }, "sha512-fdWuhEx4+jHLGeew9/IvqVU/fxT/ot70tpRGuOLxE3HzZOyKeTQfYeV1oaBXpzi93WOk668hjMuuagJ2/Qs7ng=="], - "@aws-sdk/client-bedrock-agent-runtime/@smithy/middleware-retry/@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], - "@aws-sdk/client-bedrock-agent-runtime/@smithy/node-config-provider/@smithy/property-provider": ["@smithy/property-provider@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-g2DHo08IhxV5GdY3Cpt/jr0mkTlAD39EJKN27Jb5N8Fb5qt8KG39wVKTXiTRCmHHou7lbXR8nKVU14/aRUf86w=="], "@aws-sdk/client-bedrock-agent-runtime/@smithy/node-config-provider/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.3.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-y5ozxeQ9omVjbnJo9dtTsdXj9BEvGx2X8xvRgKnV+/7wLBuYJQL6dOa/qMY6omyHi7yjt1OA97jZLoVRYi8lxA=="], @@ -6903,6 +6349,62 @@ "@aws-sdk/client-bedrock-agent-runtime/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + "@aws-sdk/client-bedrock-runtime/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.930.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" } }, "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA=="], + + "@aws-sdk/client-bedrock-runtime/@aws-sdk/core/@smithy/property-provider": ["@smithy/property-provider@4.2.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-a/tGSLPtaia2krbRdwR4xbZKO8lU67DjMk/jfY4QKt4PRlKML+2tL/gmAuhNdFDioO6wOq0sXkfnddNFH9mNUA=="], + + "@aws-sdk/client-bedrock-runtime/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.3.6", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.6", "@smithy/types": "^4.10.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.6", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-P1TXDHuQMadTMTOBv4oElZMURU4uyEhxhHfn+qOc2iofW9Rd4sZtBGx58Lzk112rIGVEYZT8eUMK4NftpewpRA=="], + + "@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.947.0", "", { "dependencies": { "@aws-sdk/core": "3.947.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-VR2V6dRELmzwAsCpK4GqxUi6UW5WNhAXS9F9AzWi5jvijwJo3nH92YNJUP4quMpgFZxJHEWyXLWgPjh9u0zYOA=="], + + "@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.947.0", "", { "dependencies": { "@aws-sdk/core": "3.947.0", "@aws-sdk/types": "3.936.0", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/node-http-handler": "^4.4.5", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/util-stream": "^4.5.6", "tslib": "^2.6.2" } }, "sha512-inF09lh9SlHj63Vmr5d+LmwPXZc2IbK8lAruhOr3KLsZAIHEgHgGPXWDC2ukTEMzg0pkexQ6FOhXXad6klK4RA=="], + + "@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.952.0", "", { "dependencies": { "@aws-sdk/core": "3.947.0", "@aws-sdk/credential-provider-env": "3.947.0", "@aws-sdk/credential-provider-http": "3.947.0", "@aws-sdk/credential-provider-login": "3.952.0", "@aws-sdk/credential-provider-process": "3.947.0", "@aws-sdk/credential-provider-sso": "3.952.0", "@aws-sdk/credential-provider-web-identity": "3.952.0", "@aws-sdk/nested-clients": "3.952.0", "@aws-sdk/types": "3.936.0", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-N5B15SwzMkZ8/LLopNksTlPEWWZn5tbafZAUfMY5Xde4rSHGWmv5H/ws2M3P8L0X77E2wKnOJsNmu+GsArBreQ=="], + + "@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.947.0", "", { "dependencies": { "@aws-sdk/core": "3.947.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-WpanFbHe08SP1hAJNeDdBDVz9SGgMu/gc0XJ9u3uNpW99nKZjDpvPRAdW7WLA4K6essMjxWkguIGNOpij6Do2Q=="], + + "@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.952.0", "", { "dependencies": { "@aws-sdk/client-sso": "3.948.0", "@aws-sdk/core": "3.947.0", "@aws-sdk/token-providers": "3.952.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-1CQdP5RzxeXuEfytbAD5TgreY1c9OacjtCdO8+n9m05tpzBABoNBof0hcjzw1dtrWFH7deyUgfwCl1TAN3yBWQ=="], + + "@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.952.0", "", { "dependencies": { "@aws-sdk/core": "3.947.0", "@aws-sdk/nested-clients": "3.952.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-5hJbfaZdHDAP8JlwplNbXJAat9Vv7L0AbTZzkbPIgjHhC3vrMf5r3a6I1HWFp5i5pXo7J45xyuf5uQGZJxJlCg=="], + + "@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.6", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.6", "@smithy/property-provider": "^4.2.6", "@smithy/types": "^4.10.0", "@smithy/url-parser": "^4.2.6", "tslib": "^2.6.2" } }, "sha512-xBmawExyTzOjbhzkZwg+vVm/khg28kG+rj2sbGlULjFd1jI70sv/cbpaR0Ev4Yfd6CpDUDRMe64cTqR//wAOyA=="], + + "@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@smithy/property-provider": ["@smithy/property-provider@4.2.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-a/tGSLPtaia2krbRdwR4xbZKO8lU67DjMk/jfY4QKt4PRlKML+2tL/gmAuhNdFDioO6wOq0sXkfnddNFH9mNUA=="], + + "@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.1", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-tph+oQYPbpN6NamF030hx1gb5YN2Plog+GLaRHpoEDwp8+ZPG26rIJvStG9hkWzN2HBn3HcWg0sHeB0tmkYzqA=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/config-resolver/@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/eventstream-serde-browser/@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.6", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.6", "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-olIfZ230B64TvPD6b0tPvrEp2eB0FkyL3KvDlqF4RVmIc/kn3orzXnV6DTQdOOW5UU+M5zKY3/BU47X420/oPw=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/eventstream-serde-node/@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.6", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.6", "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-olIfZ230B64TvPD6b0tPvrEp2eB0FkyL3KvDlqF4RVmIc/kn3orzXnV6DTQdOOW5UU+M5zKY3/BU47X420/oPw=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/hash-node/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/middleware-endpoint/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.1", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-tph+oQYPbpN6NamF030hx1gb5YN2Plog+GLaRHpoEDwp8+ZPG26rIJvStG9hkWzN2HBn3HcWg0sHeB0tmkYzqA=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/middleware-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.6", "", { "dependencies": { "@smithy/types": "^4.10.0" } }, "sha512-Q73XBrzJlGTut2nf5RglSntHKgAG0+KiTJdO5QQblLfr4TdliGwIAha1iZIjwisc3rA5ulzqwwsYC6xrclxVQg=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/node-config-provider/@smithy/property-provider": ["@smithy/property-provider@4.2.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-a/tGSLPtaia2krbRdwR4xbZKO8lU67DjMk/jfY4QKt4PRlKML+2tL/gmAuhNdFDioO6wOq0sXkfnddNFH9mNUA=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/node-config-provider/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.1", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-tph+oQYPbpN6NamF030hx1gb5YN2Plog+GLaRHpoEDwp8+ZPG26rIJvStG9hkWzN2HBn3HcWg0sHeB0tmkYzqA=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/url-parser/@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-YmWxl32SQRw/kIRccSOxzS/Ib8/b5/f9ex0r5PR40jRJg8X1wgM3KrR2In+8zvOGVhRSXgvyQpw9yOSlmfmSnA=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/util-base64/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/util-defaults-mode-browser/@smithy/property-provider": ["@smithy/property-provider@4.2.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-a/tGSLPtaia2krbRdwR4xbZKO8lU67DjMk/jfY4QKt4PRlKML+2tL/gmAuhNdFDioO6wOq0sXkfnddNFH9mNUA=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/util-defaults-mode-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.6", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.6", "@smithy/property-provider": "^4.2.6", "@smithy/types": "^4.10.0", "@smithy/url-parser": "^4.2.6", "tslib": "^2.6.2" } }, "sha512-xBmawExyTzOjbhzkZwg+vVm/khg28kG+rj2sbGlULjFd1jI70sv/cbpaR0Ev4Yfd6CpDUDRMe64cTqR//wAOyA=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/util-defaults-mode-node/@smithy/property-provider": ["@smithy/property-provider@4.2.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-a/tGSLPtaia2krbRdwR4xbZKO8lU67DjMk/jfY4QKt4PRlKML+2tL/gmAuhNdFDioO6wOq0sXkfnddNFH9mNUA=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/util-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.6", "", { "dependencies": { "@smithy/types": "^4.10.0" } }, "sha512-Q73XBrzJlGTut2nf5RglSntHKgAG0+KiTJdO5QQblLfr4TdliGwIAha1iZIjwisc3rA5ulzqwwsYC6xrclxVQg=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + "@aws-sdk/client-cognito-identity/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@4.1.0", "", { "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "@smithy/protocol-http": "^4.1.0", "@smithy/types": "^3.3.0", "@smithy/util-hex-encoding": "^3.0.0", "@smithy/util-middleware": "^3.0.3", "@smithy/util-uri-escape": "^3.0.0", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" } }, "sha512-aRryp2XNZeRcOtuJoxjydO6QTaVhxx/vjaR+gx7ZjaFgrgPRyZ3HCTbfwqYj6ZWEBHkCSUfcaymKPURaByukag=="], "@aws-sdk/client-cognito-identity/@aws-sdk/credential-provider-node/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@3.1.4", "", { "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" } }, "sha512-qMxS4hBGB8FY2GQqshcRUy1K6k8aBWP5vwm8qKkCT3A9K2dawUwOIJfqh9Yste/Bl0J2lzosVyrXDj68kLcHXQ=="], @@ -6941,8 +6443,6 @@ "@aws-sdk/client-kendra/@aws-sdk/core/@smithy/property-provider": ["@smithy/property-provider@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-g2DHo08IhxV5GdY3Cpt/jr0mkTlAD39EJKN27Jb5N8Fb5qt8KG39wVKTXiTRCmHHou7lbXR8nKVU14/aRUf86w=="], - "@aws-sdk/client-kendra/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.3.4", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.4", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-ScDCpasxH7w1HXHYbtk3jcivjvdA1VICyAdgvVqKhKKwxi+MTwZEqFw0minE+oZ7F07oF25xh4FGJxgqgShz0A=="], - "@aws-sdk/client-kendra/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.927.0", "", { "dependencies": { "@aws-sdk/core": "3.927.0", "@aws-sdk/types": "3.922.0", "@smithy/property-provider": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-bAllBpmaWINpf0brXQWh/hjkBctapknZPYb3FJRlBHytEGHi7TpgqBXi8riT0tc6RVWChhnw58rQz22acOmBuw=="], "@aws-sdk/client-kendra/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.927.0", "", { "dependencies": { "@aws-sdk/core": "3.927.0", "@aws-sdk/types": "3.922.0", "@smithy/fetch-http-handler": "^5.3.5", "@smithy/node-http-handler": "^4.4.4", "@smithy/property-provider": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/smithy-client": "^4.9.2", "@smithy/types": "^4.8.1", "@smithy/util-stream": "^4.5.5", "tslib": "^2.6.2" } }, "sha512-jEvb8C7tuRBFhe8vZY9vm9z6UQnbP85IMEt3Qiz0dxAd341Hgu0lOzMv5mSKQ5yBnTLq+t3FPKgD9tIiHLqxSQ=="], @@ -6999,57 +6499,9 @@ "@aws-sdk/client-kendra/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@aws-sdk/client-s3/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "fast-xml-parser": "5.4.1", "tslib": "^2.6.2" } }, "sha512-OnejAIVD+CxzyAUrVic7lG+3QRltyja9LoNqCE/1YVs8ichoTbJlVSaZ9iSMcnHLyzrSNtvaOGjSDRP+d/ouFA=="], + "@aws-sdk/client-s3/@smithy/node-http-handler/@smithy/abort-controller": ["@smithy/abort-controller@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-fiUIYgIgRjMWznk6iLJz35K2YxSLHzLBA/RC6lBrKfQ8fHbPfvk7Pk9UvpKoHgJjI18MnbPuEju53zcVy6KF1g=="], - "@aws-sdk/client-s3/@aws-sdk/core/@smithy/property-provider": ["@smithy/property-provider@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-14T1V64o6/ndyrnl1ze1ZhyLzIeYNN47oF/QU6P5m82AEtyOkMJTb0gO1dPubYjyyKuPD6OSVMPDKe+zioOnCg=="], - - "@aws-sdk/client-s3/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.3.11", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-V1L6N9aKOBAN4wEHLyqjLBnAz13mtILU0SeDrjOaIZEeN6IFa6DxwRt1NNpOdmSpQUfkBj0qeD3m6P77uzMhgQ=="], - - "@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.16", "", { "dependencies": { "@aws-sdk/core": "^3.973.18", "@aws-sdk/types": "^3.973.5", "@smithy/property-provider": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-HrdtnadvTGAQUr18sPzGlE5El3ICphnH6SU7UQOMOWFgRKbTRNN8msTxM4emzguUso9CzaHU2xy5ctSrmK5YNA=="], - - "@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.18", "", { "dependencies": { "@aws-sdk/core": "^3.973.18", "@aws-sdk/types": "^3.973.5", "@smithy/fetch-http-handler": "^5.3.13", "@smithy/node-http-handler": "^4.4.14", "@smithy/property-provider": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/smithy-client": "^4.12.2", "@smithy/types": "^4.13.0", "@smithy/util-stream": "^4.5.17", "tslib": "^2.6.2" } }, "sha512-NyB6smuZAixND5jZumkpkunQ0voc4Mwgkd+SZ6cvAzIB7gK8HV8Zd4rS8Kn5MmoGgusyNfVGG+RLoYc4yFiw+A=="], - - "@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.17", "", { "dependencies": { "@aws-sdk/core": "^3.973.18", "@aws-sdk/credential-provider-env": "^3.972.16", "@aws-sdk/credential-provider-http": "^3.972.18", "@aws-sdk/credential-provider-login": "^3.972.17", "@aws-sdk/credential-provider-process": "^3.972.16", "@aws-sdk/credential-provider-sso": "^3.972.17", "@aws-sdk/credential-provider-web-identity": "^3.972.17", "@aws-sdk/nested-clients": "^3.996.7", "@aws-sdk/types": "^3.973.5", "@smithy/credential-provider-imds": "^4.2.11", "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-dFqh7nfX43B8dO1aPQHOcjC0SnCJ83H3F+1LoCh3X1P7E7N09I+0/taID0asU6GCddfDExqnEvQtDdkuMe5tKQ=="], - - "@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.16", "", { "dependencies": { "@aws-sdk/core": "^3.973.18", "@aws-sdk/types": "^3.973.5", "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-n89ibATwnLEg0ZdZmUds5bq8AfBAdoYEDpqP3uzPLaRuGelsKlIvCYSNNvfgGLi8NaHPNNhs1HjJZYbqkW9b+g=="], - - "@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.17", "", { "dependencies": { "@aws-sdk/core": "^3.973.18", "@aws-sdk/nested-clients": "^3.996.7", "@aws-sdk/token-providers": "3.1004.0", "@aws-sdk/types": "^3.973.5", "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-wGtte+48xnhnhHMl/MsxzacBPs5A+7JJedjiP452IkHY7vsbYKcvQBqFye8LwdTJVeHtBHv+JFeTscnwepoWGg=="], - - "@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.17", "", { "dependencies": { "@aws-sdk/core": "^3.973.18", "@aws-sdk/nested-clients": "^3.996.7", "@aws-sdk/types": "^3.973.5", "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-8aiVJh6fTdl8gcyL+sVNcNwTtWpmoFa1Sh7xlj6Z7L/cZ/tYMEBHq44wTYG8Kt0z/PpGNopD89nbj3FHl9QmTA=="], - - "@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.11", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.11", "@smithy/property-provider": "^4.2.11", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "tslib": "^2.6.2" } }, "sha512-lBXrS6ku0kTj3xLmsJW0WwqWbGQ6ueooYyp/1L9lkyT0M02C+DWwYwc5aTyXFbRaK38ojALxNixg+LxKSHZc0g=="], - - "@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@smithy/property-provider": ["@smithy/property-provider@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-14T1V64o6/ndyrnl1ze1ZhyLzIeYNN47oF/QU6P5m82AEtyOkMJTb0gO1dPubYjyyKuPD6OSVMPDKe+zioOnCg=="], - - "@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.6", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-IB/M5I8G0EeXZTHsAxpx51tMQ5R719F3aq+fjEB6VtNcCHDc0ajFDIGDZw+FW9GxtEkgTduiPpjveJdA/CX7sw=="], - - "@aws-sdk/client-s3/@smithy/eventstream-serde-browser/@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.11", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-MJ7HcI+jEkqoWT5vp+uoVaAjBrmxBtKhZTeynDRG/seEjJfqyg3SiqMMqyPnAMzmIfLaeJ/uiuSDP/l9AnMy/Q=="], - - "@aws-sdk/client-s3/@smithy/eventstream-serde-node/@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.11", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-MJ7HcI+jEkqoWT5vp+uoVaAjBrmxBtKhZTeynDRG/seEjJfqyg3SiqMMqyPnAMzmIfLaeJ/uiuSDP/l9AnMy/Q=="], - - "@aws-sdk/client-s3/@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-7spdikrYiljpket6u0up2Ck2mxhy7dZ0+TDd+S53Dg2DHd6wg+YNJrTCHiLdgZmEXZKI7LJZcwL3721ZRDFiqA=="], - - "@aws-sdk/client-s3/@smithy/middleware-endpoint/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.6", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-IB/M5I8G0EeXZTHsAxpx51tMQ5R719F3aq+fjEB6VtNcCHDc0ajFDIGDZw+FW9GxtEkgTduiPpjveJdA/CX7sw=="], - - "@aws-sdk/client-s3/@smithy/middleware-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0" } }, "sha512-HkMFJZJUhzU3HvND1+Yw/kYWXp4RPDLBWLcK1n+Vqw8xn4y2YiBhdww8IxhkQjP/QlZun5bwm3vcHc8AqIU3zw=="], - - "@aws-sdk/client-s3/@smithy/node-config-provider/@smithy/property-provider": ["@smithy/property-provider@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-14T1V64o6/ndyrnl1ze1ZhyLzIeYNN47oF/QU6P5m82AEtyOkMJTb0gO1dPubYjyyKuPD6OSVMPDKe+zioOnCg=="], - - "@aws-sdk/client-s3/@smithy/node-config-provider/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.6", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-IB/M5I8G0EeXZTHsAxpx51tMQ5R719F3aq+fjEB6VtNcCHDc0ajFDIGDZw+FW9GxtEkgTduiPpjveJdA/CX7sw=="], - - "@aws-sdk/client-s3/@smithy/node-http-handler/@smithy/abort-controller": ["@smithy/abort-controller@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-Hj4WoYWMJnSpM6/kchsm4bUNTL9XiSyhvoMb2KIq4VJzyDt7JpGHUZHkVNPZVC7YE1tf8tPeVauxpFBKGW4/KQ=="], - - "@aws-sdk/client-s3/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-7spdikrYiljpket6u0up2Ck2mxhy7dZ0+TDd+S53Dg2DHd6wg+YNJrTCHiLdgZmEXZKI7LJZcwL3721ZRDFiqA=="], - - "@aws-sdk/client-s3/@smithy/url-parser/@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-nE3IRNjDltvGcoThD2abTozI1dkSy8aX+a2N1Rs55en5UsdyyIXgGEmevUL3okZFoJC77JgRGe99xYohhsjivQ=="], - - "@aws-sdk/client-s3/@smithy/util-defaults-mode-browser/@smithy/property-provider": ["@smithy/property-provider@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-14T1V64o6/ndyrnl1ze1ZhyLzIeYNN47oF/QU6P5m82AEtyOkMJTb0gO1dPubYjyyKuPD6OSVMPDKe+zioOnCg=="], - - "@aws-sdk/client-s3/@smithy/util-defaults-mode-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.11", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.11", "@smithy/property-provider": "^4.2.11", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "tslib": "^2.6.2" } }, "sha512-lBXrS6ku0kTj3xLmsJW0WwqWbGQ6ueooYyp/1L9lkyT0M02C+DWwYwc5aTyXFbRaK38ojALxNixg+LxKSHZc0g=="], - - "@aws-sdk/client-s3/@smithy/util-defaults-mode-node/@smithy/property-provider": ["@smithy/property-provider@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-14T1V64o6/ndyrnl1ze1ZhyLzIeYNN47oF/QU6P5m82AEtyOkMJTb0gO1dPubYjyyKuPD6OSVMPDKe+zioOnCg=="], - - "@aws-sdk/client-s3/@smithy/util-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0" } }, "sha512-HkMFJZJUhzU3HvND1+Yw/kYWXp4RPDLBWLcK1n+Vqw8xn4y2YiBhdww8IxhkQjP/QlZun5bwm3vcHc8AqIU3zw=="], + "@aws-sdk/client-s3/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "@smithy/util-uri-escape": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-wU87iWZoCbcqrwszsOewEIuq+SU2mSoBE2CcsLwE0I19m0B2gOJr1MVjxWcDQYOzHbR1xCk7AcOBbGFUYOKvdg=="], "@aws-sdk/client-sso-oidc/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@4.1.0", "", { "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "@smithy/protocol-http": "^4.1.0", "@smithy/types": "^3.3.0", "@smithy/util-hex-encoding": "^3.0.0", "@smithy/util-middleware": "^3.0.3", "@smithy/util-uri-escape": "^3.0.0", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" } }, "sha512-aRryp2XNZeRcOtuJoxjydO6QTaVhxx/vjaR+gx7ZjaFgrgPRyZ3HCTbfwqYj6ZWEBHkCSUfcaymKPURaByukag=="], @@ -7137,282 +6589,104 @@ "@aws-sdk/credential-provider-http/@smithy/util-stream/@smithy/util-utf8": ["@smithy/util-utf8@3.0.0", "", { "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" } }, "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA=="], + "@aws-sdk/credential-provider-login/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.930.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" } }, "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/core": ["@smithy/core@3.19.0", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.7", "@smithy/protocol-http": "^5.3.6", "@smithy/types": "^4.10.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.6", "@smithy/util-stream": "^4.5.7", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-Y9oHXpBcXQgYHOcAEmxjkDilUbSTkgKjoHYed3WaYUH8jngq8lPWDBSpjHblJ9uOgBdy5mh3pzebrScDdYr29w=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.6", "", { "dependencies": { "@smithy/property-provider": "^4.2.6", "@smithy/shared-ini-file-loader": "^4.4.1", "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-fYEyL59Qe82Ha1p97YQTMEQPJYmBS+ux76foqluaTVWoG9Px5J53w6NvXZNE3wP7lIicLDF7Vj1Em18XTX7fsA=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.3.6", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.6", "@smithy/types": "^4.10.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.6", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-P1TXDHuQMadTMTOBv4oElZMURU4uyEhxhHfn+qOc2iofW9Rd4sZtBGx58Lzk112rIGVEYZT8eUMK4NftpewpRA=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/smithy-client": ["@smithy/smithy-client@4.10.1", "", { "dependencies": { "@smithy/core": "^3.19.0", "@smithy/middleware-endpoint": "^4.4.0", "@smithy/middleware-stack": "^4.2.6", "@smithy/protocol-http": "^5.3.6", "@smithy/types": "^4.10.0", "@smithy/util-stream": "^4.5.7", "tslib": "^2.6.2" } }, "sha512-1ovWdxzYprhq+mWqiGZlt3kF69LJthuQcfY9BIyHx9MywTFKzFapluku1QXoaBB43GCsLDxNqS+1v30ure69AA=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/util-middleware": ["@smithy/util-middleware@4.2.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-qrvXUkxBSAFomM3/OEMuDVwjh4wtqK8D2uDZPShzIqOylPst6gor2Cdp6+XrH4dyksAWq/bE2aSDYBTTnj0Rxg=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], + + "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/node-http-handler": ["@smithy/node-http-handler@4.0.3", "", { "dependencies": { "@smithy/abort-controller": "^4.0.1", "@smithy/protocol-http": "^5.0.1", "@smithy/querystring-builder": "^4.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-dYCLeINNbYdvmMLtW0VdhW1biXt+PPCGazzT5ZjKw46mOtdgToQEwjqZSS9/EN8+tNs/RO0cEWG044+YZs97aA=="], + + "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.758.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.758.0", "@aws-sdk/middleware-host-header": "3.734.0", "@aws-sdk/middleware-logger": "3.734.0", "@aws-sdk/middleware-recursion-detection": "3.734.0", "@aws-sdk/middleware-user-agent": "3.758.0", "@aws-sdk/region-config-resolver": "3.734.0", "@aws-sdk/types": "3.734.0", "@aws-sdk/util-endpoints": "3.743.0", "@aws-sdk/util-user-agent-browser": "3.734.0", "@aws-sdk/util-user-agent-node": "3.758.0", "@smithy/config-resolver": "^4.0.1", "@smithy/core": "^3.1.5", "@smithy/fetch-http-handler": "^5.0.1", "@smithy/hash-node": "^4.0.1", "@smithy/invalid-dependency": "^4.0.1", "@smithy/middleware-content-length": "^4.0.1", "@smithy/middleware-endpoint": "^4.0.6", "@smithy/middleware-retry": "^4.0.7", "@smithy/middleware-serde": "^4.0.2", "@smithy/middleware-stack": "^4.0.1", "@smithy/node-config-provider": "^4.0.1", "@smithy/node-http-handler": "^4.0.3", "@smithy/protocol-http": "^5.0.1", "@smithy/smithy-client": "^4.1.6", "@smithy/types": "^4.1.0", "@smithy/url-parser": "^4.0.1", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.7", "@smithy/util-defaults-mode-node": "^4.0.7", "@smithy/util-endpoints": "^3.0.1", "@smithy/util-middleware": "^4.0.1", "@smithy/util-retry": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-YZ5s7PSvyF3Mt2h1EQulCG93uybprNGbBkPmVuy/HMMfbFTt4iL3SbKjxqvOZelm86epFfj7pvK7FliI2WOEcg=="], + + "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.758.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.758.0", "@aws-sdk/middleware-host-header": "3.734.0", "@aws-sdk/middleware-logger": "3.734.0", "@aws-sdk/middleware-recursion-detection": "3.734.0", "@aws-sdk/middleware-user-agent": "3.758.0", "@aws-sdk/region-config-resolver": "3.734.0", "@aws-sdk/types": "3.734.0", "@aws-sdk/util-endpoints": "3.743.0", "@aws-sdk/util-user-agent-browser": "3.734.0", "@aws-sdk/util-user-agent-node": "3.758.0", "@smithy/config-resolver": "^4.0.1", "@smithy/core": "^3.1.5", "@smithy/fetch-http-handler": "^5.0.1", "@smithy/hash-node": "^4.0.1", "@smithy/invalid-dependency": "^4.0.1", "@smithy/middleware-content-length": "^4.0.1", "@smithy/middleware-endpoint": "^4.0.6", "@smithy/middleware-retry": "^4.0.7", "@smithy/middleware-serde": "^4.0.2", "@smithy/middleware-stack": "^4.0.1", "@smithy/node-config-provider": "^4.0.1", "@smithy/node-http-handler": "^4.0.3", "@smithy/protocol-http": "^5.0.1", "@smithy/smithy-client": "^4.1.6", "@smithy/types": "^4.1.0", "@smithy/url-parser": "^4.0.1", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.7", "@smithy/util-defaults-mode-node": "^4.0.7", "@smithy/util-endpoints": "^3.0.1", "@smithy/util-middleware": "^4.0.1", "@smithy/util-retry": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-BoGO6IIWrLyLxQG6txJw6RT2urmbtlwfggapNCrNPyYjlXpzTSJhBYjndg7TpDATFd0SXL0zm8y/tXsUXNkdYQ=="], + + "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.758.0", "", { "dependencies": { "@aws-sdk/nested-clients": "3.758.0", "@aws-sdk/types": "3.734.0", "@smithy/property-provider": "^4.0.1", "@smithy/shared-ini-file-loader": "^4.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-ckptN1tNrIfQUaGWm/ayW1ddG+imbKN7HHhjFdS4VfItsP0QQOB0+Ov+tpgb4MoNR4JaUghMIVStjIeHN2ks1w=="], + + "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.758.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.758.0", "@aws-sdk/middleware-host-header": "3.734.0", "@aws-sdk/middleware-logger": "3.734.0", "@aws-sdk/middleware-recursion-detection": "3.734.0", "@aws-sdk/middleware-user-agent": "3.758.0", "@aws-sdk/region-config-resolver": "3.734.0", "@aws-sdk/types": "3.734.0", "@aws-sdk/util-endpoints": "3.743.0", "@aws-sdk/util-user-agent-browser": "3.734.0", "@aws-sdk/util-user-agent-node": "3.758.0", "@smithy/config-resolver": "^4.0.1", "@smithy/core": "^3.1.5", "@smithy/fetch-http-handler": "^5.0.1", "@smithy/hash-node": "^4.0.1", "@smithy/invalid-dependency": "^4.0.1", "@smithy/middleware-content-length": "^4.0.1", "@smithy/middleware-endpoint": "^4.0.6", "@smithy/middleware-retry": "^4.0.7", "@smithy/middleware-serde": "^4.0.2", "@smithy/middleware-stack": "^4.0.1", "@smithy/node-config-provider": "^4.0.1", "@smithy/node-http-handler": "^4.0.3", "@smithy/protocol-http": "^5.0.1", "@smithy/smithy-client": "^4.1.6", "@smithy/types": "^4.1.0", "@smithy/url-parser": "^4.0.1", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.7", "@smithy/util-defaults-mode-node": "^4.0.7", "@smithy/util-endpoints": "^3.0.1", "@smithy/util-middleware": "^4.0.1", "@smithy/util-retry": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-YZ5s7PSvyF3Mt2h1EQulCG93uybprNGbBkPmVuy/HMMfbFTt4iL3SbKjxqvOZelm86epFfj7pvK7FliI2WOEcg=="], + "@aws-sdk/credential-providers/@aws-sdk/credential-provider-node/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@3.1.4", "", { "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" } }, "sha512-qMxS4hBGB8FY2GQqshcRUy1K6k8aBWP5vwm8qKkCT3A9K2dawUwOIJfqh9Yste/Bl0J2lzosVyrXDj68kLcHXQ=="], - "@aws-sdk/middleware-bucket-endpoint/@smithy/node-config-provider/@smithy/property-provider": ["@smithy/property-provider@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-14T1V64o6/ndyrnl1ze1ZhyLzIeYNN47oF/QU6P5m82AEtyOkMJTb0gO1dPubYjyyKuPD6OSVMPDKe+zioOnCg=="], + "@aws-sdk/middleware-websocket/@smithy/eventstream-serde-browser/@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.6", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.6", "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-olIfZ230B64TvPD6b0tPvrEp2eB0FkyL3KvDlqF4RVmIc/kn3orzXnV6DTQdOOW5UU+M5zKY3/BU47X420/oPw=="], - "@aws-sdk/middleware-bucket-endpoint/@smithy/node-config-provider/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.6", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-IB/M5I8G0EeXZTHsAxpx51tMQ5R719F3aq+fjEB6VtNcCHDc0ajFDIGDZw+FW9GxtEkgTduiPpjveJdA/CX7sw=="], + "@aws-sdk/middleware-websocket/@smithy/fetch-http-handler/@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="], - "@aws-sdk/middleware-flexible-checksums/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "fast-xml-parser": "5.4.1", "tslib": "^2.6.2" } }, "sha512-OnejAIVD+CxzyAUrVic7lG+3QRltyja9LoNqCE/1YVs8ichoTbJlVSaZ9iSMcnHLyzrSNtvaOGjSDRP+d/ouFA=="], + "@aws-sdk/middleware-websocket/@smithy/signature-v4/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - "@aws-sdk/middleware-flexible-checksums/@aws-sdk/core/@smithy/core": ["@smithy/core@3.23.9", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.12", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-stream": "^4.5.17", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-1Vcut4LEL9HZsdpI0vFiRYIsaoPwZLjAxnVQDUMQK8beMS+EYPLDQCXtbzfxmM5GzSgjfe2Q9M7WaXwIMQllyQ=="], + "@aws-sdk/middleware-websocket/@smithy/signature-v4/@smithy/util-middleware": ["@smithy/util-middleware@4.2.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-qrvXUkxBSAFomM3/OEMuDVwjh4wtqK8D2uDZPShzIqOylPst6gor2Cdp6+XrH4dyksAWq/bE2aSDYBTTnj0Rxg=="], - "@aws-sdk/middleware-flexible-checksums/@aws-sdk/core/@smithy/property-provider": ["@smithy/property-provider@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-14T1V64o6/ndyrnl1ze1ZhyLzIeYNN47oF/QU6P5m82AEtyOkMJTb0gO1dPubYjyyKuPD6OSVMPDKe+zioOnCg=="], + "@aws-sdk/middleware-websocket/@smithy/signature-v4/@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], - "@aws-sdk/middleware-flexible-checksums/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.3.11", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-V1L6N9aKOBAN4wEHLyqjLBnAz13mtILU0SeDrjOaIZEeN6IFa6DxwRt1NNpOdmSpQUfkBj0qeD3m6P77uzMhgQ=="], + "@aws-sdk/nested-clients/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.930.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" } }, "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA=="], - "@aws-sdk/middleware-flexible-checksums/@aws-sdk/core/@smithy/smithy-client": ["@smithy/smithy-client@4.12.3", "", { "dependencies": { "@smithy/core": "^3.23.9", "@smithy/middleware-endpoint": "^4.4.23", "@smithy/middleware-stack": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "@smithy/util-stream": "^4.5.17", "tslib": "^2.6.2" } }, "sha512-7k4UxjSpHmPN2AxVhvIazRSzFQjWnud3sOsXcFStzagww17j1cFQYqTSiQ8xuYK3vKLR1Ni8FzuT3VlKr3xCNw=="], + "@aws-sdk/nested-clients/@aws-sdk/core/@smithy/property-provider": ["@smithy/property-provider@4.2.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-a/tGSLPtaia2krbRdwR4xbZKO8lU67DjMk/jfY4QKt4PRlKML+2tL/gmAuhNdFDioO6wOq0sXkfnddNFH9mNUA=="], - "@aws-sdk/middleware-flexible-checksums/@smithy/node-config-provider/@smithy/property-provider": ["@smithy/property-provider@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-14T1V64o6/ndyrnl1ze1ZhyLzIeYNN47oF/QU6P5m82AEtyOkMJTb0gO1dPubYjyyKuPD6OSVMPDKe+zioOnCg=="], + "@aws-sdk/nested-clients/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.3.6", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.6", "@smithy/types": "^4.10.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.6", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-P1TXDHuQMadTMTOBv4oElZMURU4uyEhxhHfn+qOc2iofW9Rd4sZtBGx58Lzk112rIGVEYZT8eUMK4NftpewpRA=="], - "@aws-sdk/middleware-flexible-checksums/@smithy/node-config-provider/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.6", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-IB/M5I8G0EeXZTHsAxpx51tMQ5R719F3aq+fjEB6VtNcCHDc0ajFDIGDZw+FW9GxtEkgTduiPpjveJdA/CX7sw=="], + "@aws-sdk/nested-clients/@smithy/config-resolver/@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q=="], - "@aws-sdk/middleware-flexible-checksums/@smithy/util-stream/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.13", "", { "dependencies": { "@smithy/protocol-http": "^5.3.11", "@smithy/querystring-builder": "^4.2.11", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-U2Hcfl2s3XaYjikN9cT4mPu8ybDbImV3baXR0PkVlC0TTx808bRP3FaPGAzPtB8OByI+JqJ1kyS+7GEgae7+qQ=="], + "@aws-sdk/nested-clients/@smithy/core/@smithy/util-stream": ["@smithy/util-stream@4.5.7", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.7", "@smithy/node-http-handler": "^4.4.6", "@smithy/types": "^4.10.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", "tslib": "^2.6.2" } }, "sha512-Uuy4S5Aj4oF6k1z+i2OtIBJUns4mlg29Ph4S+CqjR+f4XXpSFVgTCYLzMszHJTicYDBxKFtwq2/QSEDSS5l02A=="], - "@aws-sdk/middleware-flexible-checksums/@smithy/util-stream/@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.14", "", { "dependencies": { "@smithy/abort-controller": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/querystring-builder": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-DamSqaU8nuk0xTJDrYnRzZndHwwRnyj/n/+RqGGCcBKB4qrQem0mSDiWdupaNWdwxzyMU91qxDmHOCazfhtO3A=="], + "@aws-sdk/nested-clients/@smithy/hash-node/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@aws-sdk/middleware-sdk-s3/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "fast-xml-parser": "5.4.1", "tslib": "^2.6.2" } }, "sha512-OnejAIVD+CxzyAUrVic7lG+3QRltyja9LoNqCE/1YVs8ichoTbJlVSaZ9iSMcnHLyzrSNtvaOGjSDRP+d/ouFA=="], + "@aws-sdk/nested-clients/@smithy/middleware-endpoint/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.1", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-tph+oQYPbpN6NamF030hx1gb5YN2Plog+GLaRHpoEDwp8+ZPG26rIJvStG9hkWzN2HBn3HcWg0sHeB0tmkYzqA=="], - "@aws-sdk/middleware-sdk-s3/@aws-sdk/core/@smithy/property-provider": ["@smithy/property-provider@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-14T1V64o6/ndyrnl1ze1ZhyLzIeYNN47oF/QU6P5m82AEtyOkMJTb0gO1dPubYjyyKuPD6OSVMPDKe+zioOnCg=="], + "@aws-sdk/nested-clients/@smithy/middleware-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.6", "", { "dependencies": { "@smithy/types": "^4.10.0" } }, "sha512-Q73XBrzJlGTut2nf5RglSntHKgAG0+KiTJdO5QQblLfr4TdliGwIAha1iZIjwisc3rA5ulzqwwsYC6xrclxVQg=="], - "@aws-sdk/middleware-sdk-s3/@smithy/core/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-W9g1bOLui7Xn5FABRVS0o3rXL0gfN37d/8I/W7i0N7oxjx9QecUmXEMSUMADTODwdtka9cN43t5BI2CodLJpng=="], + "@aws-sdk/nested-clients/@smithy/node-config-provider/@smithy/property-provider": ["@smithy/property-provider@4.2.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-a/tGSLPtaia2krbRdwR4xbZKO8lU67DjMk/jfY4QKt4PRlKML+2tL/gmAuhNdFDioO6wOq0sXkfnddNFH9mNUA=="], - "@aws-sdk/middleware-sdk-s3/@smithy/node-config-provider/@smithy/property-provider": ["@smithy/property-provider@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-14T1V64o6/ndyrnl1ze1ZhyLzIeYNN47oF/QU6P5m82AEtyOkMJTb0gO1dPubYjyyKuPD6OSVMPDKe+zioOnCg=="], + "@aws-sdk/nested-clients/@smithy/node-config-provider/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.1", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-tph+oQYPbpN6NamF030hx1gb5YN2Plog+GLaRHpoEDwp8+ZPG26rIJvStG9hkWzN2HBn3HcWg0sHeB0tmkYzqA=="], - "@aws-sdk/middleware-sdk-s3/@smithy/node-config-provider/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.6", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-IB/M5I8G0EeXZTHsAxpx51tMQ5R719F3aq+fjEB6VtNcCHDc0ajFDIGDZw+FW9GxtEkgTduiPpjveJdA/CX7sw=="], + "@aws-sdk/nested-clients/@smithy/smithy-client/@smithy/util-stream": ["@smithy/util-stream@4.5.7", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.7", "@smithy/node-http-handler": "^4.4.6", "@smithy/types": "^4.10.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", "tslib": "^2.6.2" } }, "sha512-Uuy4S5Aj4oF6k1z+i2OtIBJUns4mlg29Ph4S+CqjR+f4XXpSFVgTCYLzMszHJTicYDBxKFtwq2/QSEDSS5l02A=="], - "@aws-sdk/middleware-sdk-s3/@smithy/smithy-client/@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.23", "", { "dependencies": { "@smithy/core": "^3.23.9", "@smithy/middleware-serde": "^4.2.12", "@smithy/node-config-provider": "^4.3.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@smithy/util-middleware": "^4.2.11", "tslib": "^2.6.2" } }, "sha512-UEFIejZy54T1EJn2aWJ45voB7RP2T+IRzUqocIdM6GFFa5ClZncakYJfcYnoXt3UsQrZZ9ZRauGm77l9UCbBLw=="], + "@aws-sdk/nested-clients/@smithy/url-parser/@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-YmWxl32SQRw/kIRccSOxzS/Ib8/b5/f9ex0r5PR40jRJg8X1wgM3KrR2In+8zvOGVhRSXgvyQpw9yOSlmfmSnA=="], - "@aws-sdk/middleware-sdk-s3/@smithy/smithy-client/@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-s+eenEPW6RgliDk2IhjD2hWOxIx1NKrOHxEwNUaUXxYBxIyCcDfNULZ2Mu15E3kwcJWBedTET/kEASPV1A1Akg=="], + "@aws-sdk/nested-clients/@smithy/util-base64/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@aws-sdk/middleware-sdk-s3/@smithy/util-stream/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.13", "", { "dependencies": { "@smithy/protocol-http": "^5.3.11", "@smithy/querystring-builder": "^4.2.11", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-U2Hcfl2s3XaYjikN9cT4mPu8ybDbImV3baXR0PkVlC0TTx808bRP3FaPGAzPtB8OByI+JqJ1kyS+7GEgae7+qQ=="], + "@aws-sdk/nested-clients/@smithy/util-defaults-mode-browser/@smithy/property-provider": ["@smithy/property-provider@4.2.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-a/tGSLPtaia2krbRdwR4xbZKO8lU67DjMk/jfY4QKt4PRlKML+2tL/gmAuhNdFDioO6wOq0sXkfnddNFH9mNUA=="], - "@aws-sdk/middleware-sdk-s3/@smithy/util-stream/@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.14", "", { "dependencies": { "@smithy/abort-controller": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/querystring-builder": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-DamSqaU8nuk0xTJDrYnRzZndHwwRnyj/n/+RqGGCcBKB4qrQem0mSDiWdupaNWdwxzyMU91qxDmHOCazfhtO3A=="], + "@aws-sdk/nested-clients/@smithy/util-defaults-mode-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.6", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.6", "@smithy/property-provider": "^4.2.6", "@smithy/types": "^4.10.0", "@smithy/url-parser": "^4.2.6", "tslib": "^2.6.2" } }, "sha512-xBmawExyTzOjbhzkZwg+vVm/khg28kG+rj2sbGlULjFd1jI70sv/cbpaR0Ev4Yfd6CpDUDRMe64cTqR//wAOyA=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.758.0", "", { "dependencies": { "@aws-sdk/core": "3.758.0", "@aws-sdk/types": "3.734.0", "@aws-sdk/util-arn-parser": "3.723.0", "@smithy/core": "^3.1.5", "@smithy/node-config-provider": "^4.0.1", "@smithy/protocol-http": "^5.0.1", "@smithy/signature-v4": "^5.0.1", "@smithy/smithy-client": "^4.1.6", "@smithy/types": "^4.1.0", "@smithy/util-config-provider": "^4.0.0", "@smithy/util-middleware": "^4.0.1", "@smithy/util-stream": "^4.1.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-6mJ2zyyHPYSV6bAcaFpsdoXZJeQlR1QgBnZZ6juY/+dcYiuyWCdyLUbGzSZSE7GTfx6i+9+QWFeoIMlWKgU63A=="], + "@aws-sdk/nested-clients/@smithy/util-defaults-mode-node/@smithy/property-provider": ["@smithy/property-provider@4.2.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-a/tGSLPtaia2krbRdwR4xbZKO8lU67DjMk/jfY4QKt4PRlKML+2tL/gmAuhNdFDioO6wOq0sXkfnddNFH9mNUA=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4": ["@smithy/signature-v4@5.3.4", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.4", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-ScDCpasxH7w1HXHYbtk3jcivjvdA1VICyAdgvVqKhKKwxi+MTwZEqFw0minE+oZ7F07oF25xh4FGJxgqgShz0A=="], + "@aws-sdk/nested-clients/@smithy/util-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.6", "", { "dependencies": { "@smithy/types": "^4.10.0" } }, "sha512-Q73XBrzJlGTut2nf5RglSntHKgAG0+KiTJdO5QQblLfr4TdliGwIAha1iZIjwisc3rA5ulzqwwsYC6xrclxVQg=="], - "@aws-sdk/s3-request-presigner/@smithy/middleware-endpoint/@smithy/core": ["@smithy/core@3.1.5", "", { "dependencies": { "@smithy/middleware-serde": "^4.0.2", "@smithy/protocol-http": "^5.0.1", "@smithy/types": "^4.1.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-middleware": "^4.0.1", "@smithy/util-stream": "^4.1.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-HLclGWPkCsekQgsyzxLhCQLa8THWXtB5PxyYN+2O6nkyLt550KQKTlbV2D1/j5dNIQapAZM1+qFnpBFxZQkgCA=="], + "@aws-sdk/nested-clients/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@aws-sdk/s3-request-presigner/@smithy/middleware-endpoint/@smithy/middleware-serde": ["@smithy/middleware-serde@4.0.2", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-Sdr5lOagCn5tt+zKsaW+U2/iwr6bI9p08wOkCp6/eL6iMbgdtc2R5Ety66rf87PeohR0ExI84Txz9GYv5ou3iQ=="], + "@aws-sdk/token-providers/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.930.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" } }, "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA=="], - "@aws-sdk/s3-request-presigner/@smithy/middleware-endpoint/@smithy/node-config-provider": ["@smithy/node-config-provider@4.0.1", "", { "dependencies": { "@smithy/property-provider": "^4.0.1", "@smithy/shared-ini-file-loader": "^4.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-8mRTjvCtVET8+rxvmzRNRR0hH2JjV0DFOmwXPrISmTIJEfnCBugpYYGAsCj8t41qd+RB5gbheSQ/6aKZCQvFLQ=="], + "@aws-sdk/token-providers/@aws-sdk/core/@smithy/core": ["@smithy/core@3.19.0", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.7", "@smithy/protocol-http": "^5.3.6", "@smithy/types": "^4.10.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.6", "@smithy/util-stream": "^4.5.7", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-Y9oHXpBcXQgYHOcAEmxjkDilUbSTkgKjoHYed3WaYUH8jngq8lPWDBSpjHblJ9uOgBdy5mh3pzebrScDdYr29w=="], - "@aws-sdk/s3-request-presigner/@smithy/middleware-endpoint/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-hC8F6qTBbuHRI/uqDgqqi6J0R4GtEZcgrZPhFQnMhfJs3MnUTGSnR1NSJCJs5VWlMydu0kJz15M640fJlRsIOw=="], + "@aws-sdk/token-providers/@aws-sdk/core/@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.6", "", { "dependencies": { "@smithy/property-provider": "^4.2.6", "@smithy/shared-ini-file-loader": "^4.4.1", "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-fYEyL59Qe82Ha1p97YQTMEQPJYmBS+ux76foqluaTVWoG9Px5J53w6NvXZNE3wP7lIicLDF7Vj1Em18XTX7fsA=="], - "@aws-sdk/s3-request-presigner/@smithy/middleware-endpoint/@smithy/url-parser": ["@smithy/url-parser@4.0.1", "", { "dependencies": { "@smithy/querystring-parser": "^4.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-gPXcIEUtw7VlK8f/QcruNXm7q+T5hhvGu9tl63LsJPZ27exB6dtNwvh2HIi0v7JcXJ5emBxB+CJxwaLEdJfA+g=="], + "@aws-sdk/token-providers/@aws-sdk/core/@smithy/protocol-http": ["@smithy/protocol-http@5.3.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-qLRZzP2+PqhE3OSwvY2jpBbP0WKTZ9opTsn+6IWYI0SKVpbG+imcfNxXPq9fj5XeaUTr7odpsNpK6dmoiM1gJQ=="], - "@aws-sdk/s3-request-presigner/@smithy/middleware-endpoint/@smithy/util-middleware": ["@smithy/util-middleware@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-fKGQAPAn8sgV0plRikRVo6g6aR0KyKvgzNrPuM74RZKy/wWVzx3BMk+ZWEueyN3L5v5EDg+P582mKU+sH5OAsg=="], + "@aws-sdk/token-providers/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.3.6", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.6", "@smithy/types": "^4.10.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.6", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-P1TXDHuQMadTMTOBv4oElZMURU4uyEhxhHfn+qOc2iofW9Rd4sZtBGx58Lzk112rIGVEYZT8eUMK4NftpewpRA=="], - "@aws-sdk/s3-request-presigner/@smithy/smithy-client/@smithy/core": ["@smithy/core@3.1.5", "", { "dependencies": { "@smithy/middleware-serde": "^4.0.2", "@smithy/protocol-http": "^5.0.1", "@smithy/types": "^4.1.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-middleware": "^4.0.1", "@smithy/util-stream": "^4.1.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-HLclGWPkCsekQgsyzxLhCQLa8THWXtB5PxyYN+2O6nkyLt550KQKTlbV2D1/j5dNIQapAZM1+qFnpBFxZQkgCA=="], + "@aws-sdk/token-providers/@aws-sdk/core/@smithy/smithy-client": ["@smithy/smithy-client@4.10.1", "", { "dependencies": { "@smithy/core": "^3.19.0", "@smithy/middleware-endpoint": "^4.4.0", "@smithy/middleware-stack": "^4.2.6", "@smithy/protocol-http": "^5.3.6", "@smithy/types": "^4.10.0", "@smithy/util-stream": "^4.5.7", "tslib": "^2.6.2" } }, "sha512-1ovWdxzYprhq+mWqiGZlt3kF69LJthuQcfY9BIyHx9MywTFKzFapluku1QXoaBB43GCsLDxNqS+1v30ure69AA=="], - "@aws-sdk/s3-request-presigner/@smithy/smithy-client/@smithy/middleware-stack": ["@smithy/middleware-stack@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-dHwDmrtR/ln8UTHpaIavRSzeIk5+YZTBtLnKwDW3G2t6nAupCiQUvNzNoHBpik63fwUaJPtlnMzXbQrNFWssIA=="], + "@aws-sdk/token-providers/@aws-sdk/core/@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="], - "@aws-sdk/s3-request-presigner/@smithy/smithy-client/@smithy/util-stream": ["@smithy/util-stream@4.1.2", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.0.1", "@smithy/node-http-handler": "^4.0.3", "@smithy/types": "^4.1.0", "@smithy/util-base64": "^4.0.0", "@smithy/util-buffer-from": "^4.0.0", "@smithy/util-hex-encoding": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-44PKEqQ303d3rlQuiDpcCcu//hV8sn+u2JBo84dWCE0rvgeiVl0IlLMagbU++o0jCWhYCsHaAt9wZuZqNe05Hw=="], + "@aws-sdk/token-providers/@aws-sdk/core/@smithy/util-middleware": ["@smithy/util-middleware@4.2.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-qrvXUkxBSAFomM3/OEMuDVwjh4wtqK8D2uDZPShzIqOylPst6gor2Cdp6+XrH4dyksAWq/bE2aSDYBTTnj0Rxg=="], - "@aws-sdk/signature-v4-multi-region/@smithy/signature-v4/@smithy/util-middleware": ["@smithy/util-middleware@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-r3dtF9F+TpSZUxpOVVtPfk09Rlo4lT6ORBqEvX3IBT6SkQAdDSVKR5GcfmZbtl7WKhKnmb3wbDTQ6ibR2XHClw=="], + "@aws-sdk/token-providers/@aws-sdk/core/@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], - "@aws-sdk/util-format-url/@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=="], - - "@babel/core/@babel/helper-compilation-targets/@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="], - - "@babel/core/@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], - - "@babel/helper-compilation-targets/browserslist/baseline-browser-mapping": ["baseline-browser-mapping@2.8.28", "", { "bin": "dist/cli.js" }, "sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ=="], - - "@babel/helper-compilation-targets/browserslist/electron-to-chromium": ["electron-to-chromium@1.5.254", "", {}, "sha512-DcUsWpVhv9svsKRxnSCZ86SjD+sp32SGidNB37KpqXJncp1mfUgKbHvBomE89WJDbfVKw1mdv5+ikrvd43r+Bg=="], + "@azure/core-xml/fast-xml-parser/strnum": ["strnum@2.0.5", "", {}, "sha512-YAT3K/sgpCUxhxNMrrdhtod3jckkpYwH6JAuwmUdXZsmzH1wUyzTMrrK2wYCEEqlKwrWDd35NeuUkbBy/1iK+Q=="], "@babel/helper-compilation-targets/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=="], "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], - "@babel/helper-create-class-features-plugin/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], - - "@babel/helper-create-class-features-plugin/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], - - "@babel/helper-create-class-features-plugin/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], - - "@babel/helper-create-class-features-plugin/@babel/traverse/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], - - "@babel/helper-create-class-features-plugin/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - - "@babel/helper-create-class-features-plugin/@babel/traverse/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "@babel/helper-create-regexp-features-plugin/@babel/helper-annotate-as-pure/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - - "@babel/helper-member-expression-to-functions/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], - - "@babel/helper-member-expression-to-functions/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], - - "@babel/helper-member-expression-to-functions/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], - - "@babel/helper-member-expression-to-functions/@babel/traverse/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], - - "@babel/helper-member-expression-to-functions/@babel/traverse/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "@babel/helper-module-imports/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], - - "@babel/helper-module-imports/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], - - "@babel/helper-module-imports/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], - - "@babel/helper-module-imports/@babel/traverse/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], - - "@babel/helper-module-imports/@babel/traverse/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "@babel/helper-remap-async-to-generator/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], - - "@babel/helper-remap-async-to-generator/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], - - "@babel/helper-remap-async-to-generator/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], - - "@babel/helper-remap-async-to-generator/@babel/traverse/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], - - "@babel/helper-remap-async-to-generator/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - - "@babel/helper-remap-async-to-generator/@babel/traverse/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "@babel/helper-replace-supers/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], - - "@babel/helper-replace-supers/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], - - "@babel/helper-replace-supers/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], - - "@babel/helper-replace-supers/@babel/traverse/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], - - "@babel/helper-replace-supers/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - - "@babel/helper-replace-supers/@babel/traverse/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], - - "@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], - - "@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], - - "@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], - - "@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "@babel/helper-wrap-function/@babel/template/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], - - "@babel/helper-wrap-function/@babel/template/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], - - "@babel/helper-wrap-function/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], - - "@babel/helper-wrap-function/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], - - "@babel/helper-wrap-function/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], - - "@babel/helper-wrap-function/@babel/traverse/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], - - "@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], - - "@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], - - "@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], - - "@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - - "@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], - - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], - - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], - - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], - - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "@babel/plugin-transform-async-generator-functions/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], - - "@babel/plugin-transform-async-generator-functions/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], - - "@babel/plugin-transform-async-generator-functions/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], - - "@babel/plugin-transform-async-generator-functions/@babel/traverse/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], - - "@babel/plugin-transform-async-generator-functions/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - - "@babel/plugin-transform-async-generator-functions/@babel/traverse/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "@babel/plugin-transform-classes/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], - - "@babel/plugin-transform-classes/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], - - "@babel/plugin-transform-classes/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], - - "@babel/plugin-transform-classes/@babel/traverse/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], - - "@babel/plugin-transform-classes/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - - "@babel/plugin-transform-classes/@babel/traverse/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "@babel/plugin-transform-computed-properties/@babel/template/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], - - "@babel/plugin-transform-computed-properties/@babel/template/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], - - "@babel/plugin-transform-computed-properties/@babel/template/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - - "@babel/plugin-transform-destructuring/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], - - "@babel/plugin-transform-destructuring/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], - - "@babel/plugin-transform-destructuring/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], - - "@babel/plugin-transform-destructuring/@babel/traverse/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], - - "@babel/plugin-transform-destructuring/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - - "@babel/plugin-transform-destructuring/@babel/traverse/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "@babel/plugin-transform-dotall-regex/@babel/helper-create-regexp-features-plugin/regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="], - - "@babel/plugin-transform-duplicate-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="], - - "@babel/plugin-transform-function-name/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], - - "@babel/plugin-transform-function-name/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], - - "@babel/plugin-transform-function-name/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], - - "@babel/plugin-transform-function-name/@babel/traverse/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], - - "@babel/plugin-transform-function-name/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - - "@babel/plugin-transform-function-name/@babel/traverse/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "@babel/plugin-transform-modules-amd/@babel/helper-module-transforms/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], - - "@babel/plugin-transform-modules-commonjs/@babel/helper-module-transforms/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], - - "@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], - - "@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], - - "@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], - - "@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], - - "@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - - "@babel/plugin-transform-modules-systemjs/@babel/traverse/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "@babel/plugin-transform-modules-umd/@babel/helper-module-transforms/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], - - "@babel/plugin-transform-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="], - - "@babel/plugin-transform-object-rest-spread/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], - - "@babel/plugin-transform-object-rest-spread/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], - - "@babel/plugin-transform-object-rest-spread/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], - - "@babel/plugin-transform-object-rest-spread/@babel/traverse/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], - - "@babel/plugin-transform-object-rest-spread/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - - "@babel/plugin-transform-object-rest-spread/@babel/traverse/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "@babel/plugin-transform-regexp-modifiers/@babel/helper-create-regexp-features-plugin/regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="], - "@babel/plugin-transform-runtime/babel-plugin-polyfill-corejs2/@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.5.0", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", "@babel/helper-plugin-utils": "^7.22.5", "debug": "^4.1.1", "lodash.debounce": "^4.0.8", "resolve": "^1.14.2" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q=="], "@babel/plugin-transform-runtime/babel-plugin-polyfill-corejs3/@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.5.0", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", "@babel/helper-plugin-utils": "^7.22.5", "debug": "^4.1.1", "lodash.debounce": "^4.0.8", "resolve": "^1.14.2" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q=="], @@ -7421,21 +6695,13 @@ "@babel/plugin-transform-runtime/babel-plugin-polyfill-regenerator/@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.5.0", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", "@babel/helper-plugin-utils": "^7.22.5", "debug": "^4.1.1", "lodash.debounce": "^4.0.8", "resolve": "^1.14.2" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q=="], - "@babel/plugin-transform-unicode-property-regex/@babel/helper-create-regexp-features-plugin/regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="], + "@babel/plugin-transform-typescript/@babel/helper-create-class-features-plugin/@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.23.0", "", { "dependencies": { "@babel/types": "^7.23.0" } }, "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA=="], - "@babel/plugin-transform-unicode-regex/@babel/helper-create-regexp-features-plugin/regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="], + "@babel/plugin-transform-typescript/@babel/helper-create-class-features-plugin/@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.22.5", "", { "dependencies": { "@babel/types": "^7.22.5" } }, "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw=="], - "@babel/plugin-transform-unicode-sets-regex/@babel/helper-create-regexp-features-plugin/regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="], + "@babel/plugin-transform-typescript/@babel/helper-create-class-features-plugin/@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.22.20", "", { "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-member-expression-to-functions": "^7.22.15", "@babel/helper-optimise-call-expression": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw=="], - "@google/genai/google-auth-library/gaxios": ["gaxios@7.1.3", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2", "rimraf": "^5.0.1" } }, "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ=="], - - "@google/genai/google-auth-library/gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="], - - "@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=="], + "@babel/plugin-transform-typescript/@babel/helper-create-class-features-plugin/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.22.5", "", { "dependencies": { "@babel/types": "^7.22.5" } }, "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q=="], "@headlessui/react/@tanstack/react-virtual/@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.12", "", {}, "sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA=="], @@ -7445,52 +6711,36 @@ "@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=="], + "@istanbuljs/load-nyc-config/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], "@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-jsdom-abstract/@jest/fake-timers/@sinonjs/fake-timers": ["@sinonjs/fake-timers@13.0.5", "", { "dependencies": { "@sinonjs/commons": "^3.0.1" } }, "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw=="], - "@jest/environment/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], + "@jest/environment/@jest/fake-timers/@sinonjs/fake-timers": ["@sinonjs/fake-timers@13.0.5", "", { "dependencies": { "@sinonjs/commons": "^3.0.1" } }, "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw=="], + + "@jest/environment/@jest/fake-timers/jest-util": ["jest-util@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", "graceful-fs": "^4.2.11", "picomatch": "^4.0.2" } }, "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA=="], + + "@jest/environment/jest-mock/jest-util": ["jest-util@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", "graceful-fs": "^4.2.11", "picomatch": "^4.0.2" } }, "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA=="], "@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/expect/expect/jest-mock": ["jest-mock@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "jest-util": "30.2.0" } }, "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw=="], - "@jest/pattern/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], + "@jest/expect/expect/jest-util": ["jest-util@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", "graceful-fs": "^4.2.11", "picomatch": "^4.0.2" } }, "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA=="], - "@jest/reporters/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], + "@jest/fake-timers/@jest/types/@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="], + + "@jest/fake-timers/jest-message-util/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], + + "@jest/globals/jest-mock/jest-util": ["jest-util@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", "graceful-fs": "^4.2.11", "picomatch": "^4.0.2" } }, "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA=="], "@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/transform/@babel/core/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], - - "@jest/transform/@babel/core/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], - - "@jest/transform/@babel/core/@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="], - - "@jest/transform/@babel/core/@babel/helpers": ["@babel/helpers@7.28.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" } }, "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w=="], - - "@jest/transform/@babel/core/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], - - "@jest/transform/@babel/core/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], - - "@jest/transform/@babel/core/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], - - "@jest/transform/@babel/core/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - - "@jest/transform/@babel/core/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "@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=="], @@ -7523,12 +6773,6 @@ "@langchain/aws/@aws-sdk/client-bedrock-runtime/@smithy/core": ["@smithy/core@3.17.2", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.4", "@smithy/util-stream": "^4.5.5", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-n3g4Nl1Te+qGPDbNFAYf+smkRVB+JhFsGy9uJXXZQEufoP4u0r+WLh6KvTDolCswaagysDc/afS1yvb2jnj1gQ=="], - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.4", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-d5T7ZS3J/r8P/PDjgmCcutmNxnSRvPH1U6iHeXjzI50sMr78GLmFcrczLw33Ap92oEKqa4CLrkAPeSSOqvGdUA=="], - - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-lxfDT0UuSc1HqltOGsTEAlZ6H29gpfDSdEPTapD5G63RbnYToZ+ezjzdonCCH90j5tRRCw3aLXVbiZaBW3VRVg=="], - - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.4", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-TPhiGByWnYyzcpU/K3pO5V7QgtXYpE0NaJPEZBCa1Y5jlw5SjqzMSbFiLb+ZkJhqoQc0ImGyVINqnq1ze0ZRcQ=="], - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.5", "", { "dependencies": { "@smithy/protocol-http": "^5.3.4", "@smithy/querystring-builder": "^4.2.4", "@smithy/types": "^4.8.1", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-mg83SM3FLI8Sa2ooTJbsh5MFfyMTyNRwxqpKHmE0ICRIa66Aodv80DMsTQI02xBLVJ0hckwqTRr5IGAbbWuFLQ=="], "@langchain/aws/@aws-sdk/client-bedrock-runtime/@smithy/hash-node": ["@smithy/hash-node@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kKU0gVhx/ppVMntvUOZE7WRMFW86HuaxLwvqileBEjL7PoILI8/djoILw3gPQloGVE6O0oOzqafxeNi2KbnUJw=="], @@ -7549,12 +6793,8 @@ "@langchain/aws/@aws-sdk/client-bedrock-runtime/@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.4", "", { "dependencies": { "@smithy/abort-controller": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/querystring-builder": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-VXHGfzCXLZeKnFp6QXjAdy+U8JF9etfpUXD1FAbzY1GzsFJiDQRQIt2CnMUvUdz3/YaHNqT3RphVWMUpXTIODA=="], - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@smithy/protocol-http": ["@smithy/protocol-http@5.3.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-3sfFd2MAzVt0Q/klOmjFi3oIkxczHs0avbwrfn1aBqtc23WqQSmjvk77MBw9WkEQcwbOYIX5/2z4ULj8DuxSsw=="], - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@smithy/smithy-client": ["@smithy/smithy-client@4.9.2", "", { "dependencies": { "@smithy/core": "^3.17.2", "@smithy/middleware-endpoint": "^4.3.6", "@smithy/middleware-stack": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-stream": "^4.5.5", "tslib": "^2.6.2" } }, "sha512-gZU4uAFcdrSi3io8U99Qs/FvVdRxPvIMToi+MFfsy/DN9UqtknJ1ais+2M9yR8e0ASQpNmFYEKeIKVcMjQg3rg=="], - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@smithy/types": ["@smithy/types@4.8.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-N0Zn0OT1zc+NA+UVfkYqQzviRh5ucWwO7mBV3TmHHprMnfcJNfhlPicDkBHi0ewbh+y3evR6cNAW0Raxvb01NA=="], - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@smithy/url-parser": ["@smithy/url-parser@4.2.4", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-w/N/Iw0/PTwJ36PDqU9PzAwVElo4qXxCC0eCTlUtIz/Z5V/2j/cViMHi0hPukSBHp4DVwvUlUhLgCzqSJ6plrg=="], "@langchain/aws/@aws-sdk/client-bedrock-runtime/@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="], @@ -7569,16 +6809,12 @@ "@langchain/aws/@aws-sdk/client-bedrock-runtime/@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.4", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-f+nBDhgYRCmUEDKEQb6q0aCcOTXRDqH5wWaFHJxt4anB4pKHlgGoYP3xtioKXH64e37ANUkzWf6p4Mnv1M5/Vg=="], - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@smithy/util-middleware": ["@smithy/util-middleware@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-fKGQAPAn8sgV0plRikRVo6g6aR0KyKvgzNrPuM74RZKy/wWVzx3BMk+ZWEueyN3L5v5EDg+P582mKU+sH5OAsg=="], - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@smithy/util-retry": ["@smithy/util-retry@4.2.4", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-yQncJmj4dtv/isTXxRb4AamZHy4QFr4ew8GxS6XLWt7sCIxkPxPzINWd7WLISEFPsIan14zrKgvyAF+/yzfwoA=="], "@langchain/aws/@aws-sdk/client-bedrock-runtime/@smithy/util-stream": ["@smithy/util-stream@4.5.5", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.5", "@smithy/node-http-handler": "^4.4.4", "@smithy/types": "^4.8.1", "@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", "tslib": "^2.6.2" } }, "sha512-7M5aVFjT+HPilPOKbOmQfCIPchZe4DSBc1wf1+NvHvSoFTiFtauZzT+onZvCj70xhXd0AEmYnZYmdJIuwxOo4w=="], "@langchain/aws/@aws-sdk/client-bedrock-runtime/@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.927.0", "", { "dependencies": { "@aws-sdk/core": "3.927.0", "@aws-sdk/types": "3.922.0", "@smithy/property-provider": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-bAllBpmaWINpf0brXQWh/hjkBctapknZPYb3FJRlBHytEGHi7TpgqBXi8riT0tc6RVWChhnw58rQz22acOmBuw=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.927.0", "", { "dependencies": { "@aws-sdk/core": "3.927.0", "@aws-sdk/types": "3.922.0", "@smithy/fetch-http-handler": "^5.3.5", "@smithy/node-http-handler": "^4.4.4", "@smithy/property-provider": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/smithy-client": "^4.9.2", "@smithy/types": "^4.8.1", "@smithy/util-stream": "^4.5.5", "tslib": "^2.6.2" } }, "sha512-jEvb8C7tuRBFhe8vZY9vm9z6UQnbP85IMEt3Qiz0dxAd341Hgu0lOzMv5mSKQ5yBnTLq+t3FPKgD9tIiHLqxSQ=="], @@ -7599,99 +6835,275 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.3.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-y5ozxeQ9omVjbnJo9dtTsdXj9BEvGx2X8xvRgKnV+/7wLBuYJQL6dOa/qMY6omyHi7yjt1OA97jZLoVRYi8lxA=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@smithy/types": ["@smithy/types@4.8.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-N0Zn0OT1zc+NA+UVfkYqQzviRh5ucWwO7mBV3TmHHprMnfcJNfhlPicDkBHi0ewbh+y3evR6cNAW0Raxvb01NA=="], - "@langchain/google-gauth/google-auth-library/gaxios": ["gaxios@7.1.3", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2", "rimraf": "^5.0.1" } }, "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ=="], "@langchain/google-gauth/google-auth-library/gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="], "@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/@aws-sdk/client-bedrock-runtime/@aws-sdk/core": ["@aws-sdk/core@3.973.18", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@aws-sdk/xml-builder": "^3.972.10", "@smithy/core": "^3.23.8", "@smithy/node-config-provider": "^4.3.11", "@smithy/property-provider": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/signature-v4": "^5.3.11", "@smithy/smithy-client": "^4.12.2", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-GUIlegfcK2LO1J2Y98sCJy63rQSiLiDOgVw7HiHPRqfI2vb3XozTVqemwO0VSGXp54ngCnAQz0Lf0YPCBINNxA=="], + "@librechat/client/@babel/preset-env/@babel/plugin-bugfix-firefox-class-in-computed-class-key": ["@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.18", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.16", "@aws-sdk/credential-provider-http": "^3.972.18", "@aws-sdk/credential-provider-ini": "^3.972.17", "@aws-sdk/credential-provider-process": "^3.972.16", "@aws-sdk/credential-provider-sso": "^3.972.17", "@aws-sdk/credential-provider-web-identity": "^3.972.17", "@aws-sdk/types": "^3.973.5", "@smithy/credential-provider-imds": "^4.2.11", "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-ZDJa2gd1xiPg/nBDGhUlat02O8obaDEnICBAVS8qieZ0+nDfaB0Z3ec6gjZj27OqFTjnB/Q5a0GwQwb7rMVViw=="], + "@librechat/client/@babel/preset-env/@babel/plugin-bugfix-safari-class-field-initializer-scope": ["@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/eventstream-handler-node": ["@aws-sdk/eventstream-handler-node@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/eventstream-codec": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-g2Z9s6Y4iNh0wICaEqutgYgt/Pmhv5Ev9G3eKGFe2w9VuZDhc76vYdop6I5OocmpHV79d4TuLG+JWg5rQIVDVA=="], + "@librechat/client/@babel/preset-env/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": ["@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/middleware-eventstream": ["@aws-sdk/middleware-eventstream@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-VWndapHYCfwLgPpCb/xwlMKG4imhFzKJzZcKOEioGn7OHY+6gdr0K7oqy1HZgbLa3ACznZ9fku+DzmAi8fUC0g=="], + "@librechat/client/@babel/preset-env/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": ["@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/plugin-transform-optional-chaining": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.13.0" } }, "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-aHQZgztBFEpDU1BB00VWCIIm85JjGjQW1OG9+98BdmaOpguJvzmXBGbnAiYcciCd+IS4e9BEq664lhzGnWJHgQ=="], + "@librechat/client/@babel/preset-env/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": ["@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-LXhiWlWb26txCU1vcI9PneESSeRp/RYY/McuM4SpdrimQR5NgwaPb4VJCadVeuGWgh6QmqZ6rAKSoL1ob16W6w=="], + "@librechat/client/@babel/preset-env/@babel/plugin-syntax-import-assertions": ["@babel/plugin-syntax-import-assertions@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-l2VQdcBcYLzIzykCHtXlbpiVCZ94/xniLIkAj0jpnpjY4xlgZx7f56Ypn+uV1y3gG0tNVytJqo3K9bfMFee7SQ=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-arrow-functions": ["@babel/plugin-transform-arrow-functions@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.19", "", { "dependencies": { "@aws-sdk/core": "^3.973.18", "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-endpoints": "^3.996.4", "@smithy/core": "^3.23.8", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "@smithy/util-retry": "^4.2.11", "tslib": "^2.6.2" } }, "sha512-Km90fcXt3W/iqujHzuM6IaDkYCj73gsYufcuWXApWdzoTy6KGk8fnchAjePMARU0xegIR3K4N3yIo1vy7OVe8A=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-async-generator-functions": ["@babel/plugin-transform-async-generator-functions@7.28.0", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-remap-async-to-generator": "^7.27.1", "@babel/traverse": "^7.28.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/middleware-websocket": ["@aws-sdk/middleware-websocket@3.972.12", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-format-url": "^3.972.7", "@smithy/eventstream-codec": "^4.2.11", "@smithy/eventstream-serde-browser": "^4.2.11", "@smithy/fetch-http-handler": "^5.3.13", "@smithy/protocol-http": "^5.3.11", "@smithy/signature-v4": "^5.3.11", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-iyPP6FVDKe/5wy5ojC0akpDFG1vX3FeCUU47JuwN8xfvT66xlEI8qUJZPtN55TJVFzzWZJpWL78eqUE31md08Q=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-async-to-generator": ["@babel/plugin-transform-async-to-generator@7.27.1", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-remap-async-to-generator": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/config-resolver": "^4.4.10", "@smithy/node-config-provider": "^4.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-/Ev/6AI8bvt4HAAptzSjThGUMjcWaX3GX8oERkB0F0F9x2dLSBdgFDiyrRz3i0u0ZFZFQ1b28is4QhyqXTUsVA=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-block-scoped-functions": ["@babel/plugin-transform-block-scoped-functions@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1004.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.18", "@aws-sdk/nested-clients": "^3.996.7", "@aws-sdk/types": "^3.973.5", "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-j9BwZZId9sFp+4GPhf6KrwO8Tben2sXibZA8D1vv2I1zBdvkUHcBA2g4pkqIpTRalMTLC0NPkBPX0gERxfy/iA=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-block-scoping": ["@babel/plugin-transform-block-scoping@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/types": ["@aws-sdk/types@3.973.5", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-class-properties": ["@babel/plugin-transform-class-properties@7.27.1", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.4", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@smithy/util-endpoints": "^3.3.2", "tslib": "^2.6.2" } }, "sha512-Hek90FBmd4joCFj+Vc98KLJh73Zqj3s2W56gjAcTkrNLMDI5nIFkG9YpfcJiVI1YlE2Ne1uOQNe+IgQ/Vz2XRA=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-class-static-block": ["@babel/plugin-transform-class-static-block@7.28.3", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.3", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.12.0" } }, "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-7SJVuvhKhMF/BkNS1n0QAJYgvEwYbK2QLKBrzDiwQGiTRU6Yf1f3nehTzm/l21xdAOtWSfp2uWSddPnP2ZtsVw=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-classes": ["@babel/plugin-transform-classes@7.28.4", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-globals": "^7.28.0", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/traverse": "^7.28.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.4", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.19", "@aws-sdk/types": "^3.973.5", "@smithy/node-config-provider": "^4.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-uqKeLqZ9D3nQjH7HGIERNXK9qnSpUK08l4MlJ5/NZqSSdeJsVANYp437EM9sEzwU28c2xfj2V6qlkqzsgtKs6Q=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-computed-properties": ["@babel/plugin-transform-computed-properties@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/template": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/config-resolver": ["@smithy/config-resolver@4.4.10", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.11", "@smithy/types": "^4.13.0", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-endpoints": "^3.3.2", "@smithy/util-middleware": "^4.2.11", "tslib": "^2.6.2" } }, "sha512-IRTkd6ps0ru+lTWnfnsbXzW80A8Od8p3pYiZnW98K2Hb20rqfsX7VTlfUwhrcOeSSy68Gn9WBofwPuw3e5CCsg=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-destructuring": ["@babel/plugin-transform-destructuring@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/core": ["@smithy/core@3.23.9", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.12", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-stream": "^4.5.17", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-1Vcut4LEL9HZsdpI0vFiRYIsaoPwZLjAxnVQDUMQK8beMS+EYPLDQCXtbzfxmM5GzSgjfe2Q9M7WaXwIMQllyQ=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-dotall-regex": ["@babel/plugin-transform-dotall-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.11", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-3rEpo3G6f/nRS7fQDsZmxw/ius6rnlIpz4UX6FlALEzz8JoSxFmdBt0SZnthis+km7sQo6q5/3e+UJcuQivoXA=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-duplicate-keys": ["@babel/plugin-transform-duplicate-keys@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-XeNIA8tcP/GDWnnKkO7qEm/bg0B/bP9lvIXZBXcGZwZ+VYM8h8k9wuDvUODtdQ2Wcp2RcBkPTCSMmaniVHrMlA=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-duplicate-named-capturing-groups-regex": ["@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.11", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-fzbCh18rscBDTQSCrsp1fGcclLNF//nJyhjldsEl/5wCYmgpHblv5JSppQAyQI24lClsFT0wV06N1Porn0IsEw=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-dynamic-import": ["@babel/plugin-transform-dynamic-import@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.13", "", { "dependencies": { "@smithy/protocol-http": "^5.3.11", "@smithy/querystring-builder": "^4.2.11", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-U2Hcfl2s3XaYjikN9cT4mPu8ybDbImV3baXR0PkVlC0TTx808bRP3FaPGAzPtB8OByI+JqJ1kyS+7GEgae7+qQ=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-exponentiation-operator": ["@babel/plugin-transform-exponentiation-operator@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/hash-node": ["@smithy/hash-node@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-T+p1pNynRkydpdL015ruIoyPSRw9e/SQOWmSAMmmprfswMrd5Ow5igOWNVlvyVFZlxXqGmyH3NQwfwy8r5Jx0A=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-export-namespace-from": ["@babel/plugin-transform-export-namespace-from@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-cGNMrgykRmddrNhYy1yBdrp5GwIgEkniS7k9O1VLB38yxQtlvrxpZtUVvo6T4cKpeZsriukBuuxfJcdZQc/f/g=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-for-of": ["@babel/plugin-transform-for-of@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.11", "", { "dependencies": { "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-UvIfKYAKhCzr4p6jFevPlKhQwyQwlJ6IeKLDhmV1PlYfcW3RL4ROjNEDtSik4NYMi9kDkH7eSwyTP3vNJ/u/Dw=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-function-name": ["@babel/plugin-transform-function-name@7.27.1", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.23", "", { "dependencies": { "@smithy/core": "^3.23.9", "@smithy/middleware-serde": "^4.2.12", "@smithy/node-config-provider": "^4.3.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@smithy/util-middleware": "^4.2.11", "tslib": "^2.6.2" } }, "sha512-UEFIejZy54T1EJn2aWJ45voB7RP2T+IRzUqocIdM6GFFa5ClZncakYJfcYnoXt3UsQrZZ9ZRauGm77l9UCbBLw=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-json-strings": ["@babel/plugin-transform-json-strings@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.40", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.11", "@smithy/protocol-http": "^5.3.11", "@smithy/service-error-classification": "^4.2.11", "@smithy/smithy-client": "^4.12.3", "@smithy/types": "^4.13.0", "@smithy/util-middleware": "^4.2.11", "@smithy/util-retry": "^4.2.11", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-YhEMakG1Ae57FajERdHNZ4ShOPIY7DsgV+ZoAxo/5BT0KIe+f6DDU2rtIymNNFIj22NJfeeI6LWIifrwM0f+rA=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-literals": ["@babel/plugin-transform-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-W9g1bOLui7Xn5FABRVS0o3rXL0gfN37d/8I/W7i0N7oxjx9QecUmXEMSUMADTODwdtka9cN43t5BI2CodLJpng=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-logical-assignment-operators": ["@babel/plugin-transform-logical-assignment-operators@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-s+eenEPW6RgliDk2IhjD2hWOxIx1NKrOHxEwNUaUXxYBxIyCcDfNULZ2Mu15E3kwcJWBedTET/kEASPV1A1Akg=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-member-expression-literals": ["@babel/plugin-transform-member-expression-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.11", "", { "dependencies": { "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-xD17eE7kaLgBBGf5CZQ58hh2YmwK1Z0O8YhffwB/De2jsL0U3JklmhVYJ9Uf37OtUDLF2gsW40Xwwag9U869Gg=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-modules-amd": ["@babel/plugin-transform-modules-amd@7.27.1", "", { "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/protocol-http": ["@smithy/protocol-http@5.3.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hI+barOVDJBkNt4y0L2mu3Ugc0w7+BpJ2CZuLwXtSltGAAwCb3IvnalGlbDV/UCS6a9ZuT3+exd1WxNdLb5IlQ=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.27.1", "", { "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/smithy-client": ["@smithy/smithy-client@4.12.3", "", { "dependencies": { "@smithy/core": "^3.23.9", "@smithy/middleware-endpoint": "^4.4.23", "@smithy/middleware-stack": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "@smithy/util-stream": "^4.5.17", "tslib": "^2.6.2" } }, "sha512-7k4UxjSpHmPN2AxVhvIazRSzFQjWnud3sOsXcFStzagww17j1cFQYqTSiQ8xuYK3vKLR1Ni8FzuT3VlKr3xCNw=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-modules-systemjs": ["@babel/plugin-transform-modules-systemjs@7.28.5", "", { "dependencies": { "@babel/helper-module-transforms": "^7.28.3", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/types": ["@smithy/types@4.13.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-modules-umd": ["@babel/plugin-transform-modules-umd@7.27.1", "", { "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/url-parser": ["@smithy/url-parser@4.2.11", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-oTAGGHo8ZYc5VZsBREzuf5lf2pAurJQsccMusVZ85wDkX66ojEc/XauiGjzCj50A61ObFTPe6d7Pyt6UBYaing=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-named-capturing-groups-regex": ["@babel/plugin-transform-named-capturing-groups-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.39", "", { "dependencies": { "@smithy/property-provider": "^4.2.11", "@smithy/smithy-client": "^4.12.3", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-ui7/Ho/+VHqS7Km2wBw4/Ab4RktoiSshgcgpJzC4keFPs6tLJS4IQwbeahxQS3E/w98uq6E1mirCH/id9xIXeQ=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-new-target": ["@babel/plugin-transform-new-target@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.42", "", { "dependencies": { "@smithy/config-resolver": "^4.4.10", "@smithy/credential-provider-imds": "^4.2.11", "@smithy/node-config-provider": "^4.3.11", "@smithy/property-provider": "^4.2.11", "@smithy/smithy-client": "^4.12.3", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-QDA84CWNe8Akpj15ofLO+1N3Rfg8qa2K5uX0y6HnOp4AnRYRgWrKx/xzbYNbVF9ZsyJUYOfcoaN3y93wA/QJ2A=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-nullish-coalescing-operator": ["@babel/plugin-transform-nullish-coalescing-operator@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/util-endpoints": ["@smithy/util-endpoints@3.3.2", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-+4HFLpE5u29AbFlTdlKIT7jfOzZ8PDYZKTb3e+AgLz986OYwqTourQ5H+jg79/66DB69Un1+qKecLnkZdAsYcA=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-numeric-separator": ["@babel/plugin-transform-numeric-separator@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/util-middleware": ["@smithy/util-middleware@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-r3dtF9F+TpSZUxpOVVtPfk09Rlo4lT6ORBqEvX3IBT6SkQAdDSVKR5GcfmZbtl7WKhKnmb3wbDTQ6ibR2XHClw=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-object-rest-spread": ["@babel/plugin-transform-object-rest-spread@7.28.4", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", "@babel/plugin-transform-destructuring": "^7.28.0", "@babel/plugin-transform-parameters": "^7.27.7", "@babel/traverse": "^7.28.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/util-retry": ["@smithy/util-retry@4.2.11", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-XSZULmL5x6aCTTii59wJqKsY1l3eMIAomRAccW7Tzh9r8s7T/7rdo03oektuH5jeYRlJMPcNP92EuRDvk9aXbw=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-object-super": ["@babel/plugin-transform-object-super@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/util-stream": ["@smithy/util-stream@4.5.17", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.13", "@smithy/node-http-handler": "^4.4.14", "@smithy/types": "^4.13.0", "@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" } }, "sha512-793BYZ4h2JAQkNHcEnyFxDTcZbm9bVybD0UV/LEWmZ5bkTms7JqjfrLMi2Qy0E5WFcCzLwCAPgcvcvxoeALbAQ=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-optional-catch-binding": ["@babel/plugin-transform-optional-catch-binding@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q=="], - "@librechat/backend/@smithy/node-http-handler/@smithy/abort-controller": ["@smithy/abort-controller@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-Hj4WoYWMJnSpM6/kchsm4bUNTL9XiSyhvoMb2KIq4VJzyDt7JpGHUZHkVNPZVC7YE1tf8tPeVauxpFBKGW4/KQ=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-optional-chaining": ["@babel/plugin-transform-optional-chaining@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ=="], - "@librechat/backend/@smithy/node-http-handler/@smithy/protocol-http": ["@smithy/protocol-http@5.3.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hI+barOVDJBkNt4y0L2mu3Ugc0w7+BpJ2CZuLwXtSltGAAwCb3IvnalGlbDV/UCS6a9ZuT3+exd1WxNdLb5IlQ=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-parameters": ["@babel/plugin-transform-parameters@7.27.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg=="], - "@librechat/backend/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-7spdikrYiljpket6u0up2Ck2mxhy7dZ0+TDd+S53Dg2DHd6wg+YNJrTCHiLdgZmEXZKI7LJZcwL3721ZRDFiqA=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-private-methods": ["@babel/plugin-transform-private-methods@7.27.1", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA=="], - "@librechat/backend/@smithy/node-http-handler/@smithy/types": ["@smithy/types@4.13.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-private-property-in-object": ["@babel/plugin-transform-private-property-in-object@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-create-class-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-property-literals": ["@babel/plugin-transform-property-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-regenerator": ["@babel/plugin-transform-regenerator@7.28.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-regexp-modifiers": ["@babel/plugin-transform-regexp-modifiers@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-reserved-words": ["@babel/plugin-transform-reserved-words@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-shorthand-properties": ["@babel/plugin-transform-shorthand-properties@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-spread": ["@babel/plugin-transform-spread@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-sticky-regex": ["@babel/plugin-transform-sticky-regex@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-template-literals": ["@babel/plugin-transform-template-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-typeof-symbol": ["@babel/plugin-transform-typeof-symbol@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-unicode-escapes": ["@babel/plugin-transform-unicode-escapes@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-unicode-property-regex": ["@babel/plugin-transform-unicode-property-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-unicode-regex": ["@babel/plugin-transform-unicode-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-unicode-sets-regex": ["@babel/plugin-transform-unicode-sets-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw=="], + + "@librechat/client/@babel/preset-env/babel-plugin-polyfill-corejs2": ["babel-plugin-polyfill-corejs2@0.4.14", "", { "dependencies": { "@babel/compat-data": "^7.27.7", "@babel/helper-define-polyfill-provider": "^0.6.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg=="], + + "@librechat/client/@babel/preset-env/babel-plugin-polyfill-corejs3": ["babel-plugin-polyfill-corejs3@0.13.0", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5", "core-js-compat": "^3.43.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A=="], + + "@librechat/client/@babel/preset-env/babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.5", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg=="], + + "@librechat/client/@babel/preset-env/core-js-compat": ["core-js-compat@3.47.0", "", { "dependencies": { "browserslist": "^4.28.0" } }, "sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ=="], + + "@librechat/client/@babel/preset-react/@babel/plugin-transform-react-display-name": ["@babel/plugin-transform-react-display-name@7.28.0", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA=="], + + "@librechat/client/@babel/preset-react/@babel/plugin-transform-react-jsx": ["@babel/plugin-transform-react-jsx@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-module-imports": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/types": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw=="], + + "@librechat/client/@babel/preset-react/@babel/plugin-transform-react-jsx-development": ["@babel/plugin-transform-react-jsx-development@7.27.1", "", { "dependencies": { "@babel/plugin-transform-react-jsx": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q=="], + + "@librechat/client/@babel/preset-react/@babel/plugin-transform-react-pure-annotations": ["@babel/plugin-transform-react-pure-annotations@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA=="], + + "@librechat/client/@babel/preset-typescript/@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.27.1", "", { "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw=="], + + "@librechat/client/@babel/preset-typescript/@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-create-class-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-bugfix-firefox-class-in-computed-class-key": ["@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-bugfix-safari-class-field-initializer-scope": ["@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": ["@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": ["@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/plugin-transform-optional-chaining": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.13.0" } }, "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": ["@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-syntax-import-assertions": ["@babel/plugin-syntax-import-assertions@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-arrow-functions": ["@babel/plugin-transform-arrow-functions@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-async-generator-functions": ["@babel/plugin-transform-async-generator-functions@7.28.0", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-remap-async-to-generator": "^7.27.1", "@babel/traverse": "^7.28.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-async-to-generator": ["@babel/plugin-transform-async-to-generator@7.27.1", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-remap-async-to-generator": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-block-scoped-functions": ["@babel/plugin-transform-block-scoped-functions@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-block-scoping": ["@babel/plugin-transform-block-scoping@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-class-properties": ["@babel/plugin-transform-class-properties@7.27.1", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-class-static-block": ["@babel/plugin-transform-class-static-block@7.28.3", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.3", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.12.0" } }, "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-classes": ["@babel/plugin-transform-classes@7.28.4", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-globals": "^7.28.0", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/traverse": "^7.28.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-computed-properties": ["@babel/plugin-transform-computed-properties@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/template": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-destructuring": ["@babel/plugin-transform-destructuring@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-dotall-regex": ["@babel/plugin-transform-dotall-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-duplicate-keys": ["@babel/plugin-transform-duplicate-keys@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-duplicate-named-capturing-groups-regex": ["@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-dynamic-import": ["@babel/plugin-transform-dynamic-import@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-exponentiation-operator": ["@babel/plugin-transform-exponentiation-operator@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-export-namespace-from": ["@babel/plugin-transform-export-namespace-from@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-for-of": ["@babel/plugin-transform-for-of@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-function-name": ["@babel/plugin-transform-function-name@7.27.1", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-json-strings": ["@babel/plugin-transform-json-strings@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-literals": ["@babel/plugin-transform-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-logical-assignment-operators": ["@babel/plugin-transform-logical-assignment-operators@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-member-expression-literals": ["@babel/plugin-transform-member-expression-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-modules-amd": ["@babel/plugin-transform-modules-amd@7.27.1", "", { "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.27.1", "", { "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-modules-systemjs": ["@babel/plugin-transform-modules-systemjs@7.28.5", "", { "dependencies": { "@babel/helper-module-transforms": "^7.28.3", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-modules-umd": ["@babel/plugin-transform-modules-umd@7.27.1", "", { "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-named-capturing-groups-regex": ["@babel/plugin-transform-named-capturing-groups-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-new-target": ["@babel/plugin-transform-new-target@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-nullish-coalescing-operator": ["@babel/plugin-transform-nullish-coalescing-operator@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-numeric-separator": ["@babel/plugin-transform-numeric-separator@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-object-rest-spread": ["@babel/plugin-transform-object-rest-spread@7.28.4", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", "@babel/plugin-transform-destructuring": "^7.28.0", "@babel/plugin-transform-parameters": "^7.27.7", "@babel/traverse": "^7.28.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-object-super": ["@babel/plugin-transform-object-super@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-optional-catch-binding": ["@babel/plugin-transform-optional-catch-binding@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-optional-chaining": ["@babel/plugin-transform-optional-chaining@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-parameters": ["@babel/plugin-transform-parameters@7.27.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-private-methods": ["@babel/plugin-transform-private-methods@7.27.1", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-private-property-in-object": ["@babel/plugin-transform-private-property-in-object@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-create-class-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-property-literals": ["@babel/plugin-transform-property-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-regenerator": ["@babel/plugin-transform-regenerator@7.28.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-regexp-modifiers": ["@babel/plugin-transform-regexp-modifiers@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-reserved-words": ["@babel/plugin-transform-reserved-words@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-shorthand-properties": ["@babel/plugin-transform-shorthand-properties@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-spread": ["@babel/plugin-transform-spread@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-sticky-regex": ["@babel/plugin-transform-sticky-regex@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-template-literals": ["@babel/plugin-transform-template-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-typeof-symbol": ["@babel/plugin-transform-typeof-symbol@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-unicode-escapes": ["@babel/plugin-transform-unicode-escapes@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-unicode-property-regex": ["@babel/plugin-transform-unicode-property-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-unicode-regex": ["@babel/plugin-transform-unicode-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-unicode-sets-regex": ["@babel/plugin-transform-unicode-sets-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw=="], + + "@librechat/frontend/@babel/preset-env/babel-plugin-polyfill-corejs2": ["babel-plugin-polyfill-corejs2@0.4.14", "", { "dependencies": { "@babel/compat-data": "^7.27.7", "@babel/helper-define-polyfill-provider": "^0.6.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg=="], + + "@librechat/frontend/@babel/preset-env/babel-plugin-polyfill-corejs3": ["babel-plugin-polyfill-corejs3@0.13.0", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5", "core-js-compat": "^3.43.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A=="], + + "@librechat/frontend/@babel/preset-env/babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.5", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg=="], + + "@librechat/frontend/@babel/preset-env/core-js-compat": ["core-js-compat@3.47.0", "", { "dependencies": { "browserslist": "^4.28.0" } }, "sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ=="], + + "@librechat/frontend/@babel/preset-react/@babel/plugin-transform-react-display-name": ["@babel/plugin-transform-react-display-name@7.28.0", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA=="], + + "@librechat/frontend/@babel/preset-react/@babel/plugin-transform-react-jsx": ["@babel/plugin-transform-react-jsx@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-module-imports": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/types": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw=="], + + "@librechat/frontend/@babel/preset-react/@babel/plugin-transform-react-jsx-development": ["@babel/plugin-transform-react-jsx-development@7.27.1", "", { "dependencies": { "@babel/plugin-transform-react-jsx": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q=="], + + "@librechat/frontend/@babel/preset-react/@babel/plugin-transform-react-pure-annotations": ["@babel/plugin-transform-react-pure-annotations@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA=="], + + "@librechat/frontend/@babel/preset-typescript/@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.27.1", "", { "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw=="], + + "@librechat/frontend/@babel/preset-typescript/@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-create-class-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA=="], "@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=="], @@ -7709,20 +7121,22 @@ "@librechat/frontend/@testing-library/jest-dom/dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="], - "@librechat/frontend/@testing-library/jest-dom/lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], - "@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=="], + "@librechat/frontend/jest-environment-jsdom/@jest/environment": ["@jest/environment@29.7.0", "", { "dependencies": { "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "jest-mock": "^29.7.0" } }, "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw=="], + + "@librechat/frontend/jest-environment-jsdom/@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=="], + + "@librechat/frontend/jest-environment-jsdom/@types/jsdom": ["@types/jsdom@20.0.1", "", { "dependencies": { "@types/node": "*", "@types/tough-cookie": "*", "parse5": "^7.0.0" } }, "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ=="], + + "@librechat/frontend/jest-environment-jsdom/jsdom": ["jsdom@20.0.3", "", { "dependencies": { "abab": "^2.0.6", "acorn": "^8.8.1", "acorn-globals": "^7.0.0", "cssom": "^0.5.0", "cssstyle": "^2.3.0", "data-urls": "^3.0.2", "decimal.js": "^10.4.2", "domexception": "^4.0.0", "escodegen": "^2.0.0", "form-data": "^4.0.0", "html-encoding-sniffer": "^3.0.0", "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.1", "is-potential-custom-element-name": "^1.0.1", "nwsapi": "^2.2.2", "parse5": "^7.1.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^4.1.2", "w3c-xmlserializer": "^4.0.0", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^2.0.0", "whatwg-mimetype": "^3.0.0", "whatwg-url": "^11.0.0", "ws": "^8.11.0", "xml-name-validator": "^4.0.0" }, "peerDependencies": { "canvas": "^2.5.0" }, "optionalPeers": ["canvas"] }, "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ=="], + "@mcp-ui/client/@modelcontextprotocol/sdk/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], - "@mcp-ui/client/@modelcontextprotocol/sdk/express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="], - "@mcp-ui/client/@modelcontextprotocol/sdk/express-rate-limit": ["express-rate-limit@7.5.0", "", { "peerDependencies": { "express": "^4.11 || 5 || ^5.0.0-beta.1" } }, "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg=="], - "@mcp-ui/client/@modelcontextprotocol/sdk/zod-to-json-schema": ["zod-to-json-schema@3.24.3", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-HIAfWdYIt1sssHfYZFCXp4rU1w2r8hVVXYIlmoa0r0gABLs5di3RCqPU5DDROogVz1pAdYBaz7HK5n9pSUNs3A=="], - "@modelcontextprotocol/sdk/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], "@node-saml/passport-saml/@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=="], @@ -7733,32 +7147,18 @@ "@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-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/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=="], "@radix-ui/react-checkbox/@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=="], "@radix-ui/react-checkbox/@radix-ui/react-use-controllable-state/@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ=="], - "@radix-ui/react-dialog/@radix-ui/react-id/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ=="], - - "@radix-ui/react-dialog/@radix-ui/react-presence/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ=="], - - "@radix-ui/react-dialog/@radix-ui/react-use-controllable-state/@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg=="], - - "@radix-ui/react-dismissable-layer/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-compose-refs": "1.0.0" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw=="], - "@radix-ui/react-dropdown-menu/@radix-ui/react-id/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w=="], "@radix-ui/react-dropdown-menu/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw=="], "@radix-ui/react-dropdown-menu/@radix-ui/react-use-controllable-state/@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw=="], - "@radix-ui/react-focus-scope/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-compose-refs": "1.0.0" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw=="], - "@radix-ui/react-hover-card/@radix-ui/react-dismissable-layer/@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ=="], "@radix-ui/react-hover-card/@radix-ui/react-dismissable-layer/@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.0.3", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-use-callback-ref": "1.0.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg=="], @@ -7799,12 +7199,8 @@ "@radix-ui/react-popper/@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=="], - "@radix-ui/react-portal/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-compose-refs": "1.0.0" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw=="], - "@radix-ui/react-progress/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ=="], - "@radix-ui/react-select/@radix-ui/react-dismissable-layer/@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.1", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g=="], - "@radix-ui/react-select/@radix-ui/react-popper/@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w=="], "@radix-ui/react-select/@radix-ui/react-popper/@radix-ui/react-use-rect": ["@radix-ui/react-use-rect@1.1.1", "", { "dependencies": { "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w=="], @@ -7841,51 +7237,25 @@ "@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=="], + "@smithy/signature-v4/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@types/connect/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], + "@smithy/util-stream/@smithy/node-http-handler/@smithy/abort-controller": ["@smithy/abort-controller@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-fiUIYgIgRjMWznk6iLJz35K2YxSLHzLBA/RC6lBrKfQ8fHbPfvk7Pk9UvpKoHgJjI18MnbPuEju53zcVy6KF1g=="], - "@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=="], + "@smithy/util-stream/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "@smithy/util-uri-escape": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-wU87iWZoCbcqrwszsOewEIuq+SU2mSoBE2CcsLwE0I19m0B2gOJr1MVjxWcDQYOzHbR1xCk7AcOBbGFUYOKvdg=="], "@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/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=="], - "@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/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=="], "ajv-formats/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], - "babel-plugin-transform-import-meta/@babel/template/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], + "browserify-sign/readable-stream/core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="], - "babel-plugin-transform-import-meta/@babel/template/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], + "browserify-sign/readable-stream/isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="], - "babel-plugin-transform-import-meta/@babel/template/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - - "body-parser/raw-body/http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], - - "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/electron-to-chromium": ["electron-to-chromium@1.5.254", "", {}, "sha512-DcUsWpVhv9svsKRxnSCZ86SjD+sp32SGidNB37KpqXJncp1mfUgKbHvBomE89WJDbfVKw1mdv5+ikrvd43r+Bg=="], + "browserify-sign/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], "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=="], @@ -7901,38 +7271,14 @@ "compression/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], - "core-js-compat/browserslist/baseline-browser-mapping": ["baseline-browser-mapping@2.8.28", "", { "bin": "dist/cli.js" }, "sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ=="], - - "core-js-compat/browserslist/electron-to-chromium": ["electron-to-chromium@1.5.254", "", {}, "sha512-DcUsWpVhv9svsKRxnSCZ86SjD+sp32SGidNB37KpqXJncp1mfUgKbHvBomE89WJDbfVKw1mdv5+ikrvd43r+Bg=="], - - "core-js-compat/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=="], - - "cytoscape-fcose/cose-base/layout-base": ["layout-base@2.0.1", "", {}, "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg=="], - - "d3-sankey/d3-array/internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="], - - "d3-sankey/d3-shape/d3-path": ["d3-path@1.0.9", "", {}, "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="], - "data-urls/whatwg-url/tr46": ["tr46@5.1.1", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw=="], "data-urls/whatwg-url/webidl-conversions": ["webidl-conversions@7.0.0", "", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="], - "eslint/@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], - - "expect/jest-message-util/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], - "expect/jest-message-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-message-util/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], - "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=="], - "express-session/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], "express-static-gzip/serve-static/send": ["send@0.19.0", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "2.4.1", "range-parser": "~1.2.1", "statuses": "2.0.1" } }, "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw=="], @@ -7941,16 +7287,16 @@ "form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], - "gaxios/https-proxy-agent/agent-base": ["agent-base@7.1.0", "", { "dependencies": { "debug": "^4.3.4" } }, "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg=="], + "gaxios/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], - "gaxios/https-proxy-agent/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "gcp-metadata/gaxios/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], - - "glob/minimatch/brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="], + "google-auth-library/gaxios/https-proxy-agent": ["https-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.0.2", "debug": "4" } }, "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA=="], "google-auth-library/gcp-metadata/google-logging-utils": ["google-logging-utils@0.0.2", "", {}, "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ=="], + "googleapis-common/gaxios/https-proxy-agent": ["https-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.0.2", "debug": "4" } }, "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA=="], + + "gtoken/gaxios/https-proxy-agent": ["https-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.0.2", "debug": "4" } }, "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA=="], + "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=="], @@ -7971,26 +7317,6 @@ "hastscript/@types/hast/@types/unist": ["@types/unist@2.0.10", "", {}, "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA=="], - "istanbul-lib-instrument/@babel/core/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], - - "istanbul-lib-instrument/@babel/core/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], - - "istanbul-lib-instrument/@babel/core/@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="], - - "istanbul-lib-instrument/@babel/core/@babel/helpers": ["@babel/helpers@7.28.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" } }, "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w=="], - - "istanbul-lib-instrument/@babel/core/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], - - "istanbul-lib-instrument/@babel/core/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], - - "istanbul-lib-instrument/@babel/core/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - - "istanbul-lib-instrument/@babel/core/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "istanbul-lib-instrument/@babel/core/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - - "istanbul-lib-instrument/@babel/parser/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - "istanbul-lib-report/make-dir/semver": ["semver@7.7.3", "", { "bin": "bin/semver.js" }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], "istanbul-lib-report/supports-color/has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], @@ -8007,32 +7333,10 @@ "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/@babel/core/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], - - "jest-config/@babel/core/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], - - "jest-config/@babel/core/@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="], - - "jest-config/@babel/core/@babel/helpers": ["@babel/helpers@7.28.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" } }, "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w=="], - - "jest-config/@babel/core/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], - - "jest-config/@babel/core/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], - - "jest-config/@babel/core/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], - - "jest-config/@babel/core/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - - "jest-config/@babel/core/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - "jest-config/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - "jest-config/glob/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], - "jest-config/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-config/pretty-format/react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], @@ -8041,60 +7345,30 @@ "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-environment-node/@jest/fake-timers/@sinonjs/fake-timers": ["@sinonjs/fake-timers@13.0.5", "", { "dependencies": { "@sinonjs/commons": "^3.0.1" } }, "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw=="], "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-mock/@jest/types/@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="], - "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/@jest/fake-timers/@sinonjs/fake-timers": ["@sinonjs/fake-timers@13.0.5", "", { "dependencies": { "@sinonjs/commons": "^3.0.1" } }, "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw=="], "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=="], - "jest-runtime/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-snapshot/@babel/core/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], - - "jest-snapshot/@babel/core/@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="], - - "jest-snapshot/@babel/core/@babel/helpers": ["@babel/helpers@7.28.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" } }, "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w=="], - - "jest-snapshot/@babel/core/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], - - "jest-snapshot/@babel/core/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], - - "jest-snapshot/@babel/core/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], - - "jest-snapshot/@babel/core/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "jest-snapshot/@babel/core/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - - "jest-snapshot/@babel/generator/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], - - "jest-snapshot/@babel/generator/@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-snapshot/expect/jest-mock": ["jest-mock@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "jest-util": "30.2.0" } }, "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw=="], "jest-snapshot/pretty-format/react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], "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-util/@jest/types/@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="], "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=="], @@ -8103,6 +7377,138 @@ "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/@babel/preset-env/@babel/plugin-bugfix-firefox-class-in-computed-class-key": ["@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-bugfix-safari-class-field-initializer-scope": ["@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": ["@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": ["@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/plugin-transform-optional-chaining": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.13.0" } }, "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": ["@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-syntax-import-assertions": ["@babel/plugin-syntax-import-assertions@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-arrow-functions": ["@babel/plugin-transform-arrow-functions@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-async-generator-functions": ["@babel/plugin-transform-async-generator-functions@7.28.0", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-remap-async-to-generator": "^7.27.1", "@babel/traverse": "^7.28.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-async-to-generator": ["@babel/plugin-transform-async-to-generator@7.27.1", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-remap-async-to-generator": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-block-scoped-functions": ["@babel/plugin-transform-block-scoped-functions@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-block-scoping": ["@babel/plugin-transform-block-scoping@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-class-properties": ["@babel/plugin-transform-class-properties@7.27.1", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-class-static-block": ["@babel/plugin-transform-class-static-block@7.28.3", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.3", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.12.0" } }, "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-classes": ["@babel/plugin-transform-classes@7.28.4", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-globals": "^7.28.0", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/traverse": "^7.28.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-computed-properties": ["@babel/plugin-transform-computed-properties@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/template": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-destructuring": ["@babel/plugin-transform-destructuring@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-dotall-regex": ["@babel/plugin-transform-dotall-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-duplicate-keys": ["@babel/plugin-transform-duplicate-keys@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-duplicate-named-capturing-groups-regex": ["@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-dynamic-import": ["@babel/plugin-transform-dynamic-import@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-exponentiation-operator": ["@babel/plugin-transform-exponentiation-operator@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-export-namespace-from": ["@babel/plugin-transform-export-namespace-from@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-for-of": ["@babel/plugin-transform-for-of@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-function-name": ["@babel/plugin-transform-function-name@7.27.1", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-json-strings": ["@babel/plugin-transform-json-strings@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-literals": ["@babel/plugin-transform-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-logical-assignment-operators": ["@babel/plugin-transform-logical-assignment-operators@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-member-expression-literals": ["@babel/plugin-transform-member-expression-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-modules-amd": ["@babel/plugin-transform-modules-amd@7.27.1", "", { "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.27.1", "", { "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-modules-systemjs": ["@babel/plugin-transform-modules-systemjs@7.28.5", "", { "dependencies": { "@babel/helper-module-transforms": "^7.28.3", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-modules-umd": ["@babel/plugin-transform-modules-umd@7.27.1", "", { "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-named-capturing-groups-regex": ["@babel/plugin-transform-named-capturing-groups-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-new-target": ["@babel/plugin-transform-new-target@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-nullish-coalescing-operator": ["@babel/plugin-transform-nullish-coalescing-operator@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-numeric-separator": ["@babel/plugin-transform-numeric-separator@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-object-rest-spread": ["@babel/plugin-transform-object-rest-spread@7.28.4", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", "@babel/plugin-transform-destructuring": "^7.28.0", "@babel/plugin-transform-parameters": "^7.27.7", "@babel/traverse": "^7.28.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-object-super": ["@babel/plugin-transform-object-super@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-optional-catch-binding": ["@babel/plugin-transform-optional-catch-binding@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-optional-chaining": ["@babel/plugin-transform-optional-chaining@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-parameters": ["@babel/plugin-transform-parameters@7.27.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-private-methods": ["@babel/plugin-transform-private-methods@7.27.1", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-private-property-in-object": ["@babel/plugin-transform-private-property-in-object@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-create-class-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-property-literals": ["@babel/plugin-transform-property-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-regenerator": ["@babel/plugin-transform-regenerator@7.28.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-regexp-modifiers": ["@babel/plugin-transform-regexp-modifiers@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-reserved-words": ["@babel/plugin-transform-reserved-words@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-shorthand-properties": ["@babel/plugin-transform-shorthand-properties@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-spread": ["@babel/plugin-transform-spread@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-sticky-regex": ["@babel/plugin-transform-sticky-regex@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-template-literals": ["@babel/plugin-transform-template-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-typeof-symbol": ["@babel/plugin-transform-typeof-symbol@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-unicode-escapes": ["@babel/plugin-transform-unicode-escapes@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-unicode-property-regex": ["@babel/plugin-transform-unicode-property-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-unicode-regex": ["@babel/plugin-transform-unicode-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-unicode-sets-regex": ["@babel/plugin-transform-unicode-sets-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw=="], + + "librechat-data-provider/@babel/preset-env/babel-plugin-polyfill-corejs2": ["babel-plugin-polyfill-corejs2@0.4.14", "", { "dependencies": { "@babel/compat-data": "^7.27.7", "@babel/helper-define-polyfill-provider": "^0.6.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg=="], + + "librechat-data-provider/@babel/preset-env/babel-plugin-polyfill-corejs3": ["babel-plugin-polyfill-corejs3@0.13.0", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5", "core-js-compat": "^3.43.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A=="], + + "librechat-data-provider/@babel/preset-env/babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.5", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg=="], + + "librechat-data-provider/@babel/preset-env/core-js-compat": ["core-js-compat@3.47.0", "", { "dependencies": { "browserslist": "^4.28.0" } }, "sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ=="], + + "librechat-data-provider/@babel/preset-react/@babel/plugin-transform-react-display-name": ["@babel/plugin-transform-react-display-name@7.28.0", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA=="], + + "librechat-data-provider/@babel/preset-react/@babel/plugin-transform-react-jsx": ["@babel/plugin-transform-react-jsx@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-module-imports": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/types": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw=="], + + "librechat-data-provider/@babel/preset-react/@babel/plugin-transform-react-jsx-development": ["@babel/plugin-transform-react-jsx-development@7.27.1", "", { "dependencies": { "@babel/plugin-transform-react-jsx": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q=="], + + "librechat-data-provider/@babel/preset-react/@babel/plugin-transform-react-pure-annotations": ["@babel/plugin-transform-react-pure-annotations@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA=="], + + "librechat-data-provider/@babel/preset-typescript/@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.27.1", "", { "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw=="], + + "librechat-data-provider/@babel/preset-typescript/@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-create-class-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA=="], + "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=="], @@ -8123,39 +7529,17 @@ "pkg-dir/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], - "postcss-colormin/browserslist/baseline-browser-mapping": ["baseline-browser-mapping@2.8.28", "", { "bin": "dist/cli.js" }, "sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ=="], - - "postcss-colormin/browserslist/electron-to-chromium": ["electron-to-chromium@1.5.254", "", {}, "sha512-DcUsWpVhv9svsKRxnSCZ86SjD+sp32SGidNB37KpqXJncp1mfUgKbHvBomE89WJDbfVKw1mdv5+ikrvd43r+Bg=="], - "postcss-colormin/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=="], - "postcss-convert-values/browserslist/baseline-browser-mapping": ["baseline-browser-mapping@2.8.28", "", { "bin": "dist/cli.js" }, "sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ=="], - - "postcss-convert-values/browserslist/electron-to-chromium": ["electron-to-chromium@1.5.254", "", {}, "sha512-DcUsWpVhv9svsKRxnSCZ86SjD+sp32SGidNB37KpqXJncp1mfUgKbHvBomE89WJDbfVKw1mdv5+ikrvd43r+Bg=="], - "postcss-convert-values/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=="], - "postcss-merge-rules/browserslist/baseline-browser-mapping": ["baseline-browser-mapping@2.8.28", "", { "bin": "dist/cli.js" }, "sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ=="], - - "postcss-merge-rules/browserslist/electron-to-chromium": ["electron-to-chromium@1.5.254", "", {}, "sha512-DcUsWpVhv9svsKRxnSCZ86SjD+sp32SGidNB37KpqXJncp1mfUgKbHvBomE89WJDbfVKw1mdv5+ikrvd43r+Bg=="], - "postcss-merge-rules/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=="], - "postcss-minify-params/browserslist/baseline-browser-mapping": ["baseline-browser-mapping@2.8.28", "", { "bin": "dist/cli.js" }, "sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ=="], - - "postcss-minify-params/browserslist/electron-to-chromium": ["electron-to-chromium@1.5.254", "", {}, "sha512-DcUsWpVhv9svsKRxnSCZ86SjD+sp32SGidNB37KpqXJncp1mfUgKbHvBomE89WJDbfVKw1mdv5+ikrvd43r+Bg=="], - "postcss-minify-params/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=="], - "postcss-normalize-unicode/browserslist/baseline-browser-mapping": ["baseline-browser-mapping@2.8.28", "", { "bin": "dist/cli.js" }, "sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ=="], - - "postcss-normalize-unicode/browserslist/electron-to-chromium": ["electron-to-chromium@1.5.254", "", {}, "sha512-DcUsWpVhv9svsKRxnSCZ86SjD+sp32SGidNB37KpqXJncp1mfUgKbHvBomE89WJDbfVKw1mdv5+ikrvd43r+Bg=="], - "postcss-normalize-unicode/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=="], - "postcss-reduce-initial/browserslist/baseline-browser-mapping": ["baseline-browser-mapping@2.8.28", "", { "bin": "dist/cli.js" }, "sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ=="], - - "postcss-reduce-initial/browserslist/electron-to-chromium": ["electron-to-chromium@1.5.254", "", {}, "sha512-DcUsWpVhv9svsKRxnSCZ86SjD+sp32SGidNB37KpqXJncp1mfUgKbHvBomE89WJDbfVKw1mdv5+ikrvd43r+Bg=="], + "postcss-preset-env/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=="], "postcss-reduce-initial/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=="], @@ -8189,39 +7573,149 @@ "rollup-plugin-typescript2/@rollup/pluginutils/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - "stylehacks/browserslist/baseline-browser-mapping": ["baseline-browser-mapping@2.8.28", "", { "bin": "dist/cli.js" }, "sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ=="], - - "stylehacks/browserslist/electron-to-chromium": ["electron-to-chromium@1.5.254", "", {}, "sha512-DcUsWpVhv9svsKRxnSCZ86SjD+sp32SGidNB37KpqXJncp1mfUgKbHvBomE89WJDbfVKw1mdv5+ikrvd43r+Bg=="], + "schema-utils/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], "stylehacks/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=="], "sucrase/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - "sucrase/glob/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], - "sucrase/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=="], "svgo/css-select/domhandler": ["domhandler@4.3.1", "", { "dependencies": { "domelementtype": "^2.2.0" } }, "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ=="], "svgo/css-select/domutils": ["domutils@2.8.0", "", { "dependencies": { "dom-serializer": "^1.0.1", "domelementtype": "^2.2.0", "domhandler": "^4.2.0" } }, "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A=="], - "tailwindcss/postcss/nanoid": ["nanoid@3.3.8", "", { "bin": "bin/nanoid.cjs" }, "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w=="], - "tailwindcss/postcss-load-config/lilconfig": ["lilconfig@3.0.0", "", {}, "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g=="], + "terser-webpack-plugin/jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], + "unist-util-remove-position/unist-util-visit/unist-util-is": ["unist-util-is@5.2.1", "", { "dependencies": { "@types/unist": "^2.0.0" } }, "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw=="], "unist-util-remove-position/unist-util-visit/unist-util-visit-parents": ["unist-util-visit-parents@5.1.3", "", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0" } }, "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg=="], - "vasync/verror/core-util-is": ["core-util-is@1.0.2", "", {}, "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="], - "vfile-location/vfile/unist-util-stringify-position": ["unist-util-stringify-position@3.0.3", "", { "dependencies": { "@types/unist": "^2.0.0" } }, "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg=="], "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=="], + "webpack/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=="], + + "webpack/eslint-scope/estraverse": ["estraverse@4.3.0", "", {}, "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="], + + "webpack/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + "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=="], - "winston-daily-rotate-file/winston-transport/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=="], + "workbox-build/@babel/preset-env/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": ["@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.23.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ=="], + + "workbox-build/@babel/preset-env/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": ["@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.23.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", "@babel/plugin-transform-optional-chaining": "^7.23.3" }, "peerDependencies": { "@babel/core": "^7.13.0" } }, "sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ=="], + + "workbox-build/@babel/preset-env/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": ["@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.23.7", "", { "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-plugin-utils": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-LlRT7HgaifEpQA1ZgLVOIJZZFVPWN5iReq/7/JixwBtwcoeVGDBD53ZV28rrsLYOZs1Y/EHhA8N/Z6aazHR8cw=="], + + "workbox-build/@babel/preset-env/@babel/plugin-syntax-import-assertions": ["@babel/plugin-syntax-import-assertions@7.23.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-arrow-functions": ["@babel/plugin-transform-arrow-functions@7.23.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-async-generator-functions": ["@babel/plugin-transform-async-generator-functions@7.23.9", "", { "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-remap-async-to-generator": "^7.22.20", "@babel/plugin-syntax-async-generators": "^7.8.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-8Q3veQEDGe14dTYuwagbRtwxQDnytyg1JFu4/HwEMETeofocrB0U0ejBJIXoeG/t2oXZ8kzCyI0ZZfbT80VFNQ=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-async-to-generator": ["@babel/plugin-transform-async-to-generator@7.23.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-remap-async-to-generator": "^7.22.20" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-block-scoped-functions": ["@babel/plugin-transform-block-scoped-functions@7.23.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-block-scoping": ["@babel/plugin-transform-block-scoping@7.23.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-class-properties": ["@babel/plugin-transform-class-properties@7.23.3", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-class-static-block": ["@babel/plugin-transform-class-static-block@7.23.4", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-class-static-block": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.12.0" } }, "sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-classes": ["@babel/plugin-transform-classes@7.23.8", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-replace-supers": "^7.22.20", "@babel/helper-split-export-declaration": "^7.22.6", "globals": "^11.1.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-yAYslGsY1bX6Knmg46RjiCiNSwJKv2IUC8qOdYKqMMr0491SXFhcHqOdRDeCRohOOIzwN/90C6mQ9qAKgrP7dg=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-computed-properties": ["@babel/plugin-transform-computed-properties@7.23.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/template": "^7.22.15" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-destructuring": ["@babel/plugin-transform-destructuring@7.23.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-dotall-regex": ["@babel/plugin-transform-dotall-regex@7.23.3", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-duplicate-keys": ["@babel/plugin-transform-duplicate-keys@7.23.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-dynamic-import": ["@babel/plugin-transform-dynamic-import@7.23.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-dynamic-import": "^7.8.3" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-exponentiation-operator": ["@babel/plugin-transform-exponentiation-operator@7.23.3", "", { "dependencies": { "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-export-namespace-from": ["@babel/plugin-transform-export-namespace-from@7.23.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-for-of": ["@babel/plugin-transform-for-of@7.23.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-function-name": ["@babel/plugin-transform-function-name@7.23.3", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.22.15", "@babel/helper-function-name": "^7.23.0", "@babel/helper-plugin-utils": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-json-strings": ["@babel/plugin-transform-json-strings@7.23.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-json-strings": "^7.8.3" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-literals": ["@babel/plugin-transform-literals@7.23.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-logical-assignment-operators": ["@babel/plugin-transform-logical-assignment-operators@7.23.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-member-expression-literals": ["@babel/plugin-transform-member-expression-literals@7.23.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-modules-amd": ["@babel/plugin-transform-modules-amd@7.23.3", "", { "dependencies": { "@babel/helper-module-transforms": "^7.23.3", "@babel/helper-plugin-utils": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.23.3", "", { "dependencies": { "@babel/helper-module-transforms": "^7.23.3", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-simple-access": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-modules-systemjs": ["@babel/plugin-transform-modules-systemjs@7.23.9", "", { "dependencies": { "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-module-transforms": "^7.23.3", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-validator-identifier": "^7.22.20" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-KDlPRM6sLo4o1FkiSlXoAa8edLXFsKKIda779fbLrvmeuc3itnjCtaO6RrtoaANsIJANj+Vk1zqbZIMhkCAHVw=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-modules-umd": ["@babel/plugin-transform-modules-umd@7.23.3", "", { "dependencies": { "@babel/helper-module-transforms": "^7.23.3", "@babel/helper-plugin-utils": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-named-capturing-groups-regex": ["@babel/plugin-transform-named-capturing-groups-regex@7.22.5", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-new-target": ["@babel/plugin-transform-new-target@7.23.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-nullish-coalescing-operator": ["@babel/plugin-transform-nullish-coalescing-operator@7.23.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-numeric-separator": ["@babel/plugin-transform-numeric-separator@7.23.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-numeric-separator": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-object-rest-spread": ["@babel/plugin-transform-object-rest-spread@7.23.4", "", { "dependencies": { "@babel/compat-data": "^7.23.3", "@babel/helper-compilation-targets": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-transform-parameters": "^7.23.3" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-object-super": ["@babel/plugin-transform-object-super@7.23.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-replace-supers": "^7.22.20" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-optional-catch-binding": ["@babel/plugin-transform-optional-catch-binding@7.23.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-optional-chaining": ["@babel/plugin-transform-optional-chaining@7.23.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", "@babel/plugin-syntax-optional-chaining": "^7.8.3" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-parameters": ["@babel/plugin-transform-parameters@7.23.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-private-methods": ["@babel/plugin-transform-private-methods@7.23.3", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-private-property-in-object": ["@babel/plugin-transform-private-property-in-object@7.23.4", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-create-class-features-plugin": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-private-property-in-object": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-property-literals": ["@babel/plugin-transform-property-literals@7.23.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-regenerator": ["@babel/plugin-transform-regenerator@7.23.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "regenerator-transform": "^0.15.2" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-reserved-words": ["@babel/plugin-transform-reserved-words@7.23.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-shorthand-properties": ["@babel/plugin-transform-shorthand-properties@7.23.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-spread": ["@babel/plugin-transform-spread@7.23.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-sticky-regex": ["@babel/plugin-transform-sticky-regex@7.23.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-template-literals": ["@babel/plugin-transform-template-literals@7.23.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-typeof-symbol": ["@babel/plugin-transform-typeof-symbol@7.23.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-unicode-escapes": ["@babel/plugin-transform-unicode-escapes@7.23.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-unicode-property-regex": ["@babel/plugin-transform-unicode-property-regex@7.23.3", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-unicode-regex": ["@babel/plugin-transform-unicode-regex@7.23.3", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-unicode-sets-regex": ["@babel/plugin-transform-unicode-sets-regex@7.23.3", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw=="], + + "workbox-build/@babel/preset-env/babel-plugin-polyfill-corejs2": ["babel-plugin-polyfill-corejs2@0.4.8", "", { "dependencies": { "@babel/compat-data": "^7.22.6", "@babel/helper-define-polyfill-provider": "^0.5.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-OtIuQfafSzpo/LhnJaykc0R/MMnuLSSVjVYy9mHArIZ9qTCSZ6TpWCuEKZYVoN//t8HqBNScHrOtCrIK5IaGLg=="], + + "workbox-build/@babel/preset-env/babel-plugin-polyfill-corejs3": ["babel-plugin-polyfill-corejs3@0.9.0", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.5.0", "core-js-compat": "^3.34.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-7nZPG1uzK2Ymhy/NbaOWTg3uibM2BmGASS4vHS4szRZAIR8R6GwA/xAujpdrXU5iyklrimWnLWU+BLF9suPTqg=="], + + "workbox-build/@babel/preset-env/babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.5.5", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.5.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-OJGYZlhLqBh2DDHeqAxWB1XIvr49CxiJ2gIt61/PU55CQK4Z58OzMqjDe1zwQdQk+rBYsRc+1rJmdajM3gimHg=="], + + "workbox-build/@babel/preset-env/core-js-compat": ["core-js-compat@3.35.1", "", { "dependencies": { "browserslist": "^4.22.2" } }, "sha512-sftHa5qUJY3rs9Zht1WEnmkvXputCyDBczPnr7QDgL8n3qrF3CMXY4VPSYtOLLiOUJcah2WNXREd48iOl6mQIw=="], "workbox-build/@rollup/plugin-replace/@rollup/pluginutils": ["@rollup/pluginutils@3.1.0", "", { "dependencies": { "@types/estree": "0.0.39", "estree-walker": "^1.0.1", "picomatch": "^2.2.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0" } }, "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg=="], @@ -8229,16 +7723,6 @@ "workbox-build/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], - "workbox-build/glob/foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], - - "workbox-build/glob/jackspeak": ["jackspeak@4.2.3", "", { "dependencies": { "@isaacs/cliui": "^9.0.0" } }, "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg=="], - - "workbox-build/glob/minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="], - - "workbox-build/glob/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], - - "workbox-build/glob/path-scurry": ["path-scurry@2.0.1", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA=="], - "workbox-build/source-map/whatwg-url": ["whatwg-url@7.1.0", "", { "dependencies": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", "webidl-conversions": "^4.0.2" } }, "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg=="], "wrap-ansi/string-width/emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="], @@ -8251,11 +7735,7 @@ "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], - "@aws-sdk/client-bedrock-agent-runtime/@aws-sdk/core/@smithy/signature-v4/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - - "@aws-sdk/client-bedrock-agent-runtime/@aws-sdk/core/@smithy/signature-v4/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - - "@aws-sdk/client-bedrock-agent-runtime/@aws-sdk/core/@smithy/signature-v4/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="], + "@aws-sdk/client-bedrock-agent-runtime/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="], "@aws-sdk/client-bedrock-agent-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/util-stream": ["@smithy/util-stream@4.5.5", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.5", "@smithy/node-http-handler": "^4.4.4", "@smithy/types": "^4.8.1", "@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", "tslib": "^2.6.2" } }, "sha512-7M5aVFjT+HPilPOKbOmQfCIPchZe4DSBc1wf1+NvHvSoFTiFtauZzT+onZvCj70xhXd0AEmYnZYmdJIuwxOo4w=="], @@ -8269,26 +7749,28 @@ "@aws-sdk/client-bedrock-agent-runtime/@smithy/core/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@aws-sdk/client-bedrock-agent-runtime/@smithy/core/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - - "@aws-sdk/client-bedrock-agent-runtime/@smithy/eventstream-serde-browser/@smithy/eventstream-serde-universal/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.4", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.8.1", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-aV8blR9RBDKrOlZVgjOdmOibTC2sBXNiT7WA558b4MPdsLTV6sbyc1WIE9QiIuYMJjYtnPLciefoqSW8Gi+MZQ=="], - - "@aws-sdk/client-bedrock-agent-runtime/@smithy/eventstream-serde-node/@smithy/eventstream-serde-universal/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.4", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.8.1", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-aV8blR9RBDKrOlZVgjOdmOibTC2sBXNiT7WA558b4MPdsLTV6sbyc1WIE9QiIuYMJjYtnPLciefoqSW8Gi+MZQ=="], - - "@aws-sdk/client-bedrock-agent-runtime/@smithy/fetch-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=="], - "@aws-sdk/client-bedrock-agent-runtime/@smithy/hash-node/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - "@aws-sdk/client-bedrock-agent-runtime/@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=="], - "@aws-sdk/client-bedrock-agent-runtime/@smithy/smithy-client/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@aws-sdk/client-bedrock-agent-runtime/@smithy/smithy-client/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - "@aws-sdk/client-bedrock-agent-runtime/@smithy/util-base64/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], "@aws-sdk/client-bedrock-agent-runtime/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + "@aws-sdk/client-bedrock-runtime/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="], + + "@aws-sdk/client-bedrock-runtime/@aws-sdk/core/@smithy/signature-v4/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + + "@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.948.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.947.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.948.0", "@aws-sdk/middleware-user-agent": "3.947.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.947.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.7", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.14", "@smithy/middleware-retry": "^4.4.14", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@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.13", "@smithy/util-defaults-mode-node": "^4.2.16", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-iWjchXy8bIAVBUsKnbfKYXRwhLgRg3EqCQ5FTr3JbR+QR75rZm4ZOYXlvHGztVTmtAZ+PQVA1Y4zO7v7N87C0A=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/hash-node/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/util-base64/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/util-stream/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + + "@aws-sdk/client-bedrock-runtime/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + "@aws-sdk/client-cognito-identity/@aws-sdk/core/@smithy/signature-v4/@smithy/is-array-buffer": ["@smithy/is-array-buffer@3.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ=="], "@aws-sdk/client-cognito-identity/@aws-sdk/core/@smithy/signature-v4/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@3.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ=="], @@ -8309,11 +7791,7 @@ "@aws-sdk/client-cognito-identity/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@3.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ=="], - "@aws-sdk/client-kendra/@aws-sdk/core/@smithy/signature-v4/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - - "@aws-sdk/client-kendra/@aws-sdk/core/@smithy/signature-v4/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - - "@aws-sdk/client-kendra/@aws-sdk/core/@smithy/signature-v4/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="], + "@aws-sdk/client-kendra/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="], "@aws-sdk/client-kendra/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/util-stream": ["@smithy/util-stream@4.5.5", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.5", "@smithy/node-http-handler": "^4.4.4", "@smithy/types": "^4.8.1", "@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", "tslib": "^2.6.2" } }, "sha512-7M5aVFjT+HPilPOKbOmQfCIPchZe4DSBc1wf1+NvHvSoFTiFtauZzT+onZvCj70xhXd0AEmYnZYmdJIuwxOo4w=="], @@ -8327,36 +7805,14 @@ "@aws-sdk/client-kendra/@smithy/core/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@aws-sdk/client-kendra/@smithy/core/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - - "@aws-sdk/client-kendra/@smithy/fetch-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=="], - "@aws-sdk/client-kendra/@smithy/hash-node/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - "@aws-sdk/client-kendra/@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=="], - "@aws-sdk/client-kendra/@smithy/smithy-client/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@aws-sdk/client-kendra/@smithy/smithy-client/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - "@aws-sdk/client-kendra/@smithy/util-base64/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], "@aws-sdk/client-kendra/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - "@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.17", "", { "dependencies": { "@aws-sdk/core": "^3.973.18", "@aws-sdk/nested-clients": "^3.996.7", "@aws-sdk/types": "^3.973.5", "@smithy/property-provider": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-gf2E5b7LpKb+JX2oQsRIDxdRZjBFZt2olCGlWCdb3vBERbXIPgm2t1R5mEnwd4j0UEO/Tbg5zN2KJbHXttJqwA=="], - - "@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.7", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.18", "@aws-sdk/middleware-host-header": "^3.972.7", "@aws-sdk/middleware-logger": "^3.972.7", "@aws-sdk/middleware-recursion-detection": "^3.972.7", "@aws-sdk/middleware-user-agent": "^3.972.19", "@aws-sdk/region-config-resolver": "^3.972.7", "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-endpoints": "^3.996.4", "@aws-sdk/util-user-agent-browser": "^3.972.7", "@aws-sdk/util-user-agent-node": "^3.973.4", "@smithy/config-resolver": "^4.4.10", "@smithy/core": "^3.23.8", "@smithy/fetch-http-handler": "^5.3.13", "@smithy/hash-node": "^4.2.11", "@smithy/invalid-dependency": "^4.2.11", "@smithy/middleware-content-length": "^4.2.11", "@smithy/middleware-endpoint": "^4.4.22", "@smithy/middleware-retry": "^4.4.39", "@smithy/middleware-serde": "^4.2.12", "@smithy/middleware-stack": "^4.2.11", "@smithy/node-config-provider": "^4.3.11", "@smithy/node-http-handler": "^4.4.14", "@smithy/protocol-http": "^5.3.11", "@smithy/smithy-client": "^4.12.2", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@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.38", "@smithy/util-defaults-mode-node": "^4.2.41", "@smithy/util-endpoints": "^3.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-retry": "^4.2.11", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-MlGWA8uPaOs5AiTZ5JLM4uuWDm9EEAnm9cqwvqQIc6kEgel/8s1BaOWm9QgUcfc9K8qd7KkC3n43yDbeXOA2tg=="], - - "@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.7", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.18", "@aws-sdk/middleware-host-header": "^3.972.7", "@aws-sdk/middleware-logger": "^3.972.7", "@aws-sdk/middleware-recursion-detection": "^3.972.7", "@aws-sdk/middleware-user-agent": "^3.972.19", "@aws-sdk/region-config-resolver": "^3.972.7", "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-endpoints": "^3.996.4", "@aws-sdk/util-user-agent-browser": "^3.972.7", "@aws-sdk/util-user-agent-node": "^3.973.4", "@smithy/config-resolver": "^4.4.10", "@smithy/core": "^3.23.8", "@smithy/fetch-http-handler": "^5.3.13", "@smithy/hash-node": "^4.2.11", "@smithy/invalid-dependency": "^4.2.11", "@smithy/middleware-content-length": "^4.2.11", "@smithy/middleware-endpoint": "^4.4.22", "@smithy/middleware-retry": "^4.4.39", "@smithy/middleware-serde": "^4.2.12", "@smithy/middleware-stack": "^4.2.11", "@smithy/node-config-provider": "^4.3.11", "@smithy/node-http-handler": "^4.4.14", "@smithy/protocol-http": "^5.3.11", "@smithy/smithy-client": "^4.12.2", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@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.38", "@smithy/util-defaults-mode-node": "^4.2.41", "@smithy/util-endpoints": "^3.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-retry": "^4.2.11", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-MlGWA8uPaOs5AiTZ5JLM4uuWDm9EEAnm9cqwvqQIc6kEgel/8s1BaOWm9QgUcfc9K8qd7KkC3n43yDbeXOA2tg=="], - - "@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1004.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.18", "@aws-sdk/nested-clients": "^3.996.7", "@aws-sdk/types": "^3.973.5", "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-j9BwZZId9sFp+4GPhf6KrwO8Tben2sXibZA8D1vv2I1zBdvkUHcBA2g4pkqIpTRalMTLC0NPkBPX0gERxfy/iA=="], - - "@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.7", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.18", "@aws-sdk/middleware-host-header": "^3.972.7", "@aws-sdk/middleware-logger": "^3.972.7", "@aws-sdk/middleware-recursion-detection": "^3.972.7", "@aws-sdk/middleware-user-agent": "^3.972.19", "@aws-sdk/region-config-resolver": "^3.972.7", "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-endpoints": "^3.996.4", "@aws-sdk/util-user-agent-browser": "^3.972.7", "@aws-sdk/util-user-agent-node": "^3.973.4", "@smithy/config-resolver": "^4.4.10", "@smithy/core": "^3.23.8", "@smithy/fetch-http-handler": "^5.3.13", "@smithy/hash-node": "^4.2.11", "@smithy/invalid-dependency": "^4.2.11", "@smithy/middleware-content-length": "^4.2.11", "@smithy/middleware-endpoint": "^4.4.22", "@smithy/middleware-retry": "^4.4.39", "@smithy/middleware-serde": "^4.2.12", "@smithy/middleware-stack": "^4.2.11", "@smithy/node-config-provider": "^4.3.11", "@smithy/node-http-handler": "^4.4.14", "@smithy/protocol-http": "^5.3.11", "@smithy/smithy-client": "^4.12.2", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@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.38", "@smithy/util-defaults-mode-node": "^4.2.41", "@smithy/util-endpoints": "^3.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-retry": "^4.2.11", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-MlGWA8uPaOs5AiTZ5JLM4uuWDm9EEAnm9cqwvqQIc6kEgel/8s1BaOWm9QgUcfc9K8qd7KkC3n43yDbeXOA2tg=="], - - "@aws-sdk/client-s3/@smithy/eventstream-serde-browser/@smithy/eventstream-serde-universal/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.11", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.13.0", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-Sf39Ml0iVX+ba/bgMPxaXWAAFmHqYLTmbjAPfLPLY8CrYkRDEqZdUsKC1OwVMCdJXfAt0v4j49GIJ8DoSYAe6w=="], - - "@aws-sdk/client-s3/@smithy/eventstream-serde-node/@smithy/eventstream-serde-universal/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.11", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.13.0", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-Sf39Ml0iVX+ba/bgMPxaXWAAFmHqYLTmbjAPfLPLY8CrYkRDEqZdUsKC1OwVMCdJXfAt0v4j49GIJ8DoSYAe6w=="], - "@aws-sdk/client-sso-oidc/@aws-sdk/core/@smithy/signature-v4/@smithy/is-array-buffer": ["@smithy/is-array-buffer@3.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ=="], "@aws-sdk/client-sso-oidc/@aws-sdk/core/@smithy/signature-v4/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@3.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ=="], @@ -8417,220 +7873,92 @@ "@aws-sdk/credential-provider-http/@smithy/util-stream/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@3.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ=="], - "@aws-sdk/middleware-flexible-checksums/@aws-sdk/core/@smithy/core/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-W9g1bOLui7Xn5FABRVS0o3rXL0gfN37d/8I/W7i0N7oxjx9QecUmXEMSUMADTODwdtka9cN43t5BI2CodLJpng=="], + "@aws-sdk/credential-provider-login/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="], - "@aws-sdk/middleware-flexible-checksums/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.23", "", { "dependencies": { "@smithy/core": "^3.23.9", "@smithy/middleware-serde": "^4.2.12", "@smithy/node-config-provider": "^4.3.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@smithy/util-middleware": "^4.2.11", "tslib": "^2.6.2" } }, "sha512-UEFIejZy54T1EJn2aWJ45voB7RP2T+IRzUqocIdM6GFFa5ClZncakYJfcYnoXt3UsQrZZ9ZRauGm77l9UCbBLw=="], + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/core/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.7", "", { "dependencies": { "@smithy/protocol-http": "^5.3.6", "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-PFMVHVPgtFECeu4iZ+4SX6VOQT0+dIpm4jSPLLL6JLSkp9RohGqKBKD0cbiXdeIFS08Forp0UHI6kc0gIHenSA=="], - "@aws-sdk/middleware-flexible-checksums/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-s+eenEPW6RgliDk2IhjD2hWOxIx1NKrOHxEwNUaUXxYBxIyCcDfNULZ2Mu15E3kwcJWBedTET/kEASPV1A1Akg=="], + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/core/@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg=="], - "@aws-sdk/middleware-flexible-checksums/@smithy/util-stream/@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-7spdikrYiljpket6u0up2Ck2mxhy7dZ0+TDd+S53Dg2DHd6wg+YNJrTCHiLdgZmEXZKI7LJZcwL3721ZRDFiqA=="], + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/core/@smithy/util-stream": ["@smithy/util-stream@4.5.7", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.7", "@smithy/node-http-handler": "^4.4.6", "@smithy/types": "^4.10.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", "tslib": "^2.6.2" } }, "sha512-Uuy4S5Aj4oF6k1z+i2OtIBJUns4mlg29Ph4S+CqjR+f4XXpSFVgTCYLzMszHJTicYDBxKFtwq2/QSEDSS5l02A=="], - "@aws-sdk/middleware-flexible-checksums/@smithy/util-stream/@smithy/node-http-handler/@smithy/abort-controller": ["@smithy/abort-controller@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-Hj4WoYWMJnSpM6/kchsm4bUNTL9XiSyhvoMb2KIq4VJzyDt7JpGHUZHkVNPZVC7YE1tf8tPeVauxpFBKGW4/KQ=="], + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/signature-v4/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - "@aws-sdk/middleware-flexible-checksums/@smithy/util-stream/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-7spdikrYiljpket6u0up2Ck2mxhy7dZ0+TDd+S53Dg2DHd6wg+YNJrTCHiLdgZmEXZKI7LJZcwL3721ZRDFiqA=="], + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.0", "", { "dependencies": { "@smithy/core": "^3.19.0", "@smithy/middleware-serde": "^4.2.7", "@smithy/node-config-provider": "^4.3.6", "@smithy/shared-ini-file-loader": "^4.4.1", "@smithy/types": "^4.10.0", "@smithy/url-parser": "^4.2.6", "@smithy/util-middleware": "^4.2.6", "tslib": "^2.6.2" } }, "sha512-M6qWfUNny6NFNy8amrCGIb9TfOMUkHVtg9bHtEFGRgfH7A7AtPpn/fcrToGPjVDK1ECuMVvqGQOXcZxmu9K+7A=="], - "@aws-sdk/middleware-sdk-s3/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-W9g1bOLui7Xn5FABRVS0o3rXL0gfN37d/8I/W7i0N7oxjx9QecUmXEMSUMADTODwdtka9cN43t5BI2CodLJpng=="], + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-JSbALU3G+JS4kyBZPqnJ3hxIYwOVRV7r9GNQMS6j5VsQDo5+Es5nddLfr9TQlxZLNHPvKSh+XSB0OuWGfSWFcA=="], - "@aws-sdk/middleware-sdk-s3/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.6", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-IB/M5I8G0EeXZTHsAxpx51tMQ5R719F3aq+fjEB6VtNcCHDc0ajFDIGDZw+FW9GxtEkgTduiPpjveJdA/CX7sw=="], + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream": ["@smithy/util-stream@4.5.7", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.7", "@smithy/node-http-handler": "^4.4.6", "@smithy/types": "^4.10.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", "tslib": "^2.6.2" } }, "sha512-Uuy4S5Aj4oF6k1z+i2OtIBJUns4mlg29Ph4S+CqjR+f4XXpSFVgTCYLzMszHJTicYDBxKFtwq2/QSEDSS5l02A=="], - "@aws-sdk/middleware-sdk-s3/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/url-parser": ["@smithy/url-parser@4.2.11", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-oTAGGHo8ZYc5VZsBREzuf5lf2pAurJQsccMusVZ85wDkX66ojEc/XauiGjzCj50A61ObFTPe6d7Pyt6UBYaing=="], + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/util-base64/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@aws-sdk/middleware-sdk-s3/@smithy/util-stream/@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-7spdikrYiljpket6u0up2Ck2mxhy7dZ0+TDd+S53Dg2DHd6wg+YNJrTCHiLdgZmEXZKI7LJZcwL3721ZRDFiqA=="], + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@aws-sdk/middleware-sdk-s3/@smithy/util-stream/@smithy/node-http-handler/@smithy/abort-controller": ["@smithy/abort-controller@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-Hj4WoYWMJnSpM6/kchsm4bUNTL9XiSyhvoMb2KIq4VJzyDt7JpGHUZHkVNPZVC7YE1tf8tPeVauxpFBKGW4/KQ=="], + "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/node-http-handler/@smithy/abort-controller": ["@smithy/abort-controller@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-fiUIYgIgRjMWznk6iLJz35K2YxSLHzLBA/RC6lBrKfQ8fHbPfvk7Pk9UvpKoHgJjI18MnbPuEju53zcVy6KF1g=="], - "@aws-sdk/middleware-sdk-s3/@smithy/util-stream/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-7spdikrYiljpket6u0up2Ck2mxhy7dZ0+TDd+S53Dg2DHd6wg+YNJrTCHiLdgZmEXZKI7LJZcwL3721ZRDFiqA=="], + "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "@smithy/util-uri-escape": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-wU87iWZoCbcqrwszsOewEIuq+SU2mSoBE2CcsLwE0I19m0B2gOJr1MVjxWcDQYOzHbR1xCk7AcOBbGFUYOKvdg=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@aws-sdk/core": ["@aws-sdk/core@3.758.0", "", { "dependencies": { "@aws-sdk/types": "3.734.0", "@smithy/core": "^3.1.5", "@smithy/node-config-provider": "^4.0.1", "@smithy/property-provider": "^4.0.1", "@smithy/protocol-http": "^5.0.1", "@smithy/signature-v4": "^5.0.1", "@smithy/smithy-client": "^4.1.6", "@smithy/types": "^4.1.0", "@smithy/util-middleware": "^4.0.1", "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" } }, "sha512-0RswbdR9jt/XKemaLNuxi2gGr4xGlHyGxkTdhSQzCyUe9A9OPCoLl3rIESRguQEech+oJnbHk/wuiwHqTuP9sg=="], + "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@smithy/node-http-handler": ["@smithy/node-http-handler@4.0.3", "", { "dependencies": { "@smithy/abort-controller": "^4.0.1", "@smithy/protocol-http": "^5.0.1", "@smithy/querystring-builder": "^4.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-dYCLeINNbYdvmMLtW0VdhW1biXt+PPCGazzT5ZjKw46mOtdgToQEwjqZSS9/EN8+tNs/RO0cEWG044+YZs97aA=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.723.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-ZhEfvUwNliOQROcAk34WJWVYTlTa4694kSVhDSjW6lE1bMataPnIN8A0ycukEzBXmd8ZSoBcQLn6lKGl7XIJ5w=="], + "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso/@smithy/node-http-handler": ["@smithy/node-http-handler@4.0.3", "", { "dependencies": { "@smithy/abort-controller": "^4.0.1", "@smithy/protocol-http": "^5.0.1", "@smithy/querystring-builder": "^4.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-dYCLeINNbYdvmMLtW0VdhW1biXt+PPCGazzT5ZjKw46mOtdgToQEwjqZSS9/EN8+tNs/RO0cEWG044+YZs97aA=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@smithy/core": ["@smithy/core@3.1.5", "", { "dependencies": { "@smithy/middleware-serde": "^4.0.2", "@smithy/protocol-http": "^5.0.1", "@smithy/types": "^4.1.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-middleware": "^4.0.1", "@smithy/util-stream": "^4.1.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-HLclGWPkCsekQgsyzxLhCQLa8THWXtB5PxyYN+2O6nkyLt550KQKTlbV2D1/j5dNIQapAZM1+qFnpBFxZQkgCA=="], + "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.758.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.758.0", "@aws-sdk/middleware-host-header": "3.734.0", "@aws-sdk/middleware-logger": "3.734.0", "@aws-sdk/middleware-recursion-detection": "3.734.0", "@aws-sdk/middleware-user-agent": "3.758.0", "@aws-sdk/region-config-resolver": "3.734.0", "@aws-sdk/types": "3.734.0", "@aws-sdk/util-endpoints": "3.743.0", "@aws-sdk/util-user-agent-browser": "3.734.0", "@aws-sdk/util-user-agent-node": "3.758.0", "@smithy/config-resolver": "^4.0.1", "@smithy/core": "^3.1.5", "@smithy/fetch-http-handler": "^5.0.1", "@smithy/hash-node": "^4.0.1", "@smithy/invalid-dependency": "^4.0.1", "@smithy/middleware-content-length": "^4.0.1", "@smithy/middleware-endpoint": "^4.0.6", "@smithy/middleware-retry": "^4.0.7", "@smithy/middleware-serde": "^4.0.2", "@smithy/middleware-stack": "^4.0.1", "@smithy/node-config-provider": "^4.0.1", "@smithy/node-http-handler": "^4.0.3", "@smithy/protocol-http": "^5.0.1", "@smithy/smithy-client": "^4.1.6", "@smithy/types": "^4.1.0", "@smithy/url-parser": "^4.0.1", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.7", "@smithy/util-defaults-mode-node": "^4.0.7", "@smithy/util-endpoints": "^3.0.1", "@smithy/util-middleware": "^4.0.1", "@smithy/util-retry": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-YZ5s7PSvyF3Mt2h1EQulCG93uybprNGbBkPmVuy/HMMfbFTt4iL3SbKjxqvOZelm86epFfj7pvK7FliI2WOEcg=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@smithy/node-config-provider": ["@smithy/node-config-provider@4.0.1", "", { "dependencies": { "@smithy/property-provider": "^4.0.1", "@smithy/shared-ini-file-loader": "^4.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-8mRTjvCtVET8+rxvmzRNRR0hH2JjV0DFOmwXPrISmTIJEfnCBugpYYGAsCj8t41qd+RB5gbheSQ/6aKZCQvFLQ=="], + "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@smithy/node-http-handler": ["@smithy/node-http-handler@4.0.3", "", { "dependencies": { "@smithy/abort-controller": "^4.0.1", "@smithy/protocol-http": "^5.0.1", "@smithy/querystring-builder": "^4.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-dYCLeINNbYdvmMLtW0VdhW1biXt+PPCGazzT5ZjKw46mOtdgToQEwjqZSS9/EN8+tNs/RO0cEWG044+YZs97aA=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@smithy/util-config-provider": ["@smithy/util-config-provider@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w=="], + "@aws-sdk/middleware-websocket/@smithy/fetch-http-handler/@smithy/util-base64/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@smithy/util-middleware": ["@smithy/util-middleware@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-fKGQAPAn8sgV0plRikRVo6g6aR0KyKvgzNrPuM74RZKy/wWVzx3BMk+ZWEueyN3L5v5EDg+P582mKU+sH5OAsg=="], + "@aws-sdk/middleware-websocket/@smithy/fetch-http-handler/@smithy/util-base64/@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@smithy/util-stream": ["@smithy/util-stream@4.1.2", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.0.1", "@smithy/node-http-handler": "^4.0.3", "@smithy/types": "^4.1.0", "@smithy/util-base64": "^4.0.0", "@smithy/util-buffer-from": "^4.0.0", "@smithy/util-hex-encoding": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-44PKEqQ303d3rlQuiDpcCcu//hV8sn+u2JBo84dWCE0rvgeiVl0IlLMagbU++o0jCWhYCsHaAt9wZuZqNe05Hw=="], + "@aws-sdk/middleware-websocket/@smithy/signature-v4/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@smithy/util-utf8": ["@smithy/util-utf8@4.0.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow=="], + "@aws-sdk/nested-clients/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + "@aws-sdk/nested-clients/@aws-sdk/core/@smithy/signature-v4/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], + "@aws-sdk/nested-clients/@smithy/core/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4/@smithy/util-middleware": ["@smithy/util-middleware@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-fKGQAPAn8sgV0plRikRVo6g6aR0KyKvgzNrPuM74RZKy/wWVzx3BMk+ZWEueyN3L5v5EDg+P582mKU+sH5OAsg=="], + "@aws-sdk/nested-clients/@smithy/hash-node/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="], + "@aws-sdk/nested-clients/@smithy/smithy-client/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4/@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], + "@aws-sdk/nested-clients/@smithy/util-base64/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - "@aws-sdk/s3-request-presigner/@smithy/middleware-endpoint/@smithy/core/@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA=="], + "@aws-sdk/nested-clients/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - "@aws-sdk/s3-request-presigner/@smithy/middleware-endpoint/@smithy/core/@smithy/util-stream": ["@smithy/util-stream@4.1.2", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.0.1", "@smithy/node-http-handler": "^4.0.3", "@smithy/types": "^4.1.0", "@smithy/util-base64": "^4.0.0", "@smithy/util-buffer-from": "^4.0.0", "@smithy/util-hex-encoding": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-44PKEqQ303d3rlQuiDpcCcu//hV8sn+u2JBo84dWCE0rvgeiVl0IlLMagbU++o0jCWhYCsHaAt9wZuZqNe05Hw=="], + "@aws-sdk/token-providers/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="], - "@aws-sdk/s3-request-presigner/@smithy/middleware-endpoint/@smithy/core/@smithy/util-utf8": ["@smithy/util-utf8@4.0.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow=="], + "@aws-sdk/token-providers/@aws-sdk/core/@smithy/core/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.7", "", { "dependencies": { "@smithy/protocol-http": "^5.3.6", "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-PFMVHVPgtFECeu4iZ+4SX6VOQT0+dIpm4jSPLLL6JLSkp9RohGqKBKD0cbiXdeIFS08Forp0UHI6kc0gIHenSA=="], - "@aws-sdk/s3-request-presigner/@smithy/middleware-endpoint/@smithy/node-config-provider/@smithy/property-provider": ["@smithy/property-provider@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-o+VRiwC2cgmk/WFV0jaETGOtX16VNPp2bSQEzu0whbReqE1BMqsP2ami2Vi3cbGVdKu1kq9gQkDAGKbt0WOHAQ=="], + "@aws-sdk/token-providers/@aws-sdk/core/@smithy/core/@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg=="], - "@aws-sdk/s3-request-presigner/@smithy/middleware-endpoint/@smithy/url-parser/@smithy/querystring-parser": ["@smithy/querystring-parser@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-Ma2XC7VS9aV77+clSFylVUnPZRindhB7BbmYiNOdr+CHt/kZNJoPP0cd3QxCnCFyPXC4eybmyE98phEHkqZ5Jw=="], + "@aws-sdk/token-providers/@aws-sdk/core/@smithy/core/@smithy/util-stream": ["@smithy/util-stream@4.5.7", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.7", "@smithy/node-http-handler": "^4.4.6", "@smithy/types": "^4.10.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", "tslib": "^2.6.2" } }, "sha512-Uuy4S5Aj4oF6k1z+i2OtIBJUns4mlg29Ph4S+CqjR+f4XXpSFVgTCYLzMszHJTicYDBxKFtwq2/QSEDSS5l02A=="], - "@aws-sdk/s3-request-presigner/@smithy/smithy-client/@smithy/core/@smithy/middleware-serde": ["@smithy/middleware-serde@4.0.2", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-Sdr5lOagCn5tt+zKsaW+U2/iwr6bI9p08wOkCp6/eL6iMbgdtc2R5Ety66rf87PeohR0ExI84Txz9GYv5ou3iQ=="], + "@aws-sdk/token-providers/@aws-sdk/core/@smithy/signature-v4/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - "@aws-sdk/s3-request-presigner/@smithy/smithy-client/@smithy/core/@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA=="], + "@aws-sdk/token-providers/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.0", "", { "dependencies": { "@smithy/core": "^3.19.0", "@smithy/middleware-serde": "^4.2.7", "@smithy/node-config-provider": "^4.3.6", "@smithy/shared-ini-file-loader": "^4.4.1", "@smithy/types": "^4.10.0", "@smithy/url-parser": "^4.2.6", "@smithy/util-middleware": "^4.2.6", "tslib": "^2.6.2" } }, "sha512-M6qWfUNny6NFNy8amrCGIb9TfOMUkHVtg9bHtEFGRgfH7A7AtPpn/fcrToGPjVDK1ECuMVvqGQOXcZxmu9K+7A=="], - "@aws-sdk/s3-request-presigner/@smithy/smithy-client/@smithy/core/@smithy/util-middleware": ["@smithy/util-middleware@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-fKGQAPAn8sgV0plRikRVo6g6aR0KyKvgzNrPuM74RZKy/wWVzx3BMk+ZWEueyN3L5v5EDg+P582mKU+sH5OAsg=="], + "@aws-sdk/token-providers/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-JSbALU3G+JS4kyBZPqnJ3hxIYwOVRV7r9GNQMS6j5VsQDo5+Es5nddLfr9TQlxZLNHPvKSh+XSB0OuWGfSWFcA=="], - "@aws-sdk/s3-request-presigner/@smithy/smithy-client/@smithy/core/@smithy/util-utf8": ["@smithy/util-utf8@4.0.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow=="], + "@aws-sdk/token-providers/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream": ["@smithy/util-stream@4.5.7", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.7", "@smithy/node-http-handler": "^4.4.6", "@smithy/types": "^4.10.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", "tslib": "^2.6.2" } }, "sha512-Uuy4S5Aj4oF6k1z+i2OtIBJUns4mlg29Ph4S+CqjR+f4XXpSFVgTCYLzMszHJTicYDBxKFtwq2/QSEDSS5l02A=="], - "@aws-sdk/s3-request-presigner/@smithy/smithy-client/@smithy/util-stream/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.0.1", "", { "dependencies": { "@smithy/protocol-http": "^5.0.1", "@smithy/querystring-builder": "^4.0.1", "@smithy/types": "^4.1.0", "@smithy/util-base64": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-3aS+fP28urrMW2KTjb6z9iFow6jO8n3MFfineGbndvzGZit3taZhKWtTorf+Gp5RpFDDafeHlhfsGlDCXvUnJA=="], + "@aws-sdk/token-providers/@aws-sdk/core/@smithy/util-base64/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@aws-sdk/s3-request-presigner/@smithy/smithy-client/@smithy/util-stream/@smithy/node-http-handler": ["@smithy/node-http-handler@4.0.3", "", { "dependencies": { "@smithy/abort-controller": "^4.0.1", "@smithy/protocol-http": "^5.0.1", "@smithy/querystring-builder": "^4.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-dYCLeINNbYdvmMLtW0VdhW1biXt+PPCGazzT5ZjKw46mOtdgToQEwjqZSS9/EN8+tNs/RO0cEWG044+YZs97aA=="], - - "@aws-sdk/s3-request-presigner/@smithy/smithy-client/@smithy/util-stream/@smithy/util-base64": ["@smithy/util-base64@4.0.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg=="], - - "@aws-sdk/s3-request-presigner/@smithy/smithy-client/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.0.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug=="], - - "@aws-sdk/s3-request-presigner/@smithy/smithy-client/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - - "@aws-sdk/s3-request-presigner/@smithy/smithy-client/@smithy/util-stream/@smithy/util-utf8": ["@smithy/util-utf8@4.0.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow=="], - - "@babel/core/@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], - - "@babel/helper-create-class-features-plugin/@babel/traverse/@babel/generator/@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=="], - - "@babel/helper-member-expression-to-functions/@babel/traverse/@babel/generator/@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=="], - - "@babel/helper-module-imports/@babel/traverse/@babel/generator/@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=="], - - "@babel/helper-remap-async-to-generator/@babel/traverse/@babel/generator/@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=="], - - "@babel/helper-replace-supers/@babel/traverse/@babel/generator/@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=="], - - "@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/generator/@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=="], - - "@babel/helper-wrap-function/@babel/traverse/@babel/generator/@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=="], - - "@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse/@babel/generator/@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=="], - - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/@babel/generator/@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=="], - - "@babel/plugin-transform-async-generator-functions/@babel/traverse/@babel/generator/@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=="], - - "@babel/plugin-transform-classes/@babel/traverse/@babel/generator/@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=="], - - "@babel/plugin-transform-destructuring/@babel/traverse/@babel/generator/@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=="], - - "@babel/plugin-transform-dotall-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="], - - "@babel/plugin-transform-dotall-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="], - - "@babel/plugin-transform-dotall-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="], - - "@babel/plugin-transform-duplicate-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="], - - "@babel/plugin-transform-duplicate-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="], - - "@babel/plugin-transform-duplicate-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="], - - "@babel/plugin-transform-function-name/@babel/traverse/@babel/generator/@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=="], - - "@babel/plugin-transform-modules-amd/@babel/helper-module-transforms/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], - - "@babel/plugin-transform-modules-amd/@babel/helper-module-transforms/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], - - "@babel/plugin-transform-modules-amd/@babel/helper-module-transforms/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], - - "@babel/plugin-transform-modules-amd/@babel/helper-module-transforms/@babel/traverse/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], - - "@babel/plugin-transform-modules-amd/@babel/helper-module-transforms/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - - "@babel/plugin-transform-modules-amd/@babel/helper-module-transforms/@babel/traverse/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "@babel/plugin-transform-modules-commonjs/@babel/helper-module-transforms/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], - - "@babel/plugin-transform-modules-commonjs/@babel/helper-module-transforms/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], - - "@babel/plugin-transform-modules-commonjs/@babel/helper-module-transforms/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], - - "@babel/plugin-transform-modules-commonjs/@babel/helper-module-transforms/@babel/traverse/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], - - "@babel/plugin-transform-modules-commonjs/@babel/helper-module-transforms/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - - "@babel/plugin-transform-modules-commonjs/@babel/helper-module-transforms/@babel/traverse/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/generator/@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=="], - - "@babel/plugin-transform-modules-umd/@babel/helper-module-transforms/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], - - "@babel/plugin-transform-modules-umd/@babel/helper-module-transforms/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], - - "@babel/plugin-transform-modules-umd/@babel/helper-module-transforms/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], - - "@babel/plugin-transform-modules-umd/@babel/helper-module-transforms/@babel/traverse/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], - - "@babel/plugin-transform-modules-umd/@babel/helper-module-transforms/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - - "@babel/plugin-transform-modules-umd/@babel/helper-module-transforms/@babel/traverse/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - - "@babel/plugin-transform-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="], - - "@babel/plugin-transform-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="], - - "@babel/plugin-transform-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="], - - "@babel/plugin-transform-object-rest-spread/@babel/traverse/@babel/generator/@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=="], - - "@babel/plugin-transform-regexp-modifiers/@babel/helper-create-regexp-features-plugin/regexpu-core/regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="], - - "@babel/plugin-transform-regexp-modifiers/@babel/helper-create-regexp-features-plugin/regexpu-core/regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="], - - "@babel/plugin-transform-regexp-modifiers/@babel/helper-create-regexp-features-plugin/regexpu-core/unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="], - - "@babel/plugin-transform-runtime/babel-plugin-polyfill-corejs2/@babel/helper-define-polyfill-provider/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], + "@aws-sdk/token-providers/@aws-sdk/core/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], "@babel/plugin-transform-runtime/babel-plugin-polyfill-corejs2/@babel/helper-define-polyfill-provider/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=="], - "@babel/plugin-transform-runtime/babel-plugin-polyfill-corejs3/@babel/helper-define-polyfill-provider/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - "@babel/plugin-transform-runtime/babel-plugin-polyfill-corejs3/@babel/helper-define-polyfill-provider/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=="], "@babel/plugin-transform-runtime/babel-plugin-polyfill-corejs3/core-js-compat/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=="], - "@babel/plugin-transform-runtime/babel-plugin-polyfill-regenerator/@babel/helper-define-polyfill-provider/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - "@babel/plugin-transform-runtime/babel-plugin-polyfill-regenerator/@babel/helper-define-polyfill-provider/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=="], - "@babel/plugin-transform-unicode-property-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="], - - "@babel/plugin-transform-unicode-property-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="], - - "@babel/plugin-transform-unicode-property-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="], - - "@babel/plugin-transform-unicode-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="], - - "@babel/plugin-transform-unicode-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="], - - "@babel/plugin-transform-unicode-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="], - - "@babel/plugin-transform-unicode-sets-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="], - - "@babel/plugin-transform-unicode-sets-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="], - - "@babel/plugin-transform-unicode-sets-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="], - - "@google/genai/google-auth-library/gaxios/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], - - "@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=="], + "@jest/fake-timers/@jest/types/@jest/schemas/@sinclair/typebox": ["@sinclair/typebox@0.27.8", "", {}, "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="], + "@jest/reporters/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], "@jest/reporters/glob/path-scurry/lru-cache": ["lru-cache@10.2.0", "", {}, "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q=="], @@ -8639,8 +7967,6 @@ "@langchain/aws/@aws-sdk/client-bedrock-runtime/@aws-sdk/core/@smithy/property-provider": ["@smithy/property-provider@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-g2DHo08IhxV5GdY3Cpt/jr0mkTlAD39EJKN27Jb5N8Fb5qt8KG39wVKTXiTRCmHHou7lbXR8nKVU14/aRUf86w=="], - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.3.4", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.4", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-ScDCpasxH7w1HXHYbtk3jcivjvdA1VICyAdgvVqKhKKwxi+MTwZEqFw0minE+oZ7F07oF25xh4FGJxgqgShz0A=="], - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@aws-sdk/eventstream-handler-node/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.4", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.8.1", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-aV8blR9RBDKrOlZVgjOdmOibTC2sBXNiT7WA558b4MPdsLTV6sbyc1WIE9QiIuYMJjYtnPLciefoqSW8Gi+MZQ=="], "@langchain/aws/@aws-sdk/client-bedrock-runtime/@aws-sdk/middleware-recursion-detection/@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.1.1", "", {}, "sha512-RcLam17LdlbSOSp9VxmUu1eI6Mwxp+OwhD2QhiSNmNCzoDb0EeUXTD2n/WbcnrAYMGlmf05th6QYq23VqvJqpA=="], @@ -8649,10 +7975,6 @@ "@langchain/aws/@aws-sdk/client-bedrock-runtime/@aws-sdk/middleware-websocket/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.4", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.8.1", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-aV8blR9RBDKrOlZVgjOdmOibTC2sBXNiT7WA558b4MPdsLTV6sbyc1WIE9QiIuYMJjYtnPLciefoqSW8Gi+MZQ=="], - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@aws-sdk/middleware-websocket/@smithy/signature-v4": ["@smithy/signature-v4@5.3.4", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.4", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-ScDCpasxH7w1HXHYbtk3jcivjvdA1VICyAdgvVqKhKKwxi+MTwZEqFw0minE+oZ7F07oF25xh4FGJxgqgShz0A=="], - - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@aws-sdk/middleware-websocket/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.927.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.927.0", "@aws-sdk/middleware-host-header": "3.922.0", "@aws-sdk/middleware-logger": "3.922.0", "@aws-sdk/middleware-recursion-detection": "3.922.0", "@aws-sdk/middleware-user-agent": "3.927.0", "@aws-sdk/region-config-resolver": "3.925.0", "@aws-sdk/types": "3.922.0", "@aws-sdk/util-endpoints": "3.922.0", "@aws-sdk/util-user-agent-browser": "3.922.0", "@aws-sdk/util-user-agent-node": "3.927.0", "@smithy/config-resolver": "^4.4.2", "@smithy/core": "^3.17.2", "@smithy/fetch-http-handler": "^5.3.5", "@smithy/hash-node": "^4.2.4", "@smithy/invalid-dependency": "^4.2.4", "@smithy/middleware-content-length": "^4.2.4", "@smithy/middleware-endpoint": "^4.3.6", "@smithy/middleware-retry": "^4.4.6", "@smithy/middleware-serde": "^4.2.4", "@smithy/middleware-stack": "^4.2.4", "@smithy/node-config-provider": "^4.3.4", "@smithy/node-http-handler": "^4.4.4", "@smithy/protocol-http": "^5.3.4", "@smithy/smithy-client": "^4.9.2", "@smithy/types": "^4.8.1", "@smithy/url-parser": "^4.2.4", "@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.5", "@smithy/util-defaults-mode-node": "^4.2.8", "@smithy/util-endpoints": "^3.2.4", "@smithy/util-middleware": "^4.2.4", "@smithy/util-retry": "^4.2.4", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Oy6w7+fzIdr10DhF/HpfVLy6raZFTdiE7pxS1rvpuj2JgxzW2y6urm2sYf3eLOpMiHyuG4xUBwFiJpU9CCEvJA=="], "@langchain/aws/@aws-sdk/client-bedrock-runtime/@aws-sdk/token-providers/@smithy/property-provider": ["@smithy/property-provider@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-g2DHo08IhxV5GdY3Cpt/jr0mkTlAD39EJKN27Jb5N8Fb5qt8KG39wVKTXiTRCmHHou7lbXR8nKVU14/aRUf86w=="], @@ -8661,10 +7983,6 @@ "@langchain/aws/@aws-sdk/client-bedrock-runtime/@smithy/config-resolver/@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q=="], - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@smithy/eventstream-serde-browser/@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.4", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-GNI/IXaY/XBB1SkGBFmbW033uWA0tj085eCxYih0eccUe/PFR7+UBQv9HNDk2fD9TJu7UVsCWsH99TkpEPSOzQ=="], - - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@smithy/eventstream-serde-node/@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.4", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-GNI/IXaY/XBB1SkGBFmbW033uWA0tj085eCxYih0eccUe/PFR7+UBQv9HNDk2fD9TJu7UVsCWsH99TkpEPSOzQ=="], - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-KQ1gFXXC+WsbPFnk7pzskzOpn4s+KheWgO3dzkIEmnb6NskAIGp/dGdbKisTPJdtov28qNDohQrgDUKzXZBLig=="], "@langchain/aws/@aws-sdk/client-bedrock-runtime/@smithy/hash-node/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], @@ -8695,8 +8013,6 @@ "@langchain/aws/@aws-sdk/client-bedrock-runtime/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env/@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=="], @@ -8707,8 +8023,6 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.4", "", { "dependencies": { "@smithy/abort-controller": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/querystring-builder": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-VXHGfzCXLZeKnFp6QXjAdy+U8JF9etfpUXD1FAbzY1GzsFJiDQRQIt2CnMUvUdz3/YaHNqT3RphVWMUpXTIODA=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/protocol-http": ["@smithy/protocol-http@5.3.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-3sfFd2MAzVt0Q/klOmjFi3oIkxczHs0avbwrfn1aBqtc23WqQSmjvk77MBw9WkEQcwbOYIX5/2z4ULj8DuxSsw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/smithy-client": ["@smithy/smithy-client@4.9.2", "", { "dependencies": { "@smithy/core": "^3.17.2", "@smithy/middleware-endpoint": "^4.3.6", "@smithy/middleware-stack": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-stream": "^4.5.5", "tslib": "^2.6.2" } }, "sha512-gZU4uAFcdrSi3io8U99Qs/FvVdRxPvIMToi+MFfsy/DN9UqtknJ1ais+2M9yR8e0ASQpNmFYEKeIKVcMjQg3rg=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/util-stream": ["@smithy/util-stream@4.5.5", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.5", "@smithy/node-http-handler": "^4.4.4", "@smithy/types": "^4.8.1", "@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", "tslib": "^2.6.2" } }, "sha512-7M5aVFjT+HPilPOKbOmQfCIPchZe4DSBc1wf1+NvHvSoFTiFtauZzT+onZvCj70xhXd0AEmYnZYmdJIuwxOo4w=="], @@ -8737,67 +8051,125 @@ "@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/@aws-sdk/client-bedrock-runtime/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "fast-xml-parser": "5.4.1", "tslib": "^2.6.2" } }, "sha512-OnejAIVD+CxzyAUrVic7lG+3QRltyja9LoNqCE/1YVs8ichoTbJlVSaZ9iSMcnHLyzrSNtvaOGjSDRP+d/ouFA=="], + "@librechat/client/@babel/preset-env/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/core/@smithy/property-provider": ["@smithy/property-provider@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-14T1V64o6/ndyrnl1ze1ZhyLzIeYNN47oF/QU6P5m82AEtyOkMJTb0gO1dPubYjyyKuPD6OSVMPDKe+zioOnCg=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-async-generator-functions/@babel/helper-remap-async-to-generator": ["@babel/helper-remap-async-to-generator@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-wrap-function": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.3.11", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-V1L6N9aKOBAN4wEHLyqjLBnAz13mtILU0SeDrjOaIZEeN6IFa6DxwRt1NNpOdmSpQUfkBj0qeD3m6P77uzMhgQ=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-async-to-generator/@babel/helper-remap-async-to-generator": ["@babel/helper-remap-async-to-generator@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-wrap-function": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.16", "", { "dependencies": { "@aws-sdk/core": "^3.973.18", "@aws-sdk/types": "^3.973.5", "@smithy/property-provider": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-HrdtnadvTGAQUr18sPzGlE5El3ICphnH6SU7UQOMOWFgRKbTRNN8msTxM4emzguUso9CzaHU2xy5ctSrmK5YNA=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-class-properties/@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.18", "", { "dependencies": { "@aws-sdk/core": "^3.973.18", "@aws-sdk/types": "^3.973.5", "@smithy/fetch-http-handler": "^5.3.13", "@smithy/node-http-handler": "^4.4.14", "@smithy/property-provider": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/smithy-client": "^4.12.2", "@smithy/types": "^4.13.0", "@smithy/util-stream": "^4.5.17", "tslib": "^2.6.2" } }, "sha512-NyB6smuZAixND5jZumkpkunQ0voc4Mwgkd+SZ6cvAzIB7gK8HV8Zd4rS8Kn5MmoGgusyNfVGG+RLoYc4yFiw+A=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-class-static-block/@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.17", "", { "dependencies": { "@aws-sdk/core": "^3.973.18", "@aws-sdk/credential-provider-env": "^3.972.16", "@aws-sdk/credential-provider-http": "^3.972.18", "@aws-sdk/credential-provider-login": "^3.972.17", "@aws-sdk/credential-provider-process": "^3.972.16", "@aws-sdk/credential-provider-sso": "^3.972.17", "@aws-sdk/credential-provider-web-identity": "^3.972.17", "@aws-sdk/nested-clients": "^3.996.7", "@aws-sdk/types": "^3.973.5", "@smithy/credential-provider-imds": "^4.2.11", "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-dFqh7nfX43B8dO1aPQHOcjC0SnCJ83H3F+1LoCh3X1P7E7N09I+0/taID0asU6GCddfDExqnEvQtDdkuMe5tKQ=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-classes/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.16", "", { "dependencies": { "@aws-sdk/core": "^3.973.18", "@aws-sdk/types": "^3.973.5", "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-n89ibATwnLEg0ZdZmUds5bq8AfBAdoYEDpqP3uzPLaRuGelsKlIvCYSNNvfgGLi8NaHPNNhs1HjJZYbqkW9b+g=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-classes/@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.27.1", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.17", "", { "dependencies": { "@aws-sdk/core": "^3.973.18", "@aws-sdk/nested-clients": "^3.996.7", "@aws-sdk/token-providers": "3.1004.0", "@aws-sdk/types": "^3.973.5", "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-wGtte+48xnhnhHMl/MsxzacBPs5A+7JJedjiP452IkHY7vsbYKcvQBqFye8LwdTJVeHtBHv+JFeTscnwepoWGg=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-dotall-regex/@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.17", "", { "dependencies": { "@aws-sdk/core": "^3.973.18", "@aws-sdk/nested-clients": "^3.996.7", "@aws-sdk/types": "^3.973.5", "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-8aiVJh6fTdl8gcyL+sVNcNwTtWpmoFa1Sh7xlj6Z7L/cZ/tYMEBHq44wTYG8Kt0z/PpGNopD89nbj3FHl9QmTA=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-duplicate-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.11", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.11", "@smithy/property-provider": "^4.2.11", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "tslib": "^2.6.2" } }, "sha512-lBXrS6ku0kTj3xLmsJW0WwqWbGQ6ueooYyp/1L9lkyT0M02C+DWwYwc5aTyXFbRaK38ojALxNixg+LxKSHZc0g=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-for-of/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@smithy/property-provider": ["@smithy/property-provider@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-14T1V64o6/ndyrnl1ze1ZhyLzIeYNN47oF/QU6P5m82AEtyOkMJTb0gO1dPubYjyyKuPD6OSVMPDKe+zioOnCg=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.6", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-IB/M5I8G0EeXZTHsAxpx51tMQ5R719F3aq+fjEB6VtNcCHDc0ajFDIGDZw+FW9GxtEkgTduiPpjveJdA/CX7sw=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-object-super/@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.27.1", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/eventstream-handler-node/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.11", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.13.0", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-Sf39Ml0iVX+ba/bgMPxaXWAAFmHqYLTmbjAPfLPLY8CrYkRDEqZdUsKC1OwVMCdJXfAt0v4j49GIJ8DoSYAe6w=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-optional-chaining/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/middleware-websocket/@aws-sdk/util-format-url": ["@aws-sdk/util-format-url@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/querystring-builder": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-V+PbnWfUl93GuFwsOHsAq7hY/fnm9kElRqR8IexIJr5Rvif9e614X5sGSyz3mVSf1YAZ+VTy63W1/pGdA55zyA=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-private-methods/@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/middleware-websocket/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.11", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.13.0", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-Sf39Ml0iVX+ba/bgMPxaXWAAFmHqYLTmbjAPfLPLY8CrYkRDEqZdUsKC1OwVMCdJXfAt0v4j49GIJ8DoSYAe6w=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-private-property-in-object/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/middleware-websocket/@smithy/signature-v4": ["@smithy/signature-v4@5.3.11", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-V1L6N9aKOBAN4wEHLyqjLBnAz13mtILU0SeDrjOaIZEeN6IFa6DxwRt1NNpOdmSpQUfkBj0qeD3m6P77uzMhgQ=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-private-property-in-object/@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.7", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.18", "@aws-sdk/middleware-host-header": "^3.972.7", "@aws-sdk/middleware-logger": "^3.972.7", "@aws-sdk/middleware-recursion-detection": "^3.972.7", "@aws-sdk/middleware-user-agent": "^3.972.19", "@aws-sdk/region-config-resolver": "^3.972.7", "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-endpoints": "^3.996.4", "@aws-sdk/util-user-agent-browser": "^3.972.7", "@aws-sdk/util-user-agent-node": "^3.973.4", "@smithy/config-resolver": "^4.4.10", "@smithy/core": "^3.23.8", "@smithy/fetch-http-handler": "^5.3.13", "@smithy/hash-node": "^4.2.11", "@smithy/invalid-dependency": "^4.2.11", "@smithy/middleware-content-length": "^4.2.11", "@smithy/middleware-endpoint": "^4.4.22", "@smithy/middleware-retry": "^4.4.39", "@smithy/middleware-serde": "^4.2.12", "@smithy/middleware-stack": "^4.2.11", "@smithy/node-config-provider": "^4.3.11", "@smithy/node-http-handler": "^4.4.14", "@smithy/protocol-http": "^5.3.11", "@smithy/smithy-client": "^4.12.2", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@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.38", "@smithy/util-defaults-mode-node": "^4.2.41", "@smithy/util-endpoints": "^3.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-retry": "^4.2.11", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-MlGWA8uPaOs5AiTZ5JLM4uuWDm9EEAnm9cqwvqQIc6kEgel/8s1BaOWm9QgUcfc9K8qd7KkC3n43yDbeXOA2tg=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-regexp-modifiers/@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/token-providers/@smithy/property-provider": ["@smithy/property-provider@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-14T1V64o6/ndyrnl1ze1ZhyLzIeYNN47oF/QU6P5m82AEtyOkMJTb0gO1dPubYjyyKuPD6OSVMPDKe+zioOnCg=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-spread/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/token-providers/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.6", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-IB/M5I8G0EeXZTHsAxpx51tMQ5R719F3aq+fjEB6VtNcCHDc0ajFDIGDZw+FW9GxtEkgTduiPpjveJdA/CX7sw=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-unicode-property-regex/@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/eventstream-serde-browser/@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.11", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-MJ7HcI+jEkqoWT5vp+uoVaAjBrmxBtKhZTeynDRG/seEjJfqyg3SiqMMqyPnAMzmIfLaeJ/uiuSDP/l9AnMy/Q=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-unicode-regex/@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/eventstream-serde-node/@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.11", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-MJ7HcI+jEkqoWT5vp+uoVaAjBrmxBtKhZTeynDRG/seEjJfqyg3SiqMMqyPnAMzmIfLaeJ/uiuSDP/l9AnMy/Q=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-unicode-sets-regex/@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-7spdikrYiljpket6u0up2Ck2mxhy7dZ0+TDd+S53Dg2DHd6wg+YNJrTCHiLdgZmEXZKI7LJZcwL3721ZRDFiqA=="], + "@librechat/client/@babel/preset-env/babel-plugin-polyfill-corejs2/@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.5", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", "debug": "^4.4.1", "lodash.debounce": "^4.0.8", "resolve": "^1.22.10" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/middleware-endpoint/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.6", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-IB/M5I8G0EeXZTHsAxpx51tMQ5R719F3aq+fjEB6VtNcCHDc0ajFDIGDZw+FW9GxtEkgTduiPpjveJdA/CX7sw=="], + "@librechat/client/@babel/preset-env/babel-plugin-polyfill-corejs3/@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.5", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", "debug": "^4.4.1", "lodash.debounce": "^4.0.8", "resolve": "^1.22.10" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/middleware-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0" } }, "sha512-HkMFJZJUhzU3HvND1+Yw/kYWXp4RPDLBWLcK1n+Vqw8xn4y2YiBhdww8IxhkQjP/QlZun5bwm3vcHc8AqIU3zw=="], + "@librechat/client/@babel/preset-env/babel-plugin-polyfill-regenerator/@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.5", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", "debug": "^4.4.1", "lodash.debounce": "^4.0.8", "resolve": "^1.22.10" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/node-config-provider/@smithy/property-provider": ["@smithy/property-provider@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-14T1V64o6/ndyrnl1ze1ZhyLzIeYNN47oF/QU6P5m82AEtyOkMJTb0gO1dPubYjyyKuPD6OSVMPDKe+zioOnCg=="], + "@librechat/client/@babel/preset-env/core-js-compat/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=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/node-config-provider/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.6", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-IB/M5I8G0EeXZTHsAxpx51tMQ5R719F3aq+fjEB6VtNcCHDc0ajFDIGDZw+FW9GxtEkgTduiPpjveJdA/CX7sw=="], + "@librechat/client/@babel/preset-react/@babel/plugin-transform-react-jsx/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/url-parser/@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-nE3IRNjDltvGcoThD2abTozI1dkSy8aX+a2N1Rs55en5UsdyyIXgGEmevUL3okZFoJC77JgRGe99xYohhsjivQ=="], + "@librechat/client/@babel/preset-react/@babel/plugin-transform-react-pure-annotations/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/util-defaults-mode-browser/@smithy/property-provider": ["@smithy/property-provider@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-14T1V64o6/ndyrnl1ze1ZhyLzIeYNN47oF/QU6P5m82AEtyOkMJTb0gO1dPubYjyyKuPD6OSVMPDKe+zioOnCg=="], + "@librechat/client/@babel/preset-typescript/@babel/plugin-transform-typescript/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/util-defaults-mode-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.11", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.11", "@smithy/property-provider": "^4.2.11", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "tslib": "^2.6.2" } }, "sha512-lBXrS6ku0kTj3xLmsJW0WwqWbGQ6ueooYyp/1L9lkyT0M02C+DWwYwc5aTyXFbRaK38ojALxNixg+LxKSHZc0g=="], + "@librechat/client/@babel/preset-typescript/@babel/plugin-transform-typescript/@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/util-defaults-mode-node/@smithy/property-provider": ["@smithy/property-provider@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-14T1V64o6/ndyrnl1ze1ZhyLzIeYNN47oF/QU6P5m82AEtyOkMJTb0gO1dPubYjyyKuPD6OSVMPDKe+zioOnCg=="], + "@librechat/client/@babel/preset-typescript/@babel/plugin-transform-typescript/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/util-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0" } }, "sha512-HkMFJZJUhzU3HvND1+Yw/kYWXp4RPDLBWLcK1n+Vqw8xn4y2YiBhdww8IxhkQjP/QlZun5bwm3vcHc8AqIU3zw=="], + "@librechat/frontend/@babel/preset-env/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-async-generator-functions/@babel/helper-remap-async-to-generator": ["@babel/helper-remap-async-to-generator@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-wrap-function": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-async-to-generator/@babel/helper-remap-async-to-generator": ["@babel/helper-remap-async-to-generator@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-wrap-function": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-class-properties/@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-class-static-block/@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-classes/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-classes/@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.27.1", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-dotall-regex/@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-duplicate-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-for-of/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-object-super/@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.27.1", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-optional-chaining/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-private-methods/@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-private-property-in-object/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-private-property-in-object/@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-regexp-modifiers/@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-spread/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-unicode-property-regex/@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-unicode-regex/@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-unicode-sets-regex/@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="], + + "@librechat/frontend/@babel/preset-env/babel-plugin-polyfill-corejs2/@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.5", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", "debug": "^4.4.1", "lodash.debounce": "^4.0.8", "resolve": "^1.22.10" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg=="], + + "@librechat/frontend/@babel/preset-env/babel-plugin-polyfill-corejs3/@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.5", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", "debug": "^4.4.1", "lodash.debounce": "^4.0.8", "resolve": "^1.22.10" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg=="], + + "@librechat/frontend/@babel/preset-env/babel-plugin-polyfill-regenerator/@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.5", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", "debug": "^4.4.1", "lodash.debounce": "^4.0.8", "resolve": "^1.22.10" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg=="], + + "@librechat/frontend/@babel/preset-env/core-js-compat/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=="], + + "@librechat/frontend/@babel/preset-react/@babel/plugin-transform-react-jsx/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@librechat/frontend/@babel/preset-react/@babel/plugin-transform-react-pure-annotations/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@librechat/frontend/@babel/preset-typescript/@babel/plugin-transform-typescript/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@librechat/frontend/@babel/preset-typescript/@babel/plugin-transform-typescript/@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ=="], + + "@librechat/frontend/@babel/preset-typescript/@babel/plugin-transform-typescript/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], "@librechat/frontend/@react-spring/web/@react-spring/shared/@react-spring/rafz": ["@react-spring/rafz@9.7.5", "", {}, "sha512-5ZenDQMC48wjUzPAm1EtwQ5Ot3bLIAwwqP2w2owG5KoNdNHpEJV263nGhCeKKmuA3vG2zLLOdu3or6kuDjA6Aw=="], @@ -8805,32 +8177,46 @@ "@librechat/frontend/@testing-library/jest-dom/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "@librechat/frontend/jest-environment-jsdom/@jest/types/@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="], + + "@librechat/frontend/jest-environment-jsdom/jsdom/cssstyle": ["cssstyle@2.3.0", "", { "dependencies": { "cssom": "~0.3.6" } }, "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A=="], + + "@librechat/frontend/jest-environment-jsdom/jsdom/data-urls": ["data-urls@3.0.2", "", { "dependencies": { "abab": "^2.0.6", "whatwg-mimetype": "^3.0.0", "whatwg-url": "^11.0.0" } }, "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ=="], + + "@librechat/frontend/jest-environment-jsdom/jsdom/html-encoding-sniffer": ["html-encoding-sniffer@3.0.0", "", { "dependencies": { "whatwg-encoding": "^2.0.0" } }, "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA=="], + + "@librechat/frontend/jest-environment-jsdom/jsdom/http-proxy-agent": ["http-proxy-agent@5.0.0", "", { "dependencies": { "@tootallnate/once": "2", "agent-base": "6", "debug": "4" } }, "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w=="], + + "@librechat/frontend/jest-environment-jsdom/jsdom/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], + + "@librechat/frontend/jest-environment-jsdom/jsdom/nwsapi": ["nwsapi@2.2.7", "", {}, "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ=="], + + "@librechat/frontend/jest-environment-jsdom/jsdom/tough-cookie": ["tough-cookie@4.1.3", "", { "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", "universalify": "^0.2.0", "url-parse": "^1.5.3" } }, "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw=="], + + "@librechat/frontend/jest-environment-jsdom/jsdom/w3c-xmlserializer": ["w3c-xmlserializer@4.0.0", "", { "dependencies": { "xml-name-validator": "^4.0.0" } }, "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw=="], + + "@librechat/frontend/jest-environment-jsdom/jsdom/webidl-conversions": ["webidl-conversions@7.0.0", "", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="], + + "@librechat/frontend/jest-environment-jsdom/jsdom/whatwg-encoding": ["whatwg-encoding@2.0.0", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg=="], + + "@librechat/frontend/jest-environment-jsdom/jsdom/whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="], + + "@librechat/frontend/jest-environment-jsdom/jsdom/whatwg-url": ["whatwg-url@11.0.0", "", { "dependencies": { "tr46": "^3.0.0", "webidl-conversions": "^7.0.0" } }, "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ=="], + + "@librechat/frontend/jest-environment-jsdom/jsdom/xml-name-validator": ["xml-name-validator@4.0.0", "", {}, "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw=="], + "@mcp-ui/client/@modelcontextprotocol/sdk/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], - "@mcp-ui/client/@modelcontextprotocol/sdk/express/body-parser": ["body-parser@2.2.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="], - - "@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/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=="], - "@radix-ui/react-portal/@radix-ui/react-primitive/@radix-ui/react-slot/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA=="], - "@radix-ui/react-progress/@radix-ui/react-primitive/@radix-ui/react-slot/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw=="], "@radix-ui/react-tabs/@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=="], "@radix-ui/react-tabs/@radix-ui/react-roving-focus/@radix-ui/react-collection/@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=="], - "body-parser/raw-body/http-errors/statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], - "cli-truncate/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], "colorspace/color/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="], @@ -8839,12 +8225,6 @@ "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=="], @@ -8853,29 +8233,87 @@ "express-static-gzip/serve-static/send/mime": ["mime@1.6.0", "", { "bin": "cli.js" }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="], - "gcp-metadata/gaxios/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], + "google-auth-library/gaxios/https-proxy-agent/agent-base": ["agent-base@7.1.0", "", { "dependencies": { "debug": "^4.3.4" } }, "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg=="], - "gcp-metadata/gaxios/https-proxy-agent/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], + "googleapis-common/gaxios/https-proxy-agent/agent-base": ["agent-base@7.1.0", "", { "dependencies": { "debug": "^4.3.4" } }, "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg=="], - "glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - - "istanbul-lib-instrument/@babel/core/@babel/generator/@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=="], + "gtoken/gaxios/https-proxy-agent/agent-base": ["agent-base@7.1.0", "", { "dependencies": { "debug": "^4.3.4" } }, "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg=="], "jest-changed-files/execa/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], - "jest-config/@babel/core/@babel/generator/@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-config/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], "jest-config/glob/path-scurry/lru-cache": ["lru-cache@10.2.0", "", {}, "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q=="], + "jest-mock/@jest/types/@jest/schemas/@sinclair/typebox": ["@sinclair/typebox@0.27.8", "", {}, "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="], + "jest-runtime/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], "jest-runtime/glob/path-scurry/lru-cache": ["lru-cache@10.2.0", "", {}, "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q=="], + "jest-util/@jest/types/@jest/schemas/@sinclair/typebox": ["@sinclair/typebox@0.27.8", "", {}, "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="], + "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=="], + "librechat-data-provider/@babel/preset-env/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-async-generator-functions/@babel/helper-remap-async-to-generator": ["@babel/helper-remap-async-to-generator@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-wrap-function": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-async-to-generator/@babel/helper-remap-async-to-generator": ["@babel/helper-remap-async-to-generator@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-wrap-function": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-class-properties/@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-class-static-block/@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-classes/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-classes/@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.27.1", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-dotall-regex/@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-duplicate-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-for-of/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-object-super/@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.27.1", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-optional-chaining/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-private-methods/@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-private-property-in-object/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-private-property-in-object/@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-regexp-modifiers/@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-spread/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-unicode-property-regex/@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-unicode-regex/@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-unicode-sets-regex/@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="], + + "librechat-data-provider/@babel/preset-env/babel-plugin-polyfill-corejs2/@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.5", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", "debug": "^4.4.1", "lodash.debounce": "^4.0.8", "resolve": "^1.22.10" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg=="], + + "librechat-data-provider/@babel/preset-env/babel-plugin-polyfill-corejs3/@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.5", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", "debug": "^4.4.1", "lodash.debounce": "^4.0.8", "resolve": "^1.22.10" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg=="], + + "librechat-data-provider/@babel/preset-env/babel-plugin-polyfill-regenerator/@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.5", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", "debug": "^4.4.1", "lodash.debounce": "^4.0.8", "resolve": "^1.22.10" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg=="], + + "librechat-data-provider/@babel/preset-env/core-js-compat/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=="], + + "librechat-data-provider/@babel/preset-react/@babel/plugin-transform-react-jsx/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "librechat-data-provider/@babel/preset-react/@babel/plugin-transform-react-pure-annotations/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "librechat-data-provider/@babel/preset-typescript/@babel/plugin-transform-typescript/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "librechat-data-provider/@babel/preset-typescript/@babel/plugin-transform-typescript/@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ=="], + + "librechat-data-provider/@babel/preset-typescript/@babel/plugin-transform-typescript/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], "mongodb-connection-string-url/whatwg-url/tr46/punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], @@ -8893,39 +8331,69 @@ "svgo/css-select/domutils/dom-serializer": ["dom-serializer@1.4.1", "", { "dependencies": { "domelementtype": "^2.0.1", "domhandler": "^4.2.0", "entities": "^2.0.0" } }, "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag=="], + "terser-webpack-plugin/jest-worker/supports-color/has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "workbox-build/@babel/preset-env/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.22.5", "", { "dependencies": { "@babel/types": "^7.22.5" } }, "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-async-generator-functions/@babel/helper-remap-async-to-generator": ["@babel/helper-remap-async-to-generator@7.22.20", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-wrap-function": "^7.22.20" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-async-to-generator/@babel/helper-remap-async-to-generator": ["@babel/helper-remap-async-to-generator@7.22.20", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-wrap-function": "^7.22.20" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-class-properties/@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.23.10", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-member-expression-to-functions": "^7.23.0", "@babel/helper-optimise-call-expression": "^7.22.5", "@babel/helper-replace-supers": "^7.22.20", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-2XpP2XhkXzgxecPNEEK8Vz8Asj9aRxt08oKOqtiZoqV2UGZ5T+EkyP9sXQ9nwMxBIG34a7jmasVqoMop7VdPUw=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-class-static-block/@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.23.10", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-member-expression-to-functions": "^7.23.0", "@babel/helper-optimise-call-expression": "^7.22.5", "@babel/helper-replace-supers": "^7.22.20", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-2XpP2XhkXzgxecPNEEK8Vz8Asj9aRxt08oKOqtiZoqV2UGZ5T+EkyP9sXQ9nwMxBIG34a7jmasVqoMop7VdPUw=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-classes/@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.22.20", "", { "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-member-expression-to-functions": "^7.22.15", "@babel/helper-optimise-call-expression": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-classes/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-for-of/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.22.5", "", { "dependencies": { "@babel/types": "^7.22.5" } }, "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-object-super/@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.22.20", "", { "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-member-expression-to-functions": "^7.22.15", "@babel/helper-optimise-call-expression": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-optional-chaining/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.22.5", "", { "dependencies": { "@babel/types": "^7.22.5" } }, "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-private-methods/@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.23.10", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-member-expression-to-functions": "^7.23.0", "@babel/helper-optimise-call-expression": "^7.22.5", "@babel/helper-replace-supers": "^7.22.20", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-2XpP2XhkXzgxecPNEEK8Vz8Asj9aRxt08oKOqtiZoqV2UGZ5T+EkyP9sXQ9nwMxBIG34a7jmasVqoMop7VdPUw=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-private-property-in-object/@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.23.10", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-member-expression-to-functions": "^7.23.0", "@babel/helper-optimise-call-expression": "^7.22.5", "@babel/helper-replace-supers": "^7.22.20", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-2XpP2XhkXzgxecPNEEK8Vz8Asj9aRxt08oKOqtiZoqV2UGZ5T+EkyP9sXQ9nwMxBIG34a7jmasVqoMop7VdPUw=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-spread/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.22.5", "", { "dependencies": { "@babel/types": "^7.22.5" } }, "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q=="], + + "workbox-build/@babel/preset-env/babel-plugin-polyfill-corejs2/@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.5.0", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", "@babel/helper-plugin-utils": "^7.22.5", "debug": "^4.1.1", "lodash.debounce": "^4.0.8", "resolve": "^1.14.2" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q=="], + + "workbox-build/@babel/preset-env/babel-plugin-polyfill-corejs3/@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.5.0", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", "@babel/helper-plugin-utils": "^7.22.5", "debug": "^4.1.1", "lodash.debounce": "^4.0.8", "resolve": "^1.14.2" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q=="], + + "workbox-build/@babel/preset-env/babel-plugin-polyfill-regenerator/@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.5.0", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", "@babel/helper-plugin-utils": "^7.22.5", "debug": "^4.1.1", "lodash.debounce": "^4.0.8", "resolve": "^1.14.2" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q=="], + + "workbox-build/@babel/preset-env/core-js-compat/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=="], + "workbox-build/@rollup/plugin-replace/@rollup/pluginutils/@types/estree": ["@types/estree@0.0.39", "", {}, "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw=="], "workbox-build/@rollup/plugin-replace/@rollup/pluginutils/estree-walker": ["estree-walker@1.0.1", "", {}, "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg=="], "workbox-build/@rollup/plugin-replace/@rollup/pluginutils/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - "workbox-build/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@9.0.0", "", {}, "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg=="], - - "workbox-build/glob/path-scurry/lru-cache": ["lru-cache@11.2.2", "", {}, "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg=="], - "workbox-build/source-map/whatwg-url/tr46": ["tr46@1.0.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA=="], "workbox-build/source-map/whatwg-url/webidl-conversions": ["webidl-conversions@4.0.2", "", {}, "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="], - "@aws-sdk/client-bedrock-agent-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + "@aws-sdk/client-bedrock-agent-runtime/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.1", "", {}, "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw=="], - "@aws-sdk/client-bedrock-agent-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], + "@aws-sdk/client-bedrock-agent-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], "@aws-sdk/client-bedrock-agent-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.927.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.927.0", "@aws-sdk/middleware-host-header": "3.922.0", "@aws-sdk/middleware-logger": "3.922.0", "@aws-sdk/middleware-recursion-detection": "3.922.0", "@aws-sdk/middleware-user-agent": "3.927.0", "@aws-sdk/region-config-resolver": "3.925.0", "@aws-sdk/types": "3.922.0", "@aws-sdk/util-endpoints": "3.922.0", "@aws-sdk/util-user-agent-browser": "3.922.0", "@aws-sdk/util-user-agent-node": "3.927.0", "@smithy/config-resolver": "^4.4.2", "@smithy/core": "^3.17.2", "@smithy/fetch-http-handler": "^5.3.5", "@smithy/hash-node": "^4.2.4", "@smithy/invalid-dependency": "^4.2.4", "@smithy/middleware-content-length": "^4.2.4", "@smithy/middleware-endpoint": "^4.3.6", "@smithy/middleware-retry": "^4.4.6", "@smithy/middleware-serde": "^4.2.4", "@smithy/middleware-stack": "^4.2.4", "@smithy/node-config-provider": "^4.3.4", "@smithy/node-http-handler": "^4.4.4", "@smithy/protocol-http": "^5.3.4", "@smithy/smithy-client": "^4.9.2", "@smithy/types": "^4.8.1", "@smithy/url-parser": "^4.2.4", "@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.5", "@smithy/util-defaults-mode-node": "^4.2.8", "@smithy/util-endpoints": "^3.2.4", "@smithy/util-middleware": "^4.2.4", "@smithy/util-retry": "^4.2.4", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Oy6w7+fzIdr10DhF/HpfVLy6raZFTdiE7pxS1rvpuj2JgxzW2y6urm2sYf3eLOpMiHyuG4xUBwFiJpU9CCEvJA=="], "@aws-sdk/client-bedrock-agent-runtime/@smithy/core/@smithy/util-stream/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - "@aws-sdk/client-bedrock-agent-runtime/@smithy/eventstream-serde-browser/@smithy/eventstream-serde-universal/@smithy/eventstream-codec/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - - "@aws-sdk/client-bedrock-agent-runtime/@smithy/eventstream-serde-node/@smithy/eventstream-serde-universal/@smithy/eventstream-codec/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - "@aws-sdk/client-bedrock-agent-runtime/@smithy/smithy-client/@smithy/util-stream/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + "@aws-sdk/client-bedrock-runtime/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.1", "", {}, "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw=="], + "@aws-sdk/client-cognito-identity/@smithy/smithy-client/@smithy/util-stream/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@3.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ=="], - "@aws-sdk/client-kendra/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + "@aws-sdk/client-kendra/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.1", "", {}, "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw=="], - "@aws-sdk/client-kendra/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], + "@aws-sdk/client-kendra/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], "@aws-sdk/client-kendra/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.927.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.927.0", "@aws-sdk/middleware-host-header": "3.922.0", "@aws-sdk/middleware-logger": "3.922.0", "@aws-sdk/middleware-recursion-detection": "3.922.0", "@aws-sdk/middleware-user-agent": "3.927.0", "@aws-sdk/region-config-resolver": "3.925.0", "@aws-sdk/types": "3.922.0", "@aws-sdk/util-endpoints": "3.922.0", "@aws-sdk/util-user-agent-browser": "3.922.0", "@aws-sdk/util-user-agent-node": "3.927.0", "@smithy/config-resolver": "^4.4.2", "@smithy/core": "^3.17.2", "@smithy/fetch-http-handler": "^5.3.5", "@smithy/hash-node": "^4.2.4", "@smithy/invalid-dependency": "^4.2.4", "@smithy/middleware-content-length": "^4.2.4", "@smithy/middleware-endpoint": "^4.3.6", "@smithy/middleware-retry": "^4.4.6", "@smithy/middleware-serde": "^4.2.4", "@smithy/middleware-stack": "^4.2.4", "@smithy/node-config-provider": "^4.3.4", "@smithy/node-http-handler": "^4.4.4", "@smithy/protocol-http": "^5.3.4", "@smithy/smithy-client": "^4.9.2", "@smithy/types": "^4.8.1", "@smithy/url-parser": "^4.2.4", "@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.5", "@smithy/util-defaults-mode-node": "^4.2.8", "@smithy/util-endpoints": "^3.2.4", "@smithy/util-middleware": "^4.2.4", "@smithy/util-retry": "^4.2.4", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Oy6w7+fzIdr10DhF/HpfVLy6raZFTdiE7pxS1rvpuj2JgxzW2y6urm2sYf3eLOpMiHyuG4xUBwFiJpU9CCEvJA=="], @@ -8941,102 +8409,76 @@ "@aws-sdk/credential-provider-http/@smithy/smithy-client/@smithy/middleware-endpoint/@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=="], - "@aws-sdk/middleware-flexible-checksums/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-W9g1bOLui7Xn5FABRVS0o3rXL0gfN37d/8I/W7i0N7oxjx9QecUmXEMSUMADTODwdtka9cN43t5BI2CodLJpng=="], + "@aws-sdk/credential-provider-login/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.1", "", {}, "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw=="], - "@aws-sdk/middleware-flexible-checksums/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.6", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-IB/M5I8G0EeXZTHsAxpx51tMQ5R719F3aq+fjEB6VtNcCHDc0ajFDIGDZw+FW9GxtEkgTduiPpjveJdA/CX7sw=="], + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.7", "", { "dependencies": { "@smithy/protocol-http": "^5.3.6", "@smithy/querystring-builder": "^4.2.6", "@smithy/types": "^4.10.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-fcVap4QwqmzQwQK9QU3keeEpCzTjnP9NJ171vI7GnD7nbkAIcP9biZhDUx88uRH9BabSsQDS0unUps88uZvFIQ=="], - "@aws-sdk/middleware-flexible-checksums/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/url-parser": ["@smithy/url-parser@4.2.11", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-oTAGGHo8ZYc5VZsBREzuf5lf2pAurJQsccMusVZ85wDkX66ojEc/XauiGjzCj50A61ObFTPe6d7Pyt6UBYaing=="], + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@aws-sdk/middleware-sdk-s3/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/url-parser/@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-nE3IRNjDltvGcoThD2abTozI1dkSy8aX+a2N1Rs55en5UsdyyIXgGEmevUL3okZFoJC77JgRGe99xYohhsjivQ=="], + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.7", "", { "dependencies": { "@smithy/protocol-http": "^5.3.6", "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-PFMVHVPgtFECeu4iZ+4SX6VOQT0+dIpm4jSPLLL6JLSkp9RohGqKBKD0cbiXdeIFS08Forp0UHI6kc0gIHenSA=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@aws-sdk/core/@smithy/property-provider": ["@smithy/property-provider@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-o+VRiwC2cgmk/WFV0jaETGOtX16VNPp2bSQEzu0whbReqE1BMqsP2ami2Vi3cbGVdKu1kq9gQkDAGKbt0WOHAQ=="], + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/url-parser": ["@smithy/url-parser@4.2.6", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.6", "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-tVoyzJ2vXp4R3/aeV4EQjBDmCuWxRa8eo3KybL7Xv4wEM16nObYh7H1sNfcuLWHAAAzb0RVyxUz1S3sGj4X+Tg=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@smithy/core/@smithy/middleware-serde": ["@smithy/middleware-serde@4.0.2", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-Sdr5lOagCn5tt+zKsaW+U2/iwr6bI9p08wOkCp6/eL6iMbgdtc2R5Ety66rf87PeohR0ExI84Txz9GYv5ou3iQ=="], + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.7", "", { "dependencies": { "@smithy/protocol-http": "^5.3.6", "@smithy/querystring-builder": "^4.2.6", "@smithy/types": "^4.10.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-fcVap4QwqmzQwQK9QU3keeEpCzTjnP9NJ171vI7GnD7nbkAIcP9biZhDUx88uRH9BabSsQDS0unUps88uZvFIQ=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@smithy/core/@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA=="], + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@smithy/node-config-provider/@smithy/property-provider": ["@smithy/property-provider@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-o+VRiwC2cgmk/WFV0jaETGOtX16VNPp2bSQEzu0whbReqE1BMqsP2ami2Vi3cbGVdKu1kq9gQkDAGKbt0WOHAQ=="], + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/util-base64/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@smithy/node-config-provider/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-hC8F6qTBbuHRI/uqDgqqi6J0R4GtEZcgrZPhFQnMhfJs3MnUTGSnR1NSJCJs5VWlMydu0kJz15M640fJlRsIOw=="], + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@smithy/util-stream/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.0.1", "", { "dependencies": { "@smithy/protocol-http": "^5.0.1", "@smithy/querystring-builder": "^4.0.1", "@smithy/types": "^4.1.0", "@smithy/util-base64": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-3aS+fP28urrMW2KTjb6z9iFow6jO8n3MFfineGbndvzGZit3taZhKWtTorf+Gp5RpFDDafeHlhfsGlDCXvUnJA=="], + "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@smithy/node-http-handler/@smithy/abort-controller": ["@smithy/abort-controller@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-fiUIYgIgRjMWznk6iLJz35K2YxSLHzLBA/RC6lBrKfQ8fHbPfvk7Pk9UvpKoHgJjI18MnbPuEju53zcVy6KF1g=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@smithy/util-stream/@smithy/node-http-handler": ["@smithy/node-http-handler@4.0.3", "", { "dependencies": { "@smithy/abort-controller": "^4.0.1", "@smithy/protocol-http": "^5.0.1", "@smithy/querystring-builder": "^4.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-dYCLeINNbYdvmMLtW0VdhW1biXt+PPCGazzT5ZjKw46mOtdgToQEwjqZSS9/EN8+tNs/RO0cEWG044+YZs97aA=="], + "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "@smithy/util-uri-escape": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-wU87iWZoCbcqrwszsOewEIuq+SU2mSoBE2CcsLwE0I19m0B2gOJr1MVjxWcDQYOzHbR1xCk7AcOBbGFUYOKvdg=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@smithy/util-stream/@smithy/util-base64": ["@smithy/util-base64@4.0.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg=="], + "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso/@smithy/node-http-handler/@smithy/abort-controller": ["@smithy/abort-controller@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-fiUIYgIgRjMWznk6iLJz35K2YxSLHzLBA/RC6lBrKfQ8fHbPfvk7Pk9UvpKoHgJjI18MnbPuEju53zcVy6KF1g=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.0.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug=="], + "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "@smithy/util-uri-escape": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-wU87iWZoCbcqrwszsOewEIuq+SU2mSoBE2CcsLwE0I19m0B2gOJr1MVjxWcDQYOzHbR1xCk7AcOBbGFUYOKvdg=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], + "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/node-http-handler": ["@smithy/node-http-handler@4.0.3", "", { "dependencies": { "@smithy/abort-controller": "^4.0.1", "@smithy/protocol-http": "^5.0.1", "@smithy/querystring-builder": "^4.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-dYCLeINNbYdvmMLtW0VdhW1biXt+PPCGazzT5ZjKw46mOtdgToQEwjqZSS9/EN8+tNs/RO0cEWG044+YZs97aA=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.0.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug=="], + "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@smithy/node-http-handler/@smithy/abort-controller": ["@smithy/abort-controller@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-fiUIYgIgRjMWznk6iLJz35K2YxSLHzLBA/RC6lBrKfQ8fHbPfvk7Pk9UvpKoHgJjI18MnbPuEju53zcVy6KF1g=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "@smithy/util-uri-escape": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-wU87iWZoCbcqrwszsOewEIuq+SU2mSoBE2CcsLwE0I19m0B2gOJr1MVjxWcDQYOzHbR1xCk7AcOBbGFUYOKvdg=="], - "@aws-sdk/s3-request-presigner/@smithy/middleware-endpoint/@smithy/core/@smithy/util-stream/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.0.1", "", { "dependencies": { "@smithy/protocol-http": "^5.0.1", "@smithy/querystring-builder": "^4.0.1", "@smithy/types": "^4.1.0", "@smithy/util-base64": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-3aS+fP28urrMW2KTjb6z9iFow6jO8n3MFfineGbndvzGZit3taZhKWtTorf+Gp5RpFDDafeHlhfsGlDCXvUnJA=="], + "@aws-sdk/middleware-websocket/@smithy/fetch-http-handler/@smithy/util-base64/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - "@aws-sdk/s3-request-presigner/@smithy/middleware-endpoint/@smithy/core/@smithy/util-stream/@smithy/node-http-handler": ["@smithy/node-http-handler@4.0.3", "", { "dependencies": { "@smithy/abort-controller": "^4.0.1", "@smithy/protocol-http": "^5.0.1", "@smithy/querystring-builder": "^4.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-dYCLeINNbYdvmMLtW0VdhW1biXt+PPCGazzT5ZjKw46mOtdgToQEwjqZSS9/EN8+tNs/RO0cEWG044+YZs97aA=="], + "@aws-sdk/nested-clients/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.1", "", {}, "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw=="], - "@aws-sdk/s3-request-presigner/@smithy/middleware-endpoint/@smithy/core/@smithy/util-stream/@smithy/util-base64": ["@smithy/util-base64@4.0.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg=="], + "@aws-sdk/nested-clients/@smithy/core/@smithy/util-stream/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - "@aws-sdk/s3-request-presigner/@smithy/middleware-endpoint/@smithy/core/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.0.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug=="], + "@aws-sdk/nested-clients/@smithy/smithy-client/@smithy/util-stream/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - "@aws-sdk/s3-request-presigner/@smithy/middleware-endpoint/@smithy/core/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], + "@aws-sdk/token-providers/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.1", "", {}, "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw=="], - "@aws-sdk/s3-request-presigner/@smithy/middleware-endpoint/@smithy/core/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.0.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug=="], + "@aws-sdk/token-providers/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.7", "", { "dependencies": { "@smithy/protocol-http": "^5.3.6", "@smithy/querystring-builder": "^4.2.6", "@smithy/types": "^4.10.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-fcVap4QwqmzQwQK9QU3keeEpCzTjnP9NJ171vI7GnD7nbkAIcP9biZhDUx88uRH9BabSsQDS0unUps88uZvFIQ=="], - "@aws-sdk/s3-request-presigner/@smithy/smithy-client/@smithy/core/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.0.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug=="], + "@aws-sdk/token-providers/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@aws-sdk/s3-request-presigner/@smithy/smithy-client/@smithy/util-stream/@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "@smithy/util-uri-escape": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-wU87iWZoCbcqrwszsOewEIuq+SU2mSoBE2CcsLwE0I19m0B2gOJr1MVjxWcDQYOzHbR1xCk7AcOBbGFUYOKvdg=="], + "@aws-sdk/token-providers/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.7", "", { "dependencies": { "@smithy/protocol-http": "^5.3.6", "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-PFMVHVPgtFECeu4iZ+4SX6VOQT0+dIpm4jSPLLL6JLSkp9RohGqKBKD0cbiXdeIFS08Forp0UHI6kc0gIHenSA=="], - "@aws-sdk/s3-request-presigner/@smithy/smithy-client/@smithy/util-stream/@smithy/node-http-handler/@smithy/abort-controller": ["@smithy/abort-controller@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-fiUIYgIgRjMWznk6iLJz35K2YxSLHzLBA/RC6lBrKfQ8fHbPfvk7Pk9UvpKoHgJjI18MnbPuEju53zcVy6KF1g=="], + "@aws-sdk/token-providers/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/url-parser": ["@smithy/url-parser@4.2.6", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.6", "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-tVoyzJ2vXp4R3/aeV4EQjBDmCuWxRa8eo3KybL7Xv4wEM16nObYh7H1sNfcuLWHAAAzb0RVyxUz1S3sGj4X+Tg=="], - "@aws-sdk/s3-request-presigner/@smithy/smithy-client/@smithy/util-stream/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "@smithy/util-uri-escape": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-wU87iWZoCbcqrwszsOewEIuq+SU2mSoBE2CcsLwE0I19m0B2gOJr1MVjxWcDQYOzHbR1xCk7AcOBbGFUYOKvdg=="], + "@aws-sdk/token-providers/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.7", "", { "dependencies": { "@smithy/protocol-http": "^5.3.6", "@smithy/querystring-builder": "^4.2.6", "@smithy/types": "^4.10.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-fcVap4QwqmzQwQK9QU3keeEpCzTjnP9NJ171vI7GnD7nbkAIcP9biZhDUx88uRH9BabSsQDS0unUps88uZvFIQ=="], - "@aws-sdk/s3-request-presigner/@smithy/smithy-client/@smithy/util-stream/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw=="], + "@aws-sdk/token-providers/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@babel/plugin-transform-modules-amd/@babel/helper-module-transforms/@babel/traverse/@babel/generator/@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=="], + "@aws-sdk/token-providers/@aws-sdk/core/@smithy/util-base64/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - "@babel/plugin-transform-modules-commonjs/@babel/helper-module-transforms/@babel/traverse/@babel/generator/@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=="], - - "@babel/plugin-transform-modules-umd/@babel/helper-module-transforms/@babel/traverse/@babel/generator/@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=="], - - "@babel/plugin-transform-runtime/babel-plugin-polyfill-corejs3/core-js-compat/browserslist/baseline-browser-mapping": ["baseline-browser-mapping@2.8.28", "", { "bin": "dist/cli.js" }, "sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ=="], - - "@babel/plugin-transform-runtime/babel-plugin-polyfill-corejs3/core-js-compat/browserslist/electron-to-chromium": ["electron-to-chromium@1.5.254", "", {}, "sha512-DcUsWpVhv9svsKRxnSCZ86SjD+sp32SGidNB37KpqXJncp1mfUgKbHvBomE89WJDbfVKw1mdv5+ikrvd43r+Bg=="], + "@aws-sdk/token-providers/@aws-sdk/core/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], "@babel/plugin-transform-runtime/babel-plugin-polyfill-corejs3/core-js-compat/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=="], - "@google/genai/google-auth-library/gaxios/rimraf/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=="], - "@istanbuljs/load-nyc-config/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], "@jest/expect/expect/jest-matcher-utils/pretty-format/react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@aws-sdk/core/@smithy/signature-v4/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@aws-sdk/core/@smithy/signature-v4/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@aws-sdk/core/@smithy/signature-v4/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="], - - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@aws-sdk/eventstream-handler-node/@smithy/eventstream-codec/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], + "@langchain/aws/@aws-sdk/client-bedrock-runtime/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="], "@langchain/aws/@aws-sdk/client-bedrock-runtime/@aws-sdk/middleware-websocket/@aws-sdk/util-format-url/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-KQ1gFXXC+WsbPFnk7pzskzOpn4s+KheWgO3dzkIEmnb6NskAIGp/dGdbKisTPJdtov28qNDohQrgDUKzXZBLig=="], - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@aws-sdk/middleware-websocket/@smithy/signature-v4/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@aws-sdk/middleware-websocket/@smithy/signature-v4/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="], - - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@smithy/eventstream-serde-browser/@smithy/eventstream-serde-universal/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.4", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.8.1", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-aV8blR9RBDKrOlZVgjOdmOibTC2sBXNiT7WA558b4MPdsLTV6sbyc1WIE9QiIuYMJjYtnPLciefoqSW8Gi+MZQ=="], - - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@smithy/eventstream-serde-node/@smithy/eventstream-serde-universal/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.4", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.8.1", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-aV8blR9RBDKrOlZVgjOdmOibTC2sBXNiT7WA558b4MPdsLTV6sbyc1WIE9QiIuYMJjYtnPLciefoqSW8Gi+MZQ=="], - - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@smithy/fetch-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=="], - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@smithy/hash-node/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@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=="], - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@smithy/util-base64/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], "@langchain/aws/@aws-sdk/client-bedrock-runtime/@smithy/util-stream/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], @@ -9049,16 +8491,10 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env/@aws-sdk/core/@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.4", "", { "dependencies": { "@smithy/property-provider": "^4.2.4", "@smithy/shared-ini-file-loader": "^4.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-3X3w7qzmo4XNNdPKNS4nbJcGSwiEMsNsRSunMA92S4DJLLIrH5g1AyuOA2XKM9PAPi8mIWfqC+fnfKNsI4KvHw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env/@aws-sdk/core/@smithy/protocol-http": ["@smithy/protocol-http@5.3.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-3sfFd2MAzVt0Q/klOmjFi3oIkxczHs0avbwrfn1aBqtc23WqQSmjvk77MBw9WkEQcwbOYIX5/2z4ULj8DuxSsw=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.3.4", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.4", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-ScDCpasxH7w1HXHYbtk3jcivjvdA1VICyAdgvVqKhKKwxi+MTwZEqFw0minE+oZ7F07oF25xh4FGJxgqgShz0A=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env/@aws-sdk/core/@smithy/smithy-client": ["@smithy/smithy-client@4.9.2", "", { "dependencies": { "@smithy/core": "^3.17.2", "@smithy/middleware-endpoint": "^4.3.6", "@smithy/middleware-stack": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-stream": "^4.5.5", "tslib": "^2.6.2" } }, "sha512-gZU4uAFcdrSi3io8U99Qs/FvVdRxPvIMToi+MFfsy/DN9UqtknJ1ais+2M9yR8e0ASQpNmFYEKeIKVcMjQg3rg=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env/@aws-sdk/core/@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env/@aws-sdk/core/@smithy/util-middleware": ["@smithy/util-middleware@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-fKGQAPAn8sgV0plRikRVo6g6aR0KyKvgzNrPuM74RZKy/wWVzx3BMk+ZWEueyN3L5v5EDg+P582mKU+sH5OAsg=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env/@aws-sdk/core/@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.921.0", "", { "dependencies": { "@smithy/types": "^4.8.1", "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" } }, "sha512-LVHg0jgjyicKKvpNIEMXIMr1EBViESxcPkqfOlT+X1FkmUMTNZEEVF18tOJg4m4hV5vxtkWcqtr4IEeWa1C41Q=="], @@ -9067,12 +8503,8 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@aws-sdk/core/@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.4", "", { "dependencies": { "@smithy/property-provider": "^4.2.4", "@smithy/shared-ini-file-loader": "^4.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-3X3w7qzmo4XNNdPKNS4nbJcGSwiEMsNsRSunMA92S4DJLLIrH5g1AyuOA2XKM9PAPi8mIWfqC+fnfKNsI4KvHw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.3.4", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.4", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-ScDCpasxH7w1HXHYbtk3jcivjvdA1VICyAdgvVqKhKKwxi+MTwZEqFw0minE+oZ7F07oF25xh4FGJxgqgShz0A=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@aws-sdk/core/@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@aws-sdk/core/@smithy/util-middleware": ["@smithy/util-middleware@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-fKGQAPAn8sgV0plRikRVo6g6aR0KyKvgzNrPuM74RZKy/wWVzx3BMk+ZWEueyN3L5v5EDg+P582mKU+sH5OAsg=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@aws-sdk/core/@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-KQ1gFXXC+WsbPFnk7pzskzOpn4s+KheWgO3dzkIEmnb6NskAIGp/dGdbKisTPJdtov28qNDohQrgDUKzXZBLig=="], @@ -9093,8 +8525,6 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/util-stream/@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.921.0", "", { "dependencies": { "@smithy/types": "^4.8.1", "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" } }, "sha512-LVHg0jgjyicKKvpNIEMXIMr1EBViESxcPkqfOlT+X1FkmUMTNZEEVF18tOJg4m4hV5vxtkWcqtr4IEeWa1C41Q=="], @@ -9103,16 +8533,10 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/core/@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.4", "", { "dependencies": { "@smithy/property-provider": "^4.2.4", "@smithy/shared-ini-file-loader": "^4.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-3X3w7qzmo4XNNdPKNS4nbJcGSwiEMsNsRSunMA92S4DJLLIrH5g1AyuOA2XKM9PAPi8mIWfqC+fnfKNsI4KvHw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/core/@smithy/protocol-http": ["@smithy/protocol-http@5.3.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-3sfFd2MAzVt0Q/klOmjFi3oIkxczHs0avbwrfn1aBqtc23WqQSmjvk77MBw9WkEQcwbOYIX5/2z4ULj8DuxSsw=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.3.4", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.4", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-ScDCpasxH7w1HXHYbtk3jcivjvdA1VICyAdgvVqKhKKwxi+MTwZEqFw0minE+oZ7F07oF25xh4FGJxgqgShz0A=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/core/@smithy/smithy-client": ["@smithy/smithy-client@4.9.2", "", { "dependencies": { "@smithy/core": "^3.17.2", "@smithy/middleware-endpoint": "^4.3.6", "@smithy/middleware-stack": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-stream": "^4.5.5", "tslib": "^2.6.2" } }, "sha512-gZU4uAFcdrSi3io8U99Qs/FvVdRxPvIMToi+MFfsy/DN9UqtknJ1ais+2M9yR8e0ASQpNmFYEKeIKVcMjQg3rg=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/core/@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/core/@smithy/util-middleware": ["@smithy/util-middleware@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-fKGQAPAn8sgV0plRikRVo6g6aR0KyKvgzNrPuM74RZKy/wWVzx3BMk+ZWEueyN3L5v5EDg+P582mKU+sH5OAsg=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/core/@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.922.0", "", { "dependencies": { "@aws-sdk/types": "3.922.0", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-HPquFgBnq/KqKRVkiuCt97PmWbKtxQ5iUNLEc6FIviqOoZTmaYG3EDsIbuFBz9C4RHJU4FKLmHL2bL3FEId6AA=="], @@ -9155,8 +8579,6 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.4", "", { "dependencies": { "@smithy/abort-controller": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/querystring-builder": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-VXHGfzCXLZeKnFp6QXjAdy+U8JF9etfpUXD1FAbzY1GzsFJiDQRQIt2CnMUvUdz3/YaHNqT3RphVWMUpXTIODA=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@smithy/protocol-http": ["@smithy/protocol-http@5.3.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-3sfFd2MAzVt0Q/klOmjFi3oIkxczHs0avbwrfn1aBqtc23WqQSmjvk77MBw9WkEQcwbOYIX5/2z4ULj8DuxSsw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@smithy/smithy-client": ["@smithy/smithy-client@4.9.2", "", { "dependencies": { "@smithy/core": "^3.17.2", "@smithy/middleware-endpoint": "^4.3.6", "@smithy/middleware-stack": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-stream": "^4.5.5", "tslib": "^2.6.2" } }, "sha512-gZU4uAFcdrSi3io8U99Qs/FvVdRxPvIMToi+MFfsy/DN9UqtknJ1ais+2M9yR8e0ASQpNmFYEKeIKVcMjQg3rg=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@smithy/url-parser": ["@smithy/url-parser@4.2.4", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-w/N/Iw0/PTwJ36PDqU9PzAwVElo4qXxCC0eCTlUtIz/Z5V/2j/cViMHi0hPukSBHp4DVwvUlUhLgCzqSJ6plrg=="], @@ -9173,8 +8595,6 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.4", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-f+nBDhgYRCmUEDKEQb6q0aCcOTXRDqH5wWaFHJxt4anB4pKHlgGoYP3xtioKXH64e37ANUkzWf6p4Mnv1M5/Vg=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@smithy/util-middleware": ["@smithy/util-middleware@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-fKGQAPAn8sgV0plRikRVo6g6aR0KyKvgzNrPuM74RZKy/wWVzx3BMk+ZWEueyN3L5v5EDg+P582mKU+sH5OAsg=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@smithy/util-retry": ["@smithy/util-retry@4.2.4", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-yQncJmj4dtv/isTXxRb4AamZHy4QFr4ew8GxS6XLWt7sCIxkPxPzINWd7WLISEFPsIan14zrKgvyAF+/yzfwoA=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], @@ -9185,16 +8605,10 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process/@aws-sdk/core/@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.4", "", { "dependencies": { "@smithy/property-provider": "^4.2.4", "@smithy/shared-ini-file-loader": "^4.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-3X3w7qzmo4XNNdPKNS4nbJcGSwiEMsNsRSunMA92S4DJLLIrH5g1AyuOA2XKM9PAPi8mIWfqC+fnfKNsI4KvHw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process/@aws-sdk/core/@smithy/protocol-http": ["@smithy/protocol-http@5.3.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-3sfFd2MAzVt0Q/klOmjFi3oIkxczHs0avbwrfn1aBqtc23WqQSmjvk77MBw9WkEQcwbOYIX5/2z4ULj8DuxSsw=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.3.4", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.4", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-ScDCpasxH7w1HXHYbtk3jcivjvdA1VICyAdgvVqKhKKwxi+MTwZEqFw0minE+oZ7F07oF25xh4FGJxgqgShz0A=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process/@aws-sdk/core/@smithy/smithy-client": ["@smithy/smithy-client@4.9.2", "", { "dependencies": { "@smithy/core": "^3.17.2", "@smithy/middleware-endpoint": "^4.3.6", "@smithy/middleware-stack": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-stream": "^4.5.5", "tslib": "^2.6.2" } }, "sha512-gZU4uAFcdrSi3io8U99Qs/FvVdRxPvIMToi+MFfsy/DN9UqtknJ1ais+2M9yR8e0ASQpNmFYEKeIKVcMjQg3rg=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process/@aws-sdk/core/@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process/@aws-sdk/core/@smithy/util-middleware": ["@smithy/util-middleware@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-fKGQAPAn8sgV0plRikRVo6g6aR0KyKvgzNrPuM74RZKy/wWVzx3BMk+ZWEueyN3L5v5EDg+P582mKU+sH5OAsg=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process/@aws-sdk/core/@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso/@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.922.0", "", { "dependencies": { "@aws-sdk/types": "3.922.0", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-HPquFgBnq/KqKRVkiuCt97PmWbKtxQ5iUNLEc6FIviqOoZTmaYG3EDsIbuFBz9C4RHJU4FKLmHL2bL3FEId6AA=="], @@ -9237,8 +8651,6 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso/@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.4", "", { "dependencies": { "@smithy/abort-controller": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/querystring-builder": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-VXHGfzCXLZeKnFp6QXjAdy+U8JF9etfpUXD1FAbzY1GzsFJiDQRQIt2CnMUvUdz3/YaHNqT3RphVWMUpXTIODA=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso/@smithy/protocol-http": ["@smithy/protocol-http@5.3.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-3sfFd2MAzVt0Q/klOmjFi3oIkxczHs0avbwrfn1aBqtc23WqQSmjvk77MBw9WkEQcwbOYIX5/2z4ULj8DuxSsw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso/@smithy/smithy-client": ["@smithy/smithy-client@4.9.2", "", { "dependencies": { "@smithy/core": "^3.17.2", "@smithy/middleware-endpoint": "^4.3.6", "@smithy/middleware-stack": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-stream": "^4.5.5", "tslib": "^2.6.2" } }, "sha512-gZU4uAFcdrSi3io8U99Qs/FvVdRxPvIMToi+MFfsy/DN9UqtknJ1ais+2M9yR8e0ASQpNmFYEKeIKVcMjQg3rg=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso/@smithy/url-parser": ["@smithy/url-parser@4.2.4", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-w/N/Iw0/PTwJ36PDqU9PzAwVElo4qXxCC0eCTlUtIz/Z5V/2j/cViMHi0hPukSBHp4DVwvUlUhLgCzqSJ6plrg=="], @@ -9255,8 +8667,6 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso/@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.4", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-f+nBDhgYRCmUEDKEQb6q0aCcOTXRDqH5wWaFHJxt4anB4pKHlgGoYP3xtioKXH64e37ANUkzWf6p4Mnv1M5/Vg=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso/@smithy/util-middleware": ["@smithy/util-middleware@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-fKGQAPAn8sgV0plRikRVo6g6aR0KyKvgzNrPuM74RZKy/wWVzx3BMk+ZWEueyN3L5v5EDg+P582mKU+sH5OAsg=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso/@smithy/util-retry": ["@smithy/util-retry@4.2.4", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-yQncJmj4dtv/isTXxRb4AamZHy4QFr4ew8GxS6XLWt7sCIxkPxPzINWd7WLISEFPsIan14zrKgvyAF+/yzfwoA=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso/@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], @@ -9267,16 +8677,10 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/core/@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.4", "", { "dependencies": { "@smithy/property-provider": "^4.2.4", "@smithy/shared-ini-file-loader": "^4.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-3X3w7qzmo4XNNdPKNS4nbJcGSwiEMsNsRSunMA92S4DJLLIrH5g1AyuOA2XKM9PAPi8mIWfqC+fnfKNsI4KvHw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/core/@smithy/protocol-http": ["@smithy/protocol-http@5.3.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-3sfFd2MAzVt0Q/klOmjFi3oIkxczHs0avbwrfn1aBqtc23WqQSmjvk77MBw9WkEQcwbOYIX5/2z4ULj8DuxSsw=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.3.4", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.4", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-ScDCpasxH7w1HXHYbtk3jcivjvdA1VICyAdgvVqKhKKwxi+MTwZEqFw0minE+oZ7F07oF25xh4FGJxgqgShz0A=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/core/@smithy/smithy-client": ["@smithy/smithy-client@4.9.2", "", { "dependencies": { "@smithy/core": "^3.17.2", "@smithy/middleware-endpoint": "^4.3.6", "@smithy/middleware-stack": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-stream": "^4.5.5", "tslib": "^2.6.2" } }, "sha512-gZU4uAFcdrSi3io8U99Qs/FvVdRxPvIMToi+MFfsy/DN9UqtknJ1ais+2M9yR8e0ASQpNmFYEKeIKVcMjQg3rg=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/core/@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/core/@smithy/util-middleware": ["@smithy/util-middleware@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-fKGQAPAn8sgV0plRikRVo6g6aR0KyKvgzNrPuM74RZKy/wWVzx3BMk+ZWEueyN3L5v5EDg+P582mKU+sH5OAsg=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/core/@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.927.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.927.0", "@aws-sdk/middleware-host-header": "3.922.0", "@aws-sdk/middleware-logger": "3.922.0", "@aws-sdk/middleware-recursion-detection": "3.922.0", "@aws-sdk/middleware-user-agent": "3.927.0", "@aws-sdk/region-config-resolver": "3.925.0", "@aws-sdk/types": "3.922.0", "@aws-sdk/util-endpoints": "3.922.0", "@aws-sdk/util-user-agent-browser": "3.922.0", "@aws-sdk/util-user-agent-node": "3.927.0", "@smithy/config-resolver": "^4.4.2", "@smithy/core": "^3.17.2", "@smithy/fetch-http-handler": "^5.3.5", "@smithy/hash-node": "^4.2.4", "@smithy/invalid-dependency": "^4.2.4", "@smithy/middleware-content-length": "^4.2.4", "@smithy/middleware-endpoint": "^4.3.6", "@smithy/middleware-retry": "^4.4.6", "@smithy/middleware-serde": "^4.2.4", "@smithy/middleware-stack": "^4.2.4", "@smithy/node-config-provider": "^4.3.4", "@smithy/node-http-handler": "^4.4.4", "@smithy/protocol-http": "^5.3.4", "@smithy/smithy-client": "^4.9.2", "@smithy/types": "^4.8.1", "@smithy/url-parser": "^4.2.4", "@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.5", "@smithy/util-defaults-mode-node": "^4.2.8", "@smithy/util-endpoints": "^3.2.4", "@smithy/util-middleware": "^4.2.4", "@smithy/util-retry": "^4.2.4", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Oy6w7+fzIdr10DhF/HpfVLy6raZFTdiE7pxS1rvpuj2JgxzW2y6urm2sYf3eLOpMiHyuG4xUBwFiJpU9CCEvJA=="], @@ -9287,16 +8691,10 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.4", "", { "dependencies": { "@smithy/property-provider": "^4.2.4", "@smithy/shared-ini-file-loader": "^4.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-3X3w7qzmo4XNNdPKNS4nbJcGSwiEMsNsRSunMA92S4DJLLIrH5g1AyuOA2XKM9PAPi8mIWfqC+fnfKNsI4KvHw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@smithy/protocol-http": ["@smithy/protocol-http@5.3.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-3sfFd2MAzVt0Q/klOmjFi3oIkxczHs0avbwrfn1aBqtc23WqQSmjvk77MBw9WkEQcwbOYIX5/2z4ULj8DuxSsw=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.3.4", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.4", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-ScDCpasxH7w1HXHYbtk3jcivjvdA1VICyAdgvVqKhKKwxi+MTwZEqFw0minE+oZ7F07oF25xh4FGJxgqgShz0A=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@smithy/smithy-client": ["@smithy/smithy-client@4.9.2", "", { "dependencies": { "@smithy/core": "^3.17.2", "@smithy/middleware-endpoint": "^4.3.6", "@smithy/middleware-stack": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-stream": "^4.5.5", "tslib": "^2.6.2" } }, "sha512-gZU4uAFcdrSi3io8U99Qs/FvVdRxPvIMToi+MFfsy/DN9UqtknJ1ais+2M9yR8e0ASQpNmFYEKeIKVcMjQg3rg=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@smithy/util-middleware": ["@smithy/util-middleware@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-fKGQAPAn8sgV0plRikRVo6g6aR0KyKvgzNrPuM74RZKy/wWVzx3BMk+ZWEueyN3L5v5EDg+P582mKU+sH5OAsg=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.922.0", "", { "dependencies": { "@aws-sdk/types": "3.922.0", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-HPquFgBnq/KqKRVkiuCt97PmWbKtxQ5iUNLEc6FIviqOoZTmaYG3EDsIbuFBz9C4RHJU4FKLmHL2bL3FEId6AA=="], @@ -9339,8 +8737,6 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.4", "", { "dependencies": { "@smithy/abort-controller": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/querystring-builder": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-VXHGfzCXLZeKnFp6QXjAdy+U8JF9etfpUXD1FAbzY1GzsFJiDQRQIt2CnMUvUdz3/YaHNqT3RphVWMUpXTIODA=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@smithy/protocol-http": ["@smithy/protocol-http@5.3.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-3sfFd2MAzVt0Q/klOmjFi3oIkxczHs0avbwrfn1aBqtc23WqQSmjvk77MBw9WkEQcwbOYIX5/2z4ULj8DuxSsw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@smithy/smithy-client": ["@smithy/smithy-client@4.9.2", "", { "dependencies": { "@smithy/core": "^3.17.2", "@smithy/middleware-endpoint": "^4.3.6", "@smithy/middleware-stack": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-stream": "^4.5.5", "tslib": "^2.6.2" } }, "sha512-gZU4uAFcdrSi3io8U99Qs/FvVdRxPvIMToi+MFfsy/DN9UqtknJ1ais+2M9yR8e0ASQpNmFYEKeIKVcMjQg3rg=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@smithy/url-parser": ["@smithy/url-parser@4.2.4", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-w/N/Iw0/PTwJ36PDqU9PzAwVElo4qXxCC0eCTlUtIz/Z5V/2j/cViMHi0hPukSBHp4DVwvUlUhLgCzqSJ6plrg=="], @@ -9357,8 +8753,6 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.4", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-f+nBDhgYRCmUEDKEQb6q0aCcOTXRDqH5wWaFHJxt4anB4pKHlgGoYP3xtioKXH64e37ANUkzWf6p4Mnv1M5/Vg=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@smithy/util-middleware": ["@smithy/util-middleware@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-fKGQAPAn8sgV0plRikRVo6g6aR0KyKvgzNrPuM74RZKy/wWVzx3BMk+ZWEueyN3L5v5EDg+P582mKU+sH5OAsg=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@smithy/util-retry": ["@smithy/util-retry@4.2.4", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-yQncJmj4dtv/isTXxRb4AamZHy4QFr4ew8GxS6XLWt7sCIxkPxPzINWd7WLISEFPsIan14zrKgvyAF+/yzfwoA=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], @@ -9367,85 +8761,413 @@ "@langchain/google-gauth/google-auth-library/gaxios/rimraf/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=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.17", "", { "dependencies": { "@aws-sdk/core": "^3.973.18", "@aws-sdk/nested-clients": "^3.996.7", "@aws-sdk/types": "^3.973.5", "@smithy/property-provider": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-gf2E5b7LpKb+JX2oQsRIDxdRZjBFZt2olCGlWCdb3vBERbXIPgm2t1R5mEnwd4j0UEO/Tbg5zN2KJbHXttJqwA=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-async-generator-functions/@babel/helper-remap-async-to-generator/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.7", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.18", "@aws-sdk/middleware-host-header": "^3.972.7", "@aws-sdk/middleware-logger": "^3.972.7", "@aws-sdk/middleware-recursion-detection": "^3.972.7", "@aws-sdk/middleware-user-agent": "^3.972.19", "@aws-sdk/region-config-resolver": "^3.972.7", "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-endpoints": "^3.996.4", "@aws-sdk/util-user-agent-browser": "^3.972.7", "@aws-sdk/util-user-agent-node": "^3.973.4", "@smithy/config-resolver": "^4.4.10", "@smithy/core": "^3.23.8", "@smithy/fetch-http-handler": "^5.3.13", "@smithy/hash-node": "^4.2.11", "@smithy/invalid-dependency": "^4.2.11", "@smithy/middleware-content-length": "^4.2.11", "@smithy/middleware-endpoint": "^4.4.22", "@smithy/middleware-retry": "^4.4.39", "@smithy/middleware-serde": "^4.2.12", "@smithy/middleware-stack": "^4.2.11", "@smithy/node-config-provider": "^4.3.11", "@smithy/node-http-handler": "^4.4.14", "@smithy/protocol-http": "^5.3.11", "@smithy/smithy-client": "^4.12.2", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@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.38", "@smithy/util-defaults-mode-node": "^4.2.41", "@smithy/util-endpoints": "^3.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-retry": "^4.2.11", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-MlGWA8uPaOs5AiTZ5JLM4uuWDm9EEAnm9cqwvqQIc6kEgel/8s1BaOWm9QgUcfc9K8qd7KkC3n43yDbeXOA2tg=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-async-generator-functions/@babel/helper-remap-async-to-generator/@babel/helper-wrap-function": ["@babel/helper-wrap-function@7.28.3", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.3", "@babel/types": "^7.28.2" } }, "sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.7", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.18", "@aws-sdk/middleware-host-header": "^3.972.7", "@aws-sdk/middleware-logger": "^3.972.7", "@aws-sdk/middleware-recursion-detection": "^3.972.7", "@aws-sdk/middleware-user-agent": "^3.972.19", "@aws-sdk/region-config-resolver": "^3.972.7", "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-endpoints": "^3.996.4", "@aws-sdk/util-user-agent-browser": "^3.972.7", "@aws-sdk/util-user-agent-node": "^3.973.4", "@smithy/config-resolver": "^4.4.10", "@smithy/core": "^3.23.8", "@smithy/fetch-http-handler": "^5.3.13", "@smithy/hash-node": "^4.2.11", "@smithy/invalid-dependency": "^4.2.11", "@smithy/middleware-content-length": "^4.2.11", "@smithy/middleware-endpoint": "^4.4.22", "@smithy/middleware-retry": "^4.4.39", "@smithy/middleware-serde": "^4.2.12", "@smithy/middleware-stack": "^4.2.11", "@smithy/node-config-provider": "^4.3.11", "@smithy/node-http-handler": "^4.4.14", "@smithy/protocol-http": "^5.3.11", "@smithy/smithy-client": "^4.12.2", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@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.38", "@smithy/util-defaults-mode-node": "^4.2.41", "@smithy/util-endpoints": "^3.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-retry": "^4.2.11", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-MlGWA8uPaOs5AiTZ5JLM4uuWDm9EEAnm9cqwvqQIc6kEgel/8s1BaOWm9QgUcfc9K8qd7KkC3n43yDbeXOA2tg=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-async-to-generator/@babel/helper-remap-async-to-generator/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.7", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.18", "@aws-sdk/middleware-host-header": "^3.972.7", "@aws-sdk/middleware-logger": "^3.972.7", "@aws-sdk/middleware-recursion-detection": "^3.972.7", "@aws-sdk/middleware-user-agent": "^3.972.19", "@aws-sdk/region-config-resolver": "^3.972.7", "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-endpoints": "^3.996.4", "@aws-sdk/util-user-agent-browser": "^3.972.7", "@aws-sdk/util-user-agent-node": "^3.973.4", "@smithy/config-resolver": "^4.4.10", "@smithy/core": "^3.23.8", "@smithy/fetch-http-handler": "^5.3.13", "@smithy/hash-node": "^4.2.11", "@smithy/invalid-dependency": "^4.2.11", "@smithy/middleware-content-length": "^4.2.11", "@smithy/middleware-endpoint": "^4.4.22", "@smithy/middleware-retry": "^4.4.39", "@smithy/middleware-serde": "^4.2.12", "@smithy/middleware-stack": "^4.2.11", "@smithy/node-config-provider": "^4.3.11", "@smithy/node-http-handler": "^4.4.14", "@smithy/protocol-http": "^5.3.11", "@smithy/smithy-client": "^4.12.2", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@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.38", "@smithy/util-defaults-mode-node": "^4.2.41", "@smithy/util-endpoints": "^3.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-retry": "^4.2.11", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-MlGWA8uPaOs5AiTZ5JLM4uuWDm9EEAnm9cqwvqQIc6kEgel/8s1BaOWm9QgUcfc9K8qd7KkC3n43yDbeXOA2tg=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-async-to-generator/@babel/helper-remap-async-to-generator/@babel/helper-wrap-function": ["@babel/helper-wrap-function@7.28.3", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.3", "@babel/types": "^7.28.2" } }, "sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@aws-sdk/middleware-websocket/@aws-sdk/util-format-url/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-7spdikrYiljpket6u0up2Ck2mxhy7dZ0+TDd+S53Dg2DHd6wg+YNJrTCHiLdgZmEXZKI7LJZcwL3721ZRDFiqA=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-class-properties/@babel/helper-create-class-features-plugin/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/eventstream-serde-browser/@smithy/eventstream-serde-universal/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.11", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.13.0", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-Sf39Ml0iVX+ba/bgMPxaXWAAFmHqYLTmbjAPfLPLY8CrYkRDEqZdUsKC1OwVMCdJXfAt0v4j49GIJ8DoSYAe6w=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-class-properties/@babel/helper-create-class-features-plugin/@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="], - "@librechat/backend/@aws-sdk/client-bedrock-runtime/@smithy/eventstream-serde-node/@smithy/eventstream-serde-universal/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.11", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.13.0", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-Sf39Ml0iVX+ba/bgMPxaXWAAFmHqYLTmbjAPfLPLY8CrYkRDEqZdUsKC1OwVMCdJXfAt0v4j49GIJ8DoSYAe6w=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-class-properties/@babel/helper-create-class-features-plugin/@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-class-properties/@babel/helper-create-class-features-plugin/@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.27.1", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-class-properties/@babel/helper-create-class-features-plugin/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-class-static-block/@babel/helper-create-class-features-plugin/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-class-static-block/@babel/helper-create-class-features-plugin/@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-class-static-block/@babel/helper-create-class-features-plugin/@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-class-static-block/@babel/helper-create-class-features-plugin/@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.27.1", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-class-static-block/@babel/helper-create-class-features-plugin/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-classes/@babel/helper-replace-supers/@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-classes/@babel/helper-replace-supers/@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-dotall-regex/@babel/helper-create-regexp-features-plugin/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-dotall-regex/@babel/helper-create-regexp-features-plugin/regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-duplicate-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-duplicate-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-object-super/@babel/helper-replace-supers/@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-object-super/@babel/helper-replace-supers/@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-private-methods/@babel/helper-create-class-features-plugin/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-private-methods/@babel/helper-create-class-features-plugin/@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-private-methods/@babel/helper-create-class-features-plugin/@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-private-methods/@babel/helper-create-class-features-plugin/@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.27.1", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-private-methods/@babel/helper-create-class-features-plugin/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-private-property-in-object/@babel/helper-create-class-features-plugin/@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-private-property-in-object/@babel/helper-create-class-features-plugin/@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-private-property-in-object/@babel/helper-create-class-features-plugin/@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.27.1", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-private-property-in-object/@babel/helper-create-class-features-plugin/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-regexp-modifiers/@babel/helper-create-regexp-features-plugin/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-regexp-modifiers/@babel/helper-create-regexp-features-plugin/regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-unicode-property-regex/@babel/helper-create-regexp-features-plugin/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-unicode-property-regex/@babel/helper-create-regexp-features-plugin/regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-unicode-regex/@babel/helper-create-regexp-features-plugin/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-unicode-regex/@babel/helper-create-regexp-features-plugin/regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-unicode-sets-regex/@babel/helper-create-regexp-features-plugin/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-unicode-sets-regex/@babel/helper-create-regexp-features-plugin/regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="], + + "@librechat/client/@babel/preset-env/babel-plugin-polyfill-corejs2/@babel/helper-define-polyfill-provider/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "@librechat/client/@babel/preset-env/babel-plugin-polyfill-corejs2/@babel/helper-define-polyfill-provider/resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], + + "@librechat/client/@babel/preset-env/babel-plugin-polyfill-corejs3/@babel/helper-define-polyfill-provider/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "@librechat/client/@babel/preset-env/babel-plugin-polyfill-corejs3/@babel/helper-define-polyfill-provider/resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], + + "@librechat/client/@babel/preset-env/babel-plugin-polyfill-regenerator/@babel/helper-define-polyfill-provider/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "@librechat/client/@babel/preset-env/babel-plugin-polyfill-regenerator/@babel/helper-define-polyfill-provider/resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], + + "@librechat/client/@babel/preset-env/core-js-compat/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=="], + + "@librechat/client/@babel/preset-typescript/@babel/plugin-transform-typescript/@babel/helper-create-class-features-plugin/@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="], + + "@librechat/client/@babel/preset-typescript/@babel/plugin-transform-typescript/@babel/helper-create-class-features-plugin/@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="], + + "@librechat/client/@babel/preset-typescript/@babel/plugin-transform-typescript/@babel/helper-create-class-features-plugin/@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.27.1", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-async-generator-functions/@babel/helper-remap-async-to-generator/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-async-generator-functions/@babel/helper-remap-async-to-generator/@babel/helper-wrap-function": ["@babel/helper-wrap-function@7.28.3", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.3", "@babel/types": "^7.28.2" } }, "sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-async-to-generator/@babel/helper-remap-async-to-generator/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-async-to-generator/@babel/helper-remap-async-to-generator/@babel/helper-wrap-function": ["@babel/helper-wrap-function@7.28.3", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.3", "@babel/types": "^7.28.2" } }, "sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-class-properties/@babel/helper-create-class-features-plugin/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-class-properties/@babel/helper-create-class-features-plugin/@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-class-properties/@babel/helper-create-class-features-plugin/@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-class-properties/@babel/helper-create-class-features-plugin/@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.27.1", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-class-properties/@babel/helper-create-class-features-plugin/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-class-static-block/@babel/helper-create-class-features-plugin/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-class-static-block/@babel/helper-create-class-features-plugin/@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-class-static-block/@babel/helper-create-class-features-plugin/@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-class-static-block/@babel/helper-create-class-features-plugin/@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.27.1", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-class-static-block/@babel/helper-create-class-features-plugin/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-classes/@babel/helper-replace-supers/@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-classes/@babel/helper-replace-supers/@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-dotall-regex/@babel/helper-create-regexp-features-plugin/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-dotall-regex/@babel/helper-create-regexp-features-plugin/regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-duplicate-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-duplicate-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-object-super/@babel/helper-replace-supers/@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-object-super/@babel/helper-replace-supers/@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-private-methods/@babel/helper-create-class-features-plugin/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-private-methods/@babel/helper-create-class-features-plugin/@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-private-methods/@babel/helper-create-class-features-plugin/@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-private-methods/@babel/helper-create-class-features-plugin/@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.27.1", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-private-methods/@babel/helper-create-class-features-plugin/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-private-property-in-object/@babel/helper-create-class-features-plugin/@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-private-property-in-object/@babel/helper-create-class-features-plugin/@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-private-property-in-object/@babel/helper-create-class-features-plugin/@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.27.1", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-private-property-in-object/@babel/helper-create-class-features-plugin/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-regexp-modifiers/@babel/helper-create-regexp-features-plugin/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-regexp-modifiers/@babel/helper-create-regexp-features-plugin/regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-unicode-property-regex/@babel/helper-create-regexp-features-plugin/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-unicode-property-regex/@babel/helper-create-regexp-features-plugin/regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-unicode-regex/@babel/helper-create-regexp-features-plugin/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-unicode-regex/@babel/helper-create-regexp-features-plugin/regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-unicode-sets-regex/@babel/helper-create-regexp-features-plugin/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-unicode-sets-regex/@babel/helper-create-regexp-features-plugin/regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="], + + "@librechat/frontend/@babel/preset-env/babel-plugin-polyfill-corejs2/@babel/helper-define-polyfill-provider/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "@librechat/frontend/@babel/preset-env/babel-plugin-polyfill-corejs2/@babel/helper-define-polyfill-provider/resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], + + "@librechat/frontend/@babel/preset-env/babel-plugin-polyfill-corejs3/@babel/helper-define-polyfill-provider/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "@librechat/frontend/@babel/preset-env/babel-plugin-polyfill-corejs3/@babel/helper-define-polyfill-provider/resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], + + "@librechat/frontend/@babel/preset-env/babel-plugin-polyfill-regenerator/@babel/helper-define-polyfill-provider/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "@librechat/frontend/@babel/preset-env/babel-plugin-polyfill-regenerator/@babel/helper-define-polyfill-provider/resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], + + "@librechat/frontend/@babel/preset-env/core-js-compat/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=="], + + "@librechat/frontend/@babel/preset-typescript/@babel/plugin-transform-typescript/@babel/helper-create-class-features-plugin/@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="], + + "@librechat/frontend/@babel/preset-typescript/@babel/plugin-transform-typescript/@babel/helper-create-class-features-plugin/@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="], + + "@librechat/frontend/@babel/preset-typescript/@babel/plugin-transform-typescript/@babel/helper-create-class-features-plugin/@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.27.1", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA=="], "@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=="], + "@librechat/frontend/jest-environment-jsdom/@jest/types/@jest/schemas/@sinclair/typebox": ["@sinclair/typebox@0.27.8", "", {}, "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="], - "@opentelemetry/exporter-trace-otlp-http/@opentelemetry/otlp-transformer/protobufjs/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], + "@librechat/frontend/jest-environment-jsdom/jsdom/cssstyle/cssom": ["cssom@0.3.8", "", {}, "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg=="], + + "@librechat/frontend/jest-environment-jsdom/jsdom/http-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], + + "@librechat/frontend/jest-environment-jsdom/jsdom/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], + + "@librechat/frontend/jest-environment-jsdom/jsdom/tough-cookie/punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "@librechat/frontend/jest-environment-jsdom/jsdom/tough-cookie/universalify": ["universalify@0.2.0", "", {}, "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="], + + "@librechat/frontend/jest-environment-jsdom/jsdom/whatwg-url/tr46": ["tr46@3.0.0", "", { "dependencies": { "punycode": "^2.1.1" } }, "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA=="], "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=="], + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-async-generator-functions/@babel/helper-remap-async-to-generator/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-async-generator-functions/@babel/helper-remap-async-to-generator/@babel/helper-wrap-function": ["@babel/helper-wrap-function@7.28.3", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.3", "@babel/types": "^7.28.2" } }, "sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-async-to-generator/@babel/helper-remap-async-to-generator/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-async-to-generator/@babel/helper-remap-async-to-generator/@babel/helper-wrap-function": ["@babel/helper-wrap-function@7.28.3", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.3", "@babel/types": "^7.28.2" } }, "sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-class-properties/@babel/helper-create-class-features-plugin/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-class-properties/@babel/helper-create-class-features-plugin/@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-class-properties/@babel/helper-create-class-features-plugin/@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-class-properties/@babel/helper-create-class-features-plugin/@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.27.1", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-class-properties/@babel/helper-create-class-features-plugin/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-class-static-block/@babel/helper-create-class-features-plugin/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-class-static-block/@babel/helper-create-class-features-plugin/@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-class-static-block/@babel/helper-create-class-features-plugin/@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-class-static-block/@babel/helper-create-class-features-plugin/@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.27.1", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-class-static-block/@babel/helper-create-class-features-plugin/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-classes/@babel/helper-replace-supers/@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-classes/@babel/helper-replace-supers/@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-dotall-regex/@babel/helper-create-regexp-features-plugin/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-dotall-regex/@babel/helper-create-regexp-features-plugin/regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-duplicate-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-duplicate-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-object-super/@babel/helper-replace-supers/@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-object-super/@babel/helper-replace-supers/@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-private-methods/@babel/helper-create-class-features-plugin/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-private-methods/@babel/helper-create-class-features-plugin/@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-private-methods/@babel/helper-create-class-features-plugin/@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-private-methods/@babel/helper-create-class-features-plugin/@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.27.1", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-private-methods/@babel/helper-create-class-features-plugin/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-private-property-in-object/@babel/helper-create-class-features-plugin/@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-private-property-in-object/@babel/helper-create-class-features-plugin/@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-private-property-in-object/@babel/helper-create-class-features-plugin/@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.27.1", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-private-property-in-object/@babel/helper-create-class-features-plugin/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-regexp-modifiers/@babel/helper-create-regexp-features-plugin/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-regexp-modifiers/@babel/helper-create-regexp-features-plugin/regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-unicode-property-regex/@babel/helper-create-regexp-features-plugin/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-unicode-property-regex/@babel/helper-create-regexp-features-plugin/regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-unicode-regex/@babel/helper-create-regexp-features-plugin/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-unicode-regex/@babel/helper-create-regexp-features-plugin/regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-unicode-sets-regex/@babel/helper-create-regexp-features-plugin/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-unicode-sets-regex/@babel/helper-create-regexp-features-plugin/regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="], + + "librechat-data-provider/@babel/preset-env/babel-plugin-polyfill-corejs2/@babel/helper-define-polyfill-provider/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "librechat-data-provider/@babel/preset-env/babel-plugin-polyfill-corejs2/@babel/helper-define-polyfill-provider/resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], + + "librechat-data-provider/@babel/preset-env/babel-plugin-polyfill-corejs3/@babel/helper-define-polyfill-provider/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "librechat-data-provider/@babel/preset-env/babel-plugin-polyfill-corejs3/@babel/helper-define-polyfill-provider/resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], + + "librechat-data-provider/@babel/preset-env/babel-plugin-polyfill-regenerator/@babel/helper-define-polyfill-provider/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "librechat-data-provider/@babel/preset-env/babel-plugin-polyfill-regenerator/@babel/helper-define-polyfill-provider/resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], + + "librechat-data-provider/@babel/preset-env/core-js-compat/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=="], + + "librechat-data-provider/@babel/preset-typescript/@babel/plugin-transform-typescript/@babel/helper-create-class-features-plugin/@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="], + + "librechat-data-provider/@babel/preset-typescript/@babel/plugin-transform-typescript/@babel/helper-create-class-features-plugin/@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="], + + "librechat-data-provider/@babel/preset-typescript/@babel/plugin-transform-typescript/@babel/helper-create-class-features-plugin/@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.27.1", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA=="], "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=="], + "workbox-build/@babel/preset-env/@babel/plugin-transform-async-generator-functions/@babel/helper-remap-async-to-generator/@babel/helper-wrap-function": ["@babel/helper-wrap-function@7.22.20", "", { "dependencies": { "@babel/helper-function-name": "^7.22.5", "@babel/template": "^7.22.15", "@babel/types": "^7.22.19" } }, "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-async-to-generator/@babel/helper-remap-async-to-generator/@babel/helper-wrap-function": ["@babel/helper-wrap-function@7.22.20", "", { "dependencies": { "@babel/helper-function-name": "^7.22.5", "@babel/template": "^7.22.15", "@babel/types": "^7.22.19" } }, "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-class-properties/@babel/helper-create-class-features-plugin/@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.23.0", "", { "dependencies": { "@babel/types": "^7.23.0" } }, "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-class-properties/@babel/helper-create-class-features-plugin/@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.22.5", "", { "dependencies": { "@babel/types": "^7.22.5" } }, "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-class-properties/@babel/helper-create-class-features-plugin/@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.22.20", "", { "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-member-expression-to-functions": "^7.22.15", "@babel/helper-optimise-call-expression": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-class-properties/@babel/helper-create-class-features-plugin/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.22.5", "", { "dependencies": { "@babel/types": "^7.22.5" } }, "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-class-static-block/@babel/helper-create-class-features-plugin/@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.23.0", "", { "dependencies": { "@babel/types": "^7.23.0" } }, "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-class-static-block/@babel/helper-create-class-features-plugin/@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.22.5", "", { "dependencies": { "@babel/types": "^7.22.5" } }, "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-class-static-block/@babel/helper-create-class-features-plugin/@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.22.20", "", { "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-member-expression-to-functions": "^7.22.15", "@babel/helper-optimise-call-expression": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-class-static-block/@babel/helper-create-class-features-plugin/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.22.5", "", { "dependencies": { "@babel/types": "^7.22.5" } }, "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-classes/@babel/helper-replace-supers/@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.23.0", "", { "dependencies": { "@babel/types": "^7.23.0" } }, "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-classes/@babel/helper-replace-supers/@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.22.5", "", { "dependencies": { "@babel/types": "^7.22.5" } }, "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-object-super/@babel/helper-replace-supers/@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.23.0", "", { "dependencies": { "@babel/types": "^7.23.0" } }, "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-object-super/@babel/helper-replace-supers/@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.22.5", "", { "dependencies": { "@babel/types": "^7.22.5" } }, "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-private-methods/@babel/helper-create-class-features-plugin/@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.23.0", "", { "dependencies": { "@babel/types": "^7.23.0" } }, "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-private-methods/@babel/helper-create-class-features-plugin/@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.22.5", "", { "dependencies": { "@babel/types": "^7.22.5" } }, "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-private-methods/@babel/helper-create-class-features-plugin/@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.22.20", "", { "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-member-expression-to-functions": "^7.22.15", "@babel/helper-optimise-call-expression": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-private-methods/@babel/helper-create-class-features-plugin/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.22.5", "", { "dependencies": { "@babel/types": "^7.22.5" } }, "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-private-property-in-object/@babel/helper-create-class-features-plugin/@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.23.0", "", { "dependencies": { "@babel/types": "^7.23.0" } }, "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-private-property-in-object/@babel/helper-create-class-features-plugin/@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.22.5", "", { "dependencies": { "@babel/types": "^7.22.5" } }, "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-private-property-in-object/@babel/helper-create-class-features-plugin/@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.22.20", "", { "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-member-expression-to-functions": "^7.22.15", "@babel/helper-optimise-call-expression": "^7.22.5" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw=="], + + "workbox-build/@babel/preset-env/@babel/plugin-transform-private-property-in-object/@babel/helper-create-class-features-plugin/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.22.5", "", { "dependencies": { "@babel/types": "^7.22.5" } }, "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q=="], + + "workbox-build/@babel/preset-env/babel-plugin-polyfill-corejs2/@babel/helper-define-polyfill-provider/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=="], + + "workbox-build/@babel/preset-env/babel-plugin-polyfill-corejs3/@babel/helper-define-polyfill-provider/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=="], + + "workbox-build/@babel/preset-env/babel-plugin-polyfill-regenerator/@babel/helper-define-polyfill-provider/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=="], + + "workbox-build/@babel/preset-env/core-js-compat/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=="], + "workbox-build/source-map/whatwg-url/tr46/punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], "@aws-sdk/client-bedrock-agent-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/util-stream/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], "@aws-sdk/client-kendra/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/util-stream/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - "@aws-sdk/middleware-flexible-checksums/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/url-parser/@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-nE3IRNjDltvGcoThD2abTozI1dkSy8aX+a2N1Rs55en5UsdyyIXgGEmevUL3okZFoJC77JgRGe99xYohhsjivQ=="], + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@smithy/util-stream/@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "@smithy/util-uri-escape": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-wU87iWZoCbcqrwszsOewEIuq+SU2mSoBE2CcsLwE0I19m0B2gOJr1MVjxWcDQYOzHbR1xCk7AcOBbGFUYOKvdg=="], + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/url-parser/@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-YmWxl32SQRw/kIRccSOxzS/Ib8/b5/f9ex0r5PR40jRJg8X1wgM3KrR2In+8zvOGVhRSXgvyQpw9yOSlmfmSnA=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@smithy/util-stream/@smithy/node-http-handler/@smithy/abort-controller": ["@smithy/abort-controller@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-fiUIYgIgRjMWznk6iLJz35K2YxSLHzLBA/RC6lBrKfQ8fHbPfvk7Pk9UvpKoHgJjI18MnbPuEju53zcVy6KF1g=="], + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@smithy/util-stream/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "@smithy/util-uri-escape": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-wU87iWZoCbcqrwszsOewEIuq+SU2mSoBE2CcsLwE0I19m0B2gOJr1MVjxWcDQYOzHbR1xCk7AcOBbGFUYOKvdg=="], + "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/node-http-handler/@smithy/abort-controller": ["@smithy/abort-controller@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-fiUIYgIgRjMWznk6iLJz35K2YxSLHzLBA/RC6lBrKfQ8fHbPfvk7Pk9UvpKoHgJjI18MnbPuEju53zcVy6KF1g=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@smithy/util-stream/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw=="], + "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "@smithy/util-uri-escape": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-wU87iWZoCbcqrwszsOewEIuq+SU2mSoBE2CcsLwE0I19m0B2gOJr1MVjxWcDQYOzHbR1xCk7AcOBbGFUYOKvdg=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw=="], + "@aws-sdk/token-providers/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - "@aws-sdk/s3-request-presigner/@smithy/middleware-endpoint/@smithy/core/@smithy/util-stream/@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "@smithy/util-uri-escape": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-wU87iWZoCbcqrwszsOewEIuq+SU2mSoBE2CcsLwE0I19m0B2gOJr1MVjxWcDQYOzHbR1xCk7AcOBbGFUYOKvdg=="], + "@aws-sdk/token-providers/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/url-parser/@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.6", "", { "dependencies": { "@smithy/types": "^4.10.0", "tslib": "^2.6.2" } }, "sha512-YmWxl32SQRw/kIRccSOxzS/Ib8/b5/f9ex0r5PR40jRJg8X1wgM3KrR2In+8zvOGVhRSXgvyQpw9yOSlmfmSnA=="], - "@aws-sdk/s3-request-presigner/@smithy/middleware-endpoint/@smithy/core/@smithy/util-stream/@smithy/node-http-handler/@smithy/abort-controller": ["@smithy/abort-controller@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-fiUIYgIgRjMWznk6iLJz35K2YxSLHzLBA/RC6lBrKfQ8fHbPfvk7Pk9UvpKoHgJjI18MnbPuEju53zcVy6KF1g=="], + "@aws-sdk/token-providers/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - "@aws-sdk/s3-request-presigner/@smithy/middleware-endpoint/@smithy/core/@smithy/util-stream/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "@smithy/util-uri-escape": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-wU87iWZoCbcqrwszsOewEIuq+SU2mSoBE2CcsLwE0I19m0B2gOJr1MVjxWcDQYOzHbR1xCk7AcOBbGFUYOKvdg=="], + "@langchain/aws/@aws-sdk/client-bedrock-runtime/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.1", "", {}, "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw=="], - "@aws-sdk/s3-request-presigner/@smithy/middleware-endpoint/@smithy/core/@smithy/util-stream/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw=="], - - "@aws-sdk/s3-request-presigner/@smithy/middleware-endpoint/@smithy/core/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw=="], - - "@aws-sdk/s3-request-presigner/@smithy/smithy-client/@smithy/core/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw=="], - - "@aws-sdk/s3-request-presigner/@smithy/smithy-client/@smithy/util-stream/@smithy/fetch-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=="], - - "@aws-sdk/s3-request-presigner/@smithy/smithy-client/@smithy/util-stream/@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=="], - - "@google/genai/google-auth-library/gaxios/rimraf/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - - "@google/genai/google-auth-library/gaxios/rimraf/glob/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], - - "@google/genai/google-auth-library/gaxios/rimraf/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=="], - - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@aws-sdk/middleware-websocket/@aws-sdk/util-format-url/@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=="], - - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@smithy/eventstream-serde-browser/@smithy/eventstream-serde-universal/@smithy/eventstream-codec/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - - "@langchain/aws/@aws-sdk/client-bedrock-runtime/@smithy/eventstream-serde-node/@smithy/eventstream-serde-universal/@smithy/eventstream-codec/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], + "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env/@aws-sdk/core/@smithy/core/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.4", "", { "dependencies": { "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-jUr3x2CDhV15TOX2/Uoz4gfgeqLrRoTQbYAuhLS7lcVKNev7FeYSJ1ebEfjk+l9kbb7k7LfzIR/irgxys5ZTOg=="], @@ -9453,14 +9175,6 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env/@aws-sdk/core/@smithy/core/@smithy/util-stream": ["@smithy/util-stream@4.5.5", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.5", "@smithy/node-http-handler": "^4.4.4", "@smithy/types": "^4.8.1", "@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", "tslib": "^2.6.2" } }, "sha512-7M5aVFjT+HPilPOKbOmQfCIPchZe4DSBc1wf1+NvHvSoFTiFtauZzT+onZvCj70xhXd0AEmYnZYmdJIuwxOo4w=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env/@aws-sdk/core/@smithy/core/@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env/@aws-sdk/core/@smithy/signature-v4/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env/@aws-sdk/core/@smithy/signature-v4/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env/@aws-sdk/core/@smithy/signature-v4/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.3.6", "", { "dependencies": { "@smithy/core": "^3.17.2", "@smithy/middleware-serde": "^4.2.4", "@smithy/node-config-provider": "^4.3.4", "@smithy/shared-ini-file-loader": "^4.3.4", "@smithy/types": "^4.8.1", "@smithy/url-parser": "^4.2.4", "@smithy/util-middleware": "^4.2.4", "tslib": "^2.6.2" } }, "sha512-PXehXofGMFpDqr933rxD8RGOcZ0QBAWtuzTgYRAHAL2BnKawHDEdf/TnGpcmfPJGwonhginaaeJIKluEojiF/w=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-Gy3TKCOnm9JwpFooldwAboazw+EFYlC+Bb+1QBsSi5xI0W5lX81j/P5+CXvD/9ZjtYKRgxq+kkqd/KOHflzvgA=="], @@ -9471,66 +9185,44 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env/@aws-sdk/core/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="], + "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@aws-sdk/core/@smithy/core/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.4", "", { "dependencies": { "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-jUr3x2CDhV15TOX2/Uoz4gfgeqLrRoTQbYAuhLS7lcVKNev7FeYSJ1ebEfjk+l9kbb7k7LfzIR/irgxys5ZTOg=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@aws-sdk/core/@smithy/core/@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@aws-sdk/core/@smithy/core/@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@aws-sdk/core/@smithy/signature-v4/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@aws-sdk/core/@smithy/signature-v4/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@aws-sdk/core/@smithy/signature-v4/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@aws-sdk/core/@smithy/util-base64/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@aws-sdk/core/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/fetch-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=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/fetch-http-handler/@smithy/util-base64/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/fetch-http-handler/@smithy/util-base64/@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@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=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/smithy-client/@smithy/core/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.4", "", { "dependencies": { "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-jUr3x2CDhV15TOX2/Uoz4gfgeqLrRoTQbYAuhLS7lcVKNev7FeYSJ1ebEfjk+l9kbb7k7LfzIR/irgxys5ZTOg=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/smithy-client/@smithy/core/@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/smithy-client/@smithy/core/@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/smithy-client/@smithy/core/@smithy/util-middleware": ["@smithy/util-middleware@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-fKGQAPAn8sgV0plRikRVo6g6aR0KyKvgzNrPuM74RZKy/wWVzx3BMk+ZWEueyN3L5v5EDg+P582mKU+sH5OAsg=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/smithy-client/@smithy/core/@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/smithy-client/@smithy/core/@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.4", "", { "dependencies": { "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-jUr3x2CDhV15TOX2/Uoz4gfgeqLrRoTQbYAuhLS7lcVKNev7FeYSJ1ebEfjk+l9kbb7k7LfzIR/irgxys5ZTOg=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.4", "", { "dependencies": { "@smithy/property-provider": "^4.2.4", "@smithy/shared-ini-file-loader": "^4.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-3X3w7qzmo4XNNdPKNS4nbJcGSwiEMsNsRSunMA92S4DJLLIrH5g1AyuOA2XKM9PAPi8mIWfqC+fnfKNsI4KvHw=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/url-parser": ["@smithy/url-parser@4.2.4", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-w/N/Iw0/PTwJ36PDqU9PzAwVElo4qXxCC0eCTlUtIz/Z5V/2j/cViMHi0hPukSBHp4DVwvUlUhLgCzqSJ6plrg=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/util-middleware": ["@smithy/util-middleware@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-fKGQAPAn8sgV0plRikRVo6g6aR0KyKvgzNrPuM74RZKy/wWVzx3BMk+ZWEueyN3L5v5EDg+P582mKU+sH5OAsg=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/util-stream/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="], + "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/core/@smithy/core/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.4", "", { "dependencies": { "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-jUr3x2CDhV15TOX2/Uoz4gfgeqLrRoTQbYAuhLS7lcVKNev7FeYSJ1ebEfjk+l9kbb7k7LfzIR/irgxys5ZTOg=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/core/@smithy/core/@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/core/@smithy/core/@smithy/util-stream": ["@smithy/util-stream@4.5.5", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.5", "@smithy/node-http-handler": "^4.4.4", "@smithy/types": "^4.8.1", "@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", "tslib": "^2.6.2" } }, "sha512-7M5aVFjT+HPilPOKbOmQfCIPchZe4DSBc1wf1+NvHvSoFTiFtauZzT+onZvCj70xhXd0AEmYnZYmdJIuwxOo4w=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/core/@smithy/core/@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/core/@smithy/signature-v4/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/core/@smithy/signature-v4/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/core/@smithy/signature-v4/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.3.6", "", { "dependencies": { "@smithy/core": "^3.17.2", "@smithy/middleware-serde": "^4.2.4", "@smithy/node-config-provider": "^4.3.4", "@smithy/shared-ini-file-loader": "^4.3.4", "@smithy/types": "^4.8.1", "@smithy/url-parser": "^4.2.4", "@smithy/util-middleware": "^4.2.4", "tslib": "^2.6.2" } }, "sha512-PXehXofGMFpDqr933rxD8RGOcZ0QBAWtuzTgYRAHAL2BnKawHDEdf/TnGpcmfPJGwonhginaaeJIKluEojiF/w=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-Gy3TKCOnm9JwpFooldwAboazw+EFYlC+Bb+1QBsSi5xI0W5lX81j/P5+CXvD/9ZjtYKRgxq+kkqd/KOHflzvgA=="], @@ -9547,16 +9239,12 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@smithy/core/@smithy/util-stream": ["@smithy/util-stream@4.5.5", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.5", "@smithy/node-http-handler": "^4.4.4", "@smithy/types": "^4.8.1", "@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", "tslib": "^2.6.2" } }, "sha512-7M5aVFjT+HPilPOKbOmQfCIPchZe4DSBc1wf1+NvHvSoFTiFtauZzT+onZvCj70xhXd0AEmYnZYmdJIuwxOo4w=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@smithy/core/@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-KQ1gFXXC+WsbPFnk7pzskzOpn4s+KheWgO3dzkIEmnb6NskAIGp/dGdbKisTPJdtov28qNDohQrgDUKzXZBLig=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@smithy/hash-node/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@smithy/middleware-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1" } }, "sha512-fdWuhEx4+jHLGeew9/IvqVU/fxT/ot70tpRGuOLxE3HzZOyKeTQfYeV1oaBXpzi93WOk668hjMuuagJ2/Qs7ng=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@smithy/middleware-retry/@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@smithy/node-http-handler/@smithy/abort-controller": ["@smithy/abort-controller@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-Z4DUr/AkgyFf1bOThW2HwzREagee0sB5ycl+hDiSZOfRLW8ZgrOjDi6g8mHH19yyU5E2A/64W3z6SMIf5XiUSQ=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-KQ1gFXXC+WsbPFnk7pzskzOpn4s+KheWgO3dzkIEmnb6NskAIGp/dGdbKisTPJdtov28qNDohQrgDUKzXZBLig=="], @@ -9571,20 +9259,14 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="], + "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process/@aws-sdk/core/@smithy/core/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.4", "", { "dependencies": { "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-jUr3x2CDhV15TOX2/Uoz4gfgeqLrRoTQbYAuhLS7lcVKNev7FeYSJ1ebEfjk+l9kbb7k7LfzIR/irgxys5ZTOg=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process/@aws-sdk/core/@smithy/core/@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process/@aws-sdk/core/@smithy/core/@smithy/util-stream": ["@smithy/util-stream@4.5.5", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.5", "@smithy/node-http-handler": "^4.4.4", "@smithy/types": "^4.8.1", "@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", "tslib": "^2.6.2" } }, "sha512-7M5aVFjT+HPilPOKbOmQfCIPchZe4DSBc1wf1+NvHvSoFTiFtauZzT+onZvCj70xhXd0AEmYnZYmdJIuwxOo4w=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process/@aws-sdk/core/@smithy/core/@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process/@aws-sdk/core/@smithy/signature-v4/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process/@aws-sdk/core/@smithy/signature-v4/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process/@aws-sdk/core/@smithy/signature-v4/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.3.6", "", { "dependencies": { "@smithy/core": "^3.17.2", "@smithy/middleware-serde": "^4.2.4", "@smithy/node-config-provider": "^4.3.4", "@smithy/shared-ini-file-loader": "^4.3.4", "@smithy/types": "^4.8.1", "@smithy/url-parser": "^4.2.4", "@smithy/util-middleware": "^4.2.4", "tslib": "^2.6.2" } }, "sha512-PXehXofGMFpDqr933rxD8RGOcZ0QBAWtuzTgYRAHAL2BnKawHDEdf/TnGpcmfPJGwonhginaaeJIKluEojiF/w=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-Gy3TKCOnm9JwpFooldwAboazw+EFYlC+Bb+1QBsSi5xI0W5lX81j/P5+CXvD/9ZjtYKRgxq+kkqd/KOHflzvgA=="], @@ -9601,16 +9283,12 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso/@smithy/core/@smithy/util-stream": ["@smithy/util-stream@4.5.5", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.5", "@smithy/node-http-handler": "^4.4.4", "@smithy/types": "^4.8.1", "@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", "tslib": "^2.6.2" } }, "sha512-7M5aVFjT+HPilPOKbOmQfCIPchZe4DSBc1wf1+NvHvSoFTiFtauZzT+onZvCj70xhXd0AEmYnZYmdJIuwxOo4w=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso/@smithy/core/@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso/@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-KQ1gFXXC+WsbPFnk7pzskzOpn4s+KheWgO3dzkIEmnb6NskAIGp/dGdbKisTPJdtov28qNDohQrgDUKzXZBLig=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso/@smithy/hash-node/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso/@smithy/middleware-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1" } }, "sha512-fdWuhEx4+jHLGeew9/IvqVU/fxT/ot70tpRGuOLxE3HzZOyKeTQfYeV1oaBXpzi93WOk668hjMuuagJ2/Qs7ng=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso/@smithy/middleware-retry/@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso/@smithy/node-http-handler/@smithy/abort-controller": ["@smithy/abort-controller@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-Z4DUr/AkgyFf1bOThW2HwzREagee0sB5ycl+hDiSZOfRLW8ZgrOjDi6g8mHH19yyU5E2A/64W3z6SMIf5XiUSQ=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-KQ1gFXXC+WsbPFnk7pzskzOpn4s+KheWgO3dzkIEmnb6NskAIGp/dGdbKisTPJdtov28qNDohQrgDUKzXZBLig=="], @@ -9625,20 +9303,14 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="], + "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/core/@smithy/core/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.4", "", { "dependencies": { "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-jUr3x2CDhV15TOX2/Uoz4gfgeqLrRoTQbYAuhLS7lcVKNev7FeYSJ1ebEfjk+l9kbb7k7LfzIR/irgxys5ZTOg=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/core/@smithy/core/@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/core/@smithy/core/@smithy/util-stream": ["@smithy/util-stream@4.5.5", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.5", "@smithy/node-http-handler": "^4.4.4", "@smithy/types": "^4.8.1", "@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", "tslib": "^2.6.2" } }, "sha512-7M5aVFjT+HPilPOKbOmQfCIPchZe4DSBc1wf1+NvHvSoFTiFtauZzT+onZvCj70xhXd0AEmYnZYmdJIuwxOo4w=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/core/@smithy/core/@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/core/@smithy/signature-v4/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/core/@smithy/signature-v4/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/core/@smithy/signature-v4/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.3.6", "", { "dependencies": { "@smithy/core": "^3.17.2", "@smithy/middleware-serde": "^4.2.4", "@smithy/node-config-provider": "^4.3.4", "@smithy/shared-ini-file-loader": "^4.3.4", "@smithy/types": "^4.8.1", "@smithy/url-parser": "^4.2.4", "@smithy/util-middleware": "^4.2.4", "tslib": "^2.6.2" } }, "sha512-PXehXofGMFpDqr933rxD8RGOcZ0QBAWtuzTgYRAHAL2BnKawHDEdf/TnGpcmfPJGwonhginaaeJIKluEojiF/w=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-Gy3TKCOnm9JwpFooldwAboazw+EFYlC+Bb+1QBsSi5xI0W5lX81j/P5+CXvD/9ZjtYKRgxq+kkqd/KOHflzvgA=="], @@ -9689,8 +9361,6 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.4", "", { "dependencies": { "@smithy/abort-controller": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/querystring-builder": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-VXHGfzCXLZeKnFp6QXjAdy+U8JF9etfpUXD1FAbzY1GzsFJiDQRQIt2CnMUvUdz3/YaHNqT3RphVWMUpXTIODA=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/protocol-http": ["@smithy/protocol-http@5.3.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-3sfFd2MAzVt0Q/klOmjFi3oIkxczHs0avbwrfn1aBqtc23WqQSmjvk77MBw9WkEQcwbOYIX5/2z4ULj8DuxSsw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/smithy-client": ["@smithy/smithy-client@4.9.2", "", { "dependencies": { "@smithy/core": "^3.17.2", "@smithy/middleware-endpoint": "^4.3.6", "@smithy/middleware-stack": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-stream": "^4.5.5", "tslib": "^2.6.2" } }, "sha512-gZU4uAFcdrSi3io8U99Qs/FvVdRxPvIMToi+MFfsy/DN9UqtknJ1ais+2M9yR8e0ASQpNmFYEKeIKVcMjQg3rg=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/url-parser": ["@smithy/url-parser@4.2.4", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-w/N/Iw0/PTwJ36PDqU9PzAwVElo4qXxCC0eCTlUtIz/Z5V/2j/cViMHi0hPukSBHp4DVwvUlUhLgCzqSJ6plrg=="], @@ -9707,26 +9377,18 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.4", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-f+nBDhgYRCmUEDKEQb6q0aCcOTXRDqH5wWaFHJxt4anB4pKHlgGoYP3xtioKXH64e37ANUkzWf6p4Mnv1M5/Vg=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/util-middleware": ["@smithy/util-middleware@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-fKGQAPAn8sgV0plRikRVo6g6aR0KyKvgzNrPuM74RZKy/wWVzx3BMk+ZWEueyN3L5v5EDg+P582mKU+sH5OAsg=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/util-retry": ["@smithy/util-retry@4.2.4", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-yQncJmj4dtv/isTXxRb4AamZHy4QFr4ew8GxS6XLWt7sCIxkPxPzINWd7WLISEFPsIan14zrKgvyAF+/yzfwoA=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], + "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="], + "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@smithy/core/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.4", "", { "dependencies": { "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-jUr3x2CDhV15TOX2/Uoz4gfgeqLrRoTQbYAuhLS7lcVKNev7FeYSJ1ebEfjk+l9kbb7k7LfzIR/irgxys5ZTOg=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@smithy/core/@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@smithy/core/@smithy/util-stream": ["@smithy/util-stream@4.5.5", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.5", "@smithy/node-http-handler": "^4.4.4", "@smithy/types": "^4.8.1", "@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", "tslib": "^2.6.2" } }, "sha512-7M5aVFjT+HPilPOKbOmQfCIPchZe4DSBc1wf1+NvHvSoFTiFtauZzT+onZvCj70xhXd0AEmYnZYmdJIuwxOo4w=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@smithy/core/@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@smithy/signature-v4/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@smithy/signature-v4/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@smithy/signature-v4/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.3.6", "", { "dependencies": { "@smithy/core": "^3.17.2", "@smithy/middleware-serde": "^4.2.4", "@smithy/node-config-provider": "^4.3.4", "@smithy/shared-ini-file-loader": "^4.3.4", "@smithy/types": "^4.8.1", "@smithy/url-parser": "^4.2.4", "@smithy/util-middleware": "^4.2.4", "tslib": "^2.6.2" } }, "sha512-PXehXofGMFpDqr933rxD8RGOcZ0QBAWtuzTgYRAHAL2BnKawHDEdf/TnGpcmfPJGwonhginaaeJIKluEojiF/w=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-Gy3TKCOnm9JwpFooldwAboazw+EFYlC+Bb+1QBsSi5xI0W5lX81j/P5+CXvD/9ZjtYKRgxq+kkqd/KOHflzvgA=="], @@ -9743,16 +9405,12 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@smithy/core/@smithy/util-stream": ["@smithy/util-stream@4.5.5", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.5", "@smithy/node-http-handler": "^4.4.4", "@smithy/types": "^4.8.1", "@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", "tslib": "^2.6.2" } }, "sha512-7M5aVFjT+HPilPOKbOmQfCIPchZe4DSBc1wf1+NvHvSoFTiFtauZzT+onZvCj70xhXd0AEmYnZYmdJIuwxOo4w=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@smithy/core/@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-KQ1gFXXC+WsbPFnk7pzskzOpn4s+KheWgO3dzkIEmnb6NskAIGp/dGdbKisTPJdtov28qNDohQrgDUKzXZBLig=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@smithy/hash-node/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@smithy/middleware-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1" } }, "sha512-fdWuhEx4+jHLGeew9/IvqVU/fxT/ot70tpRGuOLxE3HzZOyKeTQfYeV1oaBXpzi93WOk668hjMuuagJ2/Qs7ng=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@smithy/middleware-retry/@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@smithy/node-http-handler/@smithy/abort-controller": ["@smithy/abort-controller@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-Z4DUr/AkgyFf1bOThW2HwzREagee0sB5ycl+hDiSZOfRLW8ZgrOjDi6g8mHH19yyU5E2A/64W3z6SMIf5XiUSQ=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-KQ1gFXXC+WsbPFnk7pzskzOpn4s+KheWgO3dzkIEmnb6NskAIGp/dGdbKisTPJdtov28qNDohQrgDUKzXZBLig=="], @@ -9769,21 +9427,137 @@ "@langchain/google-gauth/google-auth-library/gaxios/rimraf/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - "@langchain/google-gauth/google-auth-library/gaxios/rimraf/glob/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], - "@langchain/google-gauth/google-auth-library/gaxios/rimraf/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=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@smithy/util-stream/@smithy/fetch-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/client/@babel/preset-env/@babel/plugin-transform-dotall-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="], - "@aws-sdk/s3-request-presigner/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@smithy/util-stream/@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/client/@babel/preset-env/@babel/plugin-transform-dotall-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="], - "@aws-sdk/s3-request-presigner/@smithy/middleware-endpoint/@smithy/core/@smithy/util-stream/@smithy/fetch-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/client/@babel/preset-env/@babel/plugin-transform-dotall-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="], - "@aws-sdk/s3-request-presigner/@smithy/middleware-endpoint/@smithy/core/@smithy/util-stream/@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/client/@babel/preset-env/@babel/plugin-transform-duplicate-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="], - "@google/genai/google-auth-library/gaxios/rimraf/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-duplicate-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="], - "@google/genai/google-auth-library/gaxios/rimraf/glob/path-scurry/lru-cache": ["lru-cache@10.2.0", "", {}, "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q=="], + "@librechat/client/@babel/preset-env/@babel/plugin-transform-duplicate-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-regexp-modifiers/@babel/helper-create-regexp-features-plugin/regexpu-core/regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-regexp-modifiers/@babel/helper-create-regexp-features-plugin/regexpu-core/regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-regexp-modifiers/@babel/helper-create-regexp-features-plugin/regexpu-core/unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-unicode-property-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-unicode-property-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-unicode-property-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-unicode-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-unicode-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-unicode-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-unicode-sets-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-unicode-sets-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="], + + "@librechat/client/@babel/preset-env/@babel/plugin-transform-unicode-sets-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-dotall-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-dotall-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-dotall-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-duplicate-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-duplicate-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-duplicate-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-regexp-modifiers/@babel/helper-create-regexp-features-plugin/regexpu-core/regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-regexp-modifiers/@babel/helper-create-regexp-features-plugin/regexpu-core/regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-regexp-modifiers/@babel/helper-create-regexp-features-plugin/regexpu-core/unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-unicode-property-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-unicode-property-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-unicode-property-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-unicode-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-unicode-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-unicode-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-unicode-sets-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-unicode-sets-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="], + + "@librechat/frontend/@babel/preset-env/@babel/plugin-transform-unicode-sets-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="], + + "@librechat/frontend/jest-environment-jsdom/jsdom/whatwg-url/tr46/punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-dotall-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-dotall-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-dotall-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-duplicate-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-duplicate-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-duplicate-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-named-capturing-groups-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-regexp-modifiers/@babel/helper-create-regexp-features-plugin/regexpu-core/regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-regexp-modifiers/@babel/helper-create-regexp-features-plugin/regexpu-core/regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-regexp-modifiers/@babel/helper-create-regexp-features-plugin/regexpu-core/unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-unicode-property-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-unicode-property-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-unicode-property-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-unicode-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-unicode-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-unicode-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-unicode-sets-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-unicode-sets-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="], + + "librechat-data-provider/@babel/preset-env/@babel/plugin-transform-unicode-sets-regex/@babel/helper-create-regexp-features-plugin/regexpu-core/unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="], + + "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.1", "", {}, "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.5", "", { "dependencies": { "@smithy/protocol-http": "^5.3.4", "@smithy/querystring-builder": "^4.2.4", "@smithy/types": "^4.8.1", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-mg83SM3FLI8Sa2ooTJbsh5MFfyMTyNRwxqpKHmE0ICRIa66Aodv80DMsTQI02xBLVJ0hckwqTRr5IGAbbWuFLQ=="], @@ -9791,8 +9565,6 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.4", "", { "dependencies": { "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-jUr3x2CDhV15TOX2/Uoz4gfgeqLrRoTQbYAuhLS7lcVKNev7FeYSJ1ebEfjk+l9kbb7k7LfzIR/irgxys5ZTOg=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/url-parser": ["@smithy/url-parser@4.2.4", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-w/N/Iw0/PTwJ36PDqU9PzAwVElo4qXxCC0eCTlUtIz/Z5V/2j/cViMHi0hPukSBHp4DVwvUlUhLgCzqSJ6plrg=="], @@ -9803,12 +9575,12 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env/@aws-sdk/core/@smithy/util-base64/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env/@aws-sdk/core/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.1", "", {}, "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw=="], + "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@aws-sdk/core/@smithy/util-base64/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@aws-sdk/core/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], @@ -9821,14 +9593,14 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/url-parser/@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-aHb5cqXZocdzEkZ/CvhVjdw5l4r1aU/9iMEyoKzH4eXMowT6M0YjBpp7W/+XjkBnY8Xh0kVd55GKjnPKlCwinQ=="], + "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.1", "", {}, "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw=="], + "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.5", "", { "dependencies": { "@smithy/protocol-http": "^5.3.4", "@smithy/querystring-builder": "^4.2.4", "@smithy/types": "^4.8.1", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-mg83SM3FLI8Sa2ooTJbsh5MFfyMTyNRwxqpKHmE0ICRIa66Aodv80DMsTQI02xBLVJ0hckwqTRr5IGAbbWuFLQ=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.4", "", { "dependencies": { "@smithy/abort-controller": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/querystring-builder": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-VXHGfzCXLZeKnFp6QXjAdy+U8JF9etfpUXD1FAbzY1GzsFJiDQRQIt2CnMUvUdz3/YaHNqT3RphVWMUpXTIODA=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.4", "", { "dependencies": { "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-jUr3x2CDhV15TOX2/Uoz4gfgeqLrRoTQbYAuhLS7lcVKNev7FeYSJ1ebEfjk+l9kbb7k7LfzIR/irgxys5ZTOg=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/url-parser": ["@smithy/url-parser@4.2.4", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-w/N/Iw0/PTwJ36PDqU9PzAwVElo4qXxCC0eCTlUtIz/Z5V/2j/cViMHi0hPukSBHp4DVwvUlUhLgCzqSJ6plrg=="], @@ -9839,38 +9611,28 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/core/@smithy/util-base64/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/core/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@smithy/core/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@smithy/core/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@smithy/fetch-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=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@smithy/hash-node/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@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=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@smithy/smithy-client/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@smithy/smithy-client/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@smithy/util-base64/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.1", "", {}, "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw=="], + "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.5", "", { "dependencies": { "@smithy/protocol-http": "^5.3.4", "@smithy/querystring-builder": "^4.2.4", "@smithy/types": "^4.8.1", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-mg83SM3FLI8Sa2ooTJbsh5MFfyMTyNRwxqpKHmE0ICRIa66Aodv80DMsTQI02xBLVJ0hckwqTRr5IGAbbWuFLQ=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.4", "", { "dependencies": { "@smithy/abort-controller": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/querystring-builder": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-VXHGfzCXLZeKnFp6QXjAdy+U8JF9etfpUXD1FAbzY1GzsFJiDQRQIt2CnMUvUdz3/YaHNqT3RphVWMUpXTIODA=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.4", "", { "dependencies": { "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-jUr3x2CDhV15TOX2/Uoz4gfgeqLrRoTQbYAuhLS7lcVKNev7FeYSJ1ebEfjk+l9kbb7k7LfzIR/irgxys5ZTOg=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/url-parser": ["@smithy/url-parser@4.2.4", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-w/N/Iw0/PTwJ36PDqU9PzAwVElo4qXxCC0eCTlUtIz/Z5V/2j/cViMHi0hPukSBHp4DVwvUlUhLgCzqSJ6plrg=="], @@ -9881,38 +9643,28 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process/@aws-sdk/core/@smithy/util-base64/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process/@aws-sdk/core/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso/@smithy/core/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso/@smithy/core/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso/@smithy/fetch-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=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso/@smithy/hash-node/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso/@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=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso/@smithy/smithy-client/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso/@smithy/smithy-client/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso/@smithy/util-base64/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.1", "", {}, "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw=="], + "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.5", "", { "dependencies": { "@smithy/protocol-http": "^5.3.4", "@smithy/querystring-builder": "^4.2.4", "@smithy/types": "^4.8.1", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-mg83SM3FLI8Sa2ooTJbsh5MFfyMTyNRwxqpKHmE0ICRIa66Aodv80DMsTQI02xBLVJ0hckwqTRr5IGAbbWuFLQ=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.4", "", { "dependencies": { "@smithy/abort-controller": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/querystring-builder": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-VXHGfzCXLZeKnFp6QXjAdy+U8JF9etfpUXD1FAbzY1GzsFJiDQRQIt2CnMUvUdz3/YaHNqT3RphVWMUpXTIODA=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.4", "", { "dependencies": { "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-jUr3x2CDhV15TOX2/Uoz4gfgeqLrRoTQbYAuhLS7lcVKNev7FeYSJ1ebEfjk+l9kbb7k7LfzIR/irgxys5ZTOg=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/url-parser": ["@smithy/url-parser@4.2.4", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-w/N/Iw0/PTwJ36PDqU9PzAwVElo4qXxCC0eCTlUtIz/Z5V/2j/cViMHi0hPukSBHp4DVwvUlUhLgCzqSJ6plrg=="], @@ -9923,8 +9675,6 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/core/@smithy/util-base64/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/core/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], @@ -9935,16 +9685,12 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/core/@smithy/util-stream": ["@smithy/util-stream@4.5.5", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.5", "@smithy/node-http-handler": "^4.4.4", "@smithy/types": "^4.8.1", "@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", "tslib": "^2.6.2" } }, "sha512-7M5aVFjT+HPilPOKbOmQfCIPchZe4DSBc1wf1+NvHvSoFTiFtauZzT+onZvCj70xhXd0AEmYnZYmdJIuwxOo4w=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/core/@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-KQ1gFXXC+WsbPFnk7pzskzOpn4s+KheWgO3dzkIEmnb6NskAIGp/dGdbKisTPJdtov28qNDohQrgDUKzXZBLig=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/hash-node/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/middleware-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1" } }, "sha512-fdWuhEx4+jHLGeew9/IvqVU/fxT/ot70tpRGuOLxE3HzZOyKeTQfYeV1oaBXpzi93WOk668hjMuuagJ2/Qs7ng=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/middleware-retry/@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/node-http-handler/@smithy/abort-controller": ["@smithy/abort-controller@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-Z4DUr/AkgyFf1bOThW2HwzREagee0sB5ycl+hDiSZOfRLW8ZgrOjDi6g8mHH19yyU5E2A/64W3z6SMIf5XiUSQ=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-KQ1gFXXC+WsbPFnk7pzskzOpn4s+KheWgO3dzkIEmnb6NskAIGp/dGdbKisTPJdtov28qNDohQrgDUKzXZBLig=="], @@ -9959,14 +9705,14 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.1", "", {}, "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw=="], + "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.5", "", { "dependencies": { "@smithy/protocol-http": "^5.3.4", "@smithy/querystring-builder": "^4.2.4", "@smithy/types": "^4.8.1", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-mg83SM3FLI8Sa2ooTJbsh5MFfyMTyNRwxqpKHmE0ICRIa66Aodv80DMsTQI02xBLVJ0hckwqTRr5IGAbbWuFLQ=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.4", "", { "dependencies": { "@smithy/abort-controller": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/querystring-builder": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-VXHGfzCXLZeKnFp6QXjAdy+U8JF9etfpUXD1FAbzY1GzsFJiDQRQIt2CnMUvUdz3/YaHNqT3RphVWMUpXTIODA=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.4", "", { "dependencies": { "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-jUr3x2CDhV15TOX2/Uoz4gfgeqLrRoTQbYAuhLS7lcVKNev7FeYSJ1ebEfjk+l9kbb7k7LfzIR/irgxys5ZTOg=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/url-parser": ["@smithy/url-parser@4.2.4", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-w/N/Iw0/PTwJ36PDqU9PzAwVElo4qXxCC0eCTlUtIz/Z5V/2j/cViMHi0hPukSBHp4DVwvUlUhLgCzqSJ6plrg=="], @@ -9977,26 +9723,16 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@smithy/util-base64/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@smithy/core/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@smithy/core/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@smithy/fetch-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=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@smithy/hash-node/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@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=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@smithy/smithy-client/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@smithy/smithy-client/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@smithy/util-base64/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], @@ -10091,18 +9827,10 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/core/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/core/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/fetch-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=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/hash-node/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@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=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/smithy-client/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/smithy-client/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/util-base64/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], @@ -10129,48 +9857,8 @@ "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@smithy/smithy-client/@smithy/util-stream/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/fetch-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=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env/@aws-sdk/core/@smithy/core/@smithy/util-stream/@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=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/fetch-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=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@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=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/fetch-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=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/core/@smithy/core/@smithy/util-stream/@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=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/fetch-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=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@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=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/fetch-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=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process/@aws-sdk/core/@smithy/core/@smithy/util-stream/@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=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/fetch-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=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@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=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/fetch-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=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/core/@smithy/core/@smithy/util-stream/@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=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/fetch-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=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@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=="], - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/core/@smithy/util-stream/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/smithy-client/@smithy/util-stream/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/fetch-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=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@smithy/core/@smithy/util-stream/@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=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/fetch-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=="], - - "@langchain/aws/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@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=="], } } diff --git a/client/jest.config.cjs b/client/jest.config.cjs index 375e4418a7..f3b0b91e9b 100644 --- a/client/jest.config.cjs +++ b/client/jest.config.cjs @@ -1,4 +1,4 @@ -/** v0.8.4 */ +/** v0.8.3-rc1 */ module.exports = { roots: ['/src'], testEnvironment: 'jsdom', @@ -32,7 +32,6 @@ module.exports = { '^librechat-data-provider/react-query$': '/../node_modules/librechat-data-provider/src/react-query', }, - maxWorkers: '50%', restoreMocks: true, testResultsProcessor: 'jest-junit', coverageReporters: ['text', 'cobertura', 'lcov'], @@ -41,9 +40,7 @@ module.exports = { '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': 'jest-file-loader', }, - transformIgnorePatterns: [ - '/node_modules/(?!(@zattoo/use-double-click|@dicebear|@react-dnd|react-dnd.*|dnd-core|filenamify|filename-reserved-regex|heic-to|lowlight|highlight\\.js|fault|react-markdown|unified|bail|trough|devlop|is-.*|parse-entities|stringify-entities|character-.*|trim-lines|style-to-object|inline-style-parser|html-url-attributes|escape-string-regexp|longest-streak|zwitch|ccount|markdown-table|comma-separated-tokens|space-separated-tokens|web-namespaces|property-information|remark-.*|rehype-.*|recma-.*|hast.*|mdast-.*|unist-.*|vfile.*|micromark.*|estree-util-.*|decode-named-character-reference)/)/', - ], + transformIgnorePatterns: ['node_modules/?!@zattoo/use-double-click'], setupFilesAfterEnv: ['@testing-library/jest-dom/extend-expect', '/test/setupTests.js'], clearMocks: true, }; diff --git a/client/nginx.conf b/client/nginx.conf index 906b3af128..c91c47a23f 100644 --- a/client/nginx.conf +++ b/client/nginx.conf @@ -86,15 +86,9 @@ server { # location /api { # proxy_pass http://api:3080/api; -# proxy_set_header X-Forwarded-Proto $scheme; -# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; -# proxy_set_header Host $host; # } # location / { # proxy_pass http://api:3080; -# proxy_set_header X-Forwarded-Proto $scheme; -# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; -# proxy_set_header Host $host; # } #} diff --git a/client/package.json b/client/package.json index 5fe9cddcc7..fcc4c99e00 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "@librechat/frontend", - "version": "v0.8.4", + "version": "v0.8.3-rc1", "description": "", "type": "module", "scripts": { @@ -32,13 +32,12 @@ "@ariakit/react": "^0.4.15", "@ariakit/react-core": "^0.4.17", "@codesandbox/sandpack-react": "^2.19.10", - "@dicebear/collection": "^9.4.1", - "@dicebear/core": "^9.4.1", + "@dicebear/collection": "^9.2.2", + "@dicebear/core": "^9.2.2", "@headlessui/react": "^2.1.2", "@librechat/client": "*", "@marsidev/react-turnstile": "^1.1.0", "@mcp-ui/client": "^5.7.0", - "@monaco-editor/react": "^4.7.0", "@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-alert-dialog": "1.0.2", "@radix-ui/react-checkbox": "^1.0.3", @@ -81,7 +80,7 @@ "lodash": "^4.17.23", "lucide-react": "^0.394.0", "match-sorter": "^8.1.0", - "mermaid": "^11.13.0", + "mermaid": "^11.12.3", "micromark-extension-llm-math": "^3.1.0", "qrcode.react": "^4.2.0", "rc-input-number": "^7.4.2", @@ -94,8 +93,9 @@ "react-gtm-module": "^2.0.11", "react-hook-form": "^7.43.9", "react-i18next": "^15.4.0", + "react-lazy-load-image-component": "^1.6.0", "react-markdown": "^9.0.1", - "react-resizable-panels": "^4.7.4", + "react-resizable-panels": "^3.0.6", "react-router-dom": "^6.30.3", "react-speech-recognition": "^3.10.0", "react-textarea-autosize": "^8.4.0", @@ -122,7 +122,6 @@ "@babel/preset-env": "^7.22.15", "@babel/preset-react": "^7.22.15", "@babel/preset-typescript": "^7.22.15", - "@happy-dom/jest-environment": "^20.8.9", "@tanstack/react-query-devtools": "^4.29.0", "@testing-library/dom": "^9.3.0", "@testing-library/jest-dom": "^5.16.5", @@ -131,10 +130,10 @@ "@types/jest": "^29.5.14", "@types/js-cookie": "^3.0.6", "@types/lodash": "^4.17.15", - "@types/node": "^20.19.35", + "@types/node": "^20.3.0", "@types/react": "^18.2.11", "@types/react-dom": "^18.2.4", - "@vitejs/plugin-react": "^5.1.4", + "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.20", "babel-plugin-replace-ts-export-assignment": "^0.0.2", "babel-plugin-root-import": "^6.6.0", @@ -145,17 +144,16 @@ "identity-obj-proxy": "^3.0.0", "jest": "^30.2.0", "jest-canvas-mock": "^2.5.2", - "jest-environment-jsdom": "^30.2.0", + "jest-environment-jsdom": "^29.7.0", "jest-file-loader": "^1.0.3", "jest-junit": "^16.0.0", - "monaco-editor": "^0.55.1", "postcss": "^8.4.31", - "postcss-preset-env": "^11.2.0", + "postcss-preset-env": "^8.2.0", "tailwindcss": "^3.4.1", "typescript": "^5.3.3", - "vite": "^7.3.1", + "vite": "^6.4.1", "vite-plugin-compression2": "^2.2.1", - "vite-plugin-node-polyfills": "^0.25.0", - "vite-plugin-pwa": "^1.2.0" + "vite-plugin-node-polyfills": "^0.23.0", + "vite-plugin-pwa": "^0.21.2" } } diff --git a/client/public/assets/azure-ai-search.svg b/client/public/assets/azure-ai-search.svg deleted file mode 100644 index 5db3422b9b..0000000000 --- a/client/public/assets/azure-ai-search.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/client/public/assets/bfl-ai.svg b/client/public/assets/bfl-ai.svg deleted file mode 100644 index c8556b8557..0000000000 --- a/client/public/assets/bfl-ai.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/client/public/assets/calculator.svg b/client/public/assets/calculator.svg deleted file mode 100644 index 440367fe9e..0000000000 --- a/client/public/assets/calculator.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/client/public/assets/google-search.svg b/client/public/assets/google-search.svg deleted file mode 100644 index be3c8db3d5..0000000000 --- a/client/public/assets/google-search.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/client/public/assets/stability-ai.svg b/client/public/assets/stability-ai.svg deleted file mode 100644 index bdc74a14d6..0000000000 --- a/client/public/assets/stability-ai.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/client/public/assets/tavily.svg b/client/public/assets/tavily.svg deleted file mode 100644 index 544d55319b..0000000000 --- a/client/public/assets/tavily.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/src/@types/react.d.ts b/client/src/@types/react.d.ts deleted file mode 100644 index edf0b7af3f..0000000000 --- a/client/src/@types/react.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -import 'react'; - -declare module 'react' { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - interface HTMLAttributes { - inert?: boolean | '' | undefined; - } -} diff --git a/client/src/Providers/ActivePanelContext.tsx b/client/src/Providers/ActivePanelContext.tsx index 46b2a189b7..4a8d6ccfc4 100644 --- a/client/src/Providers/ActivePanelContext.tsx +++ b/client/src/Providers/ActivePanelContext.tsx @@ -1,31 +1,31 @@ -import { createContext, useCallback, useContext, useMemo, useState, ReactNode } from 'react'; - -const STORAGE_KEY = 'side:active-panel'; -const DEFAULT_PANEL = 'conversations'; - -function getInitialActivePanel(): string { - const saved = localStorage.getItem(STORAGE_KEY); - return saved ? saved : DEFAULT_PANEL; -} +import { createContext, useContext, useState, ReactNode } from 'react'; interface ActivePanelContextType { - active: string; + active: string | undefined; setActive: (id: string) => void; } const ActivePanelContext = createContext(undefined); -export function ActivePanelProvider({ children }: { children: ReactNode }) { - const [active, _setActive] = useState(getInitialActivePanel); +export function ActivePanelProvider({ + children, + defaultActive, +}: { + children: ReactNode; + defaultActive?: string; +}) { + const [active, _setActive] = useState(defaultActive); - const setActive = useCallback((id: string) => { - localStorage.setItem(STORAGE_KEY, id); + const setActive = (id: string) => { + localStorage.setItem('side:active-panel', id); _setActive(id); - }, []); + }; - const value = useMemo(() => ({ active, setActive }), [active, setActive]); - - return {children}; + return ( + + {children} + + ); } export function useActivePanel() { @@ -35,11 +35,3 @@ export function useActivePanel() { } return context; } - -/** Returns `active` when it matches a known link, otherwise the first link's id. */ -export function resolveActivePanel(active: string, links: { id: string }[]): string { - if (links.length > 0 && links.some((l) => l.id === active)) { - return active; - } - return links[0]?.id ?? active; -} diff --git a/client/src/Providers/ArtifactsContext.tsx b/client/src/Providers/ArtifactsContext.tsx index fd67d5af94..139f679003 100644 --- a/client/src/Providers/ArtifactsContext.tsx +++ b/client/src/Providers/ArtifactsContext.tsx @@ -1,8 +1,7 @@ import React, { createContext, useContext, useMemo } from 'react'; -import { useRecoilValue } from 'recoil'; import type { TMessage } from 'librechat-data-provider'; +import { useChatContext } from './ChatContext'; import { getLatestText } from '~/utils'; -import store from '~/store'; export interface ArtifactsContextValue { isSubmitting: boolean; @@ -19,28 +18,27 @@ interface ArtifactsProviderProps { } export function ArtifactsProvider({ children, value }: ArtifactsProviderProps) { - const isSubmitting = useRecoilValue(store.isSubmittingFamily(0)); - const latestMessage = useRecoilValue(store.latestMessageFamily(0)); - const conversationId = useRecoilValue(store.conversationIdByIndex(0)); + const { isSubmitting, latestMessage, conversation } = useChatContext(); const chatLatestMessageText = useMemo(() => { return getLatestText({ + messageId: latestMessage?.messageId ?? null, text: latestMessage?.text ?? null, content: latestMessage?.content ?? null, - messageId: latestMessage?.messageId ?? null, } as TMessage); }, [latestMessage?.messageId, latestMessage?.text, latestMessage?.content]); const defaultContextValue = useMemo( () => ({ isSubmitting, - conversationId: conversationId ?? null, latestMessageText: chatLatestMessageText, latestMessageId: latestMessage?.messageId ?? null, + conversationId: conversation?.conversationId ?? null, }), - [isSubmitting, chatLatestMessageText, latestMessage?.messageId, conversationId], + [isSubmitting, chatLatestMessageText, latestMessage?.messageId, conversation?.conversationId], ); + /** Context value only created when relevant values change */ const contextValue = useMemo( () => (value ? { ...defaultContextValue, ...value } : defaultContextValue), [defaultContextValue, value], diff --git a/client/src/Providers/ChatContext.tsx b/client/src/Providers/ChatContext.tsx index 8af75f90c0..3d3acbcc42 100644 --- a/client/src/Providers/ChatContext.tsx +++ b/client/src/Providers/ChatContext.tsx @@ -2,11 +2,5 @@ import { createContext, useContext } from 'react'; import useChatHelpers from '~/hooks/Chat/useChatHelpers'; type TChatContext = ReturnType; -export const ChatContext = createContext(null); -export const useChatContext = () => { - const ctx = useContext(ChatContext); - if (!ctx) { - throw new Error('useChatContext must be used within a ChatContext.Provider'); - } - return ctx; -}; +export const ChatContext = createContext({} as TChatContext); +export const useChatContext = () => useContext(ChatContext); diff --git a/client/src/Providers/DragDropContext.tsx b/client/src/Providers/DragDropContext.tsx index b519c0171f..e5a2177f2d 100644 --- a/client/src/Providers/DragDropContext.tsx +++ b/client/src/Providers/DragDropContext.tsx @@ -1,5 +1,5 @@ import React, { createContext, useContext, useMemo } from 'react'; -import { isAgentsEndpoint, resolveEndpointType } from 'librechat-data-provider'; +import { getEndpointField, isAgentsEndpoint } from 'librechat-data-provider'; import type { EModelEndpoint } from 'librechat-data-provider'; import { useGetEndpointsQuery, useGetAgentByIdQuery } from '~/data-provider'; import { useAgentsMapContext } from './AgentsMapContext'; @@ -9,7 +9,7 @@ interface DragDropContextValue { conversationId: string | null | undefined; agentId: string | null | undefined; endpoint: string | null | undefined; - endpointType?: EModelEndpoint | string | undefined; + endpointType?: EModelEndpoint | undefined; useResponsesApi?: boolean; } @@ -20,6 +20,13 @@ export function DragDropProvider({ children }: { children: React.ReactNode }) { const { data: endpointsConfig } = useGetEndpointsQuery(); const agentsMap = useAgentsMapContext(); + const endpointType = useMemo(() => { + return ( + getEndpointField(endpointsConfig, conversation?.endpoint, 'type') || + (conversation?.endpoint as EModelEndpoint | undefined) + ); + }, [conversation?.endpoint, endpointsConfig]); + const needsAgentFetch = useMemo(() => { const isAgents = isAgentsEndpoint(conversation?.endpoint); if (!isAgents || !conversation?.agent_id) { @@ -33,20 +40,6 @@ export function DragDropProvider({ children }: { children: React.ReactNode }) { enabled: needsAgentFetch, }); - const agentProvider = useMemo(() => { - const isAgents = isAgentsEndpoint(conversation?.endpoint); - if (!isAgents || !conversation?.agent_id) { - return undefined; - } - const agent = agentData || agentsMap?.[conversation.agent_id]; - return agent?.provider; - }, [conversation?.endpoint, conversation?.agent_id, agentData, agentsMap]); - - const endpointType = useMemo( - () => resolveEndpointType(endpointsConfig, conversation?.endpoint, agentProvider), - [endpointsConfig, conversation?.endpoint, agentProvider], - ); - const useResponsesApi = useMemo(() => { const isAgents = isAgentsEndpoint(conversation?.endpoint); if (!isAgents || !conversation?.agent_id || conversation?.useResponsesApi) { diff --git a/client/src/Providers/MessagesViewContext.tsx b/client/src/Providers/MessagesViewContext.tsx index c44972918c..f8f5eef12a 100644 --- a/client/src/Providers/MessagesViewContext.tsx +++ b/client/src/Providers/MessagesViewContext.tsx @@ -18,8 +18,7 @@ interface MessagesViewContextValue { /** Message state management */ index: ReturnType['index']; - latestMessageId: ReturnType['latestMessageId']; - latestMessageDepth: ReturnType['latestMessageDepth']; + latestMessage: ReturnType['latestMessage']; setLatestMessage: ReturnType['setLatestMessage']; getMessages: ReturnType['getMessages']; setMessages: ReturnType['setMessages']; @@ -40,8 +39,7 @@ export function MessagesViewProvider({ children }: { children: React.ReactNode } regenerate, isSubmitting, conversation, - latestMessageId, - latestMessageDepth, + latestMessage, setAbortScroll, handleContinue, setLatestMessage, @@ -85,11 +83,10 @@ export function MessagesViewProvider({ children }: { children: React.ReactNode } const messageState = useMemo( () => ({ index, - latestMessageId, - latestMessageDepth, + latestMessage, setLatestMessage, }), - [index, latestMessageId, latestMessageDepth, setLatestMessage], + [index, latestMessage, setLatestMessage], ); /** Combine all values into final context value */ @@ -140,60 +137,11 @@ export function useMessagesOperations() { ); } -type OptionalMessagesOps = Pick< - MessagesViewContextValue, - 'ask' | 'regenerate' | 'handleContinue' | 'getMessages' | 'setMessages' ->; - -const NOOP_OPS: OptionalMessagesOps = { - ask: () => {}, - regenerate: () => {}, - handleContinue: () => {}, - getMessages: () => undefined, - setMessages: () => {}, -}; - -/** - * Hook for components that need message operations but may render outside MessagesViewProvider - * (e.g. the /search route). Returns no-op stubs when the provider is absent — UI actions will - * be silently discarded rather than crashing. Callers must use optional chaining on - * `getMessages()` results, as it returns `undefined` outside the provider. - */ -export function useOptionalMessagesOperations(): OptionalMessagesOps { - const context = useContext(MessagesViewContext); - const ask = context?.ask; - const regenerate = context?.regenerate; - const handleContinue = context?.handleContinue; - const getMessages = context?.getMessages; - const setMessages = context?.setMessages; - return useMemo( - () => ({ - ask: ask ?? NOOP_OPS.ask, - regenerate: regenerate ?? NOOP_OPS.regenerate, - handleContinue: handleContinue ?? NOOP_OPS.handleContinue, - getMessages: getMessages ?? NOOP_OPS.getMessages, - setMessages: setMessages ?? NOOP_OPS.setMessages, - }), - [ask, regenerate, handleContinue, getMessages, setMessages], - ); -} - -/** - * Hook for components that need conversation data but may render outside MessagesViewProvider - * (e.g. the /search route). Returns `undefined` for both fields when the provider is absent. - */ -export function useOptionalMessagesConversation() { - const context = useContext(MessagesViewContext); - const conversation = context?.conversation; - const conversationId = context?.conversationId; - return useMemo(() => ({ conversation, conversationId }), [conversation, conversationId]); -} - /** Hook for components that only need message state */ export function useMessagesState() { - const { index, latestMessageId, latestMessageDepth, setLatestMessage } = useMessagesViewContext(); + const { index, latestMessage, setLatestMessage } = useMessagesViewContext(); return useMemo( - () => ({ index, latestMessageId, latestMessageDepth, setLatestMessage }), - [index, latestMessageId, latestMessageDepth, setLatestMessage], + () => ({ index, latestMessage, setLatestMessage }), + [index, latestMessage, setLatestMessage], ); } diff --git a/client/src/Providers/PromptGroupsContext.tsx b/client/src/Providers/PromptGroupsContext.tsx index 3df373b165..7c9dbe8258 100644 --- a/client/src/Providers/PromptGroupsContext.tsx +++ b/client/src/Providers/PromptGroupsContext.tsx @@ -2,9 +2,9 @@ import React, { createContext, useContext, ReactNode, useMemo } from 'react'; import { PermissionTypes, Permissions } from 'librechat-data-provider'; import type { TPromptGroup } from 'librechat-data-provider'; import type { PromptOption } from '~/common'; +import CategoryIcon from '~/components/Prompts/Groups/CategoryIcon'; import { usePromptGroupsNav, useHasAccess } from '~/hooks'; import { useGetAllPromptGroups } from '~/data-provider'; -import { CategoryIcon } from '~/components/Prompts'; import { mapPromptGroups } from '~/utils'; type AllPromptGroupsData = diff --git a/client/src/Providers/SidePanelContext.tsx b/client/src/Providers/SidePanelContext.tsx new file mode 100644 index 0000000000..3ce7834ccc --- /dev/null +++ b/client/src/Providers/SidePanelContext.tsx @@ -0,0 +1,31 @@ +import React, { createContext, useContext, useMemo } from 'react'; +import type { EModelEndpoint } from 'librechat-data-provider'; +import { useChatContext } from './ChatContext'; + +interface SidePanelContextValue { + endpoint?: EModelEndpoint | null; +} + +const SidePanelContext = createContext(undefined); + +export function SidePanelProvider({ children }: { children: React.ReactNode }) { + const { conversation } = useChatContext(); + + /** Context value only created when endpoint changes */ + const contextValue = useMemo( + () => ({ + endpoint: conversation?.endpoint, + }), + [conversation?.endpoint], + ); + + return {children}; +} + +export function useSidePanelContext() { + const context = useContext(SidePanelContext); + if (!context) { + throw new Error('useSidePanelContext must be used within SidePanelProvider'); + } + return context; +} diff --git a/client/src/Providers/__tests__/ActivePanelContext.spec.tsx b/client/src/Providers/__tests__/ActivePanelContext.spec.tsx deleted file mode 100644 index 0f2f89e8f7..0000000000 --- a/client/src/Providers/__tests__/ActivePanelContext.spec.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import React from 'react'; -import { render, fireEvent, screen } from '@testing-library/react'; -import '@testing-library/jest-dom/extend-expect'; -import { - ActivePanelProvider, - resolveActivePanel, - useActivePanel, -} from '~/Providers/ActivePanelContext'; - -const STORAGE_KEY = 'side:active-panel'; - -function TestConsumer() { - const { active, setActive } = useActivePanel(); - return ( -
- {active} -
- ); -} - -describe('ActivePanelContext', () => { - beforeEach(() => { - localStorage.clear(); - }); - - it('defaults to conversations when no localStorage value exists', () => { - render( - - - , - ); - expect(screen.getByTestId('active')).toHaveTextContent('conversations'); - }); - - it('reads initial value from localStorage', () => { - localStorage.setItem(STORAGE_KEY, 'memories'); - render( - - - , - ); - expect(screen.getByTestId('active')).toHaveTextContent('memories'); - }); - - it('setActive updates state and writes to localStorage', () => { - render( - - - , - ); - fireEvent.click(screen.getByTestId('switch-btn')); - expect(screen.getByTestId('active')).toHaveTextContent('bookmarks'); - expect(localStorage.getItem(STORAGE_KEY)).toBe('bookmarks'); - }); - - it('throws when useActivePanel is called outside provider', () => { - const spy = jest.spyOn(console, 'error').mockImplementation(() => {}); - expect(() => render()).toThrow( - 'useActivePanel must be used within an ActivePanelProvider', - ); - spy.mockRestore(); - }); -}); - -describe('resolveActivePanel', () => { - const links = [{ id: 'conversations' }, { id: 'prompts' }, { id: 'files' }]; - - it('returns active when it matches a link', () => { - expect(resolveActivePanel('prompts', links)).toBe('prompts'); - }); - - it('falls back to first link when active does not match', () => { - expect(resolveActivePanel('hide-panel', links)).toBe('conversations'); - }); - - it('returns active unchanged when links is empty', () => { - expect(resolveActivePanel('agents', [])).toBe('agents'); - }); - - it('falls back to the only link when active is stale', () => { - expect(resolveActivePanel('agents', [{ id: 'conversations' }])).toBe('conversations'); - }); -}); diff --git a/client/src/Providers/__tests__/ChatContext.spec.tsx b/client/src/Providers/__tests__/ChatContext.spec.tsx deleted file mode 100644 index 0ed00bf580..0000000000 --- a/client/src/Providers/__tests__/ChatContext.spec.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; -import '@testing-library/jest-dom/extend-expect'; -import { ChatContext, useChatContext } from '~/Providers/ChatContext'; - -function TestConsumer() { - const ctx = useChatContext(); - return {ctx.index}; -} - -describe('ChatContext', () => { - it('throws when useChatContext is called outside a provider', () => { - const spy = jest.spyOn(console, 'error').mockImplementation(() => {}); - expect(() => render()).toThrow( - 'useChatContext must be used within a ChatContext.Provider', - ); - spy.mockRestore(); - }); - - it('provides context value when wrapped in provider', () => { - const mockHelpers = { index: 0 } as ReturnType< - typeof import('~/hooks/Chat/useChatHelpers').default - >; - render( - - - , - ); - expect(screen.getByTestId('index')).toHaveTextContent('0'); - }); -}); diff --git a/client/src/Providers/__tests__/DragDropContext.spec.tsx b/client/src/Providers/__tests__/DragDropContext.spec.tsx deleted file mode 100644 index 3c5e0f0796..0000000000 --- a/client/src/Providers/__tests__/DragDropContext.spec.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import React from 'react'; -import { renderHook } from '@testing-library/react'; -import { EModelEndpoint } from 'librechat-data-provider'; -import type { TEndpointsConfig, Agent } from 'librechat-data-provider'; -import { DragDropProvider, useDragDropContext } from '../DragDropContext'; - -const mockEndpointsConfig: TEndpointsConfig = { - [EModelEndpoint.openAI]: { userProvide: false, order: 0 }, - [EModelEndpoint.agents]: { userProvide: false, order: 1 }, - [EModelEndpoint.anthropic]: { userProvide: false, order: 6 }, - Moonshot: { type: EModelEndpoint.custom, userProvide: false, order: 9999 }, - 'Some Endpoint': { type: EModelEndpoint.custom, userProvide: false, order: 9999 }, -}; - -let mockConversation: Record | null = null; -let mockAgentsMap: Record> = {}; -let mockAgentQueryData: Partial | undefined; - -jest.mock('~/data-provider', () => ({ - useGetEndpointsQuery: () => ({ data: mockEndpointsConfig }), - useGetAgentByIdQuery: () => ({ data: mockAgentQueryData }), -})); - -jest.mock('../AgentsMapContext', () => ({ - useAgentsMapContext: () => mockAgentsMap, -})); - -jest.mock('../ChatContext', () => ({ - useChatContext: () => ({ conversation: mockConversation }), -})); - -function wrapper({ children }: { children: React.ReactNode }) { - return {children}; -} - -describe('DragDropContext endpointType resolution', () => { - beforeEach(() => { - mockConversation = null; - mockAgentsMap = {}; - mockAgentQueryData = undefined; - }); - - describe('non-agents endpoints', () => { - it('resolves custom endpoint type for a custom endpoint', () => { - mockConversation = { endpoint: 'Moonshot' }; - const { result } = renderHook(() => useDragDropContext(), { wrapper }); - expect(result.current.endpointType).toBe(EModelEndpoint.custom); - }); - - it('resolves endpoint name for a standard endpoint', () => { - mockConversation = { endpoint: EModelEndpoint.openAI }; - const { result } = renderHook(() => useDragDropContext(), { wrapper }); - expect(result.current.endpointType).toBe(EModelEndpoint.openAI); - }); - }); - - describe('agents endpoint with provider from agentsMap', () => { - it('resolves to custom for agent with Moonshot provider', () => { - mockConversation = { endpoint: EModelEndpoint.agents, agent_id: 'agent-1' }; - mockAgentsMap = { - 'agent-1': { provider: 'Moonshot', model_parameters: {} } as Partial, - }; - const { result } = renderHook(() => useDragDropContext(), { wrapper }); - expect(result.current.endpointType).toBe(EModelEndpoint.custom); - }); - - it('resolves to custom for agent with custom provider with spaces', () => { - mockConversation = { endpoint: EModelEndpoint.agents, agent_id: 'agent-1' }; - mockAgentsMap = { - 'agent-1': { provider: 'Some Endpoint', model_parameters: {} } as Partial, - }; - const { result } = renderHook(() => useDragDropContext(), { wrapper }); - expect(result.current.endpointType).toBe(EModelEndpoint.custom); - }); - - it('resolves to openAI for agent with openAI provider', () => { - mockConversation = { endpoint: EModelEndpoint.agents, agent_id: 'agent-1' }; - mockAgentsMap = { - 'agent-1': { provider: EModelEndpoint.openAI, model_parameters: {} } as Partial, - }; - const { result } = renderHook(() => useDragDropContext(), { wrapper }); - expect(result.current.endpointType).toBe(EModelEndpoint.openAI); - }); - - it('resolves to anthropic for agent with anthropic provider', () => { - mockConversation = { endpoint: EModelEndpoint.agents, agent_id: 'agent-1' }; - mockAgentsMap = { - 'agent-1': { provider: EModelEndpoint.anthropic, model_parameters: {} } as Partial, - }; - const { result } = renderHook(() => useDragDropContext(), { wrapper }); - expect(result.current.endpointType).toBe(EModelEndpoint.anthropic); - }); - }); - - describe('agents endpoint with provider from agentData query', () => { - it('uses agentData when agent is not in agentsMap', () => { - mockConversation = { endpoint: EModelEndpoint.agents, agent_id: 'agent-2' }; - mockAgentsMap = {}; - mockAgentQueryData = { provider: 'Moonshot' } as Partial; - const { result } = renderHook(() => useDragDropContext(), { wrapper }); - expect(result.current.endpointType).toBe(EModelEndpoint.custom); - }); - }); - - describe('agents endpoint without provider', () => { - it('falls back to agents when no agent_id', () => { - mockConversation = { endpoint: EModelEndpoint.agents }; - const { result } = renderHook(() => useDragDropContext(), { wrapper }); - expect(result.current.endpointType).toBe(EModelEndpoint.agents); - }); - - it('falls back to agents when agent has no provider', () => { - mockConversation = { endpoint: EModelEndpoint.agents, agent_id: 'agent-1' }; - mockAgentsMap = { 'agent-1': { model_parameters: {} } as Partial }; - const { result } = renderHook(() => useDragDropContext(), { wrapper }); - expect(result.current.endpointType).toBe(EModelEndpoint.agents); - }); - }); - - describe('consistency: same endpoint type whether used directly or through agents', () => { - it('Moonshot resolves to the same type as direct endpoint and as agent provider', () => { - mockConversation = { endpoint: 'Moonshot' }; - const { result: directResult } = renderHook(() => useDragDropContext(), { wrapper }); - - mockConversation = { endpoint: EModelEndpoint.agents, agent_id: 'agent-1' }; - mockAgentsMap = { - 'agent-1': { provider: 'Moonshot', model_parameters: {} } as Partial, - }; - const { result: agentResult } = renderHook(() => useDragDropContext(), { wrapper }); - - expect(directResult.current.endpointType).toBe(agentResult.current.endpointType); - }); - }); -}); diff --git a/client/src/Providers/__tests__/MessagesViewContext.spec.tsx b/client/src/Providers/__tests__/MessagesViewContext.spec.tsx deleted file mode 100644 index 88cd6f702d..0000000000 --- a/client/src/Providers/__tests__/MessagesViewContext.spec.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { renderHook } from '@testing-library/react'; -import { - useOptionalMessagesOperations, - useOptionalMessagesConversation, -} from '../MessagesViewContext'; - -describe('useOptionalMessagesOperations', () => { - it('returns noop stubs when rendered outside MessagesViewProvider', () => { - const { result } = renderHook(() => useOptionalMessagesOperations()); - - expect(result.current.ask).toBeInstanceOf(Function); - expect(result.current.regenerate).toBeInstanceOf(Function); - expect(result.current.handleContinue).toBeInstanceOf(Function); - expect(result.current.getMessages).toBeInstanceOf(Function); - expect(result.current.setMessages).toBeInstanceOf(Function); - }); - - it('noop stubs do not throw when called', () => { - const { result } = renderHook(() => useOptionalMessagesOperations()); - - expect(() => result.current.ask({} as never)).not.toThrow(); - expect(() => result.current.regenerate({} as never)).not.toThrow(); - expect(() => result.current.handleContinue({} as never)).not.toThrow(); - expect(() => result.current.setMessages([])).not.toThrow(); - }); - - it('getMessages returns undefined outside the provider', () => { - const { result } = renderHook(() => useOptionalMessagesOperations()); - expect(result.current.getMessages()).toBeUndefined(); - }); - - it('returns stable references across re-renders', () => { - const { result, rerender } = renderHook(() => useOptionalMessagesOperations()); - const first = result.current; - rerender(); - expect(result.current).toBe(first); - }); -}); - -describe('useOptionalMessagesConversation', () => { - it('returns undefined fields when rendered outside MessagesViewProvider', () => { - const { result } = renderHook(() => useOptionalMessagesConversation()); - expect(result.current.conversation).toBeUndefined(); - expect(result.current.conversationId).toBeUndefined(); - }); - - it('returns stable references across re-renders', () => { - const { result, rerender } = renderHook(() => useOptionalMessagesConversation()); - const first = result.current; - rerender(); - expect(result.current).toBe(first); - }); -}); diff --git a/client/src/Providers/index.ts b/client/src/Providers/index.ts index 3ae90e189c..43a16fa976 100644 --- a/client/src/Providers/index.ts +++ b/client/src/Providers/index.ts @@ -22,6 +22,7 @@ export * from './ToolCallsMapContext'; export * from './SetConvoContext'; export * from './SearchContext'; export * from './BadgeRowContext'; +export * from './SidePanelContext'; export * from './DragDropContext'; export * from './ArtifactsContext'; export * from './PromptGroupsContext'; diff --git a/client/src/a11y/LiveAnnouncer.tsx b/client/src/a11y/LiveAnnouncer.tsx index ac83ff2962..9a02711556 100644 --- a/client/src/a11y/LiveAnnouncer.tsx +++ b/client/src/a11y/LiveAnnouncer.tsx @@ -21,9 +21,6 @@ const LiveAnnouncer: React.FC = ({ children }) => { start: localize('com_a11y_start'), end: localize('com_a11y_end'), composing: localize('com_a11y_ai_composing'), - summarize_started: localize('com_a11y_summarize_started'), - summarize_completed: localize('com_a11y_summarize_completed'), - summarize_failed: localize('com_a11y_summarize_failed'), }), [localize], ); @@ -59,13 +56,10 @@ const LiveAnnouncer: React.FC = ({ children }) => { const announceAssertive = announcePolite; - const contextValue = useMemo( - () => ({ - announcePolite, - announceAssertive, - }), - [announcePolite, announceAssertive], - ); + const contextValue = { + announcePolite, + announceAssertive, + }; useEffect(() => { return () => { diff --git a/client/src/common/types.ts b/client/src/common/types.ts index 6ca408685f..d47ff02bd8 100644 --- a/client/src/common/types.ts +++ b/client/src/common/types.ts @@ -132,6 +132,13 @@ export type NavLink = { id: string; }; +export interface NavProps { + isCollapsed: boolean; + links: NavLink[]; + resize?: (size: number) => void; + defaultActive?: string; +} + export interface DataColumnMeta { meta: | { @@ -355,28 +362,6 @@ export type TOptions = { export type TAskFunction = (props: TAskProps, options?: TOptions) => void; -/** - * Stable context object passed from non-memo'd wrapper components (Message, MessageContent) - * to memo'd inner components (MessageRender, ContentRender) via props. - * - * This avoids subscribing to ChatContext inside memo'd components, which would bypass React.memo - * and cause unnecessary re-renders when `isSubmitting` changes during streaming. - * - * The `isSubmitting` property should use a getter backed by a ref so it returns the current - * value at call-time (for callback guards) without being a reactive dependency. - */ -export type TMessageChatContext = { - ask: (...args: Parameters) => void; - index: number; - regenerate: (message: t.TMessage, options?: { addedConvo?: t.TConversation | null }) => void; - conversation: t.TConversation | null; - latestMessageId: string | undefined; - latestMessageDepth: number | undefined; - handleContinue: (e: React.MouseEvent) => void; - /** Should be a getter backed by a ref — reads current value without triggering re-renders */ - readonly isSubmitting: boolean; -}; - export type TMessageProps = { conversation?: t.TConversation | null; messageId?: string | null; @@ -576,6 +561,11 @@ export interface ModelItemProps { className?: string; } +export type ContextType = { + navVisible: boolean; + setNavVisible: React.Dispatch>; +}; + export interface SwitcherProps { endpoint?: t.EModelEndpoint | null; endpointKeyProvided: boolean; diff --git a/client/src/components/Agents/Marketplace.tsx b/client/src/components/Agents/Marketplace.tsx index 816705a0db..69db9fc630 100644 --- a/client/src/components/Agents/Marketplace.tsx +++ b/client/src/components/Agents/Marketplace.tsx @@ -1,16 +1,23 @@ import React, { useState, useEffect, useMemo, useRef, useCallback } from 'react'; +import { useRecoilState } from 'recoil'; +import { useOutletContext } from 'react-router-dom'; +import { useQueryClient } from '@tanstack/react-query'; import { useSearchParams, useParams, useNavigate } from 'react-router-dom'; -import { useMediaQuery } from '@librechat/client'; -import { PermissionTypes, Permissions } from 'librechat-data-provider'; +import { TooltipAnchor, Button, NewChatIcon, useMediaQuery } from '@librechat/client'; +import { PermissionTypes, Permissions, QueryKeys } from 'librechat-data-provider'; import type t from 'librechat-data-provider'; +import type { ContextType } from '~/common'; import { useDocumentTitle, useHasAccess, useLocalize, TranslationKeys } from '~/hooks'; import { useGetEndpointsQuery, useGetAgentCategoriesQuery } from '~/data-provider'; import MarketplaceAdminSettings from './MarketplaceAdminSettings'; +import { SidePanelProvider, useChatContext } from '~/Providers'; import { SidePanelGroup } from '~/components/SidePanel'; +import { OpenSidebar } from '~/components/Chat/Menus'; +import { cn, clearMessagesCache } from '~/utils'; import CategoryTabs from './CategoryTabs'; import SearchBar from './SearchBar'; import AgentGrid from './AgentGrid'; -import { cn } from '~/utils'; +import store from '~/store'; interface AgentMarketplaceProps { className?: string; @@ -27,9 +34,13 @@ const AgentMarketplace: React.FC = ({ className = '' }) = const localize = useLocalize(); const navigate = useNavigate(); const { category } = useParams(); + const queryClient = useQueryClient(); const [searchParams, setSearchParams] = useSearchParams(); + const { conversation, newConversation } = useChatContext(); const isSmallScreen = useMediaQuery('(max-width: 768px)'); + const { navVisible, setNavVisible } = useOutletContext(); + const [hideSidePanel, setHideSidePanel] = useRecoilState(store.hideSidePanel); // Get URL parameters const searchQuery = searchParams.get('q') || ''; @@ -48,6 +59,15 @@ const AgentMarketplace: React.FC = ({ className = '' }) = // Set page title useDocumentTitle(`${localize('com_agents_marketplace')} | LibreChat`); + // Ensure right sidebar is always visible in marketplace + useEffect(() => { + setHideSidePanel(false); + + // Also try to force expand via localStorage + localStorage.setItem('hideSidePanel', 'false'); + localStorage.setItem('fullPanelCollapse', 'false'); + }, [setHideSidePanel, hideSidePanel]); + // Ensure endpoints config is loaded first (required for agent queries) useGetEndpointsQuery(); @@ -173,6 +193,33 @@ const AgentMarketplace: React.FC = ({ className = '' }) = } }; + /** + * Handle new chat button click + */ + + const handleNewChat = (e: React.MouseEvent) => { + if (e.button === 0 && (e.ctrlKey || e.metaKey)) { + window.open('/c/new', '_blank'); + return; + } + clearMessagesCache(queryClient, conversation?.conversationId); + queryClient.invalidateQueries([QueryKeys.messages]); + newConversation(); + }; + + // Layout configuration for SidePanelGroup + const defaultLayout = useMemo(() => { + const resizableLayout = localStorage.getItem('react-resizable-panels:layout'); + return typeof resizableLayout === 'string' ? JSON.parse(resizableLayout) : undefined; + }, []); + + const defaultCollapsed = useMemo(() => { + const collapsedPanels = localStorage.getItem('react-resizable-panels:collapsed'); + return typeof collapsedPanels === 'string' ? JSON.parse(collapsedPanels) : true; + }, []); + + const fullCollapse = useMemo(() => localStorage.getItem('fullPanelCollapse') === 'true', []); + const hasAccessToMarketplace = useHasAccess({ permissionType: PermissionTypes.MARKETPLACE, permission: Permissions.USE, @@ -194,136 +241,99 @@ const AgentMarketplace: React.FC = ({ className = '' }) = } return (
- -
- {/* Scrollable container */} -
- {/* Hero Section - scrolls away */} - {!isSmallScreen && ( -
-
-

- {localize('com_agents_marketplace')} -

-

- {localize('com_agents_marketplace_subtitle')} -

+ + +
+ {/* Scrollable container */} +
+ {/* Simplified header for agents marketplace - only show nav controls when needed */} + {!isSmallScreen && ( +
+
+ {!navVisible ? ( + <> + + + + + } + /> + + ) : ( + // Invisible placeholder to maintain height +
+ )} +
-
- )} - {/* Sticky wrapper for search bar and categories */} -
-
- {/* Search bar */} -
- - {/* TODO: Remove this once we have a better way to handle admin settings */} - {/* Admin Settings */} - + )} + {/* Hero Section - scrolls away */} + {!isSmallScreen && ( +
+
+

+ {localize('com_agents_marketplace')} +

+

+ {localize('com_agents_marketplace_subtitle')} +

+
+ )} + {/* Sticky wrapper for search bar and categories */} +
+
+ {/* Search bar */} +
+ + {/* TODO: Remove this once we have a better way to handle admin settings */} + {/* Admin Settings */} + +
- {/* Category tabs */} - -
-
- {/* Scrollable content area */} -
- {/* Two-pane animated container wrapping category header + grid */} -
- {/* Current content pane */} -
- {/* Category header - only show when not searching */} - {!searchQuery && ( -
- {(() => { - // Get category data for display - const getCategoryData = () => { - if (displayCategory === 'promoted') { - return { - name: localize('com_agents_top_picks'), - description: localize('com_agents_recommended'), - }; - } - if (displayCategory === 'all') { - return { - name: localize('com_agents_all'), - description: localize('com_agents_all_description'), - }; - } - - // Find the category in the API data - const categoryData = categoriesQuery.data?.find( - (cat) => cat.value === displayCategory, - ); - if (categoryData) { - return { - name: categoryData.label?.startsWith('com_') - ? localize(categoryData.label as TranslationKeys) - : categoryData.label, - description: categoryData.description?.startsWith('com_') - ? localize(categoryData.description as TranslationKeys) - : categoryData.description || '', - }; - } - - // Fallback for unknown categories - return { - name: - displayCategory.charAt(0).toUpperCase() + displayCategory.slice(1), - description: '', - }; - }; - - const { name, description } = getCategoryData(); - - return ( -
-

{name}

- {description && ( -

{description}

- )} -
- ); - })()} -
- )} - - {/* Agent grid */} -
- - {/* Next content pane, only during transition */} - {isTransitioning && nextCategory && ( +
+ {/* Scrollable content area */} +
+ {/* Two-pane animated container wrapping category header + grid */} +
+ {/* Current content pane */}
{/* Category header - only show when not searching */} {!searchQuery && ( @@ -331,13 +341,13 @@ const AgentMarketplace: React.FC = ({ className = '' }) = {(() => { // Get category data for display const getCategoryData = () => { - if (nextCategory === 'promoted') { + if (displayCategory === 'promoted') { return { name: localize('com_agents_top_picks'), description: localize('com_agents_recommended'), }; } - if (nextCategory === 'all') { + if (displayCategory === 'all') { return { name: localize('com_agents_all'), description: localize('com_agents_all_description'), @@ -346,7 +356,7 @@ const AgentMarketplace: React.FC = ({ className = '' }) = // Find the category in the API data const categoryData = categoriesQuery.data?.find( - (cat) => cat.value === nextCategory, + (cat) => cat.value === displayCategory, ); if (categoryData) { return { @@ -354,9 +364,7 @@ const AgentMarketplace: React.FC = ({ className = '' }) = ? localize(categoryData.label as TranslationKeys) : categoryData.label, description: categoryData.description?.startsWith('com_') - ? localize( - categoryData.description as Parameters[0], - ) + ? localize(categoryData.description as TranslationKeys) : categoryData.description || '', }; } @@ -364,8 +372,7 @@ const AgentMarketplace: React.FC = ({ className = '' }) = // Fallback for unknown categories return { name: - (nextCategory || '').charAt(0).toUpperCase() + - (nextCategory || '').slice(1), + displayCategory.charAt(0).toUpperCase() + displayCategory.slice(1), description: '', }; }; @@ -386,21 +393,102 @@ const AgentMarketplace: React.FC = ({ className = '' }) = {/* Agent grid */}
- )} - {/* Note: Using Tailwind keyframes for slide in/out animations */} + {/* Next content pane, only during transition */} + {isTransitioning && nextCategory && ( +
+ {/* Category header - only show when not searching */} + {!searchQuery && ( +
+ {(() => { + // Get category data for display + const getCategoryData = () => { + if (nextCategory === 'promoted') { + return { + name: localize('com_agents_top_picks'), + description: localize('com_agents_recommended'), + }; + } + if (nextCategory === 'all') { + return { + name: localize('com_agents_all'), + description: localize('com_agents_all_description'), + }; + } + + // Find the category in the API data + const categoryData = categoriesQuery.data?.find( + (cat) => cat.value === nextCategory, + ); + if (categoryData) { + return { + name: categoryData.label?.startsWith('com_') + ? localize(categoryData.label as TranslationKeys) + : categoryData.label, + description: categoryData.description?.startsWith('com_') + ? localize( + categoryData.description as Parameters[0], + ) + : categoryData.description || '', + }; + } + + // Fallback for unknown categories + return { + name: + (nextCategory || '').charAt(0).toUpperCase() + + (nextCategory || '').slice(1), + description: '', + }; + }; + + const { name, description } = getCategoryData(); + + return ( +
+

{name}

+ {description && ( +

{description}

+ )} +
+ ); + })()} +
+ )} + + {/* Agent grid */} + +
+ )} + + {/* Note: Using Tailwind keyframes for slide in/out animations */} +
-
-
-
+
+
+
); }; diff --git a/client/src/components/Agents/MarketplaceContext.tsx b/client/src/components/Agents/MarketplaceContext.tsx index 9193cbb82b..09c88e3291 100644 --- a/client/src/components/Agents/MarketplaceContext.tsx +++ b/client/src/components/Agents/MarketplaceContext.tsx @@ -13,5 +13,5 @@ interface MarketplaceProviderProps { export const MarketplaceProvider: React.FC = ({ children }) => { const chatHelpers = useChatHelpers(0, 'new'); - return {children}; + return {children}; }; diff --git a/client/src/components/Agents/tests/VirtualScrollingPerformance.test.tsx b/client/src/components/Agents/tests/VirtualScrollingPerformance.test.tsx index 293bd8878e..1e1b7d1e4b 100644 --- a/client/src/components/Agents/tests/VirtualScrollingPerformance.test.tsx +++ b/client/src/components/Agents/tests/VirtualScrollingPerformance.test.tsx @@ -179,7 +179,9 @@ describe('Virtual Scrolling Performance', () => { }; it('efficiently handles 1000 agents without rendering all DOM nodes', () => { + const startTime = performance.now(); renderComponent(1000); + const endTime = performance.now(); const virtualList = screen.getByTestId('virtual-list'); expect(virtualList).toBeInTheDocument(); @@ -189,10 +191,19 @@ describe('Virtual Scrolling Performance', () => { const renderedCards = screen.getAllByTestId(/agent-card-/); expect(renderedCards.length).toBeLessThan(50); // Much less than 1000 expect(renderedCards.length).toBeGreaterThan(0); + + // Performance check: rendering should be fast + const renderTime = endTime - startTime; + expect(renderTime).toBeLessThan(740); + + console.log(`Rendered 1000 agents in ${renderTime.toFixed(2)}ms`); + console.log(`Only ${renderedCards.length} DOM nodes created for 1000 agents`); }); it('efficiently handles 5000 agents (stress test)', () => { + const startTime = performance.now(); renderComponent(5000); + const endTime = performance.now(); const virtualList = screen.getByTestId('virtual-list'); expect(virtualList).toBeInTheDocument(); @@ -202,6 +213,13 @@ describe('Virtual Scrolling Performance', () => { const renderedCards = screen.getAllByTestId(/agent-card-/); expect(renderedCards.length).toBeLessThan(50); expect(renderedCards.length).toBeGreaterThan(0); + + // Performance should still be reasonable + const renderTime = endTime - startTime; + expect(renderTime).toBeLessThan(200); // Should render in less than 200ms + + console.log(`Rendered 5000 agents in ${renderTime.toFixed(2)}ms`); + console.log(`Only ${renderedCards.length} DOM nodes created for 5000 agents`); }); it('calculates correct number of virtual rows for different screen sizes', () => { diff --git a/client/src/components/Artifacts/ArtifactCodeEditor.tsx b/client/src/components/Artifacts/ArtifactCodeEditor.tsx index d03397821d..4ab2b182b8 100644 --- a/client/src/components/Artifacts/ArtifactCodeEditor.tsx +++ b/client/src/components/Artifacts/ArtifactCodeEditor.tsx @@ -1,326 +1,206 @@ -import React, { useMemo, useState, useEffect, useRef, useCallback } from 'react'; +import React, { useMemo, useState, useEffect, useRef, memo } from 'react'; import debounce from 'lodash/debounce'; -import MonacoEditor from '@monaco-editor/react'; -import type { Monaco } from '@monaco-editor/react'; -import type { editor } from 'monaco-editor'; -import type { Artifact } from '~/common'; +import { KeyBinding } from '@codemirror/view'; +import { autocompletion, completionKeymap } from '@codemirror/autocomplete'; +import { + useSandpack, + SandpackCodeEditor, + SandpackProvider as StyledProvider, +} from '@codesandbox/sandpack-react'; +import type { SandpackProviderProps } from '@codesandbox/sandpack-react/unstyled'; +import type { SandpackBundlerFile } from '@codesandbox/sandpack-client'; +import type { CodeEditorRef } from '@codesandbox/sandpack-react'; +import type { ArtifactFiles, Artifact } from '~/common'; +import { useEditArtifact, useGetStartupConfig } from '~/data-provider'; import { useMutationState, useCodeState } from '~/Providers/EditorContext'; import { useArtifactsContext } from '~/Providers'; -import { useEditArtifact } from '~/data-provider'; +import { sharedFiles, sharedOptions } from '~/utils/artifacts'; -const LANG_MAP: Record = { - javascript: 'javascript', - typescript: 'typescript', - python: 'python', - css: 'css', - json: 'json', - markdown: 'markdown', - html: 'html', - xml: 'xml', - sql: 'sql', - yaml: 'yaml', - shell: 'shell', - bash: 'shell', - tsx: 'typescript', - jsx: 'javascript', - c: 'c', - cpp: 'cpp', - java: 'java', - go: 'go', - rust: 'rust', - kotlin: 'kotlin', - swift: 'swift', - php: 'php', - ruby: 'ruby', - r: 'r', - lua: 'lua', - scala: 'scala', - perl: 'perl', -}; +const CodeEditor = memo( + ({ + fileKey, + readOnly, + artifact, + editorRef, + }: { + fileKey: string; + readOnly?: boolean; + artifact: Artifact; + editorRef: React.MutableRefObject; + }) => { + const { sandpack } = useSandpack(); + const [currentUpdate, setCurrentUpdate] = useState(null); + const { isMutating, setIsMutating } = useMutationState(); + const { setCurrentCode } = useCodeState(); + const editArtifact = useEditArtifact({ + onMutate: (vars) => { + setIsMutating(true); + setCurrentUpdate(vars.updated); + }, + onSuccess: () => { + setIsMutating(false); + setCurrentUpdate(null); + }, + onError: () => { + setIsMutating(false); + }, + }); -const TYPE_MAP: Record = { - 'text/html': 'html', - 'application/vnd.code-html': 'html', - 'application/vnd.react': 'typescript', - 'application/vnd.ant.react': 'typescript', - 'text/markdown': 'markdown', - 'text/md': 'markdown', - 'text/plain': 'plaintext', - 'application/vnd.mermaid': 'markdown', -}; + /** + * Create stable debounced mutation that doesn't depend on changing callbacks + * Use refs to always access the latest values without recreating the debounce + */ + const artifactRef = useRef(artifact); + const isMutatingRef = useRef(isMutating); + const currentUpdateRef = useRef(currentUpdate); + const editArtifactRef = useRef(editArtifact); + const setCurrentCodeRef = useRef(setCurrentCode); -function getMonacoLanguage(type?: string, language?: string): string { - if (language && LANG_MAP[language]) { - return LANG_MAP[language]; - } - return TYPE_MAP[type ?? ''] ?? 'plaintext'; -} + useEffect(() => { + artifactRef.current = artifact; + }, [artifact]); -export const ArtifactCodeEditor = function ArtifactCodeEditor({ + useEffect(() => { + isMutatingRef.current = isMutating; + }, [isMutating]); + + useEffect(() => { + currentUpdateRef.current = currentUpdate; + }, [currentUpdate]); + + useEffect(() => { + editArtifactRef.current = editArtifact; + }, [editArtifact]); + + useEffect(() => { + setCurrentCodeRef.current = setCurrentCode; + }, [setCurrentCode]); + + /** + * Create debounced mutation once - never recreate it + * All values are accessed via refs so they're always current + */ + const debouncedMutation = useMemo( + () => + debounce((code: string) => { + if (readOnly) { + return; + } + if (isMutatingRef.current) { + return; + } + if (artifactRef.current.index == null) { + return; + } + + const artifact = artifactRef.current; + const artifactIndex = artifact.index; + const isNotOriginal = + code && artifact.content != null && code.trim() !== artifact.content.trim(); + const isNotRepeated = + currentUpdateRef.current == null + ? true + : code != null && code.trim() !== currentUpdateRef.current.trim(); + + if (artifact.content && isNotOriginal && isNotRepeated && artifactIndex != null) { + setCurrentCodeRef.current(code); + editArtifactRef.current.mutate({ + index: artifactIndex, + messageId: artifact.messageId ?? '', + original: artifact.content, + updated: code, + }); + } + }, 500), + [readOnly], + ); + + /** + * Listen to Sandpack file changes and trigger debounced mutation + */ + useEffect(() => { + const currentCode = (sandpack.files['/' + fileKey] as SandpackBundlerFile | undefined)?.code; + if (currentCode) { + debouncedMutation(currentCode); + } + }, [sandpack.files, fileKey, debouncedMutation]); + + /** + * Cleanup: cancel pending mutations when component unmounts or artifact changes + */ + useEffect(() => { + return () => { + debouncedMutation.cancel(); + }; + }, [artifact.id, debouncedMutation]); + + return ( + (completionKeymap)} + className="hljs language-javascript bg-black" + /> + ); + }, +); + +export const ArtifactCodeEditor = function ({ + files, + fileKey, + template, artifact, - monacoRef, + editorRef, + sharedProps, readOnly: externalReadOnly, }: { + fileKey: string; artifact: Artifact; - monacoRef: React.MutableRefObject; + files: ArtifactFiles; + template: SandpackProviderProps['template']; + sharedProps: Partial; + editorRef: React.MutableRefObject; readOnly?: boolean; }) { + const { data: config } = useGetStartupConfig(); const { isSubmitting } = useArtifactsContext(); - const readOnly = (externalReadOnly ?? false) || isSubmitting; - const { setCurrentCode } = useCodeState(); - const [currentUpdate, setCurrentUpdate] = useState(null); - const { isMutating, setIsMutating } = useMutationState(); - const editArtifact = useEditArtifact({ - onMutate: (vars) => { - setIsMutating(true); - setCurrentUpdate(vars.updated); - }, - onSuccess: () => { - setIsMutating(false); - setCurrentUpdate(null); - }, - onError: () => { - setIsMutating(false); - }, - }); - - const artifactRef = useRef(artifact); - const isMutatingRef = useRef(isMutating); - const currentUpdateRef = useRef(currentUpdate); - const editArtifactRef = useRef(editArtifact); - const setCurrentCodeRef = useRef(setCurrentCode); - const prevContentRef = useRef(artifact.content ?? ''); - const prevArtifactId = useRef(artifact.id); - const prevReadOnly = useRef(readOnly); - - artifactRef.current = artifact; - isMutatingRef.current = isMutating; - currentUpdateRef.current = currentUpdate; - editArtifactRef.current = editArtifact; - setCurrentCodeRef.current = setCurrentCode; - - const debouncedMutation = useMemo( - () => - debounce((code: string) => { - if (readOnly || isMutatingRef.current || artifactRef.current.index == null) { - return; - } - const art = artifactRef.current; - const isNotOriginal = art.content != null && code.trim() !== art.content.trim(); - const isNotRepeated = - currentUpdateRef.current == null ? true : code.trim() !== currentUpdateRef.current.trim(); - - if (art.content != null && isNotOriginal && isNotRepeated && art.index != null) { - setCurrentCodeRef.current(code); - editArtifactRef.current.mutate({ - index: art.index, - messageId: art.messageId ?? '', - original: art.content, - updated: code, - }); - } - }, 500), - [readOnly], - ); - - useEffect(() => { - return () => debouncedMutation.cancel(); - }, [artifact.id, debouncedMutation]); - - /** - * Streaming: use model.applyEdits() to append new content. - * Unlike setValue/pushEditOperations, applyEdits preserves existing - * tokens so syntax highlighting doesn't flash during updates. - */ - useEffect(() => { - const ed = monacoRef.current; - if (!ed || !readOnly) { - return; + const options: typeof sharedOptions = useMemo(() => { + if (!config) { + return sharedOptions; } - const newContent = artifact.content ?? ''; - const prev = prevContentRef.current; - - if (newContent === prev) { - return; - } - - const model = ed.getModel(); - if (!model) { - return; - } - - if (newContent.startsWith(prev) && prev.length > 0) { - const appended = newContent.slice(prev.length); - const endPos = model.getPositionAt(model.getValueLength()); - model.applyEdits([ - { - range: { - startLineNumber: endPos.lineNumber, - startColumn: endPos.column, - endLineNumber: endPos.lineNumber, - endColumn: endPos.column, - }, - text: appended, - }, - ]); - } else { - model.setValue(newContent); - } - - prevContentRef.current = newContent; - ed.revealLine(model.getLineCount()); - }, [artifact.content, readOnly, monacoRef]); - - useEffect(() => { - if (artifact.id === prevArtifactId.current) { - return; - } - prevArtifactId.current = artifact.id; - prevContentRef.current = artifact.content ?? ''; - const ed = monacoRef.current; - if (ed && artifact.content != null) { - ed.getModel()?.setValue(artifact.content); - } - }, [artifact.id, artifact.content, monacoRef]); - - useEffect(() => { - if (prevReadOnly.current && !readOnly && artifact.content != null) { - const ed = monacoRef.current; - if (ed) { - ed.getModel()?.setValue(artifact.content); - prevContentRef.current = artifact.content; - } - } - prevReadOnly.current = readOnly; - }, [readOnly, artifact.content, monacoRef]); - - const handleChange = useCallback( - (value: string | undefined) => { - if (value === undefined || readOnly) { - return; - } - prevContentRef.current = value; - setCurrentCode(value); - if (value.length > 0) { - debouncedMutation(value); - } - }, - [readOnly, debouncedMutation, setCurrentCode], - ); - - /** - * Disable all validation — this is an artifact viewer/editor, not an IDE. - * Note: these are global Monaco settings that affect all editor instances on the page. - * The `as unknown` cast is required because monaco-editor v0.55 types `.languages.typescript` - * as `{ deprecated: true }` while the runtime API is fully functional. - */ - const handleBeforeMount = useCallback((monaco: Monaco) => { - const { typescriptDefaults, javascriptDefaults, JsxEmit } = monaco.languages - .typescript as unknown as { - typescriptDefaults: { - setDiagnosticsOptions: (o: { - noSemanticValidation: boolean; - noSyntaxValidation: boolean; - }) => void; - setCompilerOptions: (o: { - allowNonTsExtensions: boolean; - allowJs: boolean; - jsx: number; - }) => void; - }; - javascriptDefaults: { - setDiagnosticsOptions: (o: { - noSemanticValidation: boolean; - noSyntaxValidation: boolean; - }) => void; - setCompilerOptions: (o: { - allowNonTsExtensions: boolean; - allowJs: boolean; - jsx: number; - }) => void; - }; - JsxEmit: { React: number }; + return { + ...sharedOptions, + activeFile: '/' + fileKey, + bundlerURL: template === 'static' ? config.staticBundlerURL : config.bundlerURL, }; - const diagnosticsOff = { noSemanticValidation: true, noSyntaxValidation: true }; - const compilerBase = { allowNonTsExtensions: true, allowJs: true, jsx: JsxEmit.React }; - typescriptDefaults.setDiagnosticsOptions(diagnosticsOff); - javascriptDefaults.setDiagnosticsOptions(diagnosticsOff); - typescriptDefaults.setCompilerOptions(compilerBase); - javascriptDefaults.setCompilerOptions(compilerBase); - }, []); + }, [config, template, fileKey]); + const initialReadOnly = (externalReadOnly ?? false) || (isSubmitting ?? false); + const [readOnly, setReadOnly] = useState(initialReadOnly); + useEffect(() => { + setReadOnly((externalReadOnly ?? false) || (isSubmitting ?? false)); + }, [isSubmitting, externalReadOnly]); - const handleMount = useCallback( - (ed: editor.IStandaloneCodeEditor) => { - monacoRef.current = ed; - prevContentRef.current = ed.getModel()?.getValue() ?? artifact.content ?? ''; - if (readOnly) { - const model = ed.getModel(); - if (model) { - ed.revealLine(model.getLineCount()); - } - } - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [monacoRef], - ); - - const language = getMonacoLanguage(artifact.type, artifact.language); - - const editorOptions = useMemo( - () => ({ - readOnly, - minimap: { enabled: false }, - lineNumbers: 'on', - scrollBeyondLastLine: false, - fontSize: 13, - tabSize: 2, - wordWrap: 'on', - automaticLayout: true, - padding: { top: 8 }, - renderLineHighlight: readOnly ? 'none' : 'line', - cursorStyle: readOnly ? 'underline-thin' : 'line', - scrollbar: { - vertical: 'visible', - horizontal: 'auto', - verticalScrollbarSize: 8, - horizontalScrollbarSize: 8, - useShadows: false, - alwaysConsumeMouseWheel: false, - }, - overviewRulerLanes: 0, - hideCursorInOverviewRuler: true, - overviewRulerBorder: false, - folding: false, - glyphMargin: false, - colorDecorators: !readOnly, - occurrencesHighlight: readOnly ? 'off' : 'singleFile', - selectionHighlight: !readOnly, - renderValidationDecorations: readOnly ? 'off' : 'editable', - quickSuggestions: !readOnly, - suggestOnTriggerCharacters: !readOnly, - parameterHints: { enabled: !readOnly }, - hover: { enabled: !readOnly }, - matchBrackets: readOnly ? 'never' : 'always', - }), - [readOnly], - ); - - if (!artifact.content) { + if (Object.keys(files).length === 0) { return null; } return ( -
- -
+ + + ); }; diff --git a/client/src/components/Artifacts/ArtifactPreview.tsx b/client/src/components/Artifacts/ArtifactPreview.tsx index 8257f76887..c125889c88 100644 --- a/client/src/components/Artifacts/ArtifactPreview.tsx +++ b/client/src/components/Artifacts/ArtifactPreview.tsx @@ -6,7 +6,7 @@ import type { } from '@codesandbox/sandpack-react/unstyled'; import type { TStartupConfig } from 'librechat-data-provider'; import type { ArtifactFiles } from '~/common'; -import { sharedFiles, buildSandpackOptions } from '~/utils/artifacts'; +import { sharedFiles, sharedOptions } from '~/utils/artifacts'; export const ArtifactPreview = memo(function ({ files, @@ -39,10 +39,15 @@ export const ArtifactPreview = memo(function ({ }; }, [currentCode, files, fileKey]); - const options: SandpackProviderProps['options'] = useMemo( - () => buildSandpackOptions(template, startupConfig), - [startupConfig, template], - ); + const options: typeof sharedOptions = useMemo(() => { + if (!startupConfig) { + return sharedOptions; + } + return { + ...sharedOptions, + bundlerURL: template === 'static' ? startupConfig.staticBundlerURL : startupConfig.bundlerURL, + }; + }, [startupConfig, template]); if (Object.keys(artifactFiles).length === 0) { return null; diff --git a/client/src/components/Artifacts/ArtifactTabs.tsx b/client/src/components/Artifacts/ArtifactTabs.tsx index 32332215f0..8e2a92eb9c 100644 --- a/client/src/components/Artifacts/ArtifactTabs.tsx +++ b/client/src/components/Artifacts/ArtifactTabs.tsx @@ -1,26 +1,30 @@ import { useRef, useEffect } from 'react'; import * as Tabs from '@radix-ui/react-tabs'; import type { SandpackPreviewRef } from '@codesandbox/sandpack-react/unstyled'; -import type { editor } from 'monaco-editor'; +import type { CodeEditorRef } from '@codesandbox/sandpack-react'; import type { Artifact } from '~/common'; import { useCodeState } from '~/Providers/EditorContext'; +import { useArtifactsContext } from '~/Providers'; import useArtifactProps from '~/hooks/Artifacts/useArtifactProps'; +import { useAutoScroll } from '~/hooks/Artifacts/useAutoScroll'; import { ArtifactCodeEditor } from './ArtifactCodeEditor'; import { useGetStartupConfig } from '~/data-provider'; import { ArtifactPreview } from './ArtifactPreview'; export default function ArtifactTabs({ artifact, + editorRef, previewRef, isSharedConvo, }: { artifact: Artifact; + editorRef: React.MutableRefObject; previewRef: React.MutableRefObject; isSharedConvo?: boolean; }) { + const { isSubmitting } = useArtifactsContext(); const { currentCode, setCurrentCode } = useCodeState(); const { data: startupConfig } = useGetStartupConfig(); - const monacoRef = useRef(null); const lastIdRef = useRef(null); useEffect(() => { @@ -30,24 +34,33 @@ export default function ArtifactTabs({ lastIdRef.current = artifact.id; }, [setCurrentCode, artifact.id]); + const content = artifact.content ?? ''; + const contentRef = useRef(null); + useAutoScroll({ ref: contentRef, content, isSubmitting }); + const { files, fileKey, template, sharedProps } = useArtifactProps({ artifact }); return (
- + - + (); const previewRef = useRef(); const [isVisible, setIsVisible] = useState(false); const [isClosing, setIsClosing] = useState(false); @@ -31,7 +31,6 @@ export default function Artifacts() { const [height, setHeight] = useState(90); const [isDragging, setIsDragging] = useState(false); const [blurAmount, setBlurAmount] = useState(0); - const [isCopied, setIsCopied] = useState(false); const dragStartY = useRef(0); const dragStartHeight = useRef(90); const setArtifactsVisible = useSetRecoilState(store.artifactsVisibility); @@ -88,16 +87,6 @@ export default function Artifacts() { setCurrentArtifactId, } = useArtifacts(); - const handleCopyArtifact = useCallback(() => { - const content = currentArtifact?.content ?? ''; - if (!content) { - return; - } - copy(content, { format: 'text/plain' }); - setIsCopied(true); - setTimeout(() => setIsCopied(false), 3000); - }, [currentArtifact?.content]); - const handleDragStart = (e: React.PointerEvent) => { setIsDragging(true); dragStartY.current = e.clientY; @@ -228,7 +217,7 @@ export default function Artifacts() { {/* Header */}
@@ -246,7 +235,6 @@ export default function Artifacts() { value={activeTab} onChange={setActiveTab} disabled={isMutating && activeTab !== 'code'} - buttonClassName="h-9 px-3 gap-1.5" />
)} @@ -262,7 +250,6 @@ export default function Artifacts() { + ); +}; diff --git a/client/src/components/Artifacts/DownloadArtifact.tsx b/client/src/components/Artifacts/DownloadArtifact.tsx index b6d2873c46..c8fb6a12fe 100644 --- a/client/src/components/Artifacts/DownloadArtifact.tsx +++ b/client/src/components/Artifacts/DownloadArtifact.tsx @@ -38,7 +38,6 @@ const DownloadArtifact = ({ artifact }: { artifact: Artifact }) => {
diff --git a/client/src/components/Auth/Login.tsx b/client/src/components/Auth/Login.tsx index 7c3adf51bd..48a506879f 100644 --- a/client/src/components/Auth/Login.tsx +++ b/client/src/components/Auth/Login.tsx @@ -1,19 +1,15 @@ import { useEffect, useState } from 'react'; import { ErrorTypes, registerPage } from 'librechat-data-provider'; import { OpenIDIcon, useToastContext } from '@librechat/client'; -import { useOutletContext, useSearchParams, useLocation } from 'react-router-dom'; +import { useOutletContext, useSearchParams } from 'react-router-dom'; import type { TLoginLayoutContext } from '~/common'; -import { getLoginError, persistRedirectToSession } from '~/utils'; import { ErrorMessage } from '~/components/Auth/ErrorMessage'; import SocialButton from '~/components/Auth/SocialButton'; import { useAuthContext } from '~/hooks/AuthContext'; +import { getLoginError } from '~/utils'; import { useLocalize } from '~/hooks'; import LoginForm from './LoginForm'; -interface LoginLocationState { - redirect_to?: string; -} - function Login() { const localize = useLocalize(); const { showToast } = useToastContext(); @@ -21,22 +17,13 @@ function Login() { const { startupConfig } = useOutletContext(); const [searchParams, setSearchParams] = useSearchParams(); - const location = useLocation(); + // Determine if auto-redirect should be disabled based on the URL parameter const disableAutoRedirect = searchParams.get('redirect') === 'false'; + // Persist the disable flag locally so that once detected, auto-redirect stays disabled. const [isAutoRedirectDisabled, setIsAutoRedirectDisabled] = useState(disableAutoRedirect); useEffect(() => { - const redirectTo = searchParams.get('redirect_to'); - if (redirectTo) { - persistRedirectToSession(redirectTo); - } else { - const state = location.state as LoginLocationState | null; - if (state?.redirect_to) { - persistRedirectToSession(state.redirect_to); - } - } - const oauthError = searchParams?.get('error'); if (oauthError && oauthError === ErrorTypes.AUTH_FAILED) { showToast({ @@ -47,8 +34,9 @@ function Login() { newParams.delete('error'); setSearchParams(newParams, { replace: true }); } - }, [searchParams, setSearchParams, showToast, localize, location.state]); + }, [searchParams, setSearchParams, showToast, localize]); + // Once the disable flag is detected, update local state and remove the parameter from the URL. useEffect(() => { if (disableAutoRedirect) { setIsAutoRedirectDisabled(true); @@ -58,6 +46,7 @@ function Login() { } }, [disableAutoRedirect, searchParams, setSearchParams]); + // Determine whether we should auto-redirect to OpenID. const shouldAutoRedirect = startupConfig?.openidLoginEnabled && startupConfig?.openidAutoRedirect && @@ -71,6 +60,7 @@ function Login() { } }, [shouldAutoRedirect, startupConfig]); + // Render fallback UI if auto-redirect is active. if (shouldAutoRedirect) { return (
diff --git a/client/src/components/Chat/AddMultiConvo.tsx b/client/src/components/Chat/AddMultiConvo.tsx index 101dbadd19..7cabe0f336 100644 --- a/client/src/components/Chat/AddMultiConvo.tsx +++ b/client/src/components/Chat/AddMultiConvo.tsx @@ -1,21 +1,17 @@ -import { useCallback } from 'react'; -import { useSetRecoilState, useRecoilValue } from 'recoil'; import { PlusCircle } from 'lucide-react'; import { TooltipAnchor } from '@librechat/client'; import { isAssistantsEndpoint } from 'librechat-data-provider'; import type { TConversation } from 'librechat-data-provider'; -import { useGetConversation, useLocalize } from '~/hooks'; +import { useChatContext, useAddedChatContext } from '~/Providers'; import { mainTextareaId } from '~/common'; -import store from '~/store'; +import { useLocalize } from '~/hooks'; function AddMultiConvo() { + const { conversation } = useChatContext(); + const { setConversation: setAddedConvo } = useAddedChatContext(); const localize = useLocalize(); - const getConversation = useGetConversation(0); - const endpoint = useRecoilValue(store.conversationEndpointByIndex(0)); - const setAddedConvo = useSetRecoilState(store.conversationByIndex(1)); - const clickHandler = useCallback(() => { - const conversation = getConversation(); + const clickHandler = () => { const { title: _t, ...convo } = conversation ?? ({} as TConversation); setAddedConvo({ ...convo, @@ -26,13 +22,13 @@ function AddMultiConvo() { if (textarea) { textarea.focus(); } - }, [getConversation, setAddedConvo]); + }; - if (!endpoint) { + if (!conversation) { return null; } - if (isAssistantsEndpoint(endpoint)) { + if (isAssistantsEndpoint(conversation.endpoint)) { return null; } @@ -44,9 +40,9 @@ function AddMultiConvo() { aria-label={localize('com_ui_add_multi_conversation')} onClick={clickHandler} data-testid="add-multi-convo-button" - className="inline-flex size-9 flex-shrink-0 items-center justify-center rounded-xl border border-border-light bg-presentation text-text-primary transition-all ease-in-out hover:bg-surface-tertiary disabled:pointer-events-none disabled:opacity-50 radix-state-open:bg-surface-tertiary" + className="inline-flex size-10 flex-shrink-0 items-center justify-center rounded-xl border border-border-light bg-presentation text-text-primary transition-all ease-in-out hover:bg-surface-tertiary disabled:pointer-events-none disabled:opacity-50 radix-state-open:bg-surface-tertiary" > -