diff --git a/.circleci/config.yml b/.circleci/config.yml index 909331a1f34..ce9aaa9be8a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,6 +5,16 @@ orbs: win: circleci/windows@5.0 # Add Windows orb commands: + skip_if_unrelated_changes: + parameters: + category: + type: enum + enum: ["backend", "client"] + default: "backend" + steps: + - run: + name: "Skip job when no << parameters.category >>-relevant files changed" + command: bash .circleci/scripts/path_filter.sh << parameters.category >> setup_google_dns: steps: - run: @@ -282,6 +292,7 @@ jobs: parallelism: 4 steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - restore_cache: keys: @@ -354,6 +365,7 @@ jobs: parallelism: 4 steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - restore_cache: keys: @@ -427,6 +439,7 @@ jobs: steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - restore_cache: keys: @@ -480,6 +493,7 @@ jobs: steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - install_uv - run: @@ -545,6 +559,7 @@ jobs: DATABASE_URL: "postgresql://postgres:postgres@localhost:5432/litellm_test" steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - install_uv - run: @@ -584,6 +599,7 @@ jobs: DATABASE_URL: "postgresql://postgres:postgres@localhost:5432/litellm_test" steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - install_uv - run: @@ -624,6 +640,7 @@ jobs: DATABASE_URL: "postgresql://postgres:postgres@localhost:5432/litellm_test" steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - install_uv - run: @@ -656,6 +673,7 @@ jobs: FAKE_OPENAI_API_BASE: http://127.0.0.1:8190 steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - install_uv - restore_cache: @@ -705,6 +723,7 @@ jobs: steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - install_uv - restore_cache: @@ -755,6 +774,7 @@ jobs: steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - install_uv - run: @@ -787,6 +807,7 @@ jobs: steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - install_uv - restore_cache: @@ -832,6 +853,7 @@ jobs: steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - install_uv - run: @@ -877,6 +899,7 @@ jobs: steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - install_uv - run: @@ -918,6 +941,7 @@ jobs: steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - install_uv - run: @@ -963,6 +987,7 @@ jobs: steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - install_uv - run: @@ -1007,6 +1032,7 @@ jobs: steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - install_uv - restore_cache: @@ -1045,6 +1071,7 @@ jobs: steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - install_uv - run: @@ -1089,6 +1116,7 @@ jobs: steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - install_uv - run: @@ -1132,6 +1160,7 @@ jobs: steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - install_uv - run: @@ -1163,6 +1192,7 @@ jobs: steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - install_uv - run: @@ -1205,6 +1235,7 @@ jobs: steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - install_uv - run: @@ -1248,6 +1279,7 @@ jobs: steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - install_uv - run: @@ -1291,6 +1323,7 @@ jobs: steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - install_uv - run: @@ -1321,6 +1354,7 @@ jobs: steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - install_uv - run: @@ -1366,6 +1400,7 @@ jobs: steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - install_uv - run: @@ -1407,6 +1442,7 @@ jobs: steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - restore_cache: keys: @@ -1459,6 +1495,7 @@ jobs: steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - install_uv - run: @@ -1482,6 +1519,7 @@ jobs: steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - install_uv - run: @@ -1507,6 +1545,7 @@ jobs: steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - install_uv - run: @@ -1531,6 +1570,7 @@ jobs: steps: - checkout + - skip_if_unrelated_changes - attach_workspace: at: ~/project - setup_google_dns @@ -1570,14 +1610,14 @@ jobs: - run: name: Run helm lint command: | - helm lint ./deploy/charts/litellm-helm + helm lint ./helm/litellm-helm # Run helm tests - run: name: Run helm tests command: | IMAGE_TAG=${CIRCLE_SHA1:-ci} - helm install litellm ./deploy/charts/litellm-helm -f ./deploy/charts/litellm-helm/ci/test-values.yaml \ + helm install litellm ./helm/litellm-helm -f ./helm/litellm-helm/ci/test-values.yaml \ --set image.repository=litellm-ci \ --set image.tag=${IMAGE_TAG} \ --set image.pullPolicy=Never @@ -1606,6 +1646,7 @@ jobs: working_directory: ~/project steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - install_uv - run: @@ -1698,6 +1739,7 @@ jobs: working_directory: ~/project steps: - checkout + - skip_if_unrelated_changes - attach_workspace: at: ~/project - setup_google_dns @@ -1787,6 +1829,7 @@ jobs: working_directory: ~/project steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - install_uv - run: @@ -1869,6 +1912,7 @@ jobs: working_directory: ~/project steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - install_uv - run: @@ -2000,6 +2044,7 @@ jobs: working_directory: ~/project steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - install_uv - run: @@ -2085,6 +2130,7 @@ jobs: working_directory: ~/project steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - install_uv - run: @@ -2180,6 +2226,7 @@ jobs: working_directory: ~/project steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - install_uv - run: @@ -2201,6 +2248,7 @@ jobs: # the OTEL test - should get this as a trace command: | docker run -d \ + --restart on-failure \ -p 4000:4000 \ -e DATABASE_URL=postgresql://postgres:postgres@host.docker.internal:5432/circle_test \ -e STORE_MODEL_IN_DB="True" \ @@ -2252,6 +2300,7 @@ jobs: working_directory: ~/project steps: - checkout + - skip_if_unrelated_changes - setup_google_dns # Remove Docker CLI installation since it's already available in machine executor - install_uv @@ -2333,6 +2382,7 @@ jobs: working_directory: ~/project steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - install_uv - run: @@ -2471,6 +2521,7 @@ jobs: working_directory: ~/project steps: - checkout + - skip_if_unrelated_changes - setup_google_dns - install_uv - run: @@ -2537,6 +2588,7 @@ jobs: - *python312_image steps: - checkout + - skip_if_unrelated_changes - attach_workspace: at: . # Check file locations @@ -2567,6 +2619,8 @@ jobs: working_directory: ~/project steps: - checkout + - skip_if_unrelated_changes: + category: client - setup_google_dns - restore_cache: keys: @@ -2609,6 +2663,8 @@ jobs: working_directory: ~/project steps: - checkout + - skip_if_unrelated_changes: + category: client - setup_google_dns - restore_cache: keys: @@ -2654,6 +2710,8 @@ jobs: PROXY_LOGOUT_URL: "https://www.example.com" steps: - checkout + - skip_if_unrelated_changes: + category: client - setup_google_dns - install_uv - restore_cache: @@ -2791,6 +2849,8 @@ jobs: SERVER_ROOT_PATH: "/litellm" steps: - checkout + - skip_if_unrelated_changes: + category: client - setup_google_dns - install_uv - restore_cache: @@ -2892,6 +2952,7 @@ jobs: working_directory: ~/project steps: - checkout + - skip_if_unrelated_changes - run: name: Build Docker image @@ -2917,6 +2978,7 @@ jobs: working_directory: ~/project steps: - checkout + - skip_if_unrelated_changes - attach_workspace: at: ~/project - setup_google_dns diff --git a/.circleci/scripts/classify_changes.sh b/.circleci/scripts/classify_changes.sh new file mode 100755 index 00000000000..2c15428be6a --- /dev/null +++ b/.circleci/scripts/classify_changes.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -uo pipefail + +category="${1:?usage: classify_changes.sh }" + +has_client=false +has_backend=false +while IFS= read -r file || [ -n "$file" ]; do + [ -n "$file" ] || continue + case "$file" in + ui/*) has_client=true ;; + docs/* | *.md | *.mdx) : ;; + *) has_backend=true ;; + esac +done + +case "$category" in + backend) + [ "$has_backend" = true ] && echo run || echo skip + ;; + client) + { [ "$has_client" = true ] || [ "$has_backend" = true ]; } && echo run || echo skip + ;; + *) + echo run + ;; +esac diff --git a/.circleci/scripts/path_filter.sh b/.circleci/scripts/path_filter.sh new file mode 100755 index 00000000000..dcf64a24399 --- /dev/null +++ b/.circleci/scripts/path_filter.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +set -uo pipefail + +category="${1:?usage: path_filter.sh }" +here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +run_full() { + echo "path-filter[$category]: running job ($1)" + exit 0 +} + +[ -n "${CIRCLE_PULL_REQUEST:-}" ] || run_full "not a pull request" + +candidate_bases="main litellm_internal_staging litellm_oss_staging" +merge_base="" +for base in $candidate_bases; do + git fetch --quiet origin "$base" 2>/dev/null || continue + candidate="$(git merge-base HEAD FETCH_HEAD 2>/dev/null)" || continue + [ -n "$candidate" ] || continue + if [ -z "$merge_base" ] || git merge-base --is-ancestor "$merge_base" "$candidate" 2>/dev/null; then + merge_base="$candidate" + fi +done + +[ -n "$merge_base" ] || run_full "could not resolve a merge base against $candidate_bases" + +changed="$(git diff --name-only "$merge_base" HEAD 2>/dev/null)" || run_full "git diff failed" +[ -n "$changed" ] || run_full "no files changed vs $merge_base" + +echo "path-filter[$category]: changed files vs ${merge_base}:" +printf '%s\n' "$changed" | sed 's/^/ /' || true + +decision="$(printf '%s\n' "$changed" | bash "$here/classify_changes.sh" "$category")" || run_full "classify_changes.sh failed" + +if [ "$decision" = run ]; then + run_full "$category-relevant changes detected" +fi + +echo "path-filter[$category]: only unrelated (docs/client) changes detected; halting job as successful" +circleci-agent step halt diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 1051459ed44..bd9fc2285d1 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -13,7 +13,7 @@ - [ ] I have added meaningful tests - [ ] My PR passes all CI/CD checks (e.g., lint, format, unit tests) - [ ] My PR's scope is as isolated as possible; it only solves 1 specific problem -- [ ] I have requested a Greptile review by commenting `@greptileai` and received a **Confidence Score of at least 4/5** before requesting a maintainer review +- [ ] I have received a Greptile **Confidence Score of at least 4/5** before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment `@greptileai` to re-request a review after pushing changes) ## Delays in PR merge? @@ -24,6 +24,7 @@ If you're seeing a delay in your PR being merged, ping the LiteLLM Team on [Slac diff --git a/.github/workflows/codspeed.yml b/.github/workflows/codspeed.yml index 49f1d906069..a1772102b89 100644 --- a/.github/workflows/codspeed.yml +++ b/.github/workflows/codspeed.yml @@ -4,9 +4,11 @@ on: push: branches: - main + - litellm_internal_staging pull_request: branches: - main + - litellm_internal_staging # Allow CodSpeed to trigger backtest performance analysis # in order to generate initial data workflow_dispatch: @@ -22,7 +24,7 @@ concurrency: jobs: benchmarks: runs-on: ubuntu-24.04 - timeout-minutes: 15 + timeout-minutes: 60 steps: - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 diff --git a/.github/workflows/helm_unit_test.yml b/.github/workflows/helm_unit_test.yml index 06836b1d1cd..5b9d20d97f3 100644 --- a/.github/workflows/helm_unit_test.yml +++ b/.github/workflows/helm_unit_test.yml @@ -38,4 +38,6 @@ jobs: echo "Helm unittest plugin integrity verified: $ACTUAL_SHA" - name: Run unit tests - run: helm unittest -f 'tests/*.yaml' deploy/charts/litellm-helm + run: | + helm unittest -f 'tests/*.yaml' helm/litellm-helm + helm unittest -f 'tests/*.yaml' helm/litellm diff --git a/.github/workflows/test-terraform-provider.yml b/.github/workflows/test-terraform-provider.yml new file mode 100644 index 00000000000..03d8ff3461c --- /dev/null +++ b/.github/workflows/test-terraform-provider.yml @@ -0,0 +1,113 @@ +name: Terraform Provider + +on: + push: + paths: + - "terraform/provider/**" + - ".github/workflows/test-terraform-provider.yml" + pull_request: + branches: + - main + - litellm_internal_staging + - litellm_oss_staging + - "litellm_**" + paths: + - "terraform/provider/**" + - "litellm/proxy/**" + - ".github/workflows/test-terraform-provider.yml" + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + provider-checks: + name: gofmt, vet, build, test + runs-on: ubuntu-latest + timeout-minutes: 10 + defaults: + run: + working-directory: terraform/provider + steps: + - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + with: + persist-credentials: false + + - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 + with: + go-version-file: terraform/provider/go.mod + cache: true + cache-dependency-path: terraform/provider/go.sum + + - name: gofmt + run: | + UNFORMATTED=$(gofmt -l .) + if [ -n "${UNFORMATTED}" ]; then + echo "::error::gofmt required for: ${UNFORMATTED}" + exit 1 + fi + + - name: go vet + run: go vet ./... + + - name: Build + run: go build ./... + + - name: Test + run: go test -timeout 120s ./... + + endpoint-drift: + name: Provider endpoints vs proxy OpenAPI schema + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + with: + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: "3.12" + + - name: Set up uv + uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7 + with: + version: "0.10.9" + + - name: Cache uv dependencies + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: | + ~/.cache/uv + .venv + key: ${{ runner.os }}-uv-${{ hashFiles('uv.lock') }} + restore-keys: | + ${{ runner.os }}-uv- + + - name: Install dependencies + run: | + .github/scripts/uv_sync_with_retries.sh --frozen --group ci --group proxy-dev --extra google --extra proxy --extra semantic-router + + - name: Generate Prisma client + env: + PRISMA_BINARY_CACHE_DIR: ${{ runner.temp }}/prisma-cache + run: | + uv run --no-sync prisma generate --schema litellm/proxy/schema.prisma + + - name: Generate proxy OpenAPI schema + run: | + uv run --no-sync python terraform/provider/tools/dump_openapi.py "${RUNNER_TEMP}/openapi.json" + + - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 + with: + go-version-file: terraform/provider/go.mod + cache: true + cache-dependency-path: terraform/provider/go.sum + + - name: Audit provider endpoints against the schema + working-directory: terraform/provider + run: go run ./tools/endpointaudit -provider-dir ./litellm -spec "${RUNNER_TEMP}/openapi.json" diff --git a/.gitignore b/.gitignore index 62db5fe6182..e3ccf50508f 100644 --- a/.gitignore +++ b/.gitignore @@ -52,9 +52,8 @@ ui/litellm-dashboard/node_modules ui/litellm-dashboard/next-env.d.ts ui/litellm-dashboard/package.json ui/litellm-dashboard/package-lock.json -deploy/charts/litellm/*.tgz -deploy/charts/litellm/charts/* -deploy/charts/*.tgz +helm/litellm-helm/*.tgz +helm/*.tgz litellm/proxy/vertex_key.json **/.vim/ **/node_modules diff --git a/CLAUDE.md b/CLAUDE.md index 7108dfd4f32..5affa7748d7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,11 +21,11 @@ End-to-end tests belong in `tests/e2e/` and must follow the harness conventions When creating PRs, don't set base to `main`. `litellm_internal_staging` serves that purpose -When writing a PR body, treat the comments and imperative instructions inside @.github/pull_request_template.md as rules to follow, not just layout +When writing a PR body, treat the comments and imperative instructions inside @.github/pull_request_template.md as rules to follow, not just layout. Agent harnesses may strip HTML comments from copies of that file injected into context, so read .github/pull_request_template.md from disk before writing a PR body to make sure you see every comment rule If you're resolving a linear ticket, in the "## Linear ticket" section of the PR, say "Resolves LIT-1234", replacing "LIT-1234" with the actual ticket id that you're resolving. If you don't have the ticket id, don't make one up or search for it. Just leave the section blank -Never use `pytest` commands or the like as "Screenshots / Proof of Fix". We prefer curl'ing a live proxy instance running on localhost:4000 (I like to run it with `python litellm/proxy/proxy_cli.py --config litellm/proxy/dev_config.yaml --detailed_debug --reload --use_v2_migration_resolver 2>&1 | tee litellm.log`) and showing both the command run and the output. Also, it should hit real LLM provider APIs, not mocks, and cost real $$$ because that is the most realistic test. The proof of fix should be exactly what the end user / customer would see / do. The run logs in PR #27703 is a prime example of how to do it (not a huge fan of using a python test script that future me and the team will have no visibility into; I prefer just curl commands or a short list of bash commands (e.g., using `for`)). If it's a UI thing, just tell me which URLs to go to (e.g., http://localhost:4000/ui/?page=logs), where to click, what fields to fill out, etc. along with the other commands to run in an ordered list, and I'll do it myself and post the screenshots after you make the PR +Never use `pytest` commands or the like as "Screenshots / Proof of Fix". We prefer curl'ing a live proxy instance running on localhost:4000 (I like to run it with `python litellm/proxy/proxy_cli.py --config litellm/proxy/dev_config.yaml --detailed_debug --reload --use_v2_migration_resolver 2>&1 | tee litellm.log`; the Admin UI dev server is `npm run dev` in `ui/litellm-dashboard`, served on port 3000) and showing both the command run and the output. Also, it should hit real LLM provider APIs, not mocks, and cost real $$$ because that is the most realistic test. The proof of fix should be exactly what the end user / customer would see / do. The run logs in PR #27703 is a prime example of how to do it (not a huge fan of using a python test script that future me and the team will have no visibility into; I prefer just curl commands or a short list of bash commands (e.g., using `for`)). If it's a UI thing, just tell me which URLs to go to (e.g., http://localhost:4000/ui/?page=logs), where to click, what fields to fill out, etc. along with the other commands to run in an ordered list, and I'll do it myself and post the screenshots after you make the PR If you ever make public-facing PR descriptions, comments, issues, commit messages, etc., always follow these guidelines to sound less AI-y: - don't use emojis @@ -47,9 +47,11 @@ If you're trying to create a new function that relies on untyped stuff, instead If you get an LIT001 or LIT002 fail, refactor the code to follow functional programming best practices rather than introducing mutable data structures. For example, build values in one shot with comprehensions or generators wrapped in `tuple()` / `frozenset()` instead of seeding an empty `list`/`dict`/`set` and mutating it over time. Ideally `# mutable-ok` is never used; reach for it only as a genuine last resort when an immutable rewrite is truly impossible, and always pair it with a real reason +Every lint or type suppression must name the exact rule inside brackets and carry a reason comment, e.g. `# pyright: ignore[reportArgumentType] # stubs lack async overload` or `# noqa: TID251 # `. `# type: ignore` is banned (LIT009): pyrightconfig.json sets `enableTypeIgnoreComments` to false, so it silently does nothing + Commit and push your work when you're done without asking -When you must use real LLM models to, for example, write e2e tests, write a QA runbook, etc., make sure to use the latest models (doesn't have to be smartest, can also be a modern small, fast one. No strong preference for smart vs fast here, just use something modern) as of the year and month of the current date. Do a web search as necessary to figure that out +When referencing or running models (coding, QA'ing, writing docs, writing tests, etc.), use the latest model in that model family unless otherwise specified; treat your training knowledge, memories, configs, and tests as stale, and determine the family's latest with model_prices_and_context_window.json or the web If you're an internal contributor, when creating a new PR, the typical flow is to branch off litellm_internal_staging and create a branch prefixed with litellm_. Do not create a branch prefixed with claude/ and generally do not have / in your branch names diff --git a/Dockerfile b/Dockerfile index b6fef1a21fc..bc0e6a5ca6f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,10 @@ # syntax=docker/dockerfile:1.7 # Base image for building -ARG LITELLM_BUILD_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:c61ac6919b811ea53c4782d69f1fe05218ba3c25d53f01b6ab7892e621bd4370 +ARG LITELLM_BUILD_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:42df77a9974d6ec8b17a5ee8bc23b532600a44d705acef2409e0933c1251b45f # Runtime image -ARG LITELLM_RUNTIME_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:c61ac6919b811ea53c4782d69f1fe05218ba3c25d53f01b6ab7892e621bd4370 +ARG LITELLM_RUNTIME_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:42df77a9974d6ec8b17a5ee8bc23b532600a44d705acef2409e0933c1251b45f ARG UV_IMAGE=ghcr.io/astral-sh/uv:0.11.7@sha256:240fb85ab0f263ef12f492d8476aa3a2e4e1e333f7d67fbdd923d00a506a516a # Pinned by digest like the other base images; bump explicitly on Node upgrades. ARG UI_BUILD_IMAGE=node:20.18-alpine3.20@sha256:3488b10bf958af7125a176419d2d8a9937d895bf124012aae811651988d2ffe6 diff --git a/Makefile b/Makefile index f8d10de2917..f2753b09ff5 100644 --- a/Makefile +++ b/Makefile @@ -265,7 +265,7 @@ test-integration: install-test-deps $(UV_RUN) pytest tests/ -k "not test_litellm" test-unit-helm: install-helm-unittest - helm unittest -f 'tests/*.yaml' deploy/charts/litellm-helm + helm unittest -f 'tests/*.yaml' helm/litellm-helm # LLM Translation testing targets test-llm-translation: install-test-deps diff --git a/backend/Dockerfile b/backend/Dockerfile index 667bdb073eb..62bd8b56483 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,5 +1,5 @@ -ARG LITELLM_BUILD_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:c61ac6919b811ea53c4782d69f1fe05218ba3c25d53f01b6ab7892e621bd4370 -ARG LITELLM_RUNTIME_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:c61ac6919b811ea53c4782d69f1fe05218ba3c25d53f01b6ab7892e621bd4370 +ARG LITELLM_BUILD_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:42df77a9974d6ec8b17a5ee8bc23b532600a44d705acef2409e0933c1251b45f +ARG LITELLM_RUNTIME_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:42df77a9974d6ec8b17a5ee8bc23b532600a44d705acef2409e0933c1251b45f ARG UV_IMAGE=ghcr.io/astral-sh/uv:0.11.7@sha256:240fb85ab0f263ef12f492d8476aa3a2e4e1e333f7d67fbdd923d00a506a516a FROM $UV_IMAGE AS uvbin diff --git a/basedpyright-code-budget.json b/basedpyright-code-budget.json index b65cd01e1eb..cb3427bed4d 100644 --- a/basedpyright-code-budget.json +++ b/basedpyright-code-budget.json @@ -3,16 +3,16 @@ "limit": 37484 }, "reportArgumentType": { - "limit": 2721 + "limit": 2704 }, "reportAssignmentType": { "limit": 330 }, "reportAttributeAccessIssue": { - "limit": 519 + "limit": 516 }, "reportCallIssue": { - "limit": 131 + "limit": 124 }, "reportConstantRedefinition": { "limit": 59 @@ -42,7 +42,7 @@ "limit": 18 }, "reportIndexIssue": { - "limit": 39 + "limit": 37 }, "reportInvalidTypeForm": { "limit": 35 @@ -51,7 +51,7 @@ "limit": 5 }, "reportMatchNotExhaustive": { - "limit": 2 + "limit": 0 }, "reportMissingParameterType": { "limit": 5900 @@ -63,25 +63,25 @@ "limit": 41 }, "reportOperatorIssue": { - "limit": 9 + "limit": 0 }, "reportOptionalCall": { - "limit": 7 + "limit": 0 }, "reportOptionalIterable": { - "limit": 6 + "limit": 0 }, "reportOptionalMemberAccess": { - "limit": 1086 + "limit": 1085 }, "reportOptionalOperand": { - "limit": 6 + "limit": 0 }, "reportOptionalSubscript": { - "limit": 17 + "limit": 0 }, "reportPossiblyUnboundVariable": { - "limit": 78 + "limit": 77 }, "reportPrivateUsage": { "limit": 2438 @@ -90,28 +90,28 @@ "limit": 12 }, "reportReturnType": { - "limit": 226 + "limit": 225 }, "reportTypedDictNotRequiredAccess": { - "limit": 30 + "limit": 27 }, "reportUndefinedVariable": { - "limit": 5 + "limit": 0 }, "reportUnknownArgumentType": { - "limit": 45905 + "limit": 45894 }, "reportUnknownLambdaType": { "limit": 113 }, "reportUnknownMemberType": { - "limit": 40556 + "limit": 40541 }, "reportUnknownParameterType": { "limit": 20418 }, "reportUnknownVariableType": { - "limit": 32168 + "limit": 32151 }, "reportUnnecessaryCast": { "limit": 177 diff --git a/codecov.yaml b/codecov.yaml index f5acdd39136..bc0b3604329 100644 --- a/codecov.yaml +++ b/codecov.yaml @@ -15,6 +15,16 @@ ignore: flag_management: default_rules: carryforward: true + # Dead flags no CI job uploads anymore: their carried-forward sessions were + # measured against old revisions, and the stale line maps mark comment lines + # of since-edited files as missed, sinking patch coverage on unrelated PRs. + individual_flags: + - name: proxy-mgmt-behavior + carryforward: false + - name: security + carryforward: false + - name: proxy-db-schema-migration + carryforward: false component_management: individual_components: diff --git a/deploy/azure_resource_manager/azure_marketplace.zip b/deploy/azure_resource_manager/azure_marketplace.zip deleted file mode 100644 index 34751258637..00000000000 Binary files a/deploy/azure_resource_manager/azure_marketplace.zip and /dev/null differ diff --git a/deploy/azure_resource_manager/azure_marketplace/createUiDefinition.json b/deploy/azure_resource_manager/azure_marketplace/createUiDefinition.json deleted file mode 100644 index 4eba73bdba4..00000000000 --- a/deploy/azure_resource_manager/azure_marketplace/createUiDefinition.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/0.1.2-preview/CreateUIDefinition.MultiVm.json#", - "handler": "Microsoft.Azure.CreateUIDef", - "version": "0.1.2-preview", - "parameters": { - "config": { - "isWizard": false, - "basics": { } - }, - "basics": [ ], - "steps": [ ], - "outputs": { }, - "resourceTypes": [ ] - } -} \ No newline at end of file diff --git a/deploy/azure_resource_manager/azure_marketplace/mainTemplate.json b/deploy/azure_resource_manager/azure_marketplace/mainTemplate.json deleted file mode 100644 index 114e855bf54..00000000000 --- a/deploy/azure_resource_manager/azure_marketplace/mainTemplate.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "imageName": { - "type": "string", - "defaultValue": "ghcr.io/berriai/litellm:main-latest" - }, - "containerName": { - "type": "string", - "defaultValue": "litellm-container" - }, - "dnsLabelName": { - "type": "string", - "defaultValue": "litellm" - }, - "portNumber": { - "type": "int", - "defaultValue": 4000 - } - }, - "resources": [ - { - "type": "Microsoft.ContainerInstance/containerGroups", - "apiVersion": "2021-03-01", - "name": "[parameters('containerName')]", - "location": "[resourceGroup().location]", - "properties": { - "containers": [ - { - "name": "[parameters('containerName')]", - "properties": { - "image": "[parameters('imageName')]", - "resources": { - "requests": { - "cpu": 1, - "memoryInGB": 2 - } - }, - "ports": [ - { - "port": "[parameters('portNumber')]" - } - ] - } - } - ], - "osType": "Linux", - "restartPolicy": "Always", - "ipAddress": { - "type": "Public", - "ports": [ - { - "protocol": "tcp", - "port": "[parameters('portNumber')]" - } - ], - "dnsNameLabel": "[parameters('dnsLabelName')]" - } - } - } - ] - } \ No newline at end of file diff --git a/deploy/azure_resource_manager/main.bicep b/deploy/azure_resource_manager/main.bicep deleted file mode 100644 index b104cefe1e1..00000000000 --- a/deploy/azure_resource_manager/main.bicep +++ /dev/null @@ -1,42 +0,0 @@ -param imageName string = 'ghcr.io/berriai/litellm:main-latest' -param containerName string = 'litellm-container' -param dnsLabelName string = 'litellm' -param portNumber int = 4000 - -resource containerGroupName 'Microsoft.ContainerInstance/containerGroups@2021-03-01' = { - name: containerName - location: resourceGroup().location - properties: { - containers: [ - { - name: containerName - properties: { - image: imageName - resources: { - requests: { - cpu: 1 - memoryInGB: 2 - } - } - ports: [ - { - port: portNumber - } - ] - } - } - ] - osType: 'Linux' - restartPolicy: 'Always' - ipAddress: { - type: 'Public' - ports: [ - { - protocol: 'tcp' - port: portNumber - } - ] - dnsNameLabel: dnsLabelName - } - } -} diff --git a/docker/Dockerfile.database b/docker/Dockerfile.database index b3af953511d..4564ee403fe 100644 --- a/docker/Dockerfile.database +++ b/docker/Dockerfile.database @@ -1,10 +1,10 @@ # syntax=docker/dockerfile:1.7 # Base image for building -ARG LITELLM_BUILD_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:c61ac6919b811ea53c4782d69f1fe05218ba3c25d53f01b6ab7892e621bd4370 +ARG LITELLM_BUILD_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:42df77a9974d6ec8b17a5ee8bc23b532600a44d705acef2409e0933c1251b45f # Runtime image -ARG LITELLM_RUNTIME_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:c61ac6919b811ea53c4782d69f1fe05218ba3c25d53f01b6ab7892e621bd4370 +ARG LITELLM_RUNTIME_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:42df77a9974d6ec8b17a5ee8bc23b532600a44d705acef2409e0933c1251b45f ARG UV_IMAGE=ghcr.io/astral-sh/uv:0.11.7@sha256:240fb85ab0f263ef12f492d8476aa3a2e4e1e333f7d67fbdd923d00a506a516a # Pinned by digest like the other base images; bump explicitly on Node upgrades. ARG UI_BUILD_IMAGE=node:20.18-alpine3.20@sha256:3488b10bf958af7125a176419d2d8a9937d895bf124012aae811651988d2ffe6 diff --git a/docker/Dockerfile.non_root b/docker/Dockerfile.non_root index c24cb9008f0..1883e87be60 100644 --- a/docker/Dockerfile.non_root +++ b/docker/Dockerfile.non_root @@ -1,8 +1,8 @@ # syntax=docker/dockerfile:1.7 # Base images -ARG LITELLM_BUILD_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:c61ac6919b811ea53c4782d69f1fe05218ba3c25d53f01b6ab7892e621bd4370 -ARG LITELLM_RUNTIME_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:c61ac6919b811ea53c4782d69f1fe05218ba3c25d53f01b6ab7892e621bd4370 +ARG LITELLM_BUILD_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:42df77a9974d6ec8b17a5ee8bc23b532600a44d705acef2409e0933c1251b45f +ARG LITELLM_RUNTIME_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:42df77a9974d6ec8b17a5ee8bc23b532600a44d705acef2409e0933c1251b45f ARG PROXY_EXTRAS_SOURCE=published ARG UV_IMAGE=ghcr.io/astral-sh/uv:0.11.7@sha256:240fb85ab0f263ef12f492d8476aa3a2e4e1e333f7d67fbdd923d00a506a516a # Pinned by digest like the other base images; bump explicitly on Node upgrades. diff --git a/enterprise/litellm_enterprise/proxy/common_utils/check_batch_cost.py b/enterprise/litellm_enterprise/proxy/common_utils/check_batch_cost.py index 831a23ff3cd..b9ac98f515c 100644 --- a/enterprise/litellm_enterprise/proxy/common_utils/check_batch_cost.py +++ b/enterprise/litellm_enterprise/proxy/common_utils/check_batch_cost.py @@ -17,6 +17,7 @@ if TYPE_CHECKING: from litellm.proxy._types import LiteLLM_ManagedObjectTable from litellm.proxy.utils import PrismaClient, ProxyLogging from litellm.router import Router + from litellm.types.utils import LiteLLMBatch CHECK_BATCH_COST_USER_AGENT = "LiteLLM Proxy/CheckBatchCost" @@ -277,13 +278,20 @@ class CheckBatchCost: except Exception: return None - async def check_batch_cost(self): + async def _track_completed_batch_cost( + self, + job: "LiteLLM_ManagedObjectTable", + response: "LiteLLMBatch", + model_id: str, + batch_id: str, + prom_logger: Optional["PrometheusLogger"], + ) -> Optional[Tuple[Optional[str], Optional[str]]]: """ - Check if the batch JOB has been tracked. - - get all status="validating" and file_purpose="batch" jobs - - check if batch is now complete - - if not, return False - - if so, return True + Fetch a completed batch's results, compute cost/usage, and emit the + aretrieve_batch spend log. Returns (model_name, llm_provider) on + success, None when the job can't be routed to a deployment. Raises on + results-fetch or cost-computation failures so the caller can leave the + job unprocessed and retry it on a later poll. """ from litellm.batches.batch_utils import ( _get_file_content_as_dictionary, @@ -296,6 +304,184 @@ class CheckBatchCost: _is_base64_encoded_unified_file_id, ) + verbose_proxy_logger.info( + f"Batch ID: {batch_id} is complete, tracking cost and usage" + ) + + # aretrieve_batch is called with the raw provider batch ID, so response.id + # is the raw provider value (e.g. "batch_20260223-0518.234"). We need the + # unified base64 ID in the S3 log so downstream consumers can correlate it + # back to the batch they submitted via the proxy. + # + # CheckBatchCost builds its own LiteLLMLogging object (logging_obj below) and + # calls async_success_handler(result=response) directly. That handler calls + # _build_standard_logging_payload(response, ...) which reads response.id at + # that point — so setting response.id here is sufficient. + # + # The HTTP endpoint does this substitution via the managed files hook + # (async_post_call_success_hook). CheckBatchCost bypasses that hook entirely, + # so we do it explicitly here. + response.id = job.unified_object_id + + # This background job runs as default_user_id, so going through the HTTP endpoint + # would trigger check_managed_file_id_access and get 403. Instead, extract the raw + # provider file ID and call afile_content directly with deployment credentials. + raw_output_file_id = response.output_file_id + decoded = _is_base64_encoded_unified_file_id(raw_output_file_id) + if decoded: + try: + raw_output_file_id = decoded.split("llm_output_file_id,")[1].split(";")[0] + except (IndexError, AttributeError): + pass + + credentials = self.llm_router.get_deployment_credentials_with_provider(model_id) or {} + _file_content = await afile_content( + file_id=raw_output_file_id, + **credentials, + ) + + # Access content - handle both direct attribute and method call + if hasattr(_file_content, 'content'): + content_bytes = _file_content.content # type: ignore[union-attr] + elif hasattr(_file_content, 'read'): + content_bytes = await _file_content.read() # type: ignore[misc] + else: + content_bytes = _file_content # type: ignore[assignment] + + file_content_as_dict = _get_file_content_as_dictionary( + content_bytes # type: ignore[arg-type] + ) + + # Record output file size + if prom_logger and content_bytes: + try: + prom_logger.record_managed_file_size( + size_bytes=len(content_bytes), # type: ignore + purpose="batch", + file_type="output", + model=model_id, + ) + except Exception: + pass + + deployment_info = self.llm_router.get_deployment(model_id=model_id) + if deployment_info is None: + verbose_proxy_logger.info( + f"Skipping job {job.unified_object_id} because it is not a valid deployment info" + ) + self._record_error(prom_logger, "deployment_not_found") + return None + custom_llm_provider = deployment_info.litellm_params.custom_llm_provider + litellm_model_name = deployment_info.litellm_params.model + + model_name, llm_provider, _, _ = get_llm_provider( + model=litellm_model_name, + custom_llm_provider=custom_llm_provider, + ) + + # CheckBatchCost bypasses async_post_call_success_hook, so convert raw + # output/error file IDs to managed base64 IDs before the DB write here. + managed_files_hook = self.proxy_logging_obj.get_proxy_hook("managed_files") + if managed_files_hook is not None: + from litellm.proxy._types import UserAPIKeyAuth + _minimal_auth = UserAPIKeyAuth( + user_id=job.created_by or "default-user-id", + team_id=getattr(job, "team_id", None), + ) + for _file_attr in ["output_file_id", "error_file_id"]: + _raw_file_id = getattr(response, _file_attr, None) + if _raw_file_id and not _is_base64_encoded_unified_file_id(_raw_file_id): + try: + _unified_file_id = managed_files_hook.get_unified_output_file_id( + output_file_id=_raw_file_id, + model_id=model_id, + model_name=str(model_name) if model_name else deployment_info.model_name or None, + ) + await managed_files_hook.store_unified_file_id( + file_id=_unified_file_id, + file_object=None, + litellm_parent_otel_span=None, + model_mappings={model_id: _raw_file_id}, + user_api_key_dict=_minimal_auth, + ) + setattr(response, _file_attr, _unified_file_id) + verbose_proxy_logger.info( + f"CheckBatchCost: converted {_file_attr} " + f"{_raw_file_id!r} -> managed ID for batch {batch_id}" + ) + except Exception as _e: + verbose_proxy_logger.warning( + f"CheckBatchCost: failed to create managed file ID for " + f"{_file_attr}={_raw_file_id!r}: {_e}" + ) + + # Pass deployment model_info so custom batch pricing + # (input_cost_per_token_batches etc.) is used for cost calc + deployment_model_info = deployment_info.model_info.model_dump() if deployment_info.model_info else {} + batch_cost, batch_usage, batch_models = ( + await calculate_batch_cost_and_usage( + file_content_dictionary=file_content_as_dict, + custom_llm_provider=llm_provider, # type: ignore + model_name=model_name, + model_info=deployment_model_info, # type: ignore[arg-type] + ) + ) + logging_obj = LiteLLMLogging( + model=batch_models[0], + messages=[{"role": "user", "content": ""}], + stream=False, + call_type="aretrieve_batch", + start_time=datetime.now(), + litellm_call_id=str(uuid.uuid4()), + function_id=str(uuid.uuid4()), + ) + + creator_user_id = job.created_by + user_info = await self._get_user_info(batch_id, job.created_by) + + logging_obj.update_environment_variables( + litellm_params={ + # set the user-agent header so that S3 callback consumers can easily identify CheckBatchCost callbacks + "proxy_server_request": { + "headers": { + "user-agent": CHECK_BATCH_COST_USER_AGENT, + } + }, + "metadata": { + "user_api_key_user_id": creator_user_id, + **user_info, + }, + }, + optional_params={}, + ) + + await logging_obj.async_success_handler( + result=response, + batch_cost=batch_cost, + batch_usage=batch_usage, + batch_models=batch_models, + ) + + # Record batch duration (completed_at - created_at) + if prom_logger and response.completed_at and response.created_at: + duration_seconds = float(response.completed_at - response.created_at) + if duration_seconds >= 0: + prom_logger.record_managed_batch_duration( + duration_seconds=duration_seconds, + model=model_name, + api_provider=str(llm_provider) if llm_provider else None, + ) + + return model_name, str(llm_provider) if llm_provider else None + + async def check_batch_cost(self): + """ + Check if the batch JOB has been tracked. + - get all status="validating" and file_purpose="batch" jobs + - check if batch is now complete + - if not, return False + - if so, return True + """ try: from litellm.integrations.prometheus import PrometheusLogger prom_logger = PrometheusLogger.get_instance() @@ -381,177 +567,26 @@ class CheckBatchCost: response.status == "completed" and response.output_file_id is not None ): - verbose_proxy_logger.info( - f"Batch ID: {batch_id} is complete, tracking cost and usage" - ) - - # aretrieve_batch is called with the raw provider batch ID, so response.id - # is the raw provider value (e.g. "batch_20260223-0518.234"). We need the - # unified base64 ID in the S3 log so downstream consumers can correlate it - # back to the batch they submitted via the proxy. - # - # CheckBatchCost builds its own LiteLLMLogging object (logging_obj below) and - # calls async_success_handler(result=response) directly. That handler calls - # _build_standard_logging_payload(response, ...) which reads response.id at - # that point — so setting response.id here is sufficient. - # - # The HTTP endpoint does this substitution via the managed files hook - # (async_post_call_success_hook). CheckBatchCost bypasses that hook entirely, - # so we do it explicitly here. - response.id = job.unified_object_id - - # This background job runs as default_user_id, so going through the HTTP endpoint - # would trigger check_managed_file_id_access and get 403. Instead, extract the raw - # provider file ID and call afile_content directly with deployment credentials. - raw_output_file_id = response.output_file_id - decoded = _is_base64_encoded_unified_file_id(raw_output_file_id) - if decoded: - try: - raw_output_file_id = decoded.split("llm_output_file_id,")[1].split(";")[0] - except (IndexError, AttributeError): - pass - - credentials = self.llm_router.get_deployment_credentials_with_provider(model_id) or {} - _file_content = await afile_content( - file_id=raw_output_file_id, - **credentials, - ) - - # Access content - handle both direct attribute and method call - if hasattr(_file_content, 'content'): - content_bytes = _file_content.content # type: ignore[union-attr] - elif hasattr(_file_content, 'read'): - content_bytes = await _file_content.read() # type: ignore[misc] - else: - content_bytes = _file_content # type: ignore[assignment] - - file_content_as_dict = _get_file_content_as_dictionary( - content_bytes # type: ignore[arg-type] - ) - - # Record output file size - if prom_logger and content_bytes: - try: - prom_logger.record_managed_file_size( - size_bytes=len(content_bytes), # type: ignore - purpose="batch", - file_type="output", - model=model_id, - ) - except Exception: - pass - - deployment_info = self.llm_router.get_deployment(model_id=model_id) - if deployment_info is None: - verbose_proxy_logger.info( - f"Skipping job {job.unified_object_id} because it is not a valid deployment info" + try: + tracked = await self._track_completed_batch_cost( + job=job, + response=response, + model_id=model_id, + batch_id=batch_id, + prom_logger=prom_logger, ) - if prom_logger: - prom_logger.record_check_batch_cost_error("deployment_not_found") + except Exception as tracking_err: + verbose_proxy_logger.error( + f"CheckBatchCost: failed to track cost for batch {batch_id} " + f"(job {job.id}); leaving it unprocessed so the next poll retries: {tracking_err}" + ) + self._record_error(prom_logger, "cost_tracking_error") + continue + if tracked is None: continue - custom_llm_provider = deployment_info.litellm_params.custom_llm_provider - litellm_model_name = deployment_info.litellm_params.model - - model_name, llm_provider, _, _ = get_llm_provider( - model=litellm_model_name, - custom_llm_provider=custom_llm_provider, - ) - - # CheckBatchCost bypasses async_post_call_success_hook, so convert raw - # output/error file IDs to managed base64 IDs before the DB write here. - managed_files_hook = self.proxy_logging_obj.get_proxy_hook("managed_files") - if managed_files_hook is not None: - from litellm.proxy._types import UserAPIKeyAuth - _minimal_auth = UserAPIKeyAuth( - user_id=job.created_by or "default-user-id", - team_id=getattr(job, "team_id", None), - ) - for _file_attr in ["output_file_id", "error_file_id"]: - _raw_file_id = getattr(response, _file_attr, None) - if _raw_file_id and not _is_base64_encoded_unified_file_id(_raw_file_id): - try: - _unified_file_id = managed_files_hook.get_unified_output_file_id( - output_file_id=_raw_file_id, - model_id=model_id, - model_name=str(model_name) if model_name else deployment_info.model_name or None, - ) - await managed_files_hook.store_unified_file_id( - file_id=_unified_file_id, - file_object=None, - litellm_parent_otel_span=None, - model_mappings={model_id: _raw_file_id}, - user_api_key_dict=_minimal_auth, - ) - setattr(response, _file_attr, _unified_file_id) - verbose_proxy_logger.info( - f"CheckBatchCost: converted {_file_attr} " - f"{_raw_file_id!r} -> managed ID for batch {batch_id}" - ) - except Exception as _e: - verbose_proxy_logger.warning( - f"CheckBatchCost: failed to create managed file ID for " - f"{_file_attr}={_raw_file_id!r}: {_e}" - ) - - # Pass deployment model_info so custom batch pricing - # (input_cost_per_token_batches etc.) is used for cost calc - deployment_model_info = deployment_info.model_info.model_dump() if deployment_info.model_info else {} - batch_cost, batch_usage, batch_models = ( - await calculate_batch_cost_and_usage( - file_content_dictionary=file_content_as_dict, - custom_llm_provider=llm_provider, # type: ignore - model_name=model_name, - model_info=deployment_model_info, # type: ignore[arg-type] - ) - ) - logging_obj = LiteLLMLogging( - model=batch_models[0], - messages=[{"role": "user", "content": ""}], - stream=False, - call_type="aretrieve_batch", - start_time=datetime.now(), - litellm_call_id=str(uuid.uuid4()), - function_id=str(uuid.uuid4()), - ) - - creator_user_id = job.created_by - user_info = await self._get_user_info(batch_id, job.created_by) - - logging_obj.update_environment_variables( - litellm_params={ - # set the user-agent header so that S3 callback consumers can easily identify CheckBatchCost callbacks - "proxy_server_request": { - "headers": { - "user-agent": CHECK_BATCH_COST_USER_AGENT, - } - }, - "metadata": { - "user_api_key_user_id": creator_user_id, - **user_info, - }, - }, - optional_params={}, - ) - - await logging_obj.async_success_handler( - result=response, - batch_cost=batch_cost, - batch_usage=batch_usage, - batch_models=batch_models, - ) - - # Record batch duration (completed_at - created_at) - if prom_logger and response.completed_at and response.created_at: - duration_seconds = float(response.completed_at - response.created_at) - if duration_seconds >= 0: - prom_logger.record_managed_batch_duration( - duration_seconds=duration_seconds, - model=model_name, - api_provider=str(llm_provider) if llm_provider else None, - ) # Track this job for the final metrics summary - processed_models.append((model_name, str(llm_provider) if llm_provider else None)) + processed_models.append(tracked) # mark the job as complete try: diff --git a/enterprise/pyproject.toml b/enterprise/pyproject.toml index f4d756de44c..b3864ce7878 100644 --- a/enterprise/pyproject.toml +++ b/enterprise/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "litellm-enterprise" -version = "0.1.47" +version = "0.1.48" description = "Package for LiteLLM Enterprise features" readme = "README.md" requires-python = ">=3.9" @@ -26,7 +26,7 @@ required-version = ">=0.10.9" module-root = "" [tool.commitizen] -version = "0.1.47" +version = "0.1.48" version_files = [ "pyproject.toml:^version", "../pyproject.toml:litellm-enterprise==", diff --git a/gateway/Dockerfile b/gateway/Dockerfile index 716b2fa09d1..da2f2c9c1e0 100644 --- a/gateway/Dockerfile +++ b/gateway/Dockerfile @@ -1,5 +1,5 @@ -ARG LITELLM_BUILD_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:c61ac6919b811ea53c4782d69f1fe05218ba3c25d53f01b6ab7892e621bd4370 -ARG LITELLM_RUNTIME_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:c61ac6919b811ea53c4782d69f1fe05218ba3c25d53f01b6ab7892e621bd4370 +ARG LITELLM_BUILD_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:42df77a9974d6ec8b17a5ee8bc23b532600a44d705acef2409e0933c1251b45f +ARG LITELLM_RUNTIME_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:42df77a9974d6ec8b17a5ee8bc23b532600a44d705acef2409e0933c1251b45f ARG UV_IMAGE=ghcr.io/astral-sh/uv:0.11.7@sha256:240fb85ab0f263ef12f492d8476aa3a2e4e1e333f7d67fbdd923d00a506a516a FROM $UV_IMAGE AS uvbin diff --git a/gateway/main.py b/gateway/main.py index 09d30f5da3f..61b885b27e4 100644 --- a/gateway/main.py +++ b/gateway/main.py @@ -25,17 +25,25 @@ DatabaseURLSettings.from_env().apply_to_env() from litellm.proxy.proxy_server import app -from gateway.routes.allowlist import GATEWAY_EXACT_PATHS, GATEWAY_PATH_PREFIXES +from gateway.routes.allowlist import ( + GATEWAY_EXACT_PATHS, + GATEWAY_MOUNT_PATHS, + GATEWAY_PATH_PREFIXES, +) def _is_gateway_route(route) -> bool: - """Keep the route on the gateway if its path is in the LLM data-plane surface.""" + """Keep the route on the gateway if its path is in the LLM data-plane surface. + + Prometheus registers /metrics as a Mount (``app.mount("/metrics", make_asgi_app())``), + so Mounts are matched against GATEWAY_MOUNT_PATHS instead of being dropped with + the UI static mounts. + """ path = getattr(route, "path", None) if path is None: return False if isinstance(route, Mount): - # Gateway never serves the static UI or its asset bundles. - return False + return path in GATEWAY_MOUNT_PATHS if path in GATEWAY_EXACT_PATHS: return True return any(path.startswith(prefix) for prefix in GATEWAY_PATH_PREFIXES) diff --git a/gateway/routes/allowlist.py b/gateway/routes/allowlist.py index 144bb4c473f..792a56a2cd8 100644 --- a/gateway/routes/allowlist.py +++ b/gateway/routes/allowlist.py @@ -106,7 +106,7 @@ GATEWAY_PATH_PREFIXES: tuple[str, ...] = ( # Health & ops "/health", "/metrics", - "/watsonx" + "/watsonx", ) GATEWAY_EXACT_PATHS: frozenset[str] = frozenset( @@ -120,3 +120,9 @@ GATEWAY_EXACT_PATHS: frozenset[str] = frozenset( "/test", } ) + +GATEWAY_MOUNT_PATHS: frozenset[str] = frozenset( + { + "/metrics", + } +) diff --git a/deploy/charts/litellm-helm/.helmignore b/helm/litellm-helm/.helmignore similarity index 100% rename from deploy/charts/litellm-helm/.helmignore rename to helm/litellm-helm/.helmignore diff --git a/deploy/charts/litellm-helm/Chart.lock b/helm/litellm-helm/Chart.lock similarity index 100% rename from deploy/charts/litellm-helm/Chart.lock rename to helm/litellm-helm/Chart.lock diff --git a/deploy/charts/litellm-helm/Chart.yaml b/helm/litellm-helm/Chart.yaml similarity index 100% rename from deploy/charts/litellm-helm/Chart.yaml rename to helm/litellm-helm/Chart.yaml diff --git a/deploy/charts/litellm-helm/README.md b/helm/litellm-helm/README.md similarity index 100% rename from deploy/charts/litellm-helm/README.md rename to helm/litellm-helm/README.md diff --git a/deploy/charts/litellm-helm/charts/postgresql-14.3.1.tgz b/helm/litellm-helm/charts/postgresql-14.3.1.tgz similarity index 100% rename from deploy/charts/litellm-helm/charts/postgresql-14.3.1.tgz rename to helm/litellm-helm/charts/postgresql-14.3.1.tgz diff --git a/deploy/charts/litellm-helm/charts/redis-18.19.1.tgz b/helm/litellm-helm/charts/redis-18.19.1.tgz similarity index 100% rename from deploy/charts/litellm-helm/charts/redis-18.19.1.tgz rename to helm/litellm-helm/charts/redis-18.19.1.tgz diff --git a/deploy/charts/litellm-helm/ci/test-values.yaml b/helm/litellm-helm/ci/test-values.yaml similarity index 100% rename from deploy/charts/litellm-helm/ci/test-values.yaml rename to helm/litellm-helm/ci/test-values.yaml diff --git a/deploy/charts/litellm-helm/templates/NOTES.txt b/helm/litellm-helm/templates/NOTES.txt similarity index 100% rename from deploy/charts/litellm-helm/templates/NOTES.txt rename to helm/litellm-helm/templates/NOTES.txt diff --git a/deploy/charts/litellm-helm/templates/_helpers.tpl b/helm/litellm-helm/templates/_helpers.tpl similarity index 100% rename from deploy/charts/litellm-helm/templates/_helpers.tpl rename to helm/litellm-helm/templates/_helpers.tpl diff --git a/deploy/charts/litellm-helm/templates/configmap-litellm.yaml b/helm/litellm-helm/templates/configmap-litellm.yaml similarity index 100% rename from deploy/charts/litellm-helm/templates/configmap-litellm.yaml rename to helm/litellm-helm/templates/configmap-litellm.yaml diff --git a/deploy/charts/litellm-helm/templates/deployment.yaml b/helm/litellm-helm/templates/deployment.yaml similarity index 100% rename from deploy/charts/litellm-helm/templates/deployment.yaml rename to helm/litellm-helm/templates/deployment.yaml diff --git a/deploy/charts/litellm-helm/templates/extra-resources.yaml b/helm/litellm-helm/templates/extra-resources.yaml similarity index 100% rename from deploy/charts/litellm-helm/templates/extra-resources.yaml rename to helm/litellm-helm/templates/extra-resources.yaml diff --git a/deploy/charts/litellm-helm/templates/hpa.yaml b/helm/litellm-helm/templates/hpa.yaml similarity index 100% rename from deploy/charts/litellm-helm/templates/hpa.yaml rename to helm/litellm-helm/templates/hpa.yaml diff --git a/deploy/charts/litellm-helm/templates/ingress.yaml b/helm/litellm-helm/templates/ingress.yaml similarity index 100% rename from deploy/charts/litellm-helm/templates/ingress.yaml rename to helm/litellm-helm/templates/ingress.yaml diff --git a/deploy/charts/litellm-helm/templates/keda.yaml b/helm/litellm-helm/templates/keda.yaml similarity index 100% rename from deploy/charts/litellm-helm/templates/keda.yaml rename to helm/litellm-helm/templates/keda.yaml diff --git a/deploy/charts/litellm-helm/templates/migrations-job.yaml b/helm/litellm-helm/templates/migrations-job.yaml similarity index 100% rename from deploy/charts/litellm-helm/templates/migrations-job.yaml rename to helm/litellm-helm/templates/migrations-job.yaml diff --git a/deploy/charts/litellm-helm/templates/poddisruptionbudget.yaml b/helm/litellm-helm/templates/poddisruptionbudget.yaml similarity index 100% rename from deploy/charts/litellm-helm/templates/poddisruptionbudget.yaml rename to helm/litellm-helm/templates/poddisruptionbudget.yaml diff --git a/deploy/charts/litellm-helm/templates/secret-dbcredentials.yaml b/helm/litellm-helm/templates/secret-dbcredentials.yaml similarity index 100% rename from deploy/charts/litellm-helm/templates/secret-dbcredentials.yaml rename to helm/litellm-helm/templates/secret-dbcredentials.yaml diff --git a/deploy/charts/litellm-helm/templates/secret-masterkey.yaml b/helm/litellm-helm/templates/secret-masterkey.yaml similarity index 100% rename from deploy/charts/litellm-helm/templates/secret-masterkey.yaml rename to helm/litellm-helm/templates/secret-masterkey.yaml diff --git a/deploy/charts/litellm-helm/templates/service.yaml b/helm/litellm-helm/templates/service.yaml similarity index 100% rename from deploy/charts/litellm-helm/templates/service.yaml rename to helm/litellm-helm/templates/service.yaml diff --git a/deploy/charts/litellm-helm/templates/serviceaccount.yaml b/helm/litellm-helm/templates/serviceaccount.yaml similarity index 100% rename from deploy/charts/litellm-helm/templates/serviceaccount.yaml rename to helm/litellm-helm/templates/serviceaccount.yaml diff --git a/deploy/charts/litellm-helm/templates/servicemonitor.yaml b/helm/litellm-helm/templates/servicemonitor.yaml similarity index 100% rename from deploy/charts/litellm-helm/templates/servicemonitor.yaml rename to helm/litellm-helm/templates/servicemonitor.yaml diff --git a/deploy/charts/litellm-helm/templates/tests/test-connection.yaml b/helm/litellm-helm/templates/tests/test-connection.yaml similarity index 100% rename from deploy/charts/litellm-helm/templates/tests/test-connection.yaml rename to helm/litellm-helm/templates/tests/test-connection.yaml diff --git a/deploy/charts/litellm-helm/templates/tests/test-env-vars.yaml b/helm/litellm-helm/templates/tests/test-env-vars.yaml similarity index 100% rename from deploy/charts/litellm-helm/templates/tests/test-env-vars.yaml rename to helm/litellm-helm/templates/tests/test-env-vars.yaml diff --git a/deploy/charts/litellm-helm/templates/tests/test-servicemonitor.yaml b/helm/litellm-helm/templates/tests/test-servicemonitor.yaml similarity index 100% rename from deploy/charts/litellm-helm/templates/tests/test-servicemonitor.yaml rename to helm/litellm-helm/templates/tests/test-servicemonitor.yaml diff --git a/deploy/charts/litellm-helm/tests/deployment_command_args_labels_tests.yaml b/helm/litellm-helm/tests/deployment_command_args_labels_tests.yaml similarity index 100% rename from deploy/charts/litellm-helm/tests/deployment_command_args_labels_tests.yaml rename to helm/litellm-helm/tests/deployment_command_args_labels_tests.yaml diff --git a/deploy/charts/litellm-helm/tests/deployment_tests.yaml b/helm/litellm-helm/tests/deployment_tests.yaml similarity index 100% rename from deploy/charts/litellm-helm/tests/deployment_tests.yaml rename to helm/litellm-helm/tests/deployment_tests.yaml diff --git a/deploy/charts/litellm-helm/tests/hpa_tests.yaml b/helm/litellm-helm/tests/hpa_tests.yaml similarity index 100% rename from deploy/charts/litellm-helm/tests/hpa_tests.yaml rename to helm/litellm-helm/tests/hpa_tests.yaml diff --git a/deploy/charts/litellm-helm/tests/ingress_tests.yaml b/helm/litellm-helm/tests/ingress_tests.yaml similarity index 100% rename from deploy/charts/litellm-helm/tests/ingress_tests.yaml rename to helm/litellm-helm/tests/ingress_tests.yaml diff --git a/deploy/charts/litellm-helm/tests/masterkey-secret_tests.yaml b/helm/litellm-helm/tests/masterkey-secret_tests.yaml similarity index 100% rename from deploy/charts/litellm-helm/tests/masterkey-secret_tests.yaml rename to helm/litellm-helm/tests/masterkey-secret_tests.yaml diff --git a/deploy/charts/litellm-helm/tests/migrations-job_tests.yaml b/helm/litellm-helm/tests/migrations-job_tests.yaml similarity index 100% rename from deploy/charts/litellm-helm/tests/migrations-job_tests.yaml rename to helm/litellm-helm/tests/migrations-job_tests.yaml diff --git a/deploy/charts/litellm-helm/tests/pdb_tests.yaml b/helm/litellm-helm/tests/pdb_tests.yaml similarity index 100% rename from deploy/charts/litellm-helm/tests/pdb_tests.yaml rename to helm/litellm-helm/tests/pdb_tests.yaml diff --git a/deploy/charts/litellm-helm/tests/service_tests.yaml b/helm/litellm-helm/tests/service_tests.yaml similarity index 100% rename from deploy/charts/litellm-helm/tests/service_tests.yaml rename to helm/litellm-helm/tests/service_tests.yaml diff --git a/deploy/charts/litellm-helm/values.yaml b/helm/litellm-helm/values.yaml similarity index 100% rename from deploy/charts/litellm-helm/values.yaml rename to helm/litellm-helm/values.yaml diff --git a/helm/litellm/templates/backend/deployment.yaml b/helm/litellm/templates/backend/deployment.yaml index b355db43540..8b4552bf302 100644 --- a/helm/litellm/templates/backend/deployment.yaml +++ b/helm/litellm/templates/backend/deployment.yaml @@ -45,11 +45,16 @@ spec: value: /app/config/config.yaml {{- end }} {{- include "litellm.envFrom" .Values.backend | nindent 10 }} - {{- if .Values.gateway.config.create }} + {{- if or .Values.gateway.config.create .Values.backend.volumeMounts }} volumeMounts: + {{- if .Values.gateway.config.create }} - name: gateway-config mountPath: /app/config/config.yaml subPath: config.yaml + {{- end }} + {{- with .Values.backend.volumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} {{- end }} {{- with .Values.backend.livenessProbe }} livenessProbe: @@ -61,11 +66,16 @@ spec: {{- end }} resources: {{- toYaml .Values.backend.resources | nindent 12 }} - {{- if .Values.gateway.config.create }} + {{- if or .Values.gateway.config.create .Values.backend.volumes }} volumes: + {{- if .Values.gateway.config.create }} - name: gateway-config configMap: name: {{ include "litellm.gateway.fullname" . }}-config + {{- end }} + {{- with .Values.backend.volumes }} + {{- toYaml . | nindent 8 }} + {{- end }} {{- end }} {{- with .Values.backend.nodeSelector }} nodeSelector: diff --git a/helm/litellm/templates/gateway/deployment.yaml b/helm/litellm/templates/gateway/deployment.yaml index 05ea4052159..bd491b69e0f 100644 --- a/helm/litellm/templates/gateway/deployment.yaml +++ b/helm/litellm/templates/gateway/deployment.yaml @@ -47,11 +47,16 @@ spec: value: {{ .Values.gateway.numWorkers | quote }} {{- end }} {{- include "litellm.envFrom" .Values.gateway | nindent 10 }} - {{- if .Values.gateway.config.create }} + {{- if or .Values.gateway.config.create .Values.gateway.volumeMounts }} volumeMounts: + {{- if .Values.gateway.config.create }} - name: gateway-config mountPath: /app/config/config.yaml subPath: config.yaml + {{- end }} + {{- with .Values.gateway.volumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} {{- end }} {{- with .Values.gateway.livenessProbe }} livenessProbe: @@ -63,11 +68,16 @@ spec: {{- end }} resources: {{- toYaml .Values.gateway.resources | nindent 12 }} - {{- if .Values.gateway.config.create }} + {{- if or .Values.gateway.config.create .Values.gateway.volumes }} volumes: + {{- if .Values.gateway.config.create }} - name: gateway-config configMap: name: {{ include "litellm.gateway.fullname" . }}-config + {{- end }} + {{- with .Values.gateway.volumes }} + {{- toYaml . | nindent 8 }} + {{- end }} {{- end }} {{- with .Values.gateway.nodeSelector }} nodeSelector: diff --git a/helm/litellm/templates/ui/deployment.yaml b/helm/litellm/templates/ui/deployment.yaml index b40b44cca53..79e9a3e43bb 100644 --- a/helm/litellm/templates/ui/deployment.yaml +++ b/helm/litellm/templates/ui/deployment.yaml @@ -46,6 +46,10 @@ spec: {{- toYaml . | nindent 12 }} {{- end }} {{- include "litellm.envFrom" .Values.ui | nindent 10 }} + {{- with .Values.ui.volumeMounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} {{- with .Values.ui.livenessProbe }} livenessProbe: {{- toYaml . | nindent 12 }} @@ -56,6 +60,10 @@ spec: {{- end }} resources: {{- toYaml .Values.ui.resources | nindent 12 }} + {{- with .Values.ui.volumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} {{- with .Values.ui.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/helm/litellm/tests/deployment_volumes_tests.yaml b/helm/litellm/tests/deployment_volumes_tests.yaml new file mode 100644 index 00000000000..3a64300b86c --- /dev/null +++ b/helm/litellm/tests/deployment_volumes_tests.yaml @@ -0,0 +1,172 @@ +suite: test deployment volumes and volumeMounts +templates: + - gateway/deployment.yaml + - gateway/configmap.yaml + - backend/deployment.yaml + - ui/deployment.yaml +values: + - ./values/required.yaml +tests: + - it: gateway renders only the config volume by default + template: gateway/deployment.yaml + asserts: + - equal: + path: spec.template.spec.volumes + value: + - name: gateway-config + configMap: + name: RELEASE-NAME-litellm-gateway-config + - equal: + path: spec.template.spec.containers[0].volumeMounts + value: + - name: gateway-config + mountPath: /app/config/config.yaml + subPath: config.yaml + + - it: gateway merges user volumes and volumeMounts with the config volume + template: gateway/deployment.yaml + set: + gateway.volumes: + - name: custom-callbacks + configMap: + name: custom-callbacks + gateway.volumeMounts: + - name: custom-callbacks + mountPath: /app/custom_callbacks.py + subPath: custom_callbacks.py + asserts: + - equal: + path: spec.template.spec.volumes[0].name + value: gateway-config + - equal: + path: spec.template.spec.volumes[1] + value: + name: custom-callbacks + configMap: + name: custom-callbacks + - equal: + path: spec.template.spec.containers[0].volumeMounts[0].name + value: gateway-config + - equal: + path: spec.template.spec.containers[0].volumeMounts[1] + value: + name: custom-callbacks + mountPath: /app/custom_callbacks.py + subPath: custom_callbacks.py + + - it: gateway renders user volumes even when config creation is disabled + template: gateway/deployment.yaml + set: + gateway.config.create: false + gateway.volumes: + - name: certs + secret: + secretName: tls-certs + gateway.volumeMounts: + - name: certs + mountPath: /etc/certs + readOnly: true + asserts: + - equal: + path: spec.template.spec.volumes + value: + - name: certs + secret: + secretName: tls-certs + - equal: + path: spec.template.spec.containers[0].volumeMounts + value: + - name: certs + mountPath: /etc/certs + readOnly: true + + - it: gateway omits volumes when config creation is disabled and no user volumes are set + template: gateway/deployment.yaml + set: + gateway.config.create: false + asserts: + - isNull: + path: spec.template.spec.volumes + - isNull: + path: spec.template.spec.containers[0].volumeMounts + + - it: backend merges user volumes and volumeMounts with the shared config volume + template: backend/deployment.yaml + set: + backend.volumes: + - name: sso-handler + configMap: + name: sso-handler + backend.volumeMounts: + - name: sso-handler + mountPath: /app/custom_sso.py + subPath: custom_sso.py + asserts: + - equal: + path: spec.template.spec.volumes[0].name + value: gateway-config + - equal: + path: spec.template.spec.volumes[1] + value: + name: sso-handler + configMap: + name: sso-handler + - equal: + path: spec.template.spec.containers[0].volumeMounts[1] + value: + name: sso-handler + mountPath: /app/custom_sso.py + subPath: custom_sso.py + + - it: backend renders user volumes even when config creation is disabled + template: backend/deployment.yaml + set: + gateway.config.create: false + backend.volumes: + - name: data + emptyDir: {} + backend.volumeMounts: + - name: data + mountPath: /data + asserts: + - equal: + path: spec.template.spec.volumes + value: + - name: data + emptyDir: {} + - equal: + path: spec.template.spec.containers[0].volumeMounts + value: + - name: data + mountPath: /data + + - it: ui renders no volumes by default + template: ui/deployment.yaml + asserts: + - isNull: + path: spec.template.spec.volumes + - isNull: + path: spec.template.spec.containers[0].volumeMounts + + - it: ui renders user volumes and volumeMounts + template: ui/deployment.yaml + set: + ui.volumes: + - name: nginx-config + configMap: + name: custom-nginx + ui.volumeMounts: + - name: nginx-config + mountPath: /etc/nginx/conf.d + asserts: + - equal: + path: spec.template.spec.volumes + value: + - name: nginx-config + configMap: + name: custom-nginx + - equal: + path: spec.template.spec.containers[0].volumeMounts + value: + - name: nginx-config + mountPath: /etc/nginx/conf.d diff --git a/helm/litellm/tests/values/required.yaml b/helm/litellm/tests/values/required.yaml new file mode 100644 index 00000000000..21d3f7a5a6a --- /dev/null +++ b/helm/litellm/tests/values/required.yaml @@ -0,0 +1,4 @@ +database: + writer: + host: postgres.example.com + dbname: litellm diff --git a/helm/litellm/values.yaml b/helm/litellm/values.yaml index 934661643bd..6aa5dd39cd0 100644 --- a/helm/litellm/values.yaml +++ b/helm/litellm/values.yaml @@ -124,6 +124,11 @@ gateway: extraEnv: [] # Add extra environment variables to the gateway envConfigMaps: [] # Add extra environment variables to the gateway from config maps envSecrets: [] # Add extra environment variables to the gateway from secrets + # Additional volumes on the gateway Deployment (e.g. a ConfigMap holding + # custom callback / SSO handler code, mounted next to the proxy config). + volumes: [] + # Additional volumeMounts on the gateway container. + volumeMounts: [] config: create: true proxy_config: {} @@ -167,6 +172,10 @@ backend: extraEnv: [] envConfigMaps: [] envSecrets: [] + # Additional volumes on the backend Deployment. + volumes: [] + # Additional volumeMounts on the backend container. + volumeMounts: [] image: repository: ghcr.io/berriai/litellm-backend tag: "" @@ -206,6 +215,10 @@ ui: extraEnv: [] envConfigMaps: [] envSecrets: [] + # Additional volumes on the ui Deployment. + volumes: [] + # Additional volumeMounts on the ui container. + volumeMounts: [] image: repository: ghcr.io/berriai/litellm-ui tag: "" diff --git a/litellm-proxy-extras/litellm_proxy_extras/migrations/20260630120000_add_token_exchange_to_mcp_servers/migration.sql b/litellm-proxy-extras/litellm_proxy_extras/migrations/20260630120000_add_token_exchange_to_mcp_servers/migration.sql new file mode 100644 index 00000000000..dec5fccc319 --- /dev/null +++ b/litellm-proxy-extras/litellm_proxy_extras/migrations/20260630120000_add_token_exchange_to_mcp_servers/migration.sql @@ -0,0 +1,8 @@ +-- Timestamp sorts before some already-applied migrations; this is safe: the +-- runner is `prisma migrate deploy`, which applies every pending migration +-- regardless of name order (utils.py has an informational check for exactly +-- this), and IF NOT EXISTS keeps a re-apply idempotent. +-- AlterTable +ALTER TABLE "LiteLLM_MCPServerTable" ADD COLUMN IF NOT EXISTS "token_exchange_endpoint" TEXT; +ALTER TABLE "LiteLLM_MCPServerTable" ADD COLUMN IF NOT EXISTS "audience" TEXT; +ALTER TABLE "LiteLLM_MCPServerTable" ADD COLUMN IF NOT EXISTS "subject_token_type" TEXT; diff --git a/litellm-proxy-extras/litellm_proxy_extras/migrations/20260703120000_add_token_exchange_profile_to_mcp_servers/migration.sql b/litellm-proxy-extras/litellm_proxy_extras/migrations/20260703120000_add_token_exchange_profile_to_mcp_servers/migration.sql new file mode 100644 index 00000000000..6dda56c4fb3 --- /dev/null +++ b/litellm-proxy-extras/litellm_proxy_extras/migrations/20260703120000_add_token_exchange_profile_to_mcp_servers/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "LiteLLM_MCPServerTable" ADD COLUMN IF NOT EXISTS "token_exchange_profile" TEXT; diff --git a/litellm-proxy-extras/litellm_proxy_extras/schema.prisma b/litellm-proxy-extras/litellm_proxy_extras/schema.prisma index f9ab5e6aefd..6d87102a3f3 100644 --- a/litellm-proxy-extras/litellm_proxy_extras/schema.prisma +++ b/litellm-proxy-extras/litellm_proxy_extras/schema.prisma @@ -329,6 +329,12 @@ model LiteLLM_MCPServerTable { token_url String? registration_url String? oauth2_flow String? + token_exchange_endpoint String? + // Named for the RFC 8693 "audience" token-exchange request parameter (that flow only). + // RFC 8707 resource indicators are a separate concept, named "resource" in the v2 egress types. + audience String? + subject_token_type String? + token_exchange_profile String? allow_all_keys Boolean @default(false) available_on_public_internet Boolean @default(true) delegate_auth_to_upstream Boolean @default(false) diff --git a/litellm/__init__.py b/litellm/__init__.py index 2ec0830d622..6e2a03b7c7c 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -379,6 +379,7 @@ budget_duration: Optional[str] = ( None # proxy only - resets budget after fixed duration. You can set duration as seconds ("30s"), minutes ("30m"), hours ("30h"), days ("30d"). ) default_soft_budget: float = DEFAULT_SOFT_BUDGET # by default all litellm proxy keys have a soft budget of 50.0 +budget_exceeded_throttle_percentage: Optional[float] = None forward_traceparent_to_llm_provider: bool = False diff --git a/litellm/a2a_protocol/streaming_iterator.py b/litellm/a2a_protocol/streaming_iterator.py index 529154919f3..1ef174a5eee 100644 --- a/litellm/a2a_protocol/streaming_iterator.py +++ b/litellm/a2a_protocol/streaming_iterator.py @@ -11,7 +11,6 @@ from litellm._logging import verbose_logger from litellm.a2a_protocol.cost_calculator import A2ACostCalculator from litellm.a2a_protocol.utils import A2ARequestUtils from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj -from litellm.litellm_core_utils.thread_pool_executor import executor if TYPE_CHECKING: from a2a.types import SendStreamingMessageRequest, SendStreamingMessageResponse @@ -128,22 +127,15 @@ class A2AStreamingIterator: # Call success handlers - they will build standard_logging_object asyncio.create_task( - self.logging_obj.async_success_handler( - result=result, + self.logging_obj.dispatch_success_handlers( + result, start_time=self.start_time, end_time=end_time, cache_hit=None, + prefer_async_handlers=True, ) ) - executor.submit( - self.logging_obj.success_handler, - result=result, - cache_hit=None, - start_time=self.start_time, - end_time=end_time, - ) - verbose_logger.info( f"A2A streaming completed: prompt_tokens={prompt_tokens}, " f"completion_tokens={completion_tokens}, total_tokens={total_tokens}, " diff --git a/litellm/batches/batch_utils.py b/litellm/batches/batch_utils.py index 985198ce7ce..11b07d39981 100644 --- a/litellm/batches/batch_utils.py +++ b/litellm/batches/batch_utils.py @@ -3,6 +3,7 @@ from typing import Any, Iterator, List, Literal, Optional, Tuple import litellm from litellm._logging import verbose_logger +from litellm.litellm_core_utils.llm_cost_calc.utils import _parse_prompt_tokens_details from litellm.types.llms.openai import Batch from litellm.types.utils import CallTypes, ModelInfo, Usage from litellm.utils import token_counter @@ -34,7 +35,7 @@ async def calculate_batch_cost_and_usage( custom_llm_provider=custom_llm_provider, model_name=model_name, ) - batch_models = _get_batch_models_from_file_content(file_content_dictionary, model_name) + batch_models = _get_batch_models_from_file_content(file_content_dictionary, model_name, custom_llm_provider) return batch_cost, batch_usage, batch_models @@ -70,7 +71,7 @@ async def _handle_completed_batch( model_name=model_name, ) - batch_models = _get_batch_models_from_file_content(file_content_dictionary, model_name) + batch_models = _get_batch_models_from_file_content(file_content_dictionary, model_name, custom_llm_provider) return batch_cost, batch_usage, batch_models @@ -78,6 +79,7 @@ async def _handle_completed_batch( def _get_batch_models_from_file_content( file_content_dictionary: List[dict], model_name: Optional[str] = None, + custom_llm_provider: str = "openai", ) -> List[str]: """ Get the models from the file content @@ -86,8 +88,8 @@ def _get_batch_models_from_file_content( return [model_name] batch_models = [] for _item in file_content_dictionary: - if _batch_response_was_successful(_item): - _response_body = _get_response_from_batch_job_output_file(_item) + if _batch_response_was_successful(_item, custom_llm_provider): + _response_body = _get_response_from_batch_job_output_file(_item, custom_llm_provider) _model = _response_body.get("model") if _model: batch_models.append(_model) @@ -373,10 +375,10 @@ def _get_batch_job_cost_from_file_content( # parse the file content as json verbose_logger.debug("file_content_dictionary=%s", json.dumps(file_content_dictionary, indent=4)) for _item in file_content_dictionary: - if _batch_response_was_successful(_item): - _response_body = _get_response_from_batch_job_output_file(_item) - if model_info is not None: - usage = _get_batch_job_usage_from_response_body(_response_body) + if _batch_response_was_successful(_item, custom_llm_provider): + _response_body = _get_response_from_batch_job_output_file(_item, custom_llm_provider) + if model_info is not None or custom_llm_provider == "anthropic": + usage = _get_batch_job_usage_from_response_body(_response_body, custom_llm_provider) model = _response_body.get("model", "") prompt_cost, completion_cost = batch_cost_calculator( usage=usage, @@ -418,17 +420,31 @@ def _get_batch_job_total_usage_from_file_content( total_tokens: int = 0 prompt_tokens: int = 0 completion_tokens: int = 0 + cache_read_tokens: int = 0 + cache_creation_tokens: int = 0 for _item in file_content_dictionary: - if _batch_response_was_successful(_item): - _response_body = _get_response_from_batch_job_output_file(_item) - usage: Usage = _get_batch_job_usage_from_response_body(_response_body) + if _batch_response_was_successful(_item, custom_llm_provider): + _response_body = _get_response_from_batch_job_output_file(_item, custom_llm_provider) + usage: Usage = _get_batch_job_usage_from_response_body(_response_body, custom_llm_provider) total_tokens += usage.total_tokens prompt_tokens += usage.prompt_tokens completion_tokens += usage.completion_tokens + prompt_details = _parse_prompt_tokens_details(usage) + cache_read_tokens += prompt_details["cache_hit_tokens"] + cache_creation_tokens += prompt_details["cache_creation_tokens"] + cache_token_params = { + key: tokens + for key, tokens in ( + ("cache_read_input_tokens", cache_read_tokens), + ("cache_creation_input_tokens", cache_creation_tokens), + ) + if tokens > 0 + } return Usage( total_tokens=total_tokens, prompt_tokens=prompt_tokens, completion_tokens=completion_tokens, + **cache_token_params, ) @@ -465,27 +481,51 @@ def _count_prompt_or_input_tokens(model: str, value: Any) -> int: return 0 -def _get_batch_job_usage_from_response_body(response_body: dict) -> Usage: +def _get_batch_job_usage_from_response_body(response_body: dict, custom_llm_provider: str = "openai") -> Usage: """ Get the tokens of a batch job from the response body """ + if custom_llm_provider == "anthropic": + from litellm.llms.anthropic.chat.transformation import AnthropicConfig + + return AnthropicConfig().calculate_usage( + usage_object=response_body.get("usage", None) or {}, + reasoning_content=None, + ) _usage_dict = response_body.get("usage", None) or {} usage: Usage = Usage(**_usage_dict) return usage -def _get_response_from_batch_job_output_file(batch_job_output_file: dict) -> Any: +def _get_anthropic_result_from_batch_results_line(batch_results_line: dict) -> dict: + """ + Get the ``result`` object from a line of an Anthropic message batch results JSONL file. + + Anthropic batch results lines look like: + ``{"custom_id": ..., "result": {"type": "succeeded", "message": {..., "usage": {...}}}}`` + """ + return batch_results_line.get("result", None) or {} + + +def _get_response_from_batch_job_output_file(batch_job_output_file: dict, custom_llm_provider: str = "openai") -> Any: """ Get the response from the batch job output file """ + if custom_llm_provider == "anthropic": + return _get_anthropic_result_from_batch_results_line(batch_job_output_file).get("message", None) or {} _response: dict = batch_job_output_file.get("response", None) or {} _response_body = _response.get("body", None) or {} return _response_body -def _batch_response_was_successful(batch_job_output_file: dict) -> bool: +def _batch_response_was_successful(batch_job_output_file: dict, custom_llm_provider: str = "openai") -> bool: """ - Check if the batch job response status == 200 + Check if the batch job response was successful + + OpenAI-shaped output rows report ``response.status_code == 200``; Anthropic + message batch results lines report ``result.type == "succeeded"``. """ + if custom_llm_provider == "anthropic": + return _get_anthropic_result_from_batch_results_line(batch_job_output_file).get("type") == "succeeded" _response: dict = batch_job_output_file.get("response", None) or {} return _response.get("status_code", None) == 200 diff --git a/litellm/caching/disk_cache.py b/litellm/caching/disk_cache.py index b51acbe9cfd..d9f65ce949e 100644 --- a/litellm/caching/disk_cache.py +++ b/litellm/caching/disk_cache.py @@ -59,8 +59,9 @@ class DiskCache(BaseCache): def increment_cache(self, key, value: int, **kwargs) -> int: # get the value - init_value = self.get_cache(key=key) or 0 - value = init_value + value # type: ignore + cached_value = self.get_cache(key=key) + init_value = cached_value if isinstance(cached_value, int) else 0 + value = init_value + value self.set_cache(key, value, **kwargs) return value @@ -76,8 +77,9 @@ class DiskCache(BaseCache): async def async_increment(self, key, value: int, **kwargs) -> int: # get the value - init_value = await self.async_get_cache(key=key) or 0 - value = init_value + value # type: ignore + cached_value = await self.async_get_cache(key=key) + init_value = cached_value if isinstance(cached_value, int) else 0 + value = init_value + value await self.async_set_cache(key, value, **kwargs) return value diff --git a/litellm/caching/valkey_semantic_cache.py b/litellm/caching/valkey_semantic_cache.py index 746e91207d8..76b7f7d5b87 100644 --- a/litellm/caching/valkey_semantic_cache.py +++ b/litellm/caching/valkey_semantic_cache.py @@ -279,7 +279,7 @@ class ValkeySemanticCache(RedisSemanticCache): print_verbose("No prompt provided for semantic caching") return - embedding = await self._get_async_embedding(prompt, **kwargs) + embedding = await self._get_async_embedding(prompt, metadata=kwargs.get("metadata")) await self._ensure_index_async(len(embedding)) doc_key = self._doc_key(key) @@ -298,7 +298,7 @@ class ValkeySemanticCache(RedisSemanticCache): kwargs.setdefault("metadata", {})["semantic-similarity"] = 0.0 return None - embedding = await self._get_async_embedding(prompt, **kwargs) + embedding = await self._get_async_embedding(prompt, metadata=kwargs.get("metadata")) await self._ensure_index_async(len(embedding)) search_result = await self.async_client.ft(self.index_name).search( diff --git a/litellm/constants.py b/litellm/constants.py index 1300668cc70..7423d9b2211 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -1504,6 +1504,7 @@ LITELLM_SETTINGS_SAFE_DB_OVERRIDES = [ "public_model_groups_links", "cost_discount_config", "cost_margin_config", + "budget_exceeded_throttle_percentage", ] SPECIAL_LITELLM_AUTH_TOKEN = ["ui-token"] DEFAULT_MANAGEMENT_OBJECT_IN_MEMORY_CACHE_TTL = int(os.getenv("DEFAULT_MANAGEMENT_OBJECT_IN_MEMORY_CACHE_TTL", 60)) diff --git a/litellm/cost_calculator.py b/litellm/cost_calculator.py index 2cb94e02d9c..74dc0e19da3 100644 --- a/litellm/cost_calculator.py +++ b/litellm/cost_calculator.py @@ -222,7 +222,7 @@ def _cost_per_token_custom_pricing_helper( output_cost = completion_tokens * output_cost_per_token return input_cost, output_cost elif custom_cost_per_second is not None: - output_cost = custom_cost_per_second * response_time_ms / 1000 # type: ignore + output_cost = custom_cost_per_second * (response_time_ms or 0.0) / 1000 return 0, output_cost return None @@ -662,29 +662,27 @@ def cost_per_token( data_residency=data_residency, ) - if model_info.get("input_cost_per_second", None) is not None and response_time_ms is not None: + input_cost_per_second = model_info.get("input_cost_per_second") + if input_cost_per_second is not None and response_time_ms is not None: verbose_logger.debug( "For model=%s - input_cost_per_second: %s; response time: %s", model, - model_info.get("input_cost_per_second", None), + input_cost_per_second, response_time_ms, ) ## COST PER SECOND ## - prompt_tokens_cost_usd_dollar = ( - model_info["input_cost_per_second"] * response_time_ms / 1000 # type: ignore - ) + prompt_tokens_cost_usd_dollar = input_cost_per_second * response_time_ms / 1000 - if model_info.get("output_cost_per_second", None) is not None and response_time_ms is not None: + output_cost_per_second = model_info.get("output_cost_per_second") + if output_cost_per_second is not None and response_time_ms is not None: verbose_logger.debug( "For model=%s - output_cost_per_second: %s; response time: %s", model, - model_info.get("output_cost_per_second", None), + output_cost_per_second, response_time_ms, ) ## COST PER SECOND ## - completion_tokens_cost_usd_dollar = ( - model_info["output_cost_per_second"] * response_time_ms / 1000 # type: ignore - ) + completion_tokens_cost_usd_dollar = output_cost_per_second * response_time_ms / 1000 verbose_logger.debug( "Returned custom cost for model=%s - prompt_tokens_cost_usd_dollar: %s, completion_tokens_cost_usd_dollar: %s", @@ -2157,17 +2155,23 @@ def batch_cost_calculator( if input_cost_per_token_batches: total_prompt_cost = usage.prompt_tokens * input_cost_per_token_batches elif input_cost_per_token: + details = _parse_prompt_tokens_details(usage) + cache_read_tokens = details["cache_hit_tokens"] + cache_creation_tokens = details["cache_creation_tokens"] + # Subtract cached tokens from prompt_tokens before calculating cost # Fixes issue where cached tokens are being charged again + base_input_tokens = get_billable_input_tokens(usage) - cache_creation_tokens total_prompt_cost = ( - get_billable_input_tokens(usage) * (input_cost_per_token) / 2 + base_input_tokens * (input_cost_per_token) / 2 ) # batch cost is usually half of the regular token cost # Add cache read cost if applicable - details = _parse_prompt_tokens_details(usage) - cache_read_tokens = details["cache_hit_tokens"] cache_read_cost_key = _get_service_tier_cost_key("cache_read_input_token_cost", None) total_prompt_cost += calculate_cost_component(model_info, cache_read_cost_key, cache_read_tokens) / 2 + + cache_creation_cost = model_info.get("cache_creation_input_token_cost") or input_cost_per_token + total_prompt_cost += cache_creation_tokens * cache_creation_cost / 2 if output_cost_per_token_batches: total_completion_cost = usage.completion_tokens * output_cost_per_token_batches elif output_cost_per_token: diff --git a/litellm/fine_tuning/main.py b/litellm/fine_tuning/main.py index 8a8a916fa9c..ce5074cdaf5 100644 --- a/litellm/fine_tuning/main.py +++ b/litellm/fine_tuning/main.py @@ -256,8 +256,6 @@ def create_fine_tuning_job( extra_body = optional_params.get("extra_body", {}) if extra_body is not None: extra_body.pop("azure_ad_token", None) - else: - get_secret_str("AZURE_AD_TOKEN") # type: ignore # Prepare Azure-specific parameters for extra_body extra_body = _prepare_azure_extra_body(extra_body, kwargs, azure_specific_hyperparams) @@ -442,7 +440,7 @@ def cancel_fine_tuning_job( ) # Azure OpenAI elif custom_llm_provider == "azure": - api_base = optional_params.api_base or litellm.api_base or get_secret("AZURE_API_BASE") # type: ignore + api_base = optional_params.api_base or litellm.api_base or get_secret_str("AZURE_API_BASE") api_version = optional_params.api_version or litellm.api_version or get_secret_str("AZURE_API_VERSION") # type: ignore @@ -457,8 +455,6 @@ def cancel_fine_tuning_job( extra_body = optional_params.get("extra_body", {}) if extra_body is not None: extra_body.pop("azure_ad_token", None) - else: - get_secret_str("AZURE_AD_TOKEN") # type: ignore response = azure_fine_tuning_apis_instance.cancel_fine_tuning_job( api_base=api_base, @@ -616,8 +612,6 @@ def list_fine_tuning_jobs( extra_body = optional_params.get("extra_body", {}) if extra_body is not None: extra_body.pop("azure_ad_token", None) - else: - get_secret("AZURE_AD_TOKEN") # type: ignore response = azure_fine_tuning_apis_instance.list_fine_tuning_jobs( api_base=api_base, @@ -759,8 +753,6 @@ def retrieve_fine_tuning_job( extra_body = optional_params.get("extra_body", {}) if extra_body is not None: extra_body.pop("azure_ad_token", None) - else: - get_secret_str("AZURE_AD_TOKEN") # type: ignore response = azure_fine_tuning_apis_instance.retrieve_fine_tuning_job( api_base=api_base, diff --git a/litellm/integrations/datadog/datadog.py b/litellm/integrations/datadog/datadog.py index 6775858c124..bd62d1e303a 100644 --- a/litellm/integrations/datadog/datadog.py +++ b/litellm/integrations/datadog/datadog.py @@ -354,14 +354,14 @@ class DataDogLogger( Raises: Raises a NON Blocking verbose_logger.exception if an error occurs """ + if not self.log_queue: + verbose_logger.exception("Datadog: log_queue does not exist") + return + + batch_to_send = self.log_queue[:] + self.log_queue = [] + try: - if not self.log_queue: - verbose_logger.exception("Datadog: log_queue does not exist") - return - - batch_to_send = self.log_queue[:] - self.log_queue = [] - verbose_logger.debug( "Datadog - about to flush %s events on %s", len(batch_to_send), diff --git a/litellm/integrations/otel/logger.py b/litellm/integrations/otel/logger.py index 5e729e12be0..e258b239d93 100644 --- a/litellm/integrations/otel/logger.py +++ b/litellm/integrations/otel/logger.py @@ -368,7 +368,11 @@ class OpenTelemetryV2(CustomLogger): # it (named provisionally) so it isn't leaked as an open span. carrier.span.end(end_time=to_ns(end_time)) return None - data = LLMCallSpanData.from_standard_logging_payload(payload, capture_content=self.config.capture_span_content) + data = LLMCallSpanData.from_standard_logging_payload( + payload, + capture_content=self.config.capture_span_content, + time_to_first_chunk_seconds=call.time_to_first_chunk_seconds, + ) end_time_ns = to_ns(end_time) if carrier.span is not None: # Born at the boundary: stamp attributes from the typed payload, set diff --git a/litellm/integrations/otel/mappers/genai.py b/litellm/integrations/otel/mappers/genai.py index c5d8c35de7d..f568afa9e3e 100644 --- a/litellm/integrations/otel/mappers/genai.py +++ b/litellm/integrations/otel/mappers/genai.py @@ -55,6 +55,7 @@ class GenAIMapper: GenAI.RESPONSE_MODEL: lambda d: d.response_model, GenAI.RESPONSE_ID: lambda d: d.response_id, GenAI.RESPONSE_FINISH_REASONS: lambda d: list(d.finish_reasons) if d.finish_reasons else None, + GenAI.RESPONSE_TIME_TO_FIRST_CHUNK: lambda d: d.time_to_first_chunk_seconds, GenAI.USAGE_INPUT_TOKENS: lambda d: d.usage.input_tokens, GenAI.USAGE_OUTPUT_TOKENS: lambda d: d.usage.output_tokens, Error.TYPE: lambda d: d.error.error_type if d.error else None, diff --git a/litellm/integrations/otel/model/metadata.py b/litellm/integrations/otel/model/metadata.py index 37bb5464315..7ff4f540908 100644 --- a/litellm/integrations/otel/model/metadata.py +++ b/litellm/integrations/otel/model/metadata.py @@ -41,7 +41,7 @@ from typing import TYPE_CHECKING, Any, Mapping, cast from litellm.constants import LITELLM_LOGGING_NO_UPSTREAM_LLM_CALL from litellm.integrations.otel.model.semconv import resolve_operation -from litellm.integrations.otel.model.utils import as_str +from litellm.integrations.otel.model.utils import as_str, to_seconds if TYPE_CHECKING: from litellm.types.utils import StandardLoggingPayload @@ -201,6 +201,7 @@ class LLMCallEvent: # span is renamed from the typed payload at close (``finish_span``); this only # needs to be reasonable for a span that never gets closed (a leak). provisional_span_name: str + time_to_first_chunk_seconds: float | None @classmethod def from_dict(cls, kwargs: Mapping[str, Any]) -> "LLMCallEvent": @@ -214,9 +215,25 @@ class LLMCallEvent: dynamic_params=kwargs.get("standard_callback_dynamic_params"), is_no_upstream_call=bool(kwargs.get(LITELLM_LOGGING_NO_UPSTREAM_LLM_CALL)), provisional_span_name=f"{operation.value} {model}".strip(), + time_to_first_chunk_seconds=time_to_first_chunk_seconds(kwargs), ) +def time_to_first_chunk_seconds(kwargs: Mapping[str, Any]) -> float | None: + """Seconds from the upstream request being issued (``api_call_start_time``) + to the first streamed chunk (``completion_start_time``); ``None`` for + non-streaming calls, where ``completion_start_time`` is backfilled with the + end time and would not measure first-chunk latency.""" + optional_params = cast(Mapping[str, Any], kwargs.get("optional_params") or {}) + if not optional_params.get("stream"): + return None + api_call_start = to_seconds(kwargs.get("api_call_start_time")) + completion_start = to_seconds(kwargs.get("completion_start_time")) + if api_call_start is None or completion_start is None: + return None + return completion_start - api_call_start + + def _call_id(payload: "StandardLoggingPayload | None", kwargs: Mapping[str, Any]) -> str | None: """The call id from the payload (when closed) or the bare kwargs (at pre_call).""" if payload is not None: diff --git a/litellm/integrations/otel/model/payloads.py b/litellm/integrations/otel/model/payloads.py index b0dcf97b787..fcd710492f0 100644 --- a/litellm/integrations/otel/model/payloads.py +++ b/litellm/integrations/otel/model/payloads.py @@ -305,10 +305,14 @@ class LLMCallSpanData: messages_in: tuple[Mapping[str, object], ...] = () choices_out: tuple[Mapping[str, object], ...] = () system_fingerprint: str | None = None + time_to_first_chunk_seconds: float | None = None @classmethod def from_standard_logging_payload( - cls, payload: "StandardLoggingPayload", capture_content: bool = False + cls, + payload: "StandardLoggingPayload", + capture_content: bool = False, + time_to_first_chunk_seconds: float | None = None, ) -> "LLMCallSpanData": params = cast(Mapping[str, object], payload.get("model_parameters") or {}) # The single parse of the request's metadata — the request-vs-provider @@ -349,6 +353,7 @@ class LLMCallSpanData: messages_in=_dicts(payload.get("messages")) if capture_content else (), choices_out=choices_out if capture_content else (), system_fingerprint=as_str(response.get("system_fingerprint")), + time_to_first_chunk_seconds=time_to_first_chunk_seconds, ) diff --git a/litellm/integrations/otel/model/semconv.py b/litellm/integrations/otel/model/semconv.py index 6315a5a4a89..4e725ae0a29 100644 --- a/litellm/integrations/otel/model/semconv.py +++ b/litellm/integrations/otel/model/semconv.py @@ -69,6 +69,7 @@ class GenAI: RESPONSE_ID: Final = "gen_ai.response.id" RESPONSE_MODEL: Final = "gen_ai.response.model" RESPONSE_FINISH_REASONS: Final = "gen_ai.response.finish_reasons" + RESPONSE_TIME_TO_FIRST_CHUNK: Final = "gen_ai.response.time_to_first_chunk" # usage USAGE_INPUT_TOKENS: Final = "gen_ai.usage.input_tokens" USAGE_OUTPUT_TOKENS: Final = "gen_ai.usage.output_tokens" diff --git a/litellm/integrations/otel/plumbing/metrics.py b/litellm/integrations/otel/plumbing/metrics.py index cb1f9214876..50d0fb75962 100644 --- a/litellm/integrations/otel/plumbing/metrics.py +++ b/litellm/integrations/otel/plumbing/metrics.py @@ -21,6 +21,7 @@ from litellm.integrations.opentelemetry import ( _build_metric_attribute_filter, _resolve_metric_attribute_filter, ) +from litellm.integrations.otel.model.metadata import time_to_first_chunk_seconds from litellm.integrations.otel.model.semconv import Metric, resolve_operation from litellm.integrations.otel.model.utils import to_seconds from litellm.litellm_core_utils.safe_json_dumps import safe_dumps @@ -181,13 +182,10 @@ class GenAIMetricRecorder: self._metrics.token_usage.record(usage.get("completion_tokens", 0), attributes=out_attrs) def _record_time_to_first_token(self, kwargs: Mapping[str, Any], common_attrs: dict) -> None: - if not kwargs.get("optional_params", {}).get("stream", False): + time_to_first_chunk = time_to_first_chunk_seconds(kwargs) + if time_to_first_chunk is None: return - api_call_start = to_seconds(kwargs.get("api_call_start_time")) - completion_start = to_seconds(kwargs.get("completion_start_time")) - if api_call_start is None or completion_start is None: - return - self._metrics.time_to_first_token.record(completion_start - api_call_start, attributes=common_attrs) + self._metrics.time_to_first_token.record(time_to_first_chunk, attributes=common_attrs) def _record_time_per_output_token( self, diff --git a/litellm/integrations/websearch_interception/handler.py b/litellm/integrations/websearch_interception/handler.py index 60100e8c2fd..00c67e9f0fb 100644 --- a/litellm/integrations/websearch_interception/handler.py +++ b/litellm/integrations/websearch_interception/handler.py @@ -91,6 +91,7 @@ class WebSearchInterceptionLogger(CustomLogger): messages: List[Dict], tools: Optional[List[Dict]], custom_llm_provider: Optional[str], + kwargs: Optional[dict[str, Any]] = None, ) -> Optional[Dict[str, Any]]: """ Short-circuit web-search-only requests by executing the search directly. @@ -176,7 +177,10 @@ class WebSearchInterceptionLogger(CustomLogger): # Execute search — keep the structured SearchResponse so the native # block can carry per-result url/title/page_age. try: - search_result_text, structured = await self._execute_search(query) + if kwargs is None: + search_result_text, structured = await self._execute_search(query) + else: + search_result_text, structured = await self._execute_search(query, kwargs=kwargs) except Exception as e: verbose_logger.error(f"WebSearchInterception: Short-circuit search failed: {e}") search_result_text, structured = f"Search failed: {e}", None @@ -936,7 +940,7 @@ class WebSearchInterceptionLogger(CustomLogger): query = tool_call["input"].get("query") if query: verbose_logger.debug(f"WebSearchInterception: Queuing search for query='{query}'") - search_tasks.append(self._execute_search(query)) + search_tasks.append(self._execute_search(query, kwargs=kwargs)) else: verbose_logger.debug(f"WebSearchInterception: Tool call {tool_call['id']} has no query") # Add empty result for tools without query @@ -1009,7 +1013,9 @@ class WebSearchInterceptionLogger(CustomLogger): ) return patch, structured_results - async def _execute_search(self, query: str) -> Tuple[str, Optional[SearchResponse]]: + async def _execute_search( + self, query: str, kwargs: Optional[dict[str, Any]] = None + ) -> Tuple[str, Optional[SearchResponse]]: """ Execute a single web search using router's search tools. @@ -1031,36 +1037,13 @@ class WebSearchInterceptionLogger(CustomLogger): ) llm_router = None - # Determine search provider from router's search_tools + search_tool = self._select_search_tool_from_router(llm_router=llm_router) search_provider: Optional[str] = None - if llm_router is not None and hasattr(llm_router, "search_tools"): - if self.search_tool_name: - # Find specific search tool by name - matching_tools = [ - tool - for tool in llm_router.search_tools - if tool.get("search_tool_name") == self.search_tool_name - ] - if matching_tools: - search_tool = matching_tools[0] - search_provider = search_tool.get("litellm_params", {}).get("search_provider") - verbose_logger.debug( - f"WebSearchInterception: Found search tool '{self.search_tool_name}' " - f"with provider '{search_provider}'" - ) - else: - verbose_logger.debug( - f"WebSearchInterception: Search tool '{self.search_tool_name}' not found in router, " - "falling back to first available or perplexity" - ) - - # If no specific tool or not found, use first available - if not search_provider and llm_router.search_tools: - first_tool = llm_router.search_tools[0] - search_provider = first_tool.get("litellm_params", {}).get("search_provider") - verbose_logger.debug( - f"WebSearchInterception: Using first available search tool with provider '{search_provider}'" - ) + search_litellm_params: dict[str, Any] = {} + if search_tool is not None: + await self._authorize_search_tool(search_tool=search_tool, kwargs=kwargs) + search_litellm_params = dict(search_tool.get("litellm_params", {}) or {}) + search_provider = search_litellm_params.get("search_provider") # Fallback to perplexity if no router or no search tools configured if not search_provider: @@ -1073,7 +1056,12 @@ class WebSearchInterceptionLogger(CustomLogger): verbose_logger.debug( f"WebSearchInterception: Executing search for '{query}' using provider '{search_provider}'" ) - result = await litellm.asearch(query=query, search_provider=search_provider) + search_kwargs = { + key: value + for key, value in search_litellm_params.items() + if key != "search_provider" and value is not None + } + result = await litellm.asearch(query=query, search_provider=search_provider, **search_kwargs) # Format using transformation function search_result_text = WebSearchTransformation.format_search_response(result) @@ -1086,6 +1074,107 @@ class WebSearchInterceptionLogger(CustomLogger): verbose_logger.error(f"WebSearchInterception: Search failed for '{query}': {str(e)}") raise + async def _authorize_search_tool( + self, + search_tool: dict[str, Any], + kwargs: Optional[dict[str, Any]], + ) -> None: + search_tool_name = search_tool.get("search_tool_name") + if not isinstance(search_tool_name, str) or not search_tool_name: + return + + user_api_key_auth = self._get_user_api_key_auth_from_kwargs(kwargs) + if user_api_key_auth is None: + return + + from litellm.proxy.auth.auth_checks import ( + can_key_call_search_tool, + can_team_call_search_tool, + get_team_object, + ) + + await can_key_call_search_tool( + search_tool_name=search_tool_name, + valid_token=user_api_key_auth, + ) + + team_id = getattr(user_api_key_auth, "team_id", None) + if team_id: + from litellm.proxy.proxy_server import ( + prisma_client, + proxy_logging_obj, + user_api_key_cache, + ) + + team_object = await get_team_object( + team_id=team_id, + prisma_client=prisma_client, + user_api_key_cache=user_api_key_cache, + parent_otel_span=getattr(user_api_key_auth, "parent_otel_span", None), + proxy_logging_obj=proxy_logging_obj, + ) + await can_team_call_search_tool( + search_tool_name=search_tool_name, + team_object=team_object, + ) + + @staticmethod + def _get_user_api_key_auth_from_kwargs(kwargs: Optional[dict[str, Any]]) -> Any: + if not kwargs: + return None + + for metadata_key in ("metadata", "litellm_metadata"): + metadata = kwargs.get(metadata_key) + if isinstance(metadata, dict) and metadata.get("user_api_key_auth") is not None: + return metadata["user_api_key_auth"] + + litellm_params = kwargs.get("litellm_params") + if not isinstance(litellm_params, dict): + return None + + for metadata_key in ("metadata", "litellm_metadata"): + metadata = litellm_params.get(metadata_key) + if isinstance(metadata, dict) and metadata.get("user_api_key_auth") is not None: + return metadata["user_api_key_auth"] + + return None + + def _select_search_tool_from_router(self, llm_router: Any) -> Optional[dict[str, Any]]: + if llm_router is None or not hasattr(llm_router, "search_tools"): + return None + search_tools = list(getattr(llm_router, "search_tools") or []) + return self._select_search_tool_from_list(search_tools=search_tools, source="router") + + def _select_search_tool_from_list( + self, + search_tools: list[dict[str, Any]], + source: str, + ) -> Optional[dict[str, Any]]: + if self.search_tool_name: + matching_tools = [tool for tool in search_tools if tool.get("search_tool_name") == self.search_tool_name] + if matching_tools: + search_provider = (matching_tools[0].get("litellm_params", {}) or {}).get("search_provider") + verbose_logger.debug( + f"WebSearchInterception: Found search tool '{self.search_tool_name}' " + f"from {source} with provider '{search_provider}'" + ) + return matching_tools[0] + verbose_logger.debug( + f"WebSearchInterception: Search tool '{self.search_tool_name}' not found in {source}, " + "falling back to first available or perplexity" + ) + + if search_tools: + first_tool = search_tools[0] + search_provider = (first_tool.get("litellm_params", {}) or {}).get("search_provider") + verbose_logger.debug( + f"WebSearchInterception: Using first available search tool from {source} " + f"with provider '{search_provider}'" + ) + return first_tool + + return None + async def _execute_chat_completion_agentic_loop( self, model: str, @@ -1145,7 +1234,7 @@ class WebSearchInterceptionLogger(CustomLogger): if query: verbose_logger.debug(f"WebSearchInterception: Queuing search for query='{query}'") - search_tasks.append(self._execute_search(query)) + search_tasks.append(self._execute_search(query, kwargs=kwargs)) else: verbose_logger.debug(f"WebSearchInterception: Tool call {tool_call.get('id')} has no query") # Add empty result for tools without query diff --git a/litellm/interactions/streaming_iterator.py b/litellm/interactions/streaming_iterator.py index 45c5443cfd2..0d9d1b4579c 100644 --- a/litellm/interactions/streaming_iterator.py +++ b/litellm/interactions/streaming_iterator.py @@ -174,22 +174,15 @@ class InteractionsAPIStreamingIterator(BaseInteractionsAPIStreamingIterator): logging_response = copy.deepcopy(self.completed_response) asyncio.create_task( - self.logging_obj.async_success_handler( - result=logging_response, + self.logging_obj.dispatch_success_handlers( + logging_response, start_time=self.start_time, end_time=datetime.now(), cache_hit=None, + prefer_async_handlers=True, ) ) - executor.submit( - self.logging_obj.success_handler, - result=logging_response, - cache_hit=None, - start_time=self.start_time, - end_time=datetime.now(), - ) - class SyncInteractionsAPIStreamingIterator(BaseInteractionsAPIStreamingIterator): """ diff --git a/litellm/litellm_core_utils/audio_utils/utils.py b/litellm/litellm_core_utils/audio_utils/utils.py index f86243c73b7..e5007ceec34 100644 --- a/litellm/litellm_core_utils/audio_utils/utils.py +++ b/litellm/litellm_core_utils/audio_utils/utils.py @@ -123,6 +123,34 @@ def process_audio_file(audio_file: FileTypes) -> ProcessedAudioFile: return ProcessedAudioFile(file_content=file_content, filename=filename, content_type=content_type) +BARE_ISO_639_1_TO_BCP47 = { + "en": "en-US", + "es": "es-ES", + "de": "de-DE", + "fr": "fr-FR", + "it": "it-IT", + "pt": "pt-BR", + "ja": "ja-JP", + "ko": "ko-KR", + "zh": "zh-CN", + "ru": "ru-RU", + "hi": "hi-IN", + "ar": "ar-SA", +} + + +def normalize_transcription_language_to_bcp47(language: str) -> str: + """ + OpenAI's transcription `language` param accepts bare ISO-639-1 codes like + ``en``; speech APIs such as Google Speech-to-Text and NVIDIA Riva require + BCP-47 like ``en-US``. Map the most common bare codes and pass through + anything already region-qualified (or unknown, for a clear provider error). + """ + if "-" in language: + return language + return BARE_ISO_639_1_TO_BCP47.get(language.lower(), language) + + def get_audio_file_name(file_obj: FileTypes) -> str: """ Safely get the name of a file-like object or return its string representation. diff --git a/litellm/litellm_core_utils/exception_mapping_utils.py b/litellm/litellm_core_utils/exception_mapping_utils.py index 2441cbb3903..9dc202c4717 100644 --- a/litellm/litellm_core_utils/exception_mapping_utils.py +++ b/litellm/litellm_core_utils/exception_mapping_utils.py @@ -1944,7 +1944,7 @@ def _map_azure_exception( response=getattr(original_exception, "response", None), body=getattr(original_exception, "body", None), ) - elif "invalid_request_error" in error_str: + elif "invalid_request_error" in error_str and getattr(original_exception, "status_code", None) in (None, 400): raise BadRequestError( message=f"AzureException BadRequestError - {message}", llm_provider="azure", @@ -1986,6 +1986,14 @@ def _map_azure_exception( litellm_debug_info=extra_information, response=getattr(original_exception, "response", None), ) + elif original_exception.status_code == 404: + raise NotFoundError( + message=f"AzureException NotFoundError - {message}", + llm_provider="azure", + model=model, + litellm_debug_info=extra_information, + response=getattr(original_exception, "response", None), + ) elif original_exception.status_code == 408: raise Timeout( message=f"AzureException Timeout - {message}", @@ -2173,7 +2181,7 @@ def exception_type( # type: ignore litellm_response_headers = _get_response_headers(original_exception=original_exception) try: error_str = redact_string(str(original_exception)) if _ENABLE_SECRET_REDACTION else str(original_exception) - if model: + if model or custom_llm_provider: if hasattr(original_exception, "message"): error_str = ( redact_string(str(original_exception.message)) diff --git a/litellm/litellm_core_utils/get_litellm_params.py b/litellm/litellm_core_utils/get_litellm_params.py index fbed9594a0b..352e55e9c23 100644 --- a/litellm/litellm_core_utils/get_litellm_params.py +++ b/litellm/litellm_core_utils/get_litellm_params.py @@ -36,6 +36,8 @@ OPTIONAL_KWARGS_KEYS = frozenset( "aws_bedrock_project_id", "tpm", "rpm", + "itpm", + "otpm", "use_xai_oauth", } ) @@ -74,6 +76,7 @@ def get_litellm_params( proxy_server_request=None, acompletion=None, aembedding=None, + allm_passthrough_route=None, preset_cache_key=None, no_log=None, input_cost_per_second=None, @@ -116,6 +119,7 @@ def get_litellm_params( # Build base dict with explicit parameters (always included) litellm_params = { "acompletion": acompletion, + "allm_passthrough_route": allm_passthrough_route, "api_key": api_key, "force_timeout": force_timeout, "logger_fn": logger_fn, diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index db7c1c7dfb4..936d79b22d6 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -1530,6 +1530,7 @@ class Logging(LiteLLMLoggingBaseClass): and litellm_params.get(CallTypes.aembedding.value, False) is not True and litellm_params.get(CallTypes.aimage_generation.value, False) is not True and litellm_params.get(CallTypes.atranscription.value, False) is not True + and litellm_params.get(CallTypes.allm_passthrough_route.value, False) is not True ) def _is_assembled_stream_success(self, result=None) -> bool: diff --git a/litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py b/litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py index 58107d9804b..47daf33824e 100644 --- a/litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py +++ b/litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py @@ -656,7 +656,7 @@ def convert_to_model_response_object( message: Optional[Message] = None finish_reason: Optional[str] = None - if _should_convert_tool_call_to_json_mode( + if tool_calls is not None and _should_convert_tool_call_to_json_mode( tool_calls=tool_calls, convert_tool_call_to_json_mode=convert_tool_call_to_json_mode, ): diff --git a/litellm/litellm_core_utils/realtime_streaming.py b/litellm/litellm_core_utils/realtime_streaming.py index a1a070eb5b7..220d1caa3d2 100644 --- a/litellm/litellm_core_utils/realtime_streaming.py +++ b/litellm/litellm_core_utils/realtime_streaming.py @@ -1,5 +1,4 @@ import asyncio -import concurrent.futures import json from typing import TYPE_CHECKING, Any, Dict, List, Optional, Protocol, Union, cast @@ -25,9 +24,6 @@ if TYPE_CHECKING: else: CLIENT_CONNECTION_CLASS = Any -# Create a thread pool with a maximum of 10 threads -executor = concurrent.futures.ThreadPoolExecutor(max_workers=10) - class RealtimeEventNormalizer(Protocol): def should_drop(self, event: object) -> bool: ... @@ -315,13 +311,12 @@ class RealTimeStreaming: if self.session_tools or self.tool_calls: self.logging_obj.model_call_details["realtime_tools"] = self.session_tools self.logging_obj.model_call_details["realtime_tool_calls"] = self.tool_calls - ## ASYNC LOGGING # Route through the bounded logging worker (per-coroutine timeout + # concurrency cap) instead of a bare create_task, so a slow callback # can't leave suspended tasks pinning each call's response in memory. - GLOBAL_LOGGING_WORKER.ensure_initialized_and_enqueue(self.logging_obj.async_success_handler(self.messages)) - ## SYNC LOGGING - executor.submit(self.logging_obj.success_handler(self.messages)) + GLOBAL_LOGGING_WORKER.ensure_initialized_and_enqueue( + self.logging_obj.dispatch_success_handlers(self.messages, prefer_async_handlers=True) + ) async def _send_to_backend(self, message: str) -> bool: """Send a message to the backend WebSocket. diff --git a/litellm/litellm_core_utils/streaming_handler.py b/litellm/litellm_core_utils/streaming_handler.py index 587a3a58a94..128ba0bf3ab 100644 --- a/litellm/litellm_core_utils/streaming_handler.py +++ b/litellm/litellm_core_utils/streaming_handler.py @@ -1884,7 +1884,7 @@ class CustomStreamWrapper: await self.fetch_stream() if is_async_iterable(self.completion_stream): - async for chunk in self.completion_stream: # type: ignore[union-attr] + async for chunk in self.completion_stream: # pyright: ignore[reportOptionalIterable] # is_async_iterable guard proves __aiter__ if chunk == "None" or chunk is None: continue # skip None chunks diff --git a/litellm/llms/anthropic/experimental_pass_through/messages/agentic_streaming_iterator.py b/litellm/llms/anthropic/experimental_pass_through/messages/agentic_streaming_iterator.py index cb37725d79c..4bf36a0d5c6 100644 --- a/litellm/llms/anthropic/experimental_pass_through/messages/agentic_streaming_iterator.py +++ b/litellm/llms/anthropic/experimental_pass_through/messages/agentic_streaming_iterator.py @@ -193,6 +193,14 @@ class AgenticAnthropicStreamingIterator: raise StopAsyncIteration + async def aclose(self) -> None: + from litellm.llms.anthropic.experimental_pass_through.messages.streaming_iterator import ( + aclose_if_supported, + ) + + await aclose_if_supported(self._inner) + await aclose_if_supported(self._follow_up_iterator) + async def _process_agentic_hooks(self) -> None: """Rebuild the Anthropic response from collected SSE bytes and call hooks.""" if self._hook_processing_done: diff --git a/litellm/llms/anthropic/experimental_pass_through/messages/handler.py b/litellm/llms/anthropic/experimental_pass_through/messages/handler.py index effd7dda6a0..dd983f0c344 100644 --- a/litellm/llms/anthropic/experimental_pass_through/messages/handler.py +++ b/litellm/llms/anthropic/experimental_pass_through/messages/handler.py @@ -148,6 +148,7 @@ async def _try_websearch_short_circuit( tools: Optional[List[Dict]], custom_llm_provider: Optional[str], stream: Optional[bool], + kwargs: Optional[dict] = None, ) -> Optional[Union[AnthropicMessagesResponse, AsyncIterator]]: """ Attempt to short-circuit a web-search-only request. @@ -177,6 +178,7 @@ async def _try_websearch_short_circuit( messages=messages, tools=tools, custom_llm_provider=custom_llm_provider, + kwargs=kwargs, ) if response is not None: anthropic_response = cast(AnthropicMessagesResponse, response) @@ -292,6 +294,7 @@ async def anthropic_messages( tools=tools, custom_llm_provider=custom_llm_provider, stream=original_stream, + kwargs={**kwargs, "metadata": metadata}, ) if short_circuit_response is not None: return short_circuit_response diff --git a/litellm/llms/anthropic/experimental_pass_through/messages/streaming_iterator.py b/litellm/llms/anthropic/experimental_pass_through/messages/streaming_iterator.py index 2357960f716..5f2b23d7eca 100644 --- a/litellm/llms/anthropic/experimental_pass_through/messages/streaming_iterator.py +++ b/litellm/llms/anthropic/experimental_pass_through/messages/streaming_iterator.py @@ -1,8 +1,13 @@ import asyncio import json from datetime import datetime -from typing import Any, AsyncIterator, List, Union +from typing import Any, AsyncIterator, List, Protocol, Union, runtime_checkable +import httpx +from pydantic import TypeAdapter +from typing_extensions import TypedDict + +from litellm.litellm_core_utils.core_helpers import process_response_headers from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj from litellm.proxy.pass_through_endpoints.success_handler import ( PassThroughEndpointLogging, @@ -12,6 +17,93 @@ from litellm.types.utils import GenericStreamingChunk, ModelResponseStream GLOBAL_PASS_THROUGH_SUCCESS_HANDLER_OBJ = PassThroughEndpointLogging() +INCOMPLETE_STREAM_ERROR_MESSAGE = ( + "Provider stream ended before emitting a message_stop event; " + "the response is incomplete and any partial content (e.g. tool_use input JSON) may be truncated." +) + + +def _is_message_stop_chunk(chunk: object) -> bool: + if isinstance(chunk, dict): + return chunk.get("type") == "message_stop" + if isinstance(chunk, (bytes, bytearray)): + return any(line == b"event: message_stop" for line in chunk.splitlines()) + return False + + +def _is_provider_error_chunk(chunk: object) -> bool: + if isinstance(chunk, dict): + return chunk.get("type") == "error" + if isinstance(chunk, (bytes, bytearray)): + return any(line == b"event: error" for line in chunk.splitlines()) + return False + + +def _is_terminal_stream_chunk(chunk: object) -> bool: + return _is_message_stop_chunk(chunk) or _is_provider_error_chunk(chunk) + + +def _incomplete_stream_error_sse_event() -> bytes: + payload = json.dumps( + { + "type": "error", + "error": {"type": "api_error", "message": INCOMPLETE_STREAM_ERROR_MESSAGE}, + } + ) + return f"event: error\ndata: {payload}\n\n".encode() + + +class AnthropicMessagesStreamHiddenParams(TypedDict): + additional_headers: dict[str, str] + + +@runtime_checkable +class SupportsAclose(Protocol): + async def aclose(self) -> None: ... + + +async def aclose_if_supported(stream: object) -> None: + if isinstance(stream, SupportsAclose): + await stream.aclose() + + +_RESPONSE_HEADERS_ADAPTER: TypeAdapter[dict[str, str]] = TypeAdapter(dict[str, str]) + + +def anthropic_messages_stream_hidden_params( + response_headers: httpx.Headers, +) -> AnthropicMessagesStreamHiddenParams: + return AnthropicMessagesStreamHiddenParams( + additional_headers=_RESPONSE_HEADERS_ADAPTER.validate_python(process_response_headers(response_headers)) + ) + + +class AnthropicMessagesStreamingResponse: + """ + Wraps the /v1/messages SSE byte stream so upstream provider response + headers (e.g. Bedrock's x-amzn-requestid / x-amzn-trace-id) survive as + ``_hidden_params["additional_headers"]``, which the proxy forwards to + clients as ``llm_provider-*`` response headers. Bare async generators + cannot carry attributes, so header context was previously dropped. + """ + + def __init__( + self, + completion_stream: AsyncIterator[bytes], + hidden_params: AnthropicMessagesStreamHiddenParams, + ) -> None: + self.completion_stream = completion_stream + self._hidden_params = hidden_params + + def __aiter__(self) -> "AnthropicMessagesStreamingResponse": + return self + + async def __anext__(self) -> bytes: + return await self.completion_stream.__anext__() + + async def aclose(self) -> None: + await aclose_if_supported(self.completion_stream) + class BaseAnthropicMessagesStreamingIterator: """ @@ -102,13 +194,18 @@ class BaseAnthropicMessagesStreamingIterator: This method provides the common logic for both Anthropic and Bedrock implementations. """ collected_chunks = [] + saw_terminal_event = False async for chunk in completion_stream: if self.completion_start_time is None: self.completion_start_time = datetime.now() + saw_terminal_event = saw_terminal_event or _is_terminal_stream_chunk(chunk) encoded_chunk = self._convert_chunk_to_sse_format(chunk) collected_chunks.append(encoded_chunk) yield encoded_chunk + if not saw_terminal_event: + yield _incomplete_stream_error_sse_event() + # Handle logging after all chunks are processed await self._handle_streaming_logging(collected_chunks) diff --git a/litellm/llms/anthropic/files/transformation.py b/litellm/llms/anthropic/files/transformation.py index cf12ad9ab32..0fa01e09492 100644 --- a/litellm/llms/anthropic/files/transformation.py +++ b/litellm/llms/anthropic/files/transformation.py @@ -39,6 +39,7 @@ from ..common_utils import AnthropicError, AnthropicModelInfo ANTHROPIC_FILES_API_BASE = "https://api.anthropic.com" ANTHROPIC_FILES_BETA_HEADER = "files-api-2025-04-14" +ANTHROPIC_MESSAGE_BATCH_ID_PREFIX = "msgbatch_" class AnthropicFilesConfig(BaseFilesConfig): @@ -258,6 +259,8 @@ class AnthropicFilesConfig(BaseFilesConfig): file_id = file_content_request.get("file_id") api_base = AnthropicModelInfo.get_api_base(litellm_params.get("api_base")) or ANTHROPIC_FILES_API_BASE encoded_file_id = encode_url_path_segment(file_id, field_name="file_id") + if file_id.startswith(ANTHROPIC_MESSAGE_BATCH_ID_PREFIX): + return f"{api_base.rstrip('/')}/v1/messages/batches/{encoded_file_id}/results", {} return f"{api_base.rstrip('/')}/v1/files/{encoded_file_id}/content", {} def transform_file_content_response( diff --git a/litellm/llms/azure/responses/transformation.py b/litellm/llms/azure/responses/transformation.py index fef9b7d0154..d0b0dbb070d 100644 --- a/litellm/llms/azure/responses/transformation.py +++ b/litellm/llms/azure/responses/transformation.py @@ -205,7 +205,7 @@ class AzureOpenAIResponsesAPIConfig(OpenAIResponsesAPIConfig): ######################################################### ########## DELETE RESPONSE API TRANSFORMATION ############## ######################################################### - def _construct_url_for_response_id_in_path(self, api_base: str, response_id: str) -> str: + def _construct_url_for_response_id_in_path(self, api_base: str, response_id: str, path_suffix: str = "") -> str: """ Constructs a URL for the API request with the response_id in the path. """ @@ -218,14 +218,14 @@ class AzureOpenAIResponsesAPIConfig(OpenAIResponsesAPIConfig): # Remove trailing slash if present to avoid double slashes path = parsed_url.path.rstrip("/") encoded_response_id = encode_url_path_segment(response_id, field_name="response_id") - new_path = f"{path}/{encoded_response_id}" + new_path = f"{path}/{encoded_response_id}{path_suffix}" # Reconstruct the URL with all original components but with the modified path constructed_url = urlunparse( ( parsed_url.scheme, # http, https parsed_url.netloc, # domain name, port - new_path, # path with response_id added + new_path, parsed_url.params, # parameters parsed_url.query, # query string parsed_url.fragment, # fragment @@ -288,7 +288,9 @@ class AzureOpenAIResponsesAPIConfig(OpenAIResponsesAPIConfig): limit: int = 20, order: Literal["asc", "desc"] = "desc", ) -> Tuple[str, Dict]: - url = self._construct_url_for_response_id_in_path(api_base=api_base, response_id=response_id) + "/input_items" + url = self._construct_url_for_response_id_in_path( + api_base=api_base, response_id=response_id, path_suffix="/input_items" + ) params: Dict[str, Any] = {} if after is not None: params["after"] = after @@ -322,27 +324,8 @@ class AzureOpenAIResponsesAPIConfig(OpenAIResponsesAPIConfig): This function handles URLs with query parameters by inserting the response_id at the correct location (before any query parameters). """ - from urllib.parse import urlparse, urlunparse - - # Parse the URL to separate its components - parsed_url = urlparse(api_base) - - # Insert the response_id and /cancel at the end of the path component - # Remove trailing slash if present to avoid double slashes - path = parsed_url.path.rstrip("/") - encoded_response_id = encode_url_path_segment(response_id, field_name="response_id") - new_path = f"{path}/{encoded_response_id}/cancel" - - # Reconstruct the URL with all original components but with the modified path - cancel_url = urlunparse( - ( - parsed_url.scheme, # http, https - parsed_url.netloc, # domain name, port - new_path, # path with response_id and /cancel added - parsed_url.params, # parameters - parsed_url.query, # query string - parsed_url.fragment, # fragment - ) + cancel_url = self._construct_url_for_response_id_in_path( + api_base=api_base, response_id=response_id, path_suffix="/cancel" ) data: Dict = {} diff --git a/litellm/llms/bedrock/base_aws_llm.py b/litellm/llms/bedrock/base_aws_llm.py index 380cc91ed98..f449851b76f 100644 --- a/litellm/llms/bedrock/base_aws_llm.py +++ b/litellm/llms/bedrock/base_aws_llm.py @@ -50,6 +50,8 @@ _STS_REGION_FROM_ENDPOINT_PATTERN = re.compile( r"(?:^|\.)sts(?:-fips)?\.([a-z0-9-]+)\.(?:amazonaws\.com(?:\.cn)?|vpce\.amazonaws\.com)" ) +SIGV4_COMPUTED_HEADERS = frozenset({"authorization", "x-amz-date", "x-amz-security-token", "date"}) + class Boto3CredentialsInfo(BaseModel): credentials: Credentials @@ -1400,11 +1402,13 @@ class BaseAWSLLM: # Add back all original headers (including forwarded ones) after signature calculation for header_name, header_value in headers.items(): - if header_value is not None: + if header_value is not None and header_name.lower() not in SIGV4_COMPUTED_HEADERS: request.headers[header_name] = header_value if ( - extra_headers is not None and "Authorization" in extra_headers + extra_headers is not None + and "Authorization" in extra_headers + and not extra_headers["Authorization"].startswith("AWS4-HMAC-SHA256") ): # prevent sigv4 from overwriting the auth header request.headers["Authorization"] = extra_headers["Authorization"] prepped = request.prepare() @@ -1527,9 +1531,15 @@ class BaseAWSLLM: # Add back original headers after signing. Only headers in SignedHeaders # are integrity-protected; forwarded headers (x-forwarded-*) must remain unsigned. for header_name, header_value in headers.items(): - if header_value is not None: + if header_value is not None and header_name.lower() not in SIGV4_COMPUTED_HEADERS: request_headers_dict[header_name] = header_value - if headers is not None and "Authorization" in headers: # prevent sigv4 from overwriting the auth header - request_headers_dict["Authorization"] = headers["Authorization"] + incoming_authorization = next( + (value for name, value in headers.items() if name.lower() == "authorization" and value is not None), + None, + ) + if incoming_authorization is not None and not incoming_authorization.startswith( + "AWS4-HMAC-SHA256" + ): # prevent sigv4 from overwriting the auth header + request_headers_dict["Authorization"] = incoming_authorization return request_headers_dict, request.body diff --git a/litellm/llms/bedrock/chat/invoke_handler.py b/litellm/llms/bedrock/chat/invoke_handler.py index b381b5a85fe..4c256be1ab8 100644 --- a/litellm/llms/bedrock/chat/invoke_handler.py +++ b/litellm/llms/bedrock/chat/invoke_handler.py @@ -1558,7 +1558,7 @@ class AWSEventStreamDecoder: text = chunk_data["outputText"] # ai21 mapping elif "ai21" in self.model: # fake ai21 streaming - text = chunk_data.get("completions")[0].get("data").get("text") # type: ignore + text = chunk_data["completions"][0]["data"]["text"] is_finished = True finish_reason = "stop" ######## /bedrock/converse mappings ############### diff --git a/litellm/llms/bedrock/chat/invoke_transformations/amazon_qwen2_transformation.py b/litellm/llms/bedrock/chat/invoke_transformations/amazon_qwen2_transformation.py index d63642c806f..a2aa98d6676 100644 --- a/litellm/llms/bedrock/chat/invoke_transformations/amazon_qwen2_transformation.py +++ b/litellm/llms/bedrock/chat/invoke_transformations/amazon_qwen2_transformation.py @@ -51,10 +51,7 @@ class AmazonQwen2Config(AmazonQwen3Config): Qwen2 uses "text" field, but we also support "generation" field for compatibility. """ try: - if hasattr(raw_response, "json"): - response_data = raw_response.json() - else: - response_data = raw_response + response_data = raw_response.json() # Extract the generated text - Qwen2 uses "text" field, but also support "generation" for compatibility generated_text = response_data.get("generation", "") or response_data.get("text", "") diff --git a/litellm/llms/bedrock/chat/invoke_transformations/amazon_qwen3_transformation.py b/litellm/llms/bedrock/chat/invoke_transformations/amazon_qwen3_transformation.py index 762631cac5e..4f496df084e 100644 --- a/litellm/llms/bedrock/chat/invoke_transformations/amazon_qwen3_transformation.py +++ b/litellm/llms/bedrock/chat/invoke_transformations/amazon_qwen3_transformation.py @@ -175,10 +175,7 @@ class AmazonQwen3Config(AmazonInvokeConfig, BaseConfig): Transform Qwen3 Bedrock response to OpenAI format """ try: - if hasattr(raw_response, "json"): - response_data = raw_response.json() - else: - response_data = raw_response + response_data = raw_response.json() # Extract the generated text - Qwen3 uses "generation" field generated_text = response_data.get("generation", "") diff --git a/litellm/llms/bedrock/chat/mantle/transformation.py b/litellm/llms/bedrock/chat/mantle/transformation.py index d84e077c37b..d7ffff65ff0 100644 --- a/litellm/llms/bedrock/chat/mantle/transformation.py +++ b/litellm/llms/bedrock/chat/mantle/transformation.py @@ -7,13 +7,14 @@ The bedrock-mantle endpoint uses the Anthropic Messages API format but is served at a different endpoint (bedrock-mantle.{region}.api.aws) with AWS SigV4 auth. """ -from typing import TYPE_CHECKING, Any, List, Optional +from typing import TYPE_CHECKING, Any, AsyncIterator, Iterator, List, Optional from litellm.llms.bedrock.chat.invoke_transformations.anthropic_claude3_transformation import ( AmazonAnthropicClaudeConfig, ) from litellm.llms.bedrock.common_utils import build_mantle_messages_url from litellm.types.llms.openai import AllMessageValues +from litellm.types.utils import ModelResponse if TYPE_CHECKING: from litellm.litellm_core_utils.litellm_logging import Logging as _LiteLLMLoggingObj @@ -91,10 +92,14 @@ class AmazonMantleConfig(AmazonAnthropicClaudeConfig): litellm_params=litellm_params, headers=headers, ) - # The parent strips "model" from the body (Invoke API puts it in URL). - # The mantle endpoint (Messages API) requires "model" in the body. - request["model"] = model_id - return request + # The parent strips "model" and "stream" from the body (Invoke API puts + # the model in the URL and streams via a dedicated endpoint). The mantle + # endpoint (Messages API) requires both in the body. + return self._restore_mantle_body_fields( + request=request, + model_id=model_id, + optional_params=optional_params, + ) async def async_transform_request( self, @@ -114,5 +119,31 @@ class AmazonMantleConfig(AmazonAnthropicClaudeConfig): headers=headers, ) await self._async_convert_document_url_sources_to_base64(request) - request["model"] = model_id - return request + return self._restore_mantle_body_fields( + request=request, + model_id=model_id, + optional_params=optional_params, + ) + + @staticmethod + def _restore_mantle_body_fields(request: dict, model_id: str, optional_params: dict) -> dict: + stream_fields: dict = {"stream": True} if optional_params.get("stream") is True else {} + return {**request, "model": model_id, **stream_fields} + + @property + def has_custom_stream_wrapper(self) -> bool: + return False + + def get_model_response_iterator( + self, + streaming_response: Iterator[str] | AsyncIterator[str] | ModelResponse, + sync_stream: bool, + json_mode: Optional[bool] = False, + ) -> Any: + from litellm.llms.anthropic.chat.handler import ModelResponseIterator + + return ModelResponseIterator( + streaming_response=streaming_response, + sync_stream=sync_stream, + json_mode=json_mode, + ) diff --git a/litellm/llms/bedrock/messages/mantle_transformation.py b/litellm/llms/bedrock/messages/mantle_transformation.py index a8a7b7ed1d5..da7b8697a6b 100644 --- a/litellm/llms/bedrock/messages/mantle_transformation.py +++ b/litellm/llms/bedrock/messages/mantle_transformation.py @@ -6,8 +6,13 @@ AmazonAnthropicClaudeMessagesConfig. Overrides only the URL and model-prefix stripping that are specific to the bedrock-mantle endpoint. """ -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple +from typing import TYPE_CHECKING, Any, AsyncIterator, Dict, List, Optional, Tuple +import httpx + +from litellm.llms.anthropic.experimental_pass_through.messages.transformation import ( + AnthropicMessagesConfig, +) from litellm.llms.bedrock.common_utils import build_mantle_messages_url from litellm.llms.bedrock.messages.invoke_transformations.anthropic_claude3_transformation import ( AmazonAnthropicClaudeMessagesConfig, @@ -89,8 +94,26 @@ class AmazonMantleMessagesConfig(AmazonAnthropicClaudeMessagesConfig): headers=headers, ) - # Parent (AmazonAnthropicClaudeMessagesConfig) removes "model" from the - # body (Bedrock Invoke puts model in the URL). The mantle endpoint - # (Messages API) requires "model" in the request body. - request["model"] = model_id - return request + # Parent (AmazonAnthropicClaudeMessagesConfig) removes "model" and + # "stream" from the body (Bedrock Invoke puts the model in the URL and + # streams via a dedicated endpoint). The mantle endpoint (Messages API) + # requires both in the request body. + stream_fields: dict[str, bool] = ( + {"stream": True} if anthropic_messages_optional_request_params.get("stream") is True else {} + ) + return {**request, "model": model_id, **stream_fields} + + def get_async_streaming_response_iterator( + self, + model: str, + httpx_response: httpx.Response, + request_body: dict, + litellm_logging_obj: LiteLLMLoggingObj, + ) -> AsyncIterator: + return AnthropicMessagesConfig.get_async_streaming_response_iterator( + self, + model=model, + httpx_response=httpx_response, + request_body=request_body, + litellm_logging_obj=litellm_logging_obj, + ) diff --git a/litellm/llms/bedrock/realtime/handler.py b/litellm/llms/bedrock/realtime/handler.py index 557ee3348d5..b48c37791c4 100644 --- a/litellm/llms/bedrock/realtime/handler.py +++ b/litellm/llms/bedrock/realtime/handler.py @@ -13,6 +13,7 @@ from litellm._logging import _redact_string, verbose_proxy_logger from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLogging from ..base_aws_llm import BaseAWSLLM +from ..common_utils import BedrockError from .transformation import BedrockRealtimeConfig @@ -59,9 +60,7 @@ class BedrockRealtime(BaseAWSLLM): InvokeModelWithBidirectionalStreamOperationInput, ) from aws_sdk_bedrock_runtime.config import Config - from smithy_aws_core.identity.environment import ( - EnvironmentCredentialsResolver, - ) + from smithy_aws_core.identity import StaticCredentialsResolver except ImportError: raise ImportError("Missing aws_sdk_bedrock_runtime. Install with: pip install aws-sdk-bedrock-runtime") @@ -82,11 +81,36 @@ class BedrockRealtime(BaseAWSLLM): verbose_proxy_logger.debug(f"Bedrock Realtime: Connecting to {endpoint_uri} with model {model}") + credentials = self.get_credentials( + aws_access_key_id=aws_access_key_id, + aws_secret_access_key=aws_secret_access_key, + aws_session_token=aws_session_token, + aws_region_name=aws_region_name, + aws_session_name=aws_session_name, + aws_profile_name=aws_profile_name, + aws_role_name=aws_role_name, + aws_web_identity_token=aws_web_identity_token, + aws_sts_endpoint=aws_sts_endpoint, + aws_external_id=aws_external_id, + ) + if credentials is None: + raise BedrockError( + status_code=401, + message=( + "No AWS credentials found for Bedrock realtime. Set aws_* params in litellm_params " + "or configure credentials in the environment" + ), + ) + frozen_credentials = credentials.get_frozen_credentials() + # Initialize Bedrock client with aws_sdk_bedrock_runtime config = Config( endpoint_uri=endpoint_uri, region=aws_region_name, - aws_credentials_identity_resolver=EnvironmentCredentialsResolver(), + aws_access_key_id=frozen_credentials.access_key, + aws_secret_access_key=frozen_credentials.secret_key, + aws_session_token=frozen_credentials.token, + aws_credentials_identity_resolver=StaticCredentialsResolver(), ) bedrock_client = BedrockRuntimeClient(config=config) diff --git a/litellm/llms/custom_httpx/llm_http_handler.py b/litellm/llms/custom_httpx/llm_http_handler.py index 3c10239f868..c426714a1bd 100644 --- a/litellm/llms/custom_httpx/llm_http_handler.py +++ b/litellm/llms/custom_httpx/llm_http_handler.py @@ -1300,16 +1300,15 @@ class BaseLLMHTTPHandler: if client is None or not isinstance(client, HTTPHandler): client = _get_httpx_client() + json_data = data if files is None and isinstance(data, dict) else None + try: - # Make the POST request - clean and simple, always use data and files response = client.post( url=complete_url, headers=headers, - data=data, + data=data if json_data is None else None, files=files, - json=( - data if files is None and isinstance(data, dict) else None - ), # Use json param only when no files and data is dict + json=json_data, timeout=timeout, ) except Exception as e: @@ -1373,16 +1372,15 @@ class BaseLLMHTTPHandler: else: async_httpx_client = client + json_data = data if files is None and isinstance(data, dict) else None + try: - # Make the async POST request - clean and simple, always use data and files response = await async_httpx_client.post( url=complete_url, headers=headers, - data=data, + data=data if json_data is None else None, files=files, - json=( - data if files is None and isinstance(data, dict) else None - ), # Use json param only when no files and data is dict + json=json_data, timeout=timeout, ) except Exception as e: @@ -2084,12 +2082,18 @@ class BaseLLMHTTPHandler: initial_response: Union[AsyncIterator, AnthropicMessagesResponse] if stream: + from litellm.llms.anthropic.experimental_pass_through.messages.streaming_iterator import ( + AnthropicMessagesStreamingResponse, + anthropic_messages_stream_hidden_params, + ) + completion_stream = anthropic_messages_provider_config.get_async_streaming_response_iterator( model=model, httpx_response=response, request_body=request_body, litellm_logging_obj=logging_obj, ) + stream_hidden_params = anthropic_messages_stream_hidden_params(response.headers) if not self._has_agentic_completion_hook(logging_obj): # No callback overrides async_should_run_agentic_loop, so the @@ -2097,7 +2101,10 @@ class BaseLLMHTTPHandler: # and rebuilding the response from SSE at end-of-stream to call # hooks that all return (False, {}). Stream through directly and # skip that per-chunk + end-of-stream overhead. - return completion_stream + return AnthropicMessagesStreamingResponse( + completion_stream=completion_stream, + hidden_params=stream_hidden_params, + ) from litellm.llms.anthropic.experimental_pass_through.messages.agentic_streaming_iterator import ( AgenticAnthropicStreamingIterator, @@ -2114,7 +2121,10 @@ class BaseLLMHTTPHandler: custom_llm_provider=custom_llm_provider, kwargs={**kwargs, "api_key": api_key} if api_key else kwargs, ) - return initial_response + return AnthropicMessagesStreamingResponse( + completion_stream=initial_response, + hidden_params=stream_hidden_params, + ) else: initial_response = anthropic_messages_provider_config.transform_anthropic_messages_response( model=model, @@ -2902,6 +2912,7 @@ class BaseLLMHTTPHandler: try: response = sync_httpx_client.get(url=url, headers=headers, params=data) + response.raise_for_status() except Exception as e: raise self._handle_error( e=e, @@ -2973,9 +2984,9 @@ class BaseLLMHTTPHandler: try: response = await async_httpx_client.get(url=url, headers=headers, params=data) - + response.raise_for_status() except Exception as e: - verbose_logger.exception(f"Error retrieving response: {e}") + verbose_logger.debug(f"Error retrieving response: {e}") raise self._handle_error( e=e, provider_config=responses_api_provider_config, @@ -3066,6 +3077,7 @@ class BaseLLMHTTPHandler: try: response = sync_httpx_client.get(url=url, headers=headers, params=params) + response.raise_for_status() except Exception as e: raise self._handle_error(e=e, provider_config=responses_api_provider_config) @@ -3139,6 +3151,7 @@ class BaseLLMHTTPHandler: try: response = await async_httpx_client.get(url=url, headers=headers, params=params) + response.raise_for_status() except Exception as e: raise self._handle_error(e=e, provider_config=responses_api_provider_config) @@ -4725,6 +4738,13 @@ class BaseLLMHTTPHandler: except Exception as e: raise self._handle_error(e=e, provider_config=provider_config) + if response.status_code >= 400: + raise provider_config.get_error_class( + error_message=response.text, + status_code=response.status_code, + headers=response.headers, + ) + return provider_config.transform_file_content_response( raw_response=response, logging_obj=logging_obj, @@ -4781,6 +4801,13 @@ class BaseLLMHTTPHandler: except Exception as e: raise self._handle_error(e=e, provider_config=provider_config) + if response.status_code >= 400: + raise provider_config.get_error_class( + error_message=response.text, + status_code=response.status_code, + headers=response.headers, + ) + return provider_config.transform_file_content_response( raw_response=response, logging_obj=logging_obj, diff --git a/litellm/llms/oci/chat/cohere.py b/litellm/llms/oci/chat/cohere.py index 3661ac908d2..ca85a6309d7 100644 --- a/litellm/llms/oci/chat/cohere.py +++ b/litellm/llms/oci/chat/cohere.py @@ -110,7 +110,7 @@ def adapt_messages_to_cohere_standard( tool_calls: Optional[List[CohereToolCall]] = None if role == "assistant" and msg.get("tool_calls"): # type: ignore[union-attr,typeddict-item] tool_calls = [] - for tc in msg["tool_calls"]: # type: ignore[union-attr,typeddict-item] + for tc in msg["tool_calls"]: # pyright: ignore[reportOptionalIterable] # truthiness check above rules out None raw_arguments: Any = tc.get("function", {}).get("arguments", {}) if isinstance(raw_arguments, str): try: diff --git a/litellm/llms/ollama/chat/transformation.py b/litellm/llms/ollama/chat/transformation.py index 3152ded2367..694f8cdd6c2 100644 --- a/litellm/llms/ollama/chat/transformation.py +++ b/litellm/llms/ollama/chat/transformation.py @@ -363,7 +363,11 @@ class OllamaChatConfig(BaseConfig): response_json_message["reasoning_content"] = reasoning_content response_json_message["content"] = content - if request_data.get("format", "") == "json" and litellm_params.get("function_name") is not None: + if ( + request_data.get("format", "") == "json" + and litellm_params.get("function_name") is not None + and response_json_message is not None + ): function_call = json.loads(response_json_message["content"]) message = litellm.Message( content=None, diff --git a/litellm/llms/openai/chat/gpt_transformation.py b/litellm/llms/openai/chat/gpt_transformation.py index 396ad5b105e..f2498c0a7e2 100644 --- a/litellm/llms/openai/chat/gpt_transformation.py +++ b/litellm/llms/openai/chat/gpt_transformation.py @@ -2,6 +2,7 @@ Support for gpt model family """ +import json from typing import ( TYPE_CHECKING, Any, @@ -782,8 +783,30 @@ class OpenAIChatCompletionStreamingHandler(BaseModelResponseIterator): delta["reasoning_content"] = delta.pop("reasoning") return choices + @staticmethod + def _extract_error_from_chunk(chunk: dict) -> Optional[tuple[str, int]]: + """OpenAI-compatible backends (vLLM, sglang) can return an HTTP 200 + stream whose body carries an error payload, e.g. + ``data: {"error": {"message": "...", "code": 400}}``.""" + error = chunk.get("error") + if not error: + return None + if not isinstance(error, dict): + return str(error), 500 + message = error.get("message") + code = error.get("code") + status_code = code if isinstance(code, int) and 400 <= code < 600 else 500 + return (message if isinstance(message, str) else json.dumps(error)), status_code + def chunk_parser(self, chunk: dict) -> ModelResponseStream: try: + error_details = self._extract_error_from_chunk(chunk) + if error_details is not None: + error_message, error_status_code = error_details + raise OpenAIError( + status_code=error_status_code, + message=error_message, + ) choices = chunk.get("choices", []) choices = self._map_reasoning_to_reasoning_content(choices) diff --git a/litellm/llms/openai/chat/guardrail_translation/handler.py b/litellm/llms/openai/chat/guardrail_translation/handler.py index c7a49a2e47f..a6b6a6267c3 100644 --- a/litellm/llms/openai/chat/guardrail_translation/handler.py +++ b/litellm/llms/openai/chat/guardrail_translation/handler.py @@ -716,7 +716,7 @@ class OpenAIChatCompletionsHandler(BaseTranslation): elif isinstance(content, list) and content_idx_optional is not None: # Replace specific text item in list content - choice.message.content[content_idx_optional]["text"] = guardrail_response # type: ignore + content[content_idx_optional]["text"] = guardrail_response async def _apply_guardrail_responses_to_output_tool_calls( self, diff --git a/litellm/llms/openai/responses/guardrail_translation/handler.py b/litellm/llms/openai/responses/guardrail_translation/handler.py index d1323b1a2bf..093dffccac0 100644 --- a/litellm/llms/openai/responses/guardrail_translation/handler.py +++ b/litellm/llms/openai/responses/guardrail_translation/handler.py @@ -197,12 +197,13 @@ class OpenAIResponsesHandler(BaseTranslation): return data def extract_request_tool_names(self, data: dict) -> List[str]: - """Extract tool names from Responses API request (tools[].name for function, tools[].server_label for mcp).""" + """Extract tool names from Responses API request (tools[].name for function + and custom, tools[].server_label for mcp).""" names: List[str] = [] for tool in data.get("tools") or []: if not isinstance(tool, dict): continue - if tool.get("type") == "function" and tool.get("name"): + if tool.get("type") in ("function", "custom") and tool.get("name"): names.append(str(tool["name"])) elif tool.get("type") == "mcp" and tool.get("server_label"): names.append(str(tool["server_label"])) diff --git a/litellm/llms/sap/chat/transformation.py b/litellm/llms/sap/chat/transformation.py index d9d0f9bc236..4bf8272a334 100755 --- a/litellm/llms/sap/chat/transformation.py +++ b/litellm/llms/sap/chat/transformation.py @@ -155,7 +155,7 @@ class GenAIHubOrchestrationConfig(OpenAIGPTConfig): def headers(self) -> Dict[str, str]: if self.token_creator is None: self.run_env_setup() - access_token = self.token_creator() # type: ignore + access_token = self.token_creator() # pyright: ignore[reportOptionalCall] # run_env_setup set it or raised return { "Authorization": access_token, "AI-Resource-Group": self.resource_group, diff --git a/litellm/llms/vertex_ai/audio_transcription/transformation.py b/litellm/llms/vertex_ai/audio_transcription/transformation.py new file mode 100644 index 00000000000..03769bf2601 --- /dev/null +++ b/litellm/llms/vertex_ai/audio_transcription/transformation.py @@ -0,0 +1,194 @@ +import base64 + +from httpx import Headers, Response + +import litellm +from litellm.exceptions import UnsupportedParamsError +from litellm.litellm_core_utils.audio_utils.utils import ( + normalize_transcription_language_to_bcp47, + process_audio_file, +) +from litellm.llms.base_llm.audio_transcription.transformation import ( + AudioTranscriptionRequestData, + BaseAudioTranscriptionConfig, +) +from litellm.llms.base_llm.chat.transformation import BaseLLMException +from litellm.llms.vertex_ai.common_utils import VertexAIError, validate_vertex_location +from litellm.llms.vertex_ai.vertex_llm_base import VertexBase +from litellm.types.llms.openai import ( + AllMessageValues, + OpenAIAudioTranscriptionOptionalParams, +) +from litellm.types.llms.vertex_ai_speech_to_text import ( + VertexSpeechToTextAutoDecodingConfig, + VertexSpeechToTextRecognitionConfig, + VertexSpeechToTextRecognitionFeatures, + VertexSpeechToTextRecognizeRequest, + VertexSpeechToTextRecognizeResponse, +) +from litellm.types.utils import FileTypes, TranscriptionResponse + +DEFAULT_SPEECH_TO_TEXT_LOCATION = "us" +AUTO_LANGUAGE_CODE = "auto" +SUPPORTED_RESPONSE_FORMATS = ("json", "text") +_URL_UNSAFE_PROJECT_CHARS = ("/", "?", "#", "\\", ":", " ", "\t", "\n", "\r") + + +class VertexAIAudioTranscriptionConfig(BaseAudioTranscriptionConfig, VertexBase): + def __init__(self) -> None: + BaseAudioTranscriptionConfig.__init__(self) + VertexBase.__init__(self) + + def get_supported_openai_params(self, model: str) -> list[OpenAIAudioTranscriptionOptionalParams]: + return ["language", "response_format"] + + def map_openai_params( + self, + non_default_params: dict, + optional_params: dict, + model: str, + drop_params: bool, + ) -> dict: + supported_params = self.get_supported_openai_params(model) + mapped = { + **optional_params, + **{k: v for k, v in non_default_params.items() if k in supported_params}, + } + response_format = mapped.get("response_format") + if response_format is None or response_format in SUPPORTED_RESPONSE_FORMATS: + return mapped + if drop_params or litellm.drop_params: + return {k: v for k, v in mapped.items() if k != "response_format"} + raise UnsupportedParamsError( + status_code=400, + message=( + f"Google Speech-to-Text does not support response_format={response_format!r}. " + f"Supported values: {', '.join(SUPPORTED_RESPONSE_FORMATS)}. " + "To drop unsupported openai params from the call, set `litellm.drop_params = True`" + ), + ) + + def get_error_class(self, error_message: str, status_code: int, headers: dict | Headers) -> BaseLLMException: + return VertexAIError(status_code=status_code, message=error_message, headers=headers) + + def validate_environment( + self, + headers: dict, + model: str, + messages: list[AllMessageValues], + optional_params: dict, + litellm_params: dict, + api_key: str | None = None, + api_base: str | None = None, + ) -> dict: + access_token, project_id = self._ensure_access_token( + credentials=self.safe_get_vertex_ai_credentials(litellm_params), + project_id=self.safe_get_vertex_ai_project(litellm_params), + custom_llm_provider="vertex_ai", + ) + return { + **headers, + "Authorization": f"Bearer {access_token}", + "x-goog-user-project": project_id, + "Content-Type": "application/json", + } + + def get_complete_url( + self, + api_base: str | None, + api_key: str | None, + model: str, + optional_params: dict, + litellm_params: dict, + stream: bool | None = None, + ) -> str: + location = self._validate_location(self.safe_get_vertex_ai_location(litellm_params)) + project_id = self._validate_project_id( + self.safe_get_vertex_ai_project(litellm_params) or self._resolve_project_id_from_credentials(litellm_params) + ) + host = "speech.googleapis.com" if location == "global" else f"{location}-speech.googleapis.com" + base_url = (api_base or f"https://{host}").rstrip("/") + return f"{base_url}/v2/projects/{project_id}/locations/{location}/recognizers/_:recognize" + + @staticmethod + def _validate_location(location: str | None) -> str: + try: + return validate_vertex_location(location or DEFAULT_SPEECH_TO_TEXT_LOCATION) + except ValueError as e: + raise VertexAIError(status_code=400, message=str(e)) from e + + @staticmethod + def _validate_project_id(project_id: str) -> str: + if not project_id or ".." in project_id or any(c in project_id for c in _URL_UNSAFE_PROJECT_CHARS): + raise VertexAIError(status_code=400, message=f"Invalid vertex_project format: {project_id!r}") + return project_id + + def _resolve_project_id_from_credentials(self, litellm_params: dict) -> str: + _, project_id = self._ensure_access_token( + credentials=self.safe_get_vertex_ai_credentials(litellm_params), + project_id=None, + custom_llm_provider="vertex_ai", + ) + return project_id + + def transform_audio_transcription_request( + self, + model: str, + audio_file: FileTypes, + optional_params: dict, + litellm_params: dict, + ) -> AudioTranscriptionRequestData: + processed_audio = process_audio_file(audio_file) + language = optional_params.get("language") + language_codes = ( + [normalize_transcription_language_to_bcp47(language)] + if isinstance(language, str) and language + else [AUTO_LANGUAGE_CODE] + ) + request_body = VertexSpeechToTextRecognizeRequest( + config=VertexSpeechToTextRecognitionConfig( + model=model.removeprefix("vertex_ai/"), + languageCodes=language_codes, + features=VertexSpeechToTextRecognitionFeatures(enableAutomaticPunctuation=True), + autoDecodingConfig=VertexSpeechToTextAutoDecodingConfig(), + ), + content=base64.b64encode(processed_audio.file_content).decode("utf-8"), + ) + return AudioTranscriptionRequestData(data=dict(request_body)) + + def transform_audio_transcription_response( + self, + raw_response: Response, + ) -> TranscriptionResponse: + try: + response_json = raw_response.json() + except ValueError: + raise VertexAIError( + status_code=raw_response.status_code, + message=f"Received non-JSON response from Google Speech-to-Text: {raw_response.text}", + ) + parsed = VertexSpeechToTextRecognizeResponse.model_validate(response_json) + transcripts = tuple( + result.alternatives[0].transcript + for result in parsed.results + if result.alternatives and result.alternatives[0].transcript + ) + response = TranscriptionResponse(text=" ".join(transcripts)) + response["task"] = "transcribe" + detected_language = next((result.languageCode for result in parsed.results if result.languageCode), None) + if detected_language is not None: + response["language"] = detected_language + billed_duration = _parse_duration_seconds(parsed.metadata.totalBilledDuration if parsed.metadata else None) + if billed_duration is not None: + response["duration"] = billed_duration + response._hidden_params = response_json + return response + + +def _parse_duration_seconds(duration: str | None) -> float | None: + if duration is None or not duration.endswith("s"): + return None + try: + return float(duration[:-1]) + except ValueError: + return None diff --git a/litellm/llms/vertex_ai/common_utils.py b/litellm/llms/vertex_ai/common_utils.py index 36522dfe396..7dcb4dcf2e8 100644 --- a/litellm/llms/vertex_ai/common_utils.py +++ b/litellm/llms/vertex_ai/common_utils.py @@ -311,6 +311,28 @@ def get_vertex_base_model_name(model: str) -> str: return model +def validate_vertex_location(vertex_location: Optional[str]) -> str: + """ + Validate a Vertex AI location before interpolating it into a request host or + URL path. + + ``vertex_location`` is client-controllable on the proxy (it flows in from the + request body), so it must never be trusted verbatim in a URL or an attacker + could point the host at their own server and exfiltrate the admin's Google + access token. Allow the special ``global`` control plane and otherwise require + a lowercase alphanumeric-plus-hyphen token (e.g. ``us``, ``us-central1``, + ``eu``), which rejects host injection like ``attacker.example/`` or + ``evil.com#``. + """ + if vertex_location == "global": + return vertex_location + if vertex_location is None: + raise ValueError("vertex_location is required") + if not re.match(r"^[a-z][a-z0-9-]*$", vertex_location): + raise ValueError("Invalid vertex_location format") + return vertex_location + + def get_vertex_base_url( vertex_location: Optional[str], ) -> str: @@ -321,15 +343,12 @@ def get_vertex_base_url( - Multi-region geographies (e.g. ``us``, ``eu``) use ``aiplatform.{geo}.rep.googleapis.com``. - Regional locations (e.g. ``us-central1``) use ``{region}-aiplatform.googleapis.com``. """ - if vertex_location == "global": + validated_location = validate_vertex_location(vertex_location) + if validated_location == "global": return "https://aiplatform.googleapis.com" - if vertex_location is None: - raise ValueError("vertex_location is required") - if not re.match(r"^[a-z][a-z0-9-]*$", vertex_location): - raise ValueError("Invalid vertex_location format") - if "-" not in vertex_location: - return f"https://aiplatform.{vertex_location}.rep.googleapis.com" - return f"https://{vertex_location}-aiplatform.googleapis.com" + if "-" not in validated_location: + return f"https://aiplatform.{validated_location}.rep.googleapis.com" + return f"https://{validated_location}-aiplatform.googleapis.com" def _get_embedding_url( diff --git a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py index 678877c0721..f9ed8cea9b5 100644 --- a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py +++ b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py @@ -2315,17 +2315,15 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): # Store thoughtSignatures in provider_specific_fields if thought_signatures is not None: - if "provider_specific_fields" not in chat_completion_message: - chat_completion_message["provider_specific_fields"] = {} - chat_completion_message["provider_specific_fields"]["thought_signatures"] = thought_signatures # type: ignore + thought_signature_fields = chat_completion_message.get("provider_specific_fields") or {} + thought_signature_fields["thought_signatures"] = thought_signatures + chat_completion_message["provider_specific_fields"] = thought_signature_fields # Store server-side tool invocations in provider_specific_fields if server_side_tool_invocations is not None: - if "provider_specific_fields" not in chat_completion_message: - chat_completion_message["provider_specific_fields"] = {} - chat_completion_message["provider_specific_fields"]["server_side_tool_invocations"] = ( - server_side_tool_invocations # type: ignore - ) + tool_invocation_fields = chat_completion_message.get("provider_specific_fields") or {} + tool_invocation_fields["server_side_tool_invocations"] = server_side_tool_invocations + chat_completion_message["provider_specific_fields"] = tool_invocation_fields if isinstance(model_response, ModelResponseStream): choice = VertexGeminiConfig._create_streaming_choice( diff --git a/litellm/llms/vertex_ai/vertex_llm_base.py b/litellm/llms/vertex_ai/vertex_llm_base.py index d57d7bf17df..788261ac1fe 100644 --- a/litellm/llms/vertex_ai/vertex_llm_base.py +++ b/litellm/llms/vertex_ai/vertex_llm_base.py @@ -9,6 +9,7 @@ import json import os import threading from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Tuple +from urllib.parse import urlparse import litellm from litellm._logging import verbose_logger @@ -315,6 +316,9 @@ class VertexBase: api_base=api_base, ) + if partner == VertexPartnerProvider.llama: + return default_api_base + if len(default_api_base.split(":")) > 1: endpoint = default_api_base.split(":")[-1] else: @@ -615,7 +619,8 @@ class VertexBase: Handles custom api_base for: 1. Gemini (Google AI Studio) - constructs /models/{model}:{endpoint} - 2. Vertex AI with standard proxies - constructs {api_base}:{endpoint} + 2. Vertex AI with standard proxies - constructs {api_base}:{endpoint}; + if api_base has no path (bare host), grafts the default vertex URL path onto it 3. Vertex AI with PSC endpoints - constructs full path structure {api_base}/v1/projects/{project}/locations/{location}/endpoints/{model}:{endpoint} (only when use_psc_endpoint_format=True) @@ -660,8 +665,9 @@ class VertexBase: model_for_url, endpoint, ) + elif urlparse(api_base).path in ("", "/"): + url = api_base.rstrip("/") + urlparse(url).path else: - # Fallback to simple format if we don't have all parameters url = "{}:{}".format(api_base, endpoint) if stream is True: url = url + "?alt=sse" diff --git a/litellm/main.py b/litellm/main.py index 567930a4999..7d457d9cdd1 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -582,6 +582,7 @@ async def acompletion( "api_key": api_key, "model_list": model_list, "reasoning_effort": reasoning_effort, + "verbosity": verbosity, "safety_identifier": safety_identifier, "service_tier": service_tier, "extra_headers": extra_headers, @@ -1081,6 +1082,54 @@ def _build_custom_pricing_entry( return entry +def _get_router_deployment_id(kwargs: dict) -> Optional[str]: + for metadata_key in ("litellm_metadata", "metadata"): + metadata = kwargs.get(metadata_key) or {} + if not isinstance(metadata, dict): + continue + deployment_model_info = metadata.get("model_info") or {} + if not isinstance(deployment_model_info, dict): + continue + deployment_id = deployment_model_info.get("id") + if deployment_id is not None: + return str(deployment_id) + return None + + +def _register_custom_pricing_for_request( + model: str, + custom_llm_provider: str, + kwargs: dict, + model_info: Optional[dict], +) -> None: + """Register per-request custom pricing in litellm.model_cost. + + Router-originated requests (identified by the deployment id the router puts + in metadata) get their full pricing registered under that unique id only; + the shared ``{provider}/{model}`` key receives the entry with pricing fields + stripped, mirroring Router._create_deployment. This keeps one deployment's + pricing overrides (e.g. a zero-cost wildcard) from clobbering built-in + pricing used by sibling deployments of the same backend model. Direct SDK + calls keep the legacy behavior of registering the shared key with pricing. + """ + entry = _build_custom_pricing_entry( + custom_llm_provider=custom_llm_provider, + kwargs=kwargs, + model_info=model_info, + ) + shared_key = f"{custom_llm_provider}/{model}" + deployment_id = _get_router_deployment_id(kwargs) + if deployment_id is None: + litellm.register_model({shared_key: entry}) + return + litellm.register_model( + { + deployment_id: entry, + shared_key: CustomPricingLiteLLMParams.strip_custom_pricing_fields(entry), + } + ) + + def _complete_azure(ctx: _CompletionDispatchContext) -> _CompletionDispatchResult: _azure_detection_model = ctx._azure_detection_model acompletion = ctx.acompletion @@ -5107,14 +5156,11 @@ def completion( # type: ignore if ( input_cost_per_token is not None and output_cost_per_token is not None ) or input_cost_per_second is not None: - litellm.register_model( - { - f"{custom_llm_provider}/{model}": _build_custom_pricing_entry( - custom_llm_provider=custom_llm_provider, - kwargs=kwargs, - model_info=model_info, - ) - } + _register_custom_pricing_for_request( + model=model, + custom_llm_provider=custom_llm_provider, + kwargs=kwargs, + model_info=model_info, ) ### BUILD CUSTOM PROMPT TEMPLATE -- IF GIVEN ### custom_prompt_dict = {} # type: ignore @@ -5193,6 +5239,7 @@ def completion( # type: ignore "parallel_tool_calls": parallel_tool_calls, "messages": messages, "reasoning_effort": reasoning_effort, + "verbosity": verbosity, "thinking": thinking, "web_search_options": web_search_options, "include_server_side_tool_invocations": ( @@ -5957,14 +6004,11 @@ def embedding( ### REGISTER CUSTOM MODEL PRICING -- IF GIVEN ### if (input_cost_per_token is not None and output_cost_per_token is not None) or input_cost_per_second is not None: - litellm.register_model( - { - f"{custom_llm_provider}/{model}": _build_custom_pricing_entry( - custom_llm_provider=custom_llm_provider, - kwargs=kwargs, - model_info=kwargs.get("model_info"), - ) - } + _register_custom_pricing_for_request( + model=model, + custom_llm_provider=custom_llm_provider, + kwargs=kwargs, + model_info=kwargs.get("model_info"), ) litellm_params_dict = get_litellm_params(**kwargs) @@ -8304,26 +8348,6 @@ def stream_chunk_builder_text_completion(chunks: list, messages: Optional[List] finish_reason = chunks[-1]["choices"][0]["finish_reason"] logprobs = chunks[-1]["choices"][0]["logprobs"] - response = { - "id": id, - "object": object, - "created": created, - "model": model, - "system_fingerprint": system_fingerprint, - "choices": [ - { - "text": None, - "index": 0, - "logprobs": logprobs, - "finish_reason": finish_reason, - } - ], - "usage": { - "prompt_tokens": None, - "completion_tokens": None, - "total_tokens": None, - }, - } content_list = [] for chunk in chunks: choices = chunk["choices"] @@ -8335,25 +8359,37 @@ def stream_chunk_builder_text_completion(chunks: list, messages: Optional[List] # Combine the "content" strings into a single string || combine the 'function' strings into a single string combined_content = "".join(content_list) - # Update the "content" field within the response dictionary - response["choices"][0]["text"] = combined_content - - if len(combined_content) > 0: - pass - else: - pass - # # Update usage information if needed try: - response["usage"]["prompt_tokens"] = token_counter(model=model, messages=messages) + prompt_tokens = token_counter(model=model, messages=messages) except Exception: # don't allow this failing to block a complete streaming response from being returned print_verbose("token_counter failed, assuming prompt tokens is 0") - response["usage"]["prompt_tokens"] = 0 - response["usage"]["completion_tokens"] = token_counter( + prompt_tokens = 0 + completion_tokens = token_counter( model=model, text=combined_content, count_response_tokens=True, # count_response_tokens is a Flag to tell token counter this is a response, No need to add extra tokens we do for input messages ) - response["usage"]["total_tokens"] = response["usage"]["prompt_tokens"] + response["usage"]["completion_tokens"] + + response = { + "id": id, + "object": object, + "created": created, + "model": model, + "system_fingerprint": system_fingerprint, + "choices": [ + { + "text": combined_content, + "index": 0, + "logprobs": logprobs, + "finish_reason": finish_reason, + } + ], + "usage": { + "prompt_tokens": prompt_tokens, + "completion_tokens": completion_tokens, + "total_tokens": prompt_tokens + completion_tokens, + }, + } return TextCompletionResponse(**response) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 26d54c05ffb..cbca0744ed9 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -5761,6 +5761,76 @@ "supports_tool_choice": true, "supports_vision": true }, + "azure/us/gpt-5.4": { + "cache_read_input_token_cost": 2.8e-07, + "cache_read_input_token_cost_priority": 5.5e-07, + "input_cost_per_token": 2.75e-06, + "input_cost_per_token_priority": 5.5e-06, + "output_cost_per_token": 1.65e-05, + "output_cost_per_token_priority": 3.3e-05, + "litellm_provider": "azure", + "max_input_tokens": 1050000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/batch", + "/v1/responses" + ], + "supported_modalities": [ + "text", + "image" + ], + "supported_output_modalities": [ + "text" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_parallel_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": true + }, + "azure/eu/gpt-5.4": { + "cache_read_input_token_cost": 2.8e-07, + "cache_read_input_token_cost_priority": 5.5e-07, + "input_cost_per_token": 2.75e-06, + "input_cost_per_token_priority": 5.5e-06, + "output_cost_per_token": 1.65e-05, + "output_cost_per_token_priority": 3.3e-05, + "litellm_provider": "azure", + "max_input_tokens": 1050000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/batch", + "/v1/responses" + ], + "supported_modalities": [ + "text", + "image" + ], + "supported_output_modalities": [ + "text" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_parallel_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": true + }, "azure/gpt-5.4-2026-03-05": { "cache_read_input_token_cost": 2.5e-07, "cache_read_input_token_cost_above_272k_tokens": 5e-07, @@ -5802,6 +5872,76 @@ "supports_tool_choice": true, "supports_vision": true }, + "azure/us/gpt-5.4-2026-03-05": { + "cache_read_input_token_cost": 2.8e-07, + "cache_read_input_token_cost_priority": 5.5e-07, + "input_cost_per_token": 2.75e-06, + "input_cost_per_token_priority": 5.5e-06, + "output_cost_per_token": 1.65e-05, + "output_cost_per_token_priority": 3.3e-05, + "litellm_provider": "azure", + "max_input_tokens": 1050000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/batch", + "/v1/responses" + ], + "supported_modalities": [ + "text", + "image" + ], + "supported_output_modalities": [ + "text" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_parallel_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": true + }, + "azure/eu/gpt-5.4-2026-03-05": { + "cache_read_input_token_cost": 2.8e-07, + "cache_read_input_token_cost_priority": 5.5e-07, + "input_cost_per_token": 2.75e-06, + "input_cost_per_token_priority": 5.5e-06, + "output_cost_per_token": 1.65e-05, + "output_cost_per_token_priority": 3.3e-05, + "litellm_provider": "azure", + "max_input_tokens": 1050000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/batch", + "/v1/responses" + ], + "supported_modalities": [ + "text", + "image" + ], + "supported_output_modalities": [ + "text" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_parallel_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": true + }, "azure/gpt-5.4-pro": { "cache_read_input_token_cost": 3e-06, "cache_read_input_token_cost_above_272k_tokens": 6e-06, @@ -5917,6 +6057,90 @@ "supports_xhigh_reasoning_effort": true, "supports_minimal_reasoning_effort": false }, + "azure/us/gpt-5.5": { + "cache_read_input_token_cost": 5.5e-07, + "cache_read_input_token_cost_above_272k_tokens": 1.1e-06, + "cache_read_input_token_cost_priority": 1.38e-06, + "input_cost_per_token": 5.5e-06, + "input_cost_per_token_above_272k_tokens": 1.1e-05, + "input_cost_per_token_priority": 1.375e-05, + "output_cost_per_token": 3.3e-05, + "output_cost_per_token_above_272k_tokens": 4.95e-05, + "output_cost_per_token_priority": 8.25e-05, + "litellm_provider": "azure", + "max_input_tokens": 1050000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/batch", + "/v1/responses" + ], + "supported_modalities": [ + "text", + "image" + ], + "supported_output_modalities": [ + "text" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_parallel_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": true, + "supports_web_search": true, + "supports_none_reasoning_effort": true, + "supports_xhigh_reasoning_effort": true, + "supports_minimal_reasoning_effort": false + }, + "azure/eu/gpt-5.5": { + "cache_read_input_token_cost": 5.5e-07, + "cache_read_input_token_cost_above_272k_tokens": 1.1e-06, + "cache_read_input_token_cost_priority": 1.38e-06, + "input_cost_per_token": 5.5e-06, + "input_cost_per_token_above_272k_tokens": 1.1e-05, + "input_cost_per_token_priority": 1.375e-05, + "output_cost_per_token": 3.3e-05, + "output_cost_per_token_above_272k_tokens": 4.95e-05, + "output_cost_per_token_priority": 8.25e-05, + "litellm_provider": "azure", + "max_input_tokens": 1050000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/batch", + "/v1/responses" + ], + "supported_modalities": [ + "text", + "image" + ], + "supported_output_modalities": [ + "text" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_parallel_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": true, + "supports_web_search": true, + "supports_none_reasoning_effort": true, + "supports_xhigh_reasoning_effort": true, + "supports_minimal_reasoning_effort": false + }, "azure/gpt-5.5-2026-04-23": { "cache_read_input_token_cost": 5e-07, "cache_read_input_token_cost_above_272k_tokens": 1e-06, @@ -5959,6 +6183,84 @@ "supports_vision": true, "supports_web_search": true }, + "azure/us/gpt-5.5-2026-04-23": { + "cache_read_input_token_cost": 5.5e-07, + "cache_read_input_token_cost_above_272k_tokens": 1.1e-06, + "cache_read_input_token_cost_priority": 1.38e-06, + "input_cost_per_token": 5.5e-06, + "input_cost_per_token_above_272k_tokens": 1.1e-05, + "input_cost_per_token_priority": 1.375e-05, + "output_cost_per_token": 3.3e-05, + "output_cost_per_token_above_272k_tokens": 4.95e-05, + "output_cost_per_token_priority": 8.25e-05, + "litellm_provider": "azure", + "max_input_tokens": 1050000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/batch", + "/v1/responses" + ], + "supported_modalities": [ + "text", + "image" + ], + "supported_output_modalities": [ + "text" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_parallel_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": true, + "supports_web_search": true + }, + "azure/eu/gpt-5.5-2026-04-23": { + "cache_read_input_token_cost": 5.5e-07, + "cache_read_input_token_cost_above_272k_tokens": 1.1e-06, + "cache_read_input_token_cost_priority": 1.38e-06, + "input_cost_per_token": 5.5e-06, + "input_cost_per_token_above_272k_tokens": 1.1e-05, + "input_cost_per_token_priority": 1.375e-05, + "output_cost_per_token": 3.3e-05, + "output_cost_per_token_above_272k_tokens": 4.95e-05, + "output_cost_per_token_priority": 8.25e-05, + "litellm_provider": "azure", + "max_input_tokens": 1050000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/batch", + "/v1/responses" + ], + "supported_modalities": [ + "text", + "image" + ], + "supported_output_modalities": [ + "text" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_parallel_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": true, + "supports_web_search": true + }, "azure/gpt-5.5-pro": { "cache_read_input_token_cost": 3e-06, "cache_read_input_token_cost_above_272k_tokens": 6e-06, @@ -34655,6 +34957,19 @@ "/v1/audio/speech" ] }, + "vertex_ai/chirp_3": { + "input_cost_per_second": 0.00026667, + "litellm_provider": "vertex_ai", + "metadata": { + "calculation": "$0.016/60 seconds = $0.00026667 per second", + "original_pricing_per_minute": 0.016 + }, + "mode": "audio_transcription", + "source": "https://cloud.google.com/speech-to-text/pricing", + "supported_endpoints": [ + "/v1/audio/transcriptions" + ] + }, "vertex_ai/claude-3-5-haiku": { "input_cost_per_token": 1e-06, "litellm_provider": "vertex_ai-anthropic_models", diff --git a/litellm/models/managed_files.py b/litellm/models/managed_files.py index 24154768860..99ba764dd98 100644 --- a/litellm/models/managed_files.py +++ b/litellm/models/managed_files.py @@ -51,12 +51,12 @@ class LiteLLM_ManagedVectorStoreTable(LiteLLMPydanticObjectBase): class LiteLLM_ManagedVectorStoresTable(LiteLLMPydanticObjectBase): vector_store_id: str custom_llm_provider: str - vector_store_name: Optional[str] - vector_store_description: Optional[str] - vector_store_metadata: Optional[Dict[str, Any]] - created_at: Optional[datetime] - updated_at: Optional[datetime] - litellm_credential_name: Optional[str] - litellm_params: Optional[Dict[str, Any]] - team_id: Optional[str] - user_id: Optional[str] + vector_store_name: Optional[str] = None + vector_store_description: Optional[str] = None + vector_store_metadata: Optional[Dict[str, Any]] = None + created_at: Optional[datetime] = None + updated_at: Optional[datetime] = None + litellm_credential_name: Optional[str] = None + litellm_params: Optional[Dict[str, Any]] = None + team_id: Optional[str] = None + user_id: Optional[str] = None diff --git a/litellm/models/mcp_server.py b/litellm/models/mcp_server.py index 5d3bc176134..d9757cf80f4 100644 --- a/litellm/models/mcp_server.py +++ b/litellm/models/mcp_server.py @@ -83,6 +83,15 @@ class LiteLLM_MCPServerTable(LiteLLMPydanticObjectBase): token_url: Optional[str] = None registration_url: Optional[str] = None oauth2_flow: Optional[Literal["client_credentials", "authorization_code"]] = None + # Token Exchange (OBO) fields — RFC 8693. ``audience`` is named for the RFC's + # request parameter (token-exchange only); RFC 8707 resource indicators are a + # separate concept named ``resource`` in the v2 egress types. A null + # ``subject_token_type`` means DEFAULT_SUBJECT_TOKEN_TYPE (litellm.types.mcp), + # applied at the egress build sites. + token_exchange_endpoint: Optional[str] = None + audience: Optional[str] = None + subject_token_type: Optional[str] = None + token_exchange_profile: Optional[str] = None allow_all_keys: bool = False available_on_public_internet: bool = True delegate_auth_to_upstream: bool = False diff --git a/litellm/passthrough/main.py b/litellm/passthrough/main.py index 66367513062..cdeedd7b522 100644 --- a/litellm/passthrough/main.py +++ b/litellm/passthrough/main.py @@ -171,7 +171,6 @@ def llm_passthrough_route( api_key: Optional[str] = None, request_query_params: Optional[dict] = None, request_headers: Optional[dict] = None, - allm_passthrough_route: bool = False, content: Optional[Any] = None, data: Optional[dict] = None, files: Optional[RequestFiles] = None, @@ -198,7 +197,7 @@ def llm_passthrough_route( from litellm.types.utils import LlmProviders from litellm.utils import ProviderConfigManager - _is_async = allm_passthrough_route + _is_async = bool(kwargs.get("allm_passthrough_route", False)) litellm_logging_obj = cast("LiteLLMLoggingObj", kwargs.get("litellm_logging_obj")) diff --git a/litellm/proxy/_experimental/mcp_server/auth/token_exchange.py b/litellm/proxy/_experimental/mcp_server/auth/token_exchange.py index 80e72fa2bf2..cd41dd648ee 100644 --- a/litellm/proxy/_experimental/mcp_server/auth/token_exchange.py +++ b/litellm/proxy/_experimental/mcp_server/auth/token_exchange.py @@ -28,6 +28,7 @@ from litellm.proxy._experimental.mcp_server.auth.token_endpoint_auth import ( build_token_endpoint_client_auth, ) from litellm.types.llms.custom_http import httpxSpecialProvider +from litellm.types.mcp import DEFAULT_SUBJECT_TOKEN_TYPE if TYPE_CHECKING: from litellm.types.mcp_server.mcp_server_manager import MCPServer @@ -35,8 +36,6 @@ if TYPE_CHECKING: # RFC 8693 grant type constant TOKEN_EXCHANGE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange" -DEFAULT_SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token" - class TokenExchangeHandler: """Handles OAuth 2.0 Token Exchange (RFC 8693) for MCP servers. diff --git a/litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py b/litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py index 387843ee5b2..d2986a3cd82 100644 --- a/litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py +++ b/litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py @@ -8,6 +8,7 @@ from starlette.types import Scope from litellm._logging import verbose_logger from litellm.proxy._types import ( + UI_TEAM_ID, LiteLLM_TeamTable, ProxyException, SpecialHeaders, @@ -357,6 +358,7 @@ class MCPRequestHandler: # Inline imports avoid a circular dependency: mcp_server_manager imports # from this module. from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + MCPServerManager, global_mcp_server_manager, ) from litellm.types.mcp import MCPAuth @@ -382,7 +384,18 @@ class MCPRequestHandler: # fetches the upstream token automatically using stored credentials, # so allowing anonymous bypass would let any external caller invoke # tools authenticated as LiteLLM's service account. - if server.has_client_credentials: + # + # Resolve the flow rather than reading has_client_credentials directly: + # this is a security gate, and a legacy row whose oauth2_flow was never + # stamped still carries the M2M credential shape (client_id/secret + + # token_url, no authorization_url). Treating an unstamped-but-M2M-shaped + # row as non-M2M here would reopen the anonymous bypass the explicit + # column no longer closes on its own. Shares the one resolution helper + # with the egress backstop and the anonymous-delegate allowlist; all fail + # closed on the ambiguous shape and are removed together once no null rows + # remain. A pure-PKCE delegate server (no stored credentials) resolves to a + # non-M2M flow and keeps its bypass. + if MCPServerManager.effective_oauth2_flow(server) == "client_credentials": return False return True @@ -726,6 +739,9 @@ class MCPRequestHandler: if not user_api_key_auth or not user_api_key_auth.team_id or not prisma_client: return None + if user_api_key_auth.team_id == UI_TEAM_ID: + return None + # Get the team object (which has object_permission already loaded) team_obj: Optional[LiteLLM_TeamTable] = await get_team_object( team_id=user_api_key_auth.team_id, @@ -1021,6 +1037,9 @@ class MCPRequestHandler: if user_api_key_auth is None or not user_api_key_auth.team_id or prisma_client is None: return [] + if user_api_key_auth.team_id == UI_TEAM_ID: + return [] + team_obj: Optional[LiteLLM_TeamTable] = await get_team_object( team_id=user_api_key_auth.team_id, prisma_client=prisma_client, @@ -1503,6 +1522,9 @@ class MCPRequestHandler: verbose_logger.debug("prisma_client is None") return [] + if user_api_key_auth.team_id == UI_TEAM_ID: + return [] + try: team_obj: Optional[LiteLLM_TeamTable] = await get_team_object( team_id=user_api_key_auth.team_id, diff --git a/litellm/proxy/_experimental/mcp_server/db.py b/litellm/proxy/_experimental/mcp_server/db.py index 1d62b325dec..10081ce19de 100644 --- a/litellm/proxy/_experimental/mcp_server/db.py +++ b/litellm/proxy/_experimental/mcp_server/db.py @@ -46,6 +46,35 @@ from litellm.types.mcp import MCPCredentials if TYPE_CHECKING: from litellm.types.mcp_server.mcp_server_manager import MCPServer +_AUTH_FLOW_SCOPED_FIELDS: frozenset = frozenset( + { + "authorization_url", + "token_url", + "registration_url", + "oauth2_flow", + "token_exchange_endpoint", + "audience", + "subject_token_type", + "token_exchange_profile", + } +) + +# Token-exchange settings with dedicated columns that also exist on +# ``MCPCredentials`` as a legacy shape (rows and REST callers that predate the +# columns). Every write lifts blob values into the columns and strips them from +# the stored blob, so the read-time ``column or blob`` fallback only serves rows +# the current code has never written — a cleared column can then never be +# silently resurrected by a stale blob copy. These keys are stored plaintext +# (endpoints/identifiers, not secrets), so values lift as-is. +_TOKEN_EXCHANGE_COLUMN_FIELDS: frozenset = frozenset( + { + "token_exchange_endpoint", + "audience", + "subject_token_type", + "token_exchange_profile", + } +) + def _is_global_env_var_scope(scope: Any) -> bool: """``scope="user"`` entries are placeholders the user fills in; everything @@ -241,6 +270,14 @@ def _prepare_mcp_server_data( # Handle credentials serialization credentials = data_dict.get("credentials") if credentials is not None: + # Lift legacy blob-shaped token-exchange settings into their dedicated + # columns (an explicit top-level value wins, including an explicit + # null) and strip them from the blob so it never seeds the read-time + # fallback for rows written by current code. + for te_field in _TOKEN_EXCHANGE_COLUMN_FIELDS: + blob_value = credentials.pop(te_field, None) + if blob_value is not None and te_field not in data_dict: + data_dict[te_field] = blob_value data_dict["credentials"] = encrypt_credentials(credentials=credentials, encryption_key=_get_salt_key()) data_dict["credentials"] = safe_dumps(data_dict["credentials"]) @@ -603,19 +640,41 @@ async def update_mcp_server( # Pre-fetch existing record once if we need it for auth_type or credential logic existing = None has_credentials = "credentials" in data_dict and data_dict["credentials"] is not None - if data.auth_type or has_credentials: + # An explicit token-exchange column write (set or clear) also migrates the + # legacy blob copies below, so the existing row is needed for those updates. + explicit_te_write = bool(_TOKEN_EXCHANGE_COLUMN_FIELDS & data_dict.keys()) + if data.auth_type or has_credentials or explicit_te_write: existing = await MCPServerRepository(prisma_client).table.find_unique(where={"server_id": data.server_id}) + auth_type_changed = bool( + data.auth_type and existing and existing.auth_type is not None and existing.auth_type != data.auth_type + ) + # Clear stale credentials when auth_type changes but no new credentials provided - if ( - data.auth_type - and "credentials" not in data_dict - and existing - and existing.auth_type is not None - and existing.auth_type != data.auth_type - ): + if auth_type_changed and "credentials" not in data_dict: data_dict["credentials"] = None + if auth_type_changed: + data_dict.update({field: None for field in _AUTH_FLOW_SCOPED_FIELDS if field not in data_dict}) + + # An explicit column write that does not touch credentials must still migrate + # the row's legacy blob copies: lift values for columns the caller left + # untouched, strip every copy from the blob. Without this, clearing a column + # (e.g. to re-enable RFC 9728/8414 discovery) would leave the blob copy in + # place, and the next credentials update's migrate-on-write would silently + # repopulate the column the admin just cleared. (When credentials ARE in the + # update, the merge below performs the same migration.) + if explicit_te_write and "credentials" not in data_dict and existing is not None and existing.credentials: + existing_creds = ( + json.loads(existing.credentials) if isinstance(existing.credentials, str) else dict(existing.credentials) + ) + if _TOKEN_EXCHANGE_COLUMN_FIELDS & existing_creds.keys(): + for te_field in _TOKEN_EXCHANGE_COLUMN_FIELDS: + legacy_value = existing_creds.pop(te_field, None) + if legacy_value is not None and te_field not in data_dict and getattr(existing, te_field, None) is None: + data_dict[te_field] = legacy_value + data_dict["credentials"] = safe_dumps(existing_creds) + # Merge credentials: preserve existing fields not present in the update. # Without this, a partial credential update (e.g. changing only region) # would wipe encrypted secrets that the UI cannot display back. @@ -638,6 +697,19 @@ async def update_mcp_server( ) # New values override existing; existing keys not in update are preserved merged = {**existing_creds, **new_creds} + # Migrate-on-write for legacy rows: token-exchange settings the + # old blob shape carried move to their dedicated columns (unless + # the caller set the column this update, or the row already has + # one) and are never re-persisted in the blob. Stored plaintext, + # so the merged value lifts as-is. + for te_field in _TOKEN_EXCHANGE_COLUMN_FIELDS: + legacy_value = merged.pop(te_field, None) + if ( + legacy_value is not None + and te_field not in data_dict + and getattr(existing, te_field, None) is None + ): + data_dict[te_field] = legacy_value data_dict["credentials"] = safe_dumps(merged) # Add audit fields diff --git a/litellm/proxy/_experimental/mcp_server/discoverable_endpoints.py b/litellm/proxy/_experimental/mcp_server/discoverable_endpoints.py index 8d53cd889a3..89d645b6f8a 100644 --- a/litellm/proxy/_experimental/mcp_server/discoverable_endpoints.py +++ b/litellm/proxy/_experimental/mcp_server/discoverable_endpoints.py @@ -1,6 +1,7 @@ import asyncio import html as _html import json +import secrets import time from datetime import datetime, timezone from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Tuple @@ -8,7 +9,7 @@ from urllib.parse import parse_qsl, urlencode, urlparse, urlunparse import httpx from fastapi import APIRouter, Form, HTTPException, Request -from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse +from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse, Response from pydantic import BaseModel, ValidationError from litellm._logging import verbose_logger @@ -137,6 +138,72 @@ def decode_state_hash(encrypted_state: str) -> dict: return state_data +# LIT-4197: some upstream authorization servers reject an over-long ``state`` +# (the encrypted OAuth session blob routinely exceeds their limit). The upstream +# only needs an opaque value it echoes back on ``/callback``, so we forward a +# short random handle and keep the encrypted session in a per-flow HttpOnly +# cookie bound to that handle. The browser carries the cookie across the +# upstream round trip, so the flow stays correct with no server-side session +# store (works across proxy replicas, unlike an in-process map). +_OAUTH_STATE_COOKIE_PREFIX = "mcp_oauth_state_" +_OAUTH_STATE_COOKIE_TTL_SECONDS = 600 +_OAUTH_STATE_HANDLE_BYTES = 32 + + +def _oauth_state_cookie_name(relay_state: str) -> str: + return f"{_OAUTH_STATE_COOKIE_PREFIX}{relay_state}" + + +def _oauth_state_cookie_path_and_secure(request: Request) -> tuple[str, bool]: + parsed = urlparse(get_request_base_url(request)) + return parsed.path or "/", parsed.scheme == "https" + + +def _set_oauth_state_cookie( + response: Response, + request: Request, + relay_state: str, + encoded_state: str, +) -> None: + path, secure = _oauth_state_cookie_path_and_secure(request) + response.set_cookie( + key=_oauth_state_cookie_name(relay_state), + value=encoded_state, + max_age=_OAUTH_STATE_COOKIE_TTL_SECONDS, + path=path, + secure=secure, + httponly=True, + samesite="lax", + ) + + +def _resolve_encoded_oauth_state(request: Request, state: str) -> str: + """Return the encrypted OAuth session for a ``/callback`` request. + + New flows carry it in a per-flow cookie keyed by the short handle we + forwarded upstream (the IdP echoes that handle back as ``state``). Flows + started before this change - or in flight across a deploy - carry the + encrypted blob directly in ``state``, so fall back to it when the cookie + is absent. + """ + cookie_value = request.cookies.get(_oauth_state_cookie_name(state)) + return cookie_value if cookie_value else state + + +def _clear_oauth_state_cookie(response: Response, request: Request, state: str) -> None: + cookie_name = _oauth_state_cookie_name(state) + if cookie_name not in request.cookies: + return + path, secure = _oauth_state_cookie_path_and_secure(request) + response.delete_cookie( + key=cookie_name, + path=path, + secure=secure, + httponly=True, + samesite="lax", + ) + + def _get_validated_client_redirect_uri(request: Request, state_data: Dict[str, Any]) -> str: """Return a trusted (same-origin, loopback, or ops-allowlisted) client redirect URI from OAuth state. @@ -462,11 +529,12 @@ async def authorize_with_server( code_challenge_method=code_challenge_method, client_redirect_uri=redirect_uri, ) + relay_state = secrets.token_urlsafe(_OAUTH_STATE_HANDLE_BYTES) params = { "client_id": mcp_server.client_id if mcp_server.client_id else client_id, "redirect_uri": f"{request_base_url}/callback", - "state": encoded_state, + "state": relay_state, "response_type": response_type or "code", } if scope: @@ -483,7 +551,9 @@ async def authorize_with_server( existing_params = dict(parse_qsl(parsed_auth_url.query)) existing_params.update(params) final_url = urlunparse(parsed_auth_url._replace(query=urlencode(existing_params))) - return RedirectResponse(final_url) + response = RedirectResponse(final_url) + _set_oauth_state_cookie(response, request, relay_state, encoded_state) + return response async def exchange_token_with_server( @@ -772,6 +842,7 @@ async def _persist_dcr_client_registration( data=UpdateMCPServerRequest( server_id=mcp_server.server_id, credentials=credentials, + oauth2_flow="authorization_code", **({"token_url": mcp_server.token_url} if mcp_server.token_url else {}), ), touched_by="mcp_oauth_dcr", @@ -1016,17 +1087,19 @@ async def callback( error_description, ) if state: + encoded_state = _resolve_encoded_oauth_state(request, state) try: - state_data = decode_state_hash(state) + state_data = decode_state_hash(encoded_state) original_state = state_data.get("original_state") redirect_uri = _get_validated_client_redirect_uri(request, state_data) - except HTTPException: - # Untrusted/invalid client redirect_uri — surface inline rather - # than blindly forwarding the error to an attacker-controlled URL. - return _render_oauth_error_html(error, error_description) except Exception: - # State could not be decrypted (expired key, tampered, etc.). - return _render_oauth_error_html(error, error_description) + # Untrusted/invalid client redirect_uri (HTTPException), or an + # undecryptable state (expired key, tampered): surface the IdP + # error inline rather than forwarding it to an attacker-controlled + # URL, and drop the one-time cookie we can no longer consume. + response = _render_oauth_error_html(error, error_description) + _clear_oauth_state_cookie(response, request, state) + return response params: Dict[str, str] = {"error": error} if error_description: @@ -1036,7 +1109,9 @@ async def callback( if original_state is not None: params["state"] = original_state complete_returned_url = _append_query_params(redirect_uri, params) - return RedirectResponse(url=complete_returned_url, status_code=302) + response = RedirectResponse(url=complete_returned_url, status_code=302) + _clear_oauth_state_cookie(response, request, state) + return response # No state — nothing to round-trip to. Show the user the error. return _render_oauth_error_html(error, error_description) @@ -1052,7 +1127,8 @@ async def callback( # 3. Successful authorization response. try: - state_data = decode_state_hash(state) + encoded_state = _resolve_encoded_oauth_state(request, state) + state_data = decode_state_hash(encoded_state) original_state = state_data["original_state"] # Re-validate the client redirect URI at the sink. /authorize @@ -1065,14 +1141,18 @@ async def callback( params = {"code": code, "state": original_state} complete_returned_url = _append_query_params(redirect_uri, params) - return RedirectResponse(url=complete_returned_url, status_code=302) + response = RedirectResponse(url=complete_returned_url, status_code=302) + _clear_oauth_state_cookie(response, request, state) + return response except HTTPException: # Re-raise so a non-loopback base_url surfaces as 400 instead of # a generic "authentication incomplete" redirect. raise except Exception: - return HTMLResponse("Authentication incomplete. You can close this window.") + response = HTMLResponse("Authentication incomplete. You can close this window.") + _clear_oauth_state_cookie(response, request, state) + return response # ------------------------------ diff --git a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py index 8451f6dff6f..7c88c903324 100644 --- a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py +++ b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py @@ -115,7 +115,7 @@ from litellm.proxy.common_utils.user_api_key_cache import get_management_object_ from litellm.proxy.utils import ProxyLogging, get_server_root_path from litellm.repositories.table_repositories import MCPServerRepository from litellm.types.llms.custom_http import httpxSpecialProvider -from litellm.types.mcp import MCPAuth, MCPStdioConfig +from litellm.types.mcp import DEFAULT_SUBJECT_TOKEN_TYPE, MCPAuth, MCPStdioConfig from litellm.types.mcp_server.mcp_server_manager import ( MCPInfo, MCPOAuthMetadata, @@ -253,6 +253,76 @@ def _without_authorization( return filtered or None +def _format_byok_openapi_auth_header(mcp_server: MCPServer, mcp_auth_header: str) -> str: + """Format a raw BYOK credential for OpenAPI tool ``Authorization`` injection.""" + if mcp_server.auth_type == MCPAuth.api_key: + return f"ApiKey {mcp_auth_header}" + if mcp_server.auth_type == MCPAuth.basic: + return f"Basic {mcp_auth_header}" + return f"Bearer {mcp_auth_header}" + + +def _openapi_forwarded_extra_headers( + mcp_server: MCPServer, + raw_headers: Optional[dict[str, str]], + user_api_key_auth: Optional[UserAPIKeyAuth], +) -> Optional[dict[str, str]]: + if not mcp_server.extra_headers or not raw_headers: + return None + normalized_raw = {str(k).lower(): v for k, v in raw_headers.items() if isinstance(k, str)} + skip_caller_authorization = _should_strip_caller_authorization( + mcp_server=mcp_server, + raw_headers=raw_headers, + user_api_key_auth=user_api_key_auth, + ) + forwarded: dict[str, str] = {} + for header_name in mcp_server.extra_headers: + if not isinstance(header_name, str): + continue + if skip_caller_authorization and header_name.lower() == "authorization": + continue + value = normalized_raw.get(header_name.lower()) + if value is not None: + forwarded[header_name] = value + return forwarded or None + + +async def _resolve_byok_mcp_auth_header( + mcp_server: MCPServer, + user_api_key_auth: Optional[UserAPIKeyAuth], + mcp_auth_header: Optional[str], +) -> Optional[str]: + """Resolve BYOK credential for tool calls that bypass ``execute_mcp_tool``.""" + if not mcp_server.is_byok: + return mcp_auth_header + + from litellm.proxy._experimental.mcp_server.server import ( + _check_byok_credential, + _get_byok_credential, + ) + + if not mcp_auth_header: + byok_cred = await _get_byok_credential(mcp_server, user_api_key_auth) + if byok_cred is None: + raise HTTPException( + status_code=401, + detail={ + "error": "byok_auth_required", + "server_id": mcp_server.server_id, + "server_name": mcp_server.server_name or mcp_server.name, + "message": ( + "No stored credential found for this BYOK server. " + "Complete the OAuth authorization flow to provide your API key." + ), + }, + headers={"WWW-Authenticate": 'Bearer resource_metadata="/.well-known/oauth-protected-resource"'}, + ) + return byok_cred + + await _check_byok_credential(mcp_server, user_api_key_auth) + return mcp_auth_header + + def _extract_upstream_auth_failure( exc: BaseException, ) -> Optional[tuple[int, Optional[str]]]: @@ -511,6 +581,21 @@ def _create_elicitation_callback(): class MCPServerManager: _STDIO_ENV_TEMPLATE_PATTERN = re.compile(r"^\$\{(X-[^}]+)\}$") + @staticmethod + def _explicit_oauth2_flow( + oauth2_flow: Optional[str], + ) -> Optional[Literal["client_credentials", "authorization_code"]]: + """DB rows persist their flow (write-time stamps plus the startup backfill) and + config servers must declare it (validated at load), so both builds read the + value verbatim: unknown or null resolves to None, which + ``needs_user_oauth_token`` already treats as interactive. Field-shape inference + survives only in the request-time security helpers (``effective_oauth2_flow`` / + ``resolve_oauth2_flow_for_request``). + """ + if oauth2_flow in ("client_credentials", "authorization_code"): + return cast(Literal["client_credentials", "authorization_code"], oauth2_flow) + return None + @staticmethod def _resolve_oauth2_flow( *, @@ -521,11 +606,18 @@ class MCPServerManager: client_id: Optional[str], client_secret: Optional[str], ) -> Optional[Literal["client_credentials", "authorization_code"]]: - """Infer oauth2_flow for legacy records that omit the field. + """Infer oauth2_flow from field shape when the value is omitted. - DB rows created before oauth2_flow support may have OAuth2 client - credentials + token_url but a null oauth2_flow. Treat these as M2M, - unless authorization_url is present (interactive OAuth). + SECURITY-SENSITIVE: this is the shape-inference engine both request-time security + helpers delegate to, so it is what decides M2M-vs-interactive for an unstamped row. + Always access it through ``effective_oauth2_flow`` (boolean/enum decisions) or + ``resolve_oauth2_flow_for_request`` (the egress object backstop), which are the single + choke points for request-time resolution; do not call it directly from security sites + and do not weaken its M2M-shape branch without accounting for those callers. DB rows + are stamped at write time and by the startup backfill, config servers must declare + oauth2_flow (validated at load), and both builds read the value verbatim via + ``_explicit_oauth2_flow``. Delete this whole request-time layer only once the backstop + warning stays silent in production. """ if oauth2_flow in ("client_credentials", "authorization_code"): return cast(Literal["client_credentials", "authorization_code"], oauth2_flow) @@ -540,6 +632,51 @@ class MCPServerManager: return "client_credentials" return None + @staticmethod + def effective_oauth2_flow(server: "MCPServer") -> Optional[Literal["client_credentials", "authorization_code"]]: + """The oauth2_flow a security decision must use for ``server`` this request. + + Column-first, shape-fallback: a stamped row returns its explicit value; an + unstamped (null) row whose fields carry the M2M shape resolves to + ``client_credentials`` so it is treated as M2M and fails closed. Every + security-sensitive reader (anonymous-delegate allowlist and gate, egress flow + resolution) goes through this one helper rather than reading the bare + ``has_client_credentials`` column, which is unreliable for null rows. + """ + return MCPServerManager._resolve_oauth2_flow( + auth_type=server.auth_type, + oauth2_flow=server.oauth2_flow, + token_url=server.token_url, + authorization_url=server.authorization_url, + client_id=server.client_id, + client_secret=server.client_secret, + ) + + @staticmethod + def resolve_oauth2_flow_for_request(server: "MCPServer") -> "MCPServer": + """Return ``server`` with its effective oauth2_flow applied, for egress paths. + + A stamped row is returned unchanged (its effective flow equals the stored value). + An unstamped M2M-shape row is returned as a per-request copy carrying + ``oauth2_flow=client_credentials`` so downstream ``has_client_credentials`` / + ``needs_user_oauth_token`` compute correctly and the stored client credentials are + used instead of forwarding the caller's Authorization. Use this at every point that + resolves an allowed server id into an ``MCPServer`` for a tool call or listing. + """ + effective = MCPServerManager.effective_oauth2_flow(server) + if effective is None or effective == server.oauth2_flow: + return server + verbose_logger.warning( + "MCP server %s has no persisted oauth2_flow but matches the %s shape; using the " + "inferred flow for this request. The startup backfill leaves this ambiguous M2M " + "shape unstamped on purpose, so it will NOT self-heal: set oauth2_flow explicitly " + "in the dashboard or via PUT /v1/mcp/server (client_credentials for M2M, or " + "authorization_code after an interactive sign-in).", + server.server_id, + effective, + ) + return server.model_copy(update={"oauth2_flow": effective}) + @staticmethod def _obo_needs_endpoint_discovery( auth_type: Optional[MCPAuthType], @@ -756,7 +893,12 @@ class MCPServerManager: else: mcp_oauth_metadata = None - resolved_scopes = server_config.get("scopes") or (mcp_oauth_metadata.scopes if mcp_oauth_metadata else None) + # Filter blank scopes (e.g. YAML ``scopes: [""]``) the same way the DB-build path does, so + # an all-blank list normalizes to None rather than a ``("",)`` tuple that skips the + # entra_obo fail-closed scope precondition and POSTs an empty scope to the IdP. + resolved_scopes = self._extract_scopes(server_config.get("scopes")) or ( + mcp_oauth_metadata.scopes if mcp_oauth_metadata else None + ) resolved_authorization_url = server_config.get("authorization_url") or ( mcp_oauth_metadata.authorization_url if mcp_oauth_metadata else None ) @@ -767,6 +909,20 @@ class MCPServerManager: mcp_oauth_metadata.registration_url if mcp_oauth_metadata else None ) + config_oauth2_flow = server_config.get("oauth2_flow", None) + if auth_type == MCPAuth.oauth2 and config_oauth2_flow not in ( + "client_credentials", + "authorization_code", + ): + raise ValueError( + f"Invalid config for MCP server '{server_name or server_id}': auth_type oauth2 " + f"requires an explicit oauth2_flow (got {config_oauth2_flow!r}). Set " + "oauth2_flow: client_credentials for machine-to-machine servers (the proxy mints " + "a shared token at token_url using client_id/client_secret, no user interaction) " + "or oauth2_flow: authorization_code for interactive servers (per-user tokens via " + "browser sign-in, including delegate_auth_to_upstream)." + ) + new_server = MCPServer( server_id=server_id, name=name_for_prefix, @@ -780,14 +936,7 @@ class MCPServerManager: # oauth specific fields client_id=server_config.get("client_id", None), client_secret=server_config.get("client_secret", None), - oauth2_flow=self._resolve_oauth2_flow( - auth_type=auth_type, - oauth2_flow=server_config.get("oauth2_flow", None), - token_url=resolved_token_url, - authorization_url=resolved_authorization_url, - client_id=server_config.get("client_id", None), - client_secret=server_config.get("client_secret", None), - ), + oauth2_flow=self._explicit_oauth2_flow(config_oauth2_flow), scopes=resolved_scopes, authorization_url=resolved_authorization_url, token_url=resolved_token_url, @@ -823,8 +972,9 @@ class MCPServerManager: audience=server_config.get("audience", None), subject_token_type=server_config.get( "subject_token_type", - "urn:ietf:params:oauth:token-type:access_token", + DEFAULT_SUBJECT_TOKEN_TYPE, ), + token_exchange_profile=server_config.get("token_exchange_profile", "rfc8693"), allow_sampling=bool(server_config.get("allow_sampling", False)), allow_elicitation=bool(server_config.get("allow_elicitation", False)), timeout=server_config.get("timeout", None), @@ -1133,7 +1283,8 @@ class MCPServerManager: (auth_type == MCPAuth.oauth2 and not mcp_server.authorization_url) or self._obo_needs_endpoint_discovery( auth_type, - credentials_dict.get("token_exchange_endpoint") if credentials_dict else None, + mcp_server.token_exchange_endpoint + or (credentials_dict.get("token_exchange_endpoint") if credentials_dict else None), mcp_server.token_url, ) ) @@ -1164,15 +1315,7 @@ class MCPServerManager: env_vars=env_vars_list, client_id=client_id_value or getattr(mcp_server, "client_id", None), client_secret=client_secret_value or getattr(mcp_server, "client_secret", None), - oauth2_flow=self._resolve_oauth2_flow( - auth_type=auth_type, - oauth2_flow=getattr(mcp_server, "oauth2_flow", None), - token_url=mcp_server.token_url or getattr(mcp_oauth_metadata, "token_url", None), - authorization_url=mcp_server.authorization_url - or getattr(mcp_oauth_metadata, "authorization_url", None), - client_id=client_id_value or getattr(mcp_server, "client_id", None), - client_secret=client_secret_value or getattr(mcp_server, "client_secret", None), - ), + oauth2_flow=self._explicit_oauth2_flow(getattr(mcp_server, "oauth2_flow", None)), scopes=resolved_scopes, authorization_url=mcp_server.authorization_url or getattr(mcp_oauth_metadata, "authorization_url", None), token_url=mcp_server.token_url or getattr(mcp_oauth_metadata, "token_url", None), @@ -1207,11 +1350,17 @@ class MCPServerManager: aws_role_name=aws_creds.get("aws_role_name"), aws_session_name=aws_creds.get("aws_session_name"), instructions=mcp_server.instructions, - # Token Exchange (OBO) fields — read from credentials JSON blob - token_exchange_endpoint=(credentials_dict.get("token_exchange_endpoint") if credentials_dict else None), - audience=(credentials_dict.get("audience") if credentials_dict else None), - subject_token_type=(credentials_dict.get("subject_token_type") if credentials_dict else None) - or "urn:ietf:params:oauth:token-type:access_token", + # Token exchange (OBO) fields: dedicated columns, with the credentials blob as a + # back-compat fallback for servers persisted before the columns existed. + token_exchange_endpoint=mcp_server.token_exchange_endpoint + or (credentials_dict.get("token_exchange_endpoint") if credentials_dict else None), + audience=mcp_server.audience or (credentials_dict.get("audience") if credentials_dict else None), + subject_token_type=mcp_server.subject_token_type + or (credentials_dict.get("subject_token_type") if credentials_dict else None) + or DEFAULT_SUBJECT_TOKEN_TYPE, + token_exchange_profile=mcp_server.token_exchange_profile + or (credentials_dict.get("token_exchange_profile") if credentials_dict else None) + or "rfc8693", timeout=getattr(mcp_server, "timeout", None), max_concurrent_requests=getattr(mcp_server, "max_concurrent_requests", None), ) @@ -1478,8 +1627,11 @@ class MCPServerManager: and getattr(server, "delegate_auth_to_upstream", False) is True # M2M servers must not be exposed anonymously: an # unauthenticated caller would get LiteLLM to proxy tool - # calls using its stored client_credentials. - and not server.has_client_credentials + # calls using its stored client_credentials. Resolve the flow + # rather than reading has_client_credentials so an unstamped + # M2M-shape row (null column, verbatim-read as non-M2M) still + # fails closed here, matching the anonymous-delegate auth gate. + and MCPServerManager.effective_oauth2_flow(server) != "client_credentials" ] combined_servers.update(delegate_server_ids) @@ -2000,8 +2152,13 @@ class MCPServerManager: if err.tag == "unauthorized" and isinstance(spec.config, TokenExchangeConfig): # token_exchange (OBO): a missing/rejected subject token -> the RFC 9728 challenge # pointing at the IdP the client must SSO with to obtain one, rather than an opaque - # 401. No gateway-side browser flow. - raise_token_exchange_challenge(server, root_path=get_server_root_path()) + # 401. No gateway-side browser flow. An IdP step-up rejection (Entra Conditional + # Access) threads its claims blob into the challenge for the client to satisfy. + raise_token_exchange_challenge( + server, + root_path=get_server_root_path(), + claims=err.unauthorized.claims, + ) raise_public(err) async def preflight_token_exchange( @@ -2031,7 +2188,11 @@ class MCPServerManager: return case Error(err): if err.tag == "unauthorized": - raise_token_exchange_challenge(server, root_path=get_server_root_path()) + raise_token_exchange_challenge( + server, + root_path=get_server_root_path(), + claims=err.unauthorized.claims, + ) raise_public(err) async def _create_mcp_client( @@ -3650,17 +3811,21 @@ class MCPServerManager: # OBO: the exchanged token may have been revoked/rotated upstream since it was cached, so # an upstream 401 gets one re-mint + retry. Gated to this mode; all others keep the plain # single call below. - tool_call_coro = self._obo_call_tool_with_retry( - client=client, - call_tool_params=call_tool_params, - host_progress_callback=host_progress_callback, - mcp_server=mcp_server, - server_auth_header=server_auth_header, - extra_headers=extra_headers, - stdio_env=stdio_env, - subject_token=subject_token, - user_api_key_auth=user_api_key_auth, - ) + async def _obo_call_tool_limited(): + async with self._limit_outbound_concurrency(mcp_server): + return await self._obo_call_tool_with_retry( + client=client, + call_tool_params=call_tool_params, + host_progress_callback=host_progress_callback, + mcp_server=mcp_server, + server_auth_header=server_auth_header, + extra_headers=extra_headers, + stdio_env=stdio_env, + subject_token=subject_token, + user_api_key_auth=user_api_key_auth, + ) + + tool_call_coro = _obo_call_tool_limited() else: async def _call_tool_via_client(client, params): @@ -3844,6 +4009,15 @@ class MCPServerManager: start_time = datetime.datetime.now() mcp_server = self._resolve_mcp_server_for_tool_call(server_name, name) + # Resolved before any hook runs so a missing BYOK credential (401) never + # leaves during-hook side effects (audit logging, rate-limit bookkeeping) + # recorded against a call that ultimately fails. + mcp_auth_header = await _resolve_byok_mcp_auth_header( + mcp_server, + user_api_key_auth, + mcp_auth_header, + ) + ######################################################### # Pre MCP Tool Call Hook # Allow validation and modification of tool calls before execution @@ -3890,9 +4064,25 @@ class MCPServerManager: server_name, ) + auth_header_value = ( + _format_byok_openapi_auth_header(mcp_server, mcp_auth_header) if mcp_auth_header else None + ) + forwarded_headers = _openapi_forwarded_extra_headers(mcp_server, raw_headers, user_api_key_auth) + async def _call_openapi_via_handler(): - async with self._limit_outbound_concurrency(mcp_server): - return await self._call_openapi_tool_handler(mcp_server, name, arguments) + from litellm.proxy._experimental.mcp_server.openapi_to_mcp_generator import ( + _request_auth_header, + _request_extra_headers, + ) + + auth_token = _request_auth_header.set(auth_header_value) + extra_token = _request_extra_headers.set(forwarded_headers) + try: + async with self._limit_outbound_concurrency(mcp_server): + return await self._call_openapi_tool_handler(mcp_server, name, arguments) + finally: + _request_auth_header.reset(auth_token) + _request_extra_headers.reset(extra_token) tasks.append(asyncio.create_task(_call_openapi_via_handler())) else: @@ -4448,6 +4638,11 @@ class MCPServerManager: authorization_url=server.authorization_url, token_url=server.token_url, registration_url=server.registration_url, + oauth2_flow=server.oauth2_flow, + token_exchange_endpoint=server.token_exchange_endpoint, + audience=server.audience, + subject_token_type=server.subject_token_type, + token_exchange_profile=server.token_exchange_profile, allow_all_keys=server.allow_all_keys, instructions=server.instructions, timeout=server.timeout, @@ -4536,6 +4731,8 @@ class MCPServerManager: teams=[], mcp_access_groups=server.access_groups or [], allowed_tools=server.allowed_tools or [], + tool_name_to_display_name=server.tool_name_to_display_name, + tool_name_to_description=server.tool_name_to_description, extra_headers=server.extra_headers or [], mcp_info=server.mcp_info, static_headers=server.static_headers, @@ -4549,6 +4746,11 @@ class MCPServerManager: authorization_url=server.authorization_url, token_url=server.token_url, registration_url=server.registration_url, + oauth2_flow=server.oauth2_flow, + token_exchange_endpoint=server.token_exchange_endpoint, + audience=server.audience, + subject_token_type=server.subject_token_type, + token_exchange_profile=server.token_exchange_profile, allow_all_keys=server.allow_all_keys, available_on_public_internet=server.available_on_public_internet, delegate_auth_to_upstream=server.delegate_auth_to_upstream, diff --git a/litellm/proxy/_experimental/mcp_server/oauth2_flow_backfill.py b/litellm/proxy/_experimental/mcp_server/oauth2_flow_backfill.py new file mode 100644 index 00000000000..02cec2475e2 --- /dev/null +++ b/litellm/proxy/_experimental/mcp_server/oauth2_flow_backfill.py @@ -0,0 +1,155 @@ +"""Startup backfill for oauth2 MCP server rows persisted before oauth2_flow was written. + +Rows created before the write-side stamps (DCR persist, UI create, REST create) carry a +null ``oauth2_flow`` and rely on read-time field-shape inference, which cannot tell a +DCR-registered interactive server from an M2M server unless endpoint discovery succeeds +first. This backfill classifies each null row once, at rest, using signals inference +never had, and persists the result so the read path never has to infer again. + +Signal order, strongest first: + +1. Per-user OAuth token rows exist for the server: only the interactive flow mints + per-user tokens, so this is definitive and immune to the discovery trap. BYOK API + keys share the same table (``LiteLLM_MCPUserCredentials``), so only rows whose + payload decodes as a ``type: oauth2`` token count as proof; bare keys and + undecodable rows prove nothing about the flow. +2. ``authorization_url`` persisted: interactive needs a user-facing authorization + endpoint; M2M (RFC 6749 section 4.4) never has one. +3. ``registration_url`` persisted: dynamic client registration (RFC 7591) exists to mint + clients for the interactive flow; M2M servers are configured with static credentials. +4. ``token_url`` plus decryptable ``client_id`` and ``client_secret``: ambiguous, left + unstamped. The shape is shared by M2M servers and DCR-registered interactive servers + whose authorization endpoint lives only in discovery (registered but never signed + in), so stamping client_credentials here could permanently route per-user traffic + through the proxy's stored client credential. The row keeps working through the + request-time backstop and a warning names it with the one-line fix (set oauth2_flow + via the dashboard or ``PUT /v1/mcp/server``); a completed interactive sign-in also + heals it via rule 1 at the next boot. +5. Anything else is interactive: matching how ``needs_user_oauth_token`` treats a null + flow, so the stamp never changes runtime routing for rows no rule recognizes. + +The backfill never stamps client_credentials: M2M is asserted by a human (config +requires it, the API accepts it, the dashboard sets it), mirroring the config-level +validation error. Runs before the first registry load on every boot and is idempotent: +a healed fleet has no null rows and the backfill exits after one query. +""" + +import json +from collections import Counter +from typing import Any, Literal, Optional + +from litellm._logging import verbose_proxy_logger +from litellm.proxy._experimental.mcp_server.db import _decode_oauth_payload, decrypt_credentials +from litellm.proxy.utils import PrismaClient +from litellm.types.mcp import MCPCredentials + +OAuth2Flow = Literal["client_credentials", "authorization_code"] +BackfillRule = Literal[ + "per_user_tokens", + "authorization_url", + "registration_url", + "ambiguous_m2m_shape", + "interactive_default", +] + +_BACKFILL_AUDIT_ACTOR = "oauth2_flow_backfill" + + +def _decrypted_credentials(raw_credentials: Any) -> Optional[MCPCredentials]: + if raw_credentials is None: + return None + if isinstance(raw_credentials, str): + try: + parsed = json.loads(raw_credentials) + except (ValueError, TypeError): + return None + else: + parsed = raw_credentials + if not isinstance(parsed, dict): + return None + return decrypt_credentials(credentials=dict(parsed)) + + +def classify_null_flow_row( + *, + has_per_user_tokens: bool, + authorization_url: Optional[str], + registration_url: Optional[str], + token_url: Optional[str], + credentials: Optional[MCPCredentials], +) -> tuple[Optional[OAuth2Flow], BackfillRule]: + if has_per_user_tokens: + return "authorization_code", "per_user_tokens" + if authorization_url: + return "authorization_code", "authorization_url" + if registration_url: + return "authorization_code", "registration_url" + if token_url and credentials and credentials.get("client_id") and credentials.get("client_secret"): + return None, "ambiguous_m2m_shape" + return "authorization_code", "interactive_default" + + +async def backfill_null_oauth2_flows(prisma_client: PrismaClient) -> dict[BackfillRule, int]: + """Classify every ``auth_type=oauth2`` row whose ``oauth2_flow`` is null; stamp the provable + ones, warn on the ambiguous ones, and return counts per rule.""" + null_rows: list[Any] = await prisma_client.db.litellm_mcpservertable.find_many( + where={"auth_type": "oauth2", "oauth2_flow": None}, + ) + if not null_rows: + return {} + + server_ids = [row.server_id for row in null_rows] + token_rows: list[Any] = await prisma_client.db.litellm_mcpusercredentials.find_many( + where={"server_id": {"in": server_ids}}, + ) + server_ids_with_oauth_tokens: set[str] = { + token_row.server_id for token_row in token_rows if _decode_oauth_payload(token_row.credential_b64) is not None + } + + classified = tuple( + ( + row, + classify_null_flow_row( + has_per_user_tokens=row.server_id in server_ids_with_oauth_tokens, + authorization_url=row.authorization_url, + registration_url=row.registration_url, + token_url=row.token_url, + credentials=_decrypted_credentials(row.credentials), + ), + ) + for row in null_rows + ) + + for row, (flow, rule) in classified: + if flow is None: + verbose_proxy_logger.warning( + "oauth2_flow backfill: server_id=%s is ambiguous (client credentials + token_url, " + "no interactive signal); left unstamped. Set oauth2_flow explicitly via the " + "dashboard or PUT /v1/mcp/server: client_credentials if this server is M2M, or " + "complete an interactive sign-in and it will be stamped authorization_code at the " + "next boot.", + row.server_id, + ) + else: + verbose_proxy_logger.info( + "oauth2_flow backfill: server_id=%s stamped %s (rule=%s)", + row.server_id, + flow, + rule, + ) + + stamped_flows = {flow for _, (flow, _) in classified if flow is not None} + for stamped_flow in stamped_flows: + server_ids_for_flow = [row.server_id for row, (row_flow, _) in classified if row_flow == stamped_flow] + await prisma_client.db.litellm_mcpservertable.update_many( + where={"server_id": {"in": server_ids_for_flow}, "oauth2_flow": None}, + data={"oauth2_flow": stamped_flow, "updated_by": _BACKFILL_AUDIT_ACTOR}, + ) + + counts: dict[BackfillRule, int] = dict(Counter(rule for _, (_, rule) in classified)) + verbose_proxy_logger.info( + "oauth2_flow backfill: processed %d oauth2 server row(s): %s", + len(null_rows), + counts, + ) + return counts diff --git a/litellm/proxy/_experimental/mcp_server/outbound_credentials/adapter.py b/litellm/proxy/_experimental/mcp_server/outbound_credentials/adapter.py index 3ba8baab427..05896bfff74 100644 --- a/litellm/proxy/_experimental/mcp_server/outbound_credentials/adapter.py +++ b/litellm/proxy/_experimental/mcp_server/outbound_credentials/adapter.py @@ -12,7 +12,7 @@ every other mode so the caller defers to v1 (parity-safe); it grows one branch p from __future__ import annotations import base64 -from typing import TYPE_CHECKING, NoReturn, Optional +from typing import TYPE_CHECKING, Literal, NoReturn, Optional from fastapi import HTTPException from pydantic import SecretStr @@ -28,7 +28,7 @@ from litellm.proxy._experimental.mcp_server.outbound_credentials.types import ( Subject, TokenExchangeConfig, ) -from litellm.types.mcp import MCPAuth +from litellm.types.mcp import DEFAULT_SUBJECT_TOKEN_TYPE, MCPAuth if TYPE_CHECKING: from litellm.proxy._types import UserAPIKeyAuth @@ -63,7 +63,7 @@ def to_server_spec(server: MCPServer) -> Optional[ServerSpec]: explicitly mapped or explicitly deferred, rather than silently falling through to v1. Live modes: ``none``, the static-header family (``api_key`` plus the Authorization schemes, all shared-key), ``oauth2`` per-user tokens (``authorization_code``), and - ``oauth2_token_exchange`` (RFC 8693 OBO); client_credentials (M2M), delegated/passthrough + ``oauth2_token_exchange`` (OBO); client_credentials (M2M), delegated/passthrough oauth2, and SigV4 return None and stay on v1. """ if server.is_byok: @@ -102,23 +102,29 @@ def to_server_spec(server: MCPServer) -> Optional[ServerSpec]: def _token_exchange_spec(server: MCPServer, resource: str) -> Optional[ServerSpec]: - """Build a token_exchange (RFC 8693 OBO) spec, or defer (None) when it is not OBO-configured. + """Build a token_exchange (OBO) spec, or defer (None) when it is not OBO-configured. An OBO server with ``client_id``/``client_secret`` is owned by the v2 arm even if the ``token_exchange_endpoint``/``token_url`` is absent: a missing endpoint then fails closed (412) at the exchanger rather than silently deferring to v1 and connecting unauthenticated, since the gateway must not guess the IdP or fall back to a weaker source. Without client credentials there is - nothing to own, so the server stays on v1 (parity-safe). ``audience`` is forwarded only when the - operator set it; a missing one is omitted, not derived. + nothing to own, so the server stays on v1 (parity-safe). ``profile`` selects the wire dialect + (``rfc8693`` default, ``entra_obo`` for Microsoft Entra On-Behalf-Of); an unrecognized value + normalizes to ``rfc8693`` so a bad config value cannot crash spec-building. ``audience`` is + forwarded only when the operator set it; a missing one is omitted, not derived. """ endpoint = server.token_exchange_endpoint or server.token_url if not server.client_id or not server.client_secret: return None + profile: Literal["rfc8693", "entra_obo"] = ( + "entra_obo" if server.token_exchange_profile == "entra_obo" else "rfc8693" + ) return ServerSpec( server_id=server.server_id, resource=resource, config=TokenExchangeConfig( - subject_token_type=server.subject_token_type or "urn:ietf:params:oauth:token-type:access_token", + profile=profile, + subject_token_type=server.subject_token_type or DEFAULT_SUBJECT_TOKEN_TYPE, token_exchange_endpoint=endpoint, audience=server.audience, client_id=server.client_id, @@ -208,7 +214,12 @@ def raise_user_oauth_challenge(server: MCPServer, *, root_path: str) -> NoReturn ) -def raise_token_exchange_challenge(server: MCPServer, *, root_path: str) -> NoReturn: +def raise_token_exchange_challenge( + server: MCPServer, + *, + root_path: str, + claims: str | None = None, +) -> NoReturn: """Raise the RFC 9728 / RFC 6750 challenge an OBO (``token_exchange``) server returns when the caller's subject token is missing or the IdP rejected it. @@ -217,12 +228,30 @@ def raise_token_exchange_challenge(server: MCPServer, *, root_path: str) -> NoRe spec-compliant MCP client to discover that AS and retry with a fresh bearer. Mirrors ``raise_user_oauth_challenge`` but for the exchange flow: there is no gateway-side browser OAuth — the client re-authenticates directly with the IdP, and LiteLLM then exchanges the resulting token. + + An IdP step-up rejection (Entra Conditional Access / CAE) passes its ``claims`` blob. Per the + Microsoft claims-challenge format the challenge then uses ``error="insufficient_claims"`` (the + value MSAL-family clients key on) and carries the claims base64-encoded in a ``claims`` parameter + the client replays to the IdP to satisfy the step-up. Without a claims blob the challenge keeps + ``error="invalid_token"`` and is byte-identical to the static one. Both the error value (one of + two literals) and the base64 claims draw from a fixed alphabet, so nothing from the IdP body + reaches the header unescaped. """ resource_metadata = oauth_protected_resource_path(root_path, server) - www_authenticate = ( - f'Bearer resource_metadata="{resource_metadata}", ' - 'error="invalid_token", ' - 'error_description="Missing or invalid subject token; authenticate with the IdP and retry"' + encoded_claims = base64.b64encode(claims.encode()).decode() if claims else None + error = "insufficient_claims" if encoded_claims else "invalid_token" + error_description = ( + "Step-up authentication required; satisfy the returned claims challenge with the IdP and retry" + if encoded_claims + else "Missing or invalid subject token; authenticate with the IdP and retry" + ) + www_authenticate = ", ".join( + ( + f'Bearer resource_metadata="{resource_metadata}"', + f'error="{error}"', + f'error_description="{error_description}"', + *((f'claims="{encoded_claims}"',) if encoded_claims else ()), + ) ) raise HTTPException( status_code=401, diff --git a/litellm/proxy/_experimental/mcp_server/outbound_credentials/token_exchange_provider.py b/litellm/proxy/_experimental/mcp_server/outbound_credentials/token_exchange_provider.py index 7161dbb32d4..e49de4559c6 100644 --- a/litellm/proxy/_experimental/mcp_server/outbound_credentials/token_exchange_provider.py +++ b/litellm/proxy/_experimental/mcp_server/outbound_credentials/token_exchange_provider.py @@ -1,6 +1,6 @@ """Composition root for the v2-native token_exchange (OBO) exchanger. -Wires the pure ``Rfc8693TokenExchanger`` to its runtime edges: the real httpx POST against the IdP and +Wires the pure ``OboTokenExchanger`` to its runtime edges: the real httpx POST against the IdP and the configured cache sizing/TTL constants. ``build_token_exchanger`` is built once at egress construction and reused, so the in-process exchanged-token cache survives across requests. Unlike the per-user store, nothing here reads a runtime global at build time (the httpx client is acquired per @@ -22,7 +22,7 @@ from litellm.proxy._experimental.mcp_server.outbound_credentials.oauth_token_sto InMemoryTokenCacheBackend, ) from litellm.proxy._experimental.mcp_server.outbound_credentials.token_exchanger import ( - Rfc8693TokenExchanger, + OboTokenExchanger, SubjectTokenRejected, TokenExchangeClientError, ) @@ -34,21 +34,27 @@ _GATEWAY_FAULT_OAUTH_ERRORS = frozenset( ) -def _oauth_error_code(response: httpx.Response) -> str | None: - """Read the RFC 6749 5.2 ``error`` code from a token-endpoint error body, or None if absent. +def _oauth_error_fields(response: httpx.Response) -> tuple[str | None, str | None]: + """Read the RFC 6749 5.2 ``error`` code and the IdP's step-up ``claims`` blob from a + token-endpoint error body, as ``(error, claims)`` with None for whatever is absent. - The ``error_description`` is deliberately not read: it can carry IdP internals and must never - reach the caller. Only the standard machine code drives classification. + ``claims`` is the Entra Conditional Access / CAE challenge (a JSON string the client must + replay to the IdP to satisfy the step-up); it is the caller's own requirement, not an IdP + internal, so it may travel to the caller. The ``error_description`` is deliberately not read: + it can carry IdP internals and must never reach the caller. """ try: body: object = response.json() except Exception: # noqa: BLE001 - return None - if isinstance(body, dict): - code = body.get("error") - if isinstance(code, str): - return code - return None + return None, None + if not isinstance(body, dict): + return None, None + code = body.get("error") + claims = body.get("claims") + return ( + code if isinstance(code, str) else None, + claims if isinstance(claims, str) and claims else None, + ) async def _post_exchange_endpoint( @@ -72,7 +78,7 @@ async def _post_exchange_endpoint( except httpx.HTTPStatusError as status_err: status_code = status_err.response.status_code if 400 <= status_code < 500: - oauth_error = _oauth_error_code(status_err.response) + oauth_error, claims = _oauth_error_fields(status_err.response) if oauth_error in _GATEWAY_FAULT_OAUTH_ERRORS: verbose_logger.warning( "MCP token exchange rejected as %s (HTTP %d); check the gateway client credentials, " @@ -81,7 +87,10 @@ async def _post_exchange_endpoint( status_code, ) raise TokenExchangeClientError(oauth_error) from status_err - raise SubjectTokenRejected(f"IdP rejected the subject token (HTTP {status_code})") from status_err + raise SubjectTokenRejected( + f"IdP rejected the subject token (HTTP {status_code})", + claims=claims, + ) from status_err verbose_logger.warning("MCP token exchange request failed: %s", status_err) return None except Exception as exc: # noqa: BLE001 @@ -95,8 +104,8 @@ async def _post_exchange_endpoint( return parsed # pyright: ignore -def build_token_exchanger() -> Rfc8693TokenExchanger: - return Rfc8693TokenExchanger( +def build_token_exchanger() -> OboTokenExchanger: + return OboTokenExchanger( _post_exchange_endpoint, cache=InMemoryTokenCacheBackend(max_size=MCP_TOKEN_EXCHANGE_CACHE_MAX_SIZE), default_ttl_seconds=MCP_OAUTH2_TOKEN_CACHE_DEFAULT_TTL, diff --git a/litellm/proxy/_experimental/mcp_server/outbound_credentials/token_exchanger.py b/litellm/proxy/_experimental/mcp_server/outbound_credentials/token_exchanger.py index 9f1787da612..02b6d4eafb1 100644 --- a/litellm/proxy/_experimental/mcp_server/outbound_credentials/token_exchanger.py +++ b/litellm/proxy/_experimental/mcp_server/outbound_credentials/token_exchanger.py @@ -1,11 +1,14 @@ -"""v2-native RFC 8693 token exchange (OBO): swap the caller's token for an upstream one. +"""v2-native OBO token exchange: swap the caller's token for an upstream-bound one. -The pure core of the ``token_exchange`` mode. Given the caller's ``subject_token`` and the server's -``TokenExchangeConfig``, ``Rfc8693TokenExchanger.exchange`` POSTs the RFC 8693 token-exchange grant to -the configured endpoint and returns the upstream-bound ``access_token`` as a typed ``OAuthToken``, or a -typed ``CredError`` - never a raise (the HTTP edge is the injected ``ExchangeHttpPost``, whose adapter -contains the I/O). The exchanged token is cached and single-flighted per ``(subject_token, server)`` so -a repeated caller token skips the IdP round-trip and concurrent calls collapse to one exchange, reusing +The pure core of the ``token_exchange`` mode. Given the caller's inbound token and the server's +``TokenExchangeConfig``, ``OboTokenExchanger.exchange`` POSTs the grant selected by ``config.profile`` +to the configured endpoint and returns the upstream-bound ``access_token`` as a typed ``OAuthToken``, +or a typed ``CredError`` - never a raise (the HTTP edge is the injected ``ExchangeHttpPost``, whose +adapter contains the I/O). Two profiles share this one engine: ``rfc8693`` (the RFC 8693 token-exchange +grant) and ``entra_obo`` (Microsoft Entra On-Behalf-Of, which is the RFC 7523 ``jwt-bearer`` grant); +only the request form differs, so the cache, single-flight, and TTL machinery are dialect-agnostic. The +exchanged token is cached and single-flighted per ``(subject_token, tenant, config, server)`` so a +repeated caller token skips the IdP round-trip and concurrent calls collapse to one exchange, reusing the shared in-process cache + coordinator foundation. A rotated caller token hashes to a new key and re-exchanges. Pure v2 apart from the shared RFC 6749 client-auth helper, which carries no v1 state. @@ -19,7 +22,9 @@ from __future__ import annotations import hashlib import time from collections.abc import Awaitable, Callable -from typing import Protocol +from typing import Literal, Protocol + +from typing_extensions import assert_never from litellm._logging import verbose_logger from litellm.proxy._experimental.mcp_server.outbound_credentials.oauth_token_store import ( @@ -51,6 +56,10 @@ _MIN_TTL_SECONDS = 10.0 _EXPIRY_BUFFER_SECONDS = 60.0 _GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange" +# Microsoft Entra On-Behalf-Of speaks the RFC 7523 jwt-bearer grant, not RFC 8693, and gates delegation +# behind ``requested_token_use=on_behalf_of`` (a Microsoft extension present in neither RFC). +_JWT_BEARER_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer" +_REQUESTED_TOKEN_USE_OBO = "on_behalf_of" # RFC 8693 3 token-type URNs that are not usable as an upstream Bearer access token. token_type # already rejects the common non-access case (N_A); this catches a malformed STS that mints one of @@ -77,8 +86,15 @@ class SubjectTokenRejected(Exception): Distinct from a transport / IdP-availability failure, which the post adapter maps to ``None`` -> ``upstream_unavailable`` -> 503 (retryable). A rejected subject is the caller's problem, not the gateway's, so the arm surfaces it as a non-retryable 401 (the OBO challenge) instead. + ``claims`` is the IdP's step-up challenge blob (Entra Conditional Access / CAE) from the + rejection body; it threads into the 401 challenge so the client can satisfy the step-up and + retry. The ``error_description`` is never carried (it can leak IdP internals). """ + def __init__(self, detail: str, *, claims: str | None = None) -> None: + super().__init__(detail) + self.claims = claims + class TokenExchangeClientError(Exception): """The IdP rejected the exchange for a reason that is the gateway's fault, not the caller's. @@ -106,16 +122,18 @@ class TokenExchanger(Protocol): def _cache_key(subject_token: str, tenant_id: str, config: TokenExchangeConfig) -> str: """Bind the cache entry to the caller token, the tenant, AND the exchange config that minted it. - A rotated caller token, a different tenant, endpoint, audience, scope, client_id, secret, auth - method, or subject_token_type all change the key, so two tenants behind the same opaque token - never share an entry and a config change forces a fresh exchange instead of serving a token - minted for the old config until TTL. Everything is hashed, so no secret is held in the key. + A rotated caller token, a different tenant, profile, endpoint, audience, scope, client_id, secret, + auth method, or subject_token_type all change the key, so two tenants behind the same opaque token + never share an entry and a config change (including a profile flip that alters the wire form) + forces a fresh exchange instead of serving a token minted for the old config until TTL. Everything + is hashed, so no secret is held in the key. """ secret = config.client_secret.get_secret_value() if config.client_secret else "" material = "\x00".join( ( subject_token, tenant_id, + config.profile, config.token_exchange_endpoint or "", config.audience or "", config.subject_token_type, @@ -141,7 +159,7 @@ def _parse_expires_in(raw: object) -> int | None: return None -def _build_exchange_form( +def _rfc8693_form( *, subject_token: str, subject_token_type: str, @@ -157,8 +175,53 @@ def _build_exchange_form( } -class Rfc8693TokenExchanger: - """``TokenExchanger`` that runs the RFC 8693 grant once per caller token, then caches the result. +def _entra_obo_form( + *, + subject_token: str, + scopes: tuple[str, ...], +) -> dict[str, str]: + # Microsoft Entra On-Behalf-Of (RFC 7523 jwt-bearer, not RFC 8693): the caller's inbound access + # token rides as ``assertion`` (its ``aud`` must be this gateway's ``client_id``); the target + # resource is carried in ``scope`` (e.g. api:///.default), since Entra has no audience + # parameter and ignores subject_token_type; ``requested_token_use=on_behalf_of`` is the Microsoft + # extension that turns the jwt-bearer grant into a delegation. ``scope`` is required, and the + # exchange precondition rejects an empty one, so it is always present here. Client authentication + # (client_id/client_secret via post, or Basic) is layered on by the caller through + # build_token_endpoint_client_auth, so it is not built into the form here. + return { + "grant_type": _JWT_BEARER_GRANT_TYPE, + "assertion": subject_token, + "scope": " ".join(scopes), + "requested_token_use": _REQUESTED_TOKEN_USE_OBO, + } + + +def _build_exchange_form( + *, + profile: Literal["rfc8693", "entra_obo"], + subject_token: str, + subject_token_type: str, + audience: str | None, + scopes: tuple[str, ...], +) -> dict[str, str]: + match profile: + case "rfc8693": + return _rfc8693_form( + subject_token=subject_token, + subject_token_type=subject_token_type, + audience=audience, + scopes=scopes, + ) + case "entra_obo": + return _entra_obo_form( + subject_token=subject_token, + scopes=scopes, + ) + assert_never(profile) + + +class OboTokenExchanger: + """``TokenExchanger`` that runs the profile's OBO grant once per caller token, then caches the result. The HTTP post is injected (``None`` on any IdP failure, mirroring v1: a failed exchange is a miss, not a 500). The cache and single-flight coordinator default to the in-process foundation; a @@ -199,6 +262,13 @@ class Rfc8693TokenExchanger: ) if not client_id or client_secret is None: return Error(CredError.of_misconfigured("token_exchange requires client_id and client_secret")) + if config.profile == "entra_obo" and not config.scopes: + # Entra carries the target resource in ``scope`` (api:///.default); with no scope the + # IdP cannot resolve an audience, so fail closed as misconfigured rather than POST a form the + # IdP will reject. + return Error( + CredError.of_misconfigured("entra_obo token exchange requires a scope (e.g. api:///.default)") + ) cache_key = _cache_key(subject_token, tenant_id, config) server_id = server.server_id @@ -214,6 +284,7 @@ class Rfc8693TokenExchanger: ) form = { **_build_exchange_form( + profile=config.profile, subject_token=subject_token, subject_token_type=config.subject_token_type, audience=config.audience, @@ -247,7 +318,14 @@ class Rfc8693TokenExchanger: except SubjectTokenRejected as rejected: # The IdP rejected the subject token (4xx). This is non-retryable: the caller must # re-authenticate with the IdP, so it surfaces as a 401 (the OBO challenge), not a 503. - return Error(CredError.of_unauthorized(str(rejected) or "subject token rejected by the IdP")) + # A step-up rejection (Entra Conditional Access) carries the claims blob through so the + # edge's challenge tells the client how to satisfy it. + return Error( + CredError.of_unauthorized( + str(rejected) or "subject token rejected by the IdP", + claims=rejected.claims, + ) + ) except TokenExchangeClientError: # RFC 6749 5.2 gateway-fault code (invalid_client / invalid_target / ...): the caller can't # fix it by re-authenticating, so surface a 500 rather than the OBO 401 challenge. The diff --git a/litellm/proxy/_experimental/mcp_server/outbound_credentials/types.py b/litellm/proxy/_experimental/mcp_server/outbound_credentials/types.py index d475f931a25..7e04be4f045 100644 --- a/litellm/proxy/_experimental/mcp_server/outbound_credentials/types.py +++ b/litellm/proxy/_experimental/mcp_server/outbound_credentials/types.py @@ -39,6 +39,7 @@ from litellm.proxy._experimental.mcp_server.outbound_credentials.result import ( Ok, Result, ) +from litellm.types.mcp import DEFAULT_SUBJECT_TOKEN_TYPE class AuthSpecKind(str, Enum): @@ -67,11 +68,15 @@ class Unauthorized: ``detail`` is the human message; ``www_authenticate`` and ``body`` carry a scheme-specific challenge (e.g. BYOK's provisioning prompt) so the edge can reproduce it verbatim. + ``claims`` carries an IdP step-up challenge (e.g. Entra Conditional Access) so the edge can + fold it into the ``WWW-Authenticate`` it builds; the client replays the claims to the IdP to + satisfy the step-up, then retries with the fresh token. """ detail: str www_authenticate: str | None = None body: Mapping[str, str] | None = None + claims: str | None = None @tagged_union(frozen=True) @@ -104,8 +109,16 @@ class CredError: *, www_authenticate: str | None = None, body: Mapping[str, str] | None = None, + claims: str | None = None, ) -> CredError: - return CredError(unauthorized=Unauthorized(detail=detail, www_authenticate=www_authenticate, body=body)) + return CredError( + unauthorized=Unauthorized( + detail=detail, + www_authenticate=www_authenticate, + body=body, + claims=claims, + ) + ) @staticmethod def of_misconfigured(detail: str) -> CredError: @@ -182,19 +195,28 @@ class ClientCredentialsConfig(BaseModel): class TokenExchangeConfig(BaseModel): - """RFC 8693 OBO; swap the caller's live subject_token for a token bound to the upstream's - audience. The gateway authenticates to the exchange endpoint as an OAuth client - (`client_id`/`client_secret`); the inbound token is sent only to that endpoint, never to the - upstream. + """OBO: swap the caller's live inbound token for a token bound to the upstream's audience. The + gateway authenticates to the exchange endpoint as an OAuth client (`client_id`/`client_secret`); + the inbound token is sent only to that endpoint, never to the upstream. - `audience` is the RFC 8693 target; it is optional and sent only when the operator configured - one, since both `audience` and `resource` are optional in the spec and the authorization server - applies its own default when neither is sent (fabricating one risks `invalid_target`). + `profile` selects the wire dialect, since not every IdP speaks RFC 8693: + - `rfc8693` (default) is the standard token-exchange grant: the inbound token is the + `subject_token` (typed by `subject_token_type`), the target is the optional `audience`. + - `entra_obo` is Microsoft Entra On-Behalf-Of, which is the RFC 7523 `jwt-bearer` grant rather + than 8693: the inbound token rides as `assertion`, the target resource is carried in `scopes` + (`api:///.default`, since Entra has no audience parameter), and the Microsoft-only + `requested_token_use=on_behalf_of` extension makes the jwt-bearer grant a delegation. + `subject_token_type` and `audience` are unused in this profile. + + `audience` (rfc8693 only) is optional and sent only when the operator configured one, since both + `audience` and `resource` are optional in RFC 8693 and the authorization server applies its own + default when neither is sent (fabricating one risks `invalid_target`). """ model_config = ConfigDict(frozen=True) kind: Literal[AuthSpecKind.token_exchange] = AuthSpecKind.token_exchange - subject_token_type: str = "urn:ietf:params:oauth:token-type:access_token" + profile: Literal["rfc8693", "entra_obo"] = "rfc8693" + subject_token_type: str = DEFAULT_SUBJECT_TOKEN_TYPE token_exchange_endpoint: str | None = None audience: str | None = None client_id: str | None = None diff --git a/litellm/proxy/_experimental/mcp_server/rest_endpoints.py b/litellm/proxy/_experimental/mcp_server/rest_endpoints.py index a6067a60105..d482e537c5d 100644 --- a/litellm/proxy/_experimental/mcp_server/rest_endpoints.py +++ b/litellm/proxy/_experimental/mcp_server/rest_endpoints.py @@ -77,6 +77,7 @@ if MCP_AVAILABLE: ListMCPToolsRestAPIResponseObject, MCPInfo, MCPServer, + _apply_toolset_scope, _fire_mcp_success_logging, _tool_name_matches, execute_mcp_tool, @@ -541,10 +542,37 @@ if MCP_AVAILABLE: "message": "Successfully retrieved tools", } + def _as_query_str(value: Any) -> Optional[str]: + """Coerce an Optional[str] Query param to str|None, dropping unresolved FastAPI defaults.""" + return value if isinstance(value, str) else None + + async def _resolve_toolset_scope( + toolset_name: Optional[str], + user_api_key_dict: UserAPIKeyAuth, + ) -> UserAPIKeyAuth: + """Resolve ``toolset_name`` to its scoped ``UserAPIKeyAuth``, or return unchanged.""" + if not toolset_name: + return user_api_key_dict + + from litellm.proxy.utils import get_prisma_client_or_throw + + prisma_client = get_prisma_client_or_throw("Database not available. Connect a database to your proxy") + toolset = await global_mcp_server_manager.get_toolset_by_name_cached(prisma_client, toolset_name) + if toolset is None: + raise HTTPException( + status_code=404, + detail=f"Toolset '{toolset_name}' not found", + ) + return await _apply_toolset_scope(user_api_key_dict, toolset.toolset_id) + @router.get("/tools/list", dependencies=[Depends(user_api_key_auth)]) async def list_tool_rest_api( request: Request, server_id: Optional[str] = Query(None, description="The server id to list tools for"), + mcp_server_name: Optional[str] = Query( + None, description="Filter tools to a single MCP server by name or alias" + ), + toolset_name: Optional[str] = Query(None, description="Filter tools to a single toolset by name"), include_disabled_tools: bool = Query( False, description=( @@ -582,16 +610,29 @@ if MCP_AVAILABLE: ) try: + mcp_server_name = _as_query_str(mcp_server_name) + toolset_name = _as_query_str(toolset_name) + # The full catalog (allowlist filter skipped) is admin-only so the # REST endpoint can't be used to enumerate deliberately-disabled tools. apply_tool_filters = not ( include_disabled_tools and user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN ) - if apply_tool_filters and getattr( - getattr(user_api_key_dict, "object_permission", None), - "mcp_tool_search_enabled", - False, + user_api_key_dict = await _resolve_toolset_scope(toolset_name, user_api_key_dict) + + if server_id is None: + server_id = mcp_server_name + + if ( + apply_tool_filters + and server_id is None + and toolset_name is None + and getattr( + getattr(user_api_key_dict, "object_permission", None), + "mcp_tool_search_enabled", + False, + ) ): from litellm.proxy._experimental.mcp_server.tool_search import ( get_virtual_tool_definitions, @@ -719,6 +760,8 @@ if MCP_AVAILABLE: request_path=request.scope.get("_original_path") or request.url.path, ) except HTTPException as http_exc: + if http_exc.status_code == status.HTTP_404_NOT_FOUND: + raise # Internal access/IP 403s keep the legacy error-dict response shape # so the existing contract stays intact. verbose_logger.exception("HTTPException in list_tool_rest_api: %s", str(http_exc)) @@ -1079,11 +1122,6 @@ if MCP_AVAILABLE: forwarded_authorization = ( effective_oauth2_headers.get("Authorization") if effective_oauth2_headers else None ) - is_interactive_authz_code = ( - server_model.auth_type == MCPAuth.oauth2 - and forwarded_authorization is not None - and to_server_spec(server_model) is not None - ) preview_cred_provider = ( UpstreamCredentialProvider( oauth_token_store=PresentedOAuthTokenStore( @@ -1094,7 +1132,11 @@ if MCP_AVAILABLE: ) ) ) - if is_interactive_authz_code + if ( + server_model.auth_type == MCPAuth.oauth2 + and forwarded_authorization is not None + and to_server_spec(server_model) is not None + ) else None ) diff --git a/litellm/proxy/_experimental/mcp_server/server.py b/litellm/proxy/_experimental/mcp_server/server.py index d978771f433..e3812522ded 100644 --- a/litellm/proxy/_experimental/mcp_server/server.py +++ b/litellm/proxy/_experimental/mcp_server/server.py @@ -1427,18 +1427,8 @@ if MCP_AVAILABLE: for allowed_mcp_server_id in allowed_mcp_server_ids: mcp_server = global_mcp_server_manager.get_mcp_server_by_id(allowed_mcp_server_id) if mcp_server is not None: - # Apply oauth2_flow resolution for legacy DB rows where it may be NULL - resolved_flow = MCPServerManager._resolve_oauth2_flow( - auth_type=mcp_server.auth_type, - oauth2_flow=mcp_server.oauth2_flow, - token_url=mcp_server.token_url, - authorization_url=mcp_server.authorization_url, - client_id=mcp_server.client_id, - client_secret=mcp_server.client_secret, - ) - if resolved_flow and resolved_flow != mcp_server.oauth2_flow: - # Create a new instance with the resolved flow for this request - mcp_server = mcp_server.model_copy(update={"oauth2_flow": resolved_flow}) + # Apply the request-time oauth2_flow backstop for legacy null rows. + mcp_server = MCPServerManager.resolve_oauth2_flow_for_request(mcp_server) allowed_mcp_servers.append(mcp_server) if mcp_servers is not None: @@ -2800,6 +2790,9 @@ if MCP_AVAILABLE: for allowed_mcp_server_id in allowed_mcp_server_ids: allowed_server = global_mcp_server_manager.get_mcp_server_by_id(allowed_mcp_server_id) if allowed_server is not None: + # Same request-time oauth2_flow backstop the listing path applies, + # so a null-flow M2M-shape row is treated as M2M on tool calls too. + allowed_server = MCPServerManager.resolve_oauth2_flow_for_request(allowed_server) allowed_mcp_servers.append(allowed_server) allowed_mcp_servers = await _get_allowed_mcp_servers_from_mcp_server_names( diff --git a/litellm/proxy/_experimental/mcp_server/utils.py b/litellm/proxy/_experimental/mcp_server/utils.py index 9cb6d404b01..c9c60030dbc 100644 --- a/litellm/proxy/_experimental/mcp_server/utils.py +++ b/litellm/proxy/_experimental/mcp_server/utils.py @@ -214,12 +214,14 @@ def server_applies_tool_allowlist(mcp_server: Any) -> bool: def validate_and_normalize_mcp_server_payload(payload: Any) -> None: """ - Validate and normalize MCP server payload fields (server_name and alias). + Validate and normalize MCP server payload fields (server_name, alias, and + tool_name_to_display_name). This function: 1. Validates that server_name and alias don't contain the MCP_TOOL_PREFIX_SEPARATOR - 2. Normalizes alias by replacing spaces with underscores - 3. Sets default alias if not provided (using server_name as base) + 2. Validates that tool_name_to_display_name values satisfy Bedrock's tool-name pattern + 3. Normalizes alias by replacing spaces with underscores + 4. Sets default alias if not provided (using server_name as base) Args: payload: The payload object containing server_name and alias fields @@ -235,6 +237,10 @@ def validate_and_normalize_mcp_server_payload(payload: Any) -> None: if hasattr(payload, "alias") and payload.alias: validate_mcp_server_name(payload.alias, raise_http_exception=True) + # Tool display name validation: must satisfy Bedrock's tool-name pattern + if hasattr(payload, "tool_name_to_display_name") and payload.tool_name_to_display_name: + validate_tool_display_names(payload.tool_name_to_display_name) + # Alias normalization and defaulting alias = getattr(payload, "alias", None) server_name = getattr(payload, "server_name", None) @@ -409,6 +415,42 @@ def validate_mcp_server_name(server_name: str, raise_http_exception: bool = Fals raise Exception(error_message) +TOOL_DISPLAY_NAME_PATTERN = re.compile(r"^[a-zA-Z0-9_-]+$") + + +def validate_tool_display_names(tool_name_to_display_name: Optional[Mapping[str, str]]) -> None: + """ + Validate tool display name overrides against Bedrock's tool-name constraint. + + A display name replaces the tool name sent to the LLM provider, so it must + satisfy the strictest provider requirement in use (Bedrock's + ``[a-zA-Z0-9_-]+``); a name with spaces or other characters saves + successfully but fails every subsequent Bedrock tool call. + + Raises: + HTTPException: If any display name fails the pattern. + """ + if not tool_name_to_display_name: + return + + for original_name, display_name in tool_name_to_display_name.items(): + if display_name and not TOOL_DISPLAY_NAME_PATTERN.match(display_name): + from fastapi import HTTPException + from starlette import status + + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail={ + "error": ( + f"Invalid display name '{display_name}' for tool '{original_name}'. " + "Display names may only contain letters, digits, underscores, and " + "hyphens (no spaces or other special characters), since they replace " + "the tool name sent to the LLM provider." + ) + }, + ) + + class MCPMissingUserEnvVarsError(Exception): """Raised when an MCP request can't be built because the calling user has not supplied one or more required per-user environment variables. diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index e7b045d66b5..9e390312fa1 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -1070,6 +1070,7 @@ class KeyRequestBase(GenerateRequestBase): budget_id: Optional[str] = None tags: Optional[List[str]] = None disable_global_guardrails: Optional[bool] = None + throttle_on_budget_exceeded: Optional[bool] = None enforced_params: Optional[List[str]] = None allowed_routes: Optional[list] = [] allowed_passthrough_routes: Optional[list] = None @@ -1254,6 +1255,14 @@ class NewMCPServerRequest(LiteLLMPydanticObjectBase): token_url: Optional[str] = None registration_url: Optional[str] = None oauth2_flow: Optional[Literal["client_credentials", "authorization_code"]] = None + # Token Exchange (OBO) fields — RFC 8693. These top-level fields are the + # canonical shape; the same keys inside ``credentials`` are the legacy + # pre-column REST shape and are lifted into these columns on write (an + # explicit top-level value wins) and stripped from the stored blob. + token_exchange_endpoint: Optional[str] = None + audience: Optional[str] = None + subject_token_type: Optional[str] = None + token_exchange_profile: Optional[str] = None allow_all_keys: bool = False available_on_public_internet: bool = True delegate_auth_to_upstream: bool = False @@ -1340,6 +1349,14 @@ class UpdateMCPServerRequest(LiteLLMPydanticObjectBase): token_url: Optional[str] = None registration_url: Optional[str] = None oauth2_flow: Optional[Literal["client_credentials", "authorization_code"]] = None + # Token Exchange (OBO) fields — RFC 8693. These top-level fields are the + # canonical shape; the same keys inside ``credentials`` are the legacy + # pre-column REST shape and are lifted into these columns on write (an + # explicit top-level value wins) and stripped from the stored blob. + token_exchange_endpoint: Optional[str] = None + audience: Optional[str] = None + subject_token_type: Optional[str] = None + token_exchange_profile: Optional[str] = None allow_all_keys: bool = False available_on_public_internet: bool = True delegate_auth_to_upstream: bool = False @@ -2322,6 +2339,28 @@ class ConfigGeneralSettings(LiteLLMPydanticObjectBase): "is active as a reminder that hard enforcement is relaxed." ), ) + user_url_validation: Optional[bool] = Field( + None, + description=( + "Master switch for the SSRF guard applied to user-supplied URLs " + "(image_url, file_url, MCP/OpenAPI spec URLs, etc). Defaults to True. " + "Set to False to disable DNS/IP validation entirely (not recommended)." + ), + ) + user_url_allowed_hosts: Optional[list[str]] = Field( + None, + description=( + "SSRF allowlist for user-supplied URLs. Entries are `hostname` or " + "`hostname:port` (bracketed for IPv6, e.g. `[::1]:8080`). Allowlisted " + "hosts skip the blocked-network check in validate_url() but still " + "resolve DNS. Use this to permit legitimate internal targets, e.g. " + "an internal OpenAPI/MCP server." + ), + ) + provider_url_destination_allowed_hosts: Optional[list[str]] = Field( + None, + description="Allowlist of hosts a request may redirect a provider call's destination URL to.", + ) class ConfigYAML(LiteLLMPydanticObjectBase): @@ -2447,6 +2486,7 @@ class UserAPIKeyAuth(LiteLLM_VerificationTokenView): # the expected response ob request_route: Optional[str] = None is_session_token: bool = False budget_reservation: Optional[Dict[str, Any]] = Field(default=None, exclude=True) + budget_throttle_pct: Optional[float] = Field(default=None, exclude=True) user: Optional[Any] = None # Expanded user object when expand=user is used created_by_user: Optional[Any] = None # Expanded created_by user when expand=user is used end_user_object_permission: Optional[LiteLLM_ObjectPermissionTable] = None @@ -3837,6 +3877,7 @@ LiteLLM_ManagementEndpoint_MetadataFields = [ "allowed_vector_store_indexes", "enforced_batch_output_expires_after", "enforced_file_expires_after", + "throttle_on_budget_exceeded", ] LiteLLM_ManagementEndpoint_MetadataFields_Premium = [ @@ -4184,6 +4225,17 @@ class LiteLLM_JWTAuth(LiteLLMPydanticObjectBase): "authorization." ), ) + fallback_to_db_teams: bool = Field( + default=False, + description=( + "When True, users whose JWT contains no team claims are authenticated " + "using their database team memberships instead of receiving HTTP 403. " + "Usage is attributed to the user's first resolvable DB team, or to the " + "team specified via the x-litellm-team-id request header (validated " + "against DB membership). Requires user_id_upsert=True so that user " + "records exist before the fallback runs." + ), + ) issuers: Optional[List[JWTIssuerConfig]] = Field( default=None, description="Optional issuer-bound JWT validation rules. When a token's `iss` matches a configured issuer, validation uses that issuer's JWKS, audience, and claim mappings. Tokens with an unlisted `iss` fall back to the global JWT_AUDIENCE/JWT_ISSUER validation path — this is additive routing, not an allow-list.", diff --git a/litellm/proxy/auth/auth_checks.py b/litellm/proxy/auth/auth_checks.py index b749e9fbe0d..ee548ba0a43 100644 --- a/litellm/proxy/auth/auth_checks.py +++ b/litellm/proxy/auth/auth_checks.py @@ -60,6 +60,10 @@ from litellm.proxy._types import ( UserAPIKeyAuth, ) from litellm.proxy.auth.route_checks import RouteChecks +from litellm.proxy.auth.budget_throttle import ( + budget_throttle_percentage, + should_throttle_budget_exceeded, +) from litellm.proxy.spend_tracking.budget_reservation import get_budget_window_start from litellm.proxy.common_utils.cache_pydantic_utils import CacheCodec from litellm.proxy.common_utils.http_parsing_utils import ( @@ -2496,6 +2500,7 @@ def _copy_user_api_key_auth_for_cache( ) -> UserAPIKeyAuth: copied_key_obj = user_api_key_obj.model_copy() copied_key_obj.budget_reservation = None + copied_key_obj.budget_throttle_pct = None copied_key_obj.parent_otel_span = None copied_key_obj.request_route = None return copied_key_obj @@ -3428,6 +3433,24 @@ async def is_valid_fallback_model( return True +def _apply_budget_exceeded_throttle(valid_token: UserAPIKeyAuth) -> bool: + """ + Throttle an over-budget key instead of blocking it, when the key opted in + via `throttle_on_budget_exceeded` and a global percentage is configured. + + Records the percentage on the request-scoped `budget_throttle_pct` so the + rate limiter scales the key's TPM/RPM down to it; the persistent limits are + left untouched so the throttle never compounds across requests. Returns True + when the key was throttled (caller skips raising), False when it should still + be hard-blocked. + """ + pct = budget_throttle_percentage() + if pct is None or not should_throttle_budget_exceeded(valid_token): + return False + valid_token.budget_throttle_pct = pct + return True + + async def _virtual_key_max_budget_check( valid_token: UserAPIKeyAuth, proxy_logging_obj: ProxyLogging, @@ -3488,6 +3511,8 @@ async def _virtual_key_max_budget_check( # so a NaN max_budget would silently disable enforcement. Treat a # non-finite max_budget as "no configured limit" rather than as a bypass. if math.isfinite(valid_token.max_budget) and spend >= valid_token.max_budget: + if _apply_budget_exceeded_throttle(valid_token): + return # name the key in the error so operators don't have to reverse-map # spend back to a key; key_name is the masked form (last 4 chars) key_label = valid_token.key_alias or "key" diff --git a/litellm/proxy/auth/budget_throttle.py b/litellm/proxy/auth/budget_throttle.py new file mode 100644 index 00000000000..19dffee462b --- /dev/null +++ b/litellm/proxy/auth/budget_throttle.py @@ -0,0 +1,56 @@ +""" +Throttle a key after it exceeds its own ``max_budget`` instead of blocking it. + +When a key opts in via ``throttle_on_budget_exceeded`` and a global +``budget_exceeded_throttle_percentage`` is configured, an over-budget key keeps +serving requests but at a reduced TPM/RPM (the configured percentage of its +configured limits). The decision (over budget + opted in) is made once during +auth; the scaling is recomputed from the key's original limits on every request +so it never compounds across requests. +""" + +import math +from typing import Optional + +import litellm +from litellm.proxy._types import UserAPIKeyAuth + + +def budget_throttle_percentage() -> Optional[float]: + """ + The global throttle percentage, or None when throttling is disabled / + misconfigured (in which case an over-budget key is hard-blocked, the safe + default). + """ + pct = litellm.budget_exceeded_throttle_percentage + if not isinstance(pct, (int, float)) or isinstance(pct, bool): + return None + if not 0 < pct <= 1: + return None + return float(pct) + + +def should_throttle_budget_exceeded(valid_token: UserAPIKeyAuth) -> bool: + """ + True when a key that exceeded its own ``max_budget`` should be throttled + rather than blocked: it opted in, a valid global percentage is set, and the + key has a TPM or RPM limit to scale down. A key with neither limit has + nothing to throttle, so it stays hard-blocked (the safe default) rather than + serving unlimited requests past its budget. + """ + if (valid_token.metadata or {}).get("throttle_on_budget_exceeded") is not True: + return False + if valid_token.tpm_limit is None and valid_token.rpm_limit is None: + return False + return budget_throttle_percentage() is not None + + +def throttled_limit(limit: Optional[int], pct: Optional[float]) -> Optional[int]: + """ + Scale a TPM/RPM limit to ``pct`` of its value, keeping a trickle of at least + 1 so a throttled key is slowed rather than fully locked out. An unset limit + or unset percentage leaves the limit unchanged. + """ + if limit is None or pct is None: + return limit + return max(1, math.floor(limit * pct)) diff --git a/litellm/proxy/auth/handle_jwt.py b/litellm/proxy/auth/handle_jwt.py index 9db9b970d88..a44318c072c 100644 --- a/litellm/proxy/auth/handle_jwt.py +++ b/litellm/proxy/auth/handle_jwt.py @@ -12,7 +12,7 @@ import fnmatch import hashlib import os import re -from typing import Any, List, Literal, Optional, Set, Tuple, Union, cast +from typing import Any, List, Literal, NoReturn, Optional, Set, Tuple, Union, cast import jwt from cryptography import x509 @@ -1196,10 +1196,22 @@ class JWTAuthManager: ) -> Tuple[Optional[str], Optional[LiteLLM_TeamTable]]: """Find and validate specific team ID from team_id_jwt_field or team_alias_jwt_field""" individual_team_id = jwt_handler.get_team_id(token=jwt_valid_token, default_value=None) + team_alias = jwt_handler.get_team_alias(token=jwt_valid_token, default_value=None) + + # `get_team_id` silently substitutes `team_id_default` for a missing + # JWT team_id claim. When the token actually carries an alias claim, + # that substitution would mask the alias-resolved team, so prefer + # alias resolution. `get_all_jwt_team_ids` ignores `team_id_default`; + # an empty result means no real JWT team_id claim is present. + if ( + team_alias + and individual_team_id is not None + and not jwt_handler.get_all_jwt_team_ids(token=jwt_valid_token) + ): + individual_team_id = None team_object: Optional[LiteLLM_TeamTable] = None - # First try to get team by team_id if individual_team_id: try: team_object = await get_team_object( @@ -1222,8 +1234,6 @@ class JWTAuthManager: ) return None, None - # If no team_id found, try to resolve via team_alias_jwt_field - team_alias = jwt_handler.get_team_alias(token=jwt_valid_token, default_value=None) if team_alias: verbose_proxy_logger.info(f"JWT Auth: Resolving team by alias: '{team_alias}'") team_object = await get_team_object_by_alias( @@ -1329,7 +1339,10 @@ class JWTAuthManager: denied_auth_enforced_pass_through_route = False if not team_ids: - if jwt_handler.litellm_jwtauth.enforce_team_based_model_access: + if ( + jwt_handler.litellm_jwtauth.enforce_team_based_model_access + and not jwt_handler.litellm_jwtauth.fallback_to_db_teams + ): raise HTTPException( status_code=403, detail="No teams found in token. `enforce_team_based_model_access` is set to True. Token must belong to a team.", @@ -1571,6 +1584,7 @@ class JWTAuthManager: def get_team_id_from_header( request_headers: Optional[dict], allowed_team_ids: Set[str], + fallback_to_db_teams: bool = False, ) -> Optional[str]: """ Extract team_id from x-litellm-team-id header if present. @@ -1579,6 +1593,10 @@ class JWTAuthManager: Args: request_headers: Dictionary of request headers allowed_team_ids: Set of team IDs the user is allowed to access (from JWT) + fallback_to_db_teams: When True and the JWT carries no team claims + (allowed_team_ids is empty), the header value is returned + provisionally and validated against DB memberships later in + auth_builder instead of being rejected here. Returns: The team_id from header if valid, None otherwise @@ -1596,8 +1614,8 @@ class JWTAuthManager: if not header_team_id: return None - # Validate that the team_id is in the allowed teams - if header_team_id not in allowed_team_ids: + defer_to_db_membership = fallback_to_db_teams and not allowed_team_ids + if not defer_to_db_membership and header_team_id not in allowed_team_ids: raise HTTPException( status_code=403, detail=f"Team '{header_team_id}' from x-litellm-team-id header is not in your JWT's allowed teams. Allowed teams: {list(allowed_team_ids)}", @@ -1694,11 +1712,20 @@ class JWTAuthManager: ttl=get_management_object_ttl(user_api_key_cache), ) - # Sync team memberships - jwt_team_ids = set(jwt_handler.get_team_ids_from_jwt(jwt_valid_token)) + # Sync team memberships. With fallback_to_db_teams on, read both plural and + # singular claim shapes so a singular-only IdP token (e.g. Okta/Auth0) is + # not mistaken for claimless and left with stale DB memberships the fallback + # could later attribute. With the flag off, keep the upstream plural-only + # reconciliation so existing deployments are unchanged. + jwt_team_ids = set( + jwt_handler.get_all_jwt_team_ids(jwt_valid_token) + if jwt_handler.litellm_jwtauth.fallback_to_db_teams + else jwt_handler.get_team_ids_from_jwt(jwt_valid_token) + ) existing_teams = set(user_object.teams or []) teams_to_add = jwt_team_ids - existing_teams - teams_to_remove = existing_teams - jwt_team_ids + preserve_db_teams_without_claims = jwt_handler.litellm_jwtauth.fallback_to_db_teams and not jwt_team_ids + teams_to_remove = set() if preserve_db_teams_without_claims else existing_teams - jwt_team_ids if teams_to_add or teams_to_remove: from litellm.proxy.management_endpoints.scim.scim_v2 import ( patch_team_membership, @@ -1818,6 +1845,155 @@ class JWTAuthManager: ) return None, None, None + @staticmethod + async def _resolve_db_team_fallback( + user_object: LiteLLM_UserTable | None, + user_id: str | None, + requested_model: str | None, + route: str, + jwt_handler: JWTHandler, + enforce_team_based_model_access: bool, + team_id_upsert: bool, + prisma_client: PrismaClient | None, + user_api_key_cache: UserApiKeyCache, + parent_otel_span: Span | None, + proxy_logging_obj: ProxyLogging, + request_method: str | None = None, + ) -> tuple[str | None, LiteLLM_TeamTable | None, LiteLLM_TeamMembership | None]: + """ + Resolve a team for a user whose JWT carries no team claims by selecting + the first DB team membership that loads successfully and, when a model is + requested, can access that model — mirroring the per-team model-access + check the claim-based path enforces, so a team's `models` restriction is + not bypassed by the fallback. + + The same `team_allowed_routes` gate the claim-based path applies is + enforced here too, so a DB-selected team cannot reach a route the JWT + config excludes for team-role callers. Auth-enforced passthrough routes + are exempt from that gate by design (they are governed by the team's + `allowed_passthrough_routes`, re-checked by the caller). + + The resolved team's membership row is loaded too (when user_id is set) so + per-team membership budget limits are enforced on the fallback path the + same as on the claim-based path. + + Raises HTTP 403 when the user has no usable DB team membership and + `enforce_team_based_model_access` is set; otherwise returns (None, None, None). + """ + from litellm.proxy.proxy_server import llm_router + + user_team_ids = user_object.teams if user_object else [] + team_route_allowed = JWTAuthManager._is_team_route_allowed( + route=route, request_method=request_method, jwt_handler=jwt_handler + ) + any_team_resolved = False + for candidate_team_id in user_team_ids: + try: + team_object = await get_team_object( + team_id=candidate_team_id, + prisma_client=prisma_client, + user_api_key_cache=user_api_key_cache, + parent_otel_span=parent_otel_span, + proxy_logging_obj=proxy_logging_obj, + team_id_upsert=team_id_upsert, + ) + except HTTPException: + continue + any_team_resolved = True + if requested_model: + try: + await can_team_access_model( + model=requested_model, + team_object=team_object, + llm_router=llm_router, + team_model_aliases=None, + ) + except ProxyException: + continue + if not team_route_allowed: + continue + verbose_proxy_logger.debug( + "JWT DB team fallback: resolved team_id=%s from user DB membership", + candidate_team_id, + ) + if user_id: + return ( + candidate_team_id, + team_object, + await get_team_membership( + user_id=user_id, + team_id=candidate_team_id, + prisma_client=prisma_client, + user_api_key_cache=user_api_key_cache, + parent_otel_span=parent_otel_span, + proxy_logging_obj=proxy_logging_obj, + ), + ) + return candidate_team_id, team_object, None + + if enforce_team_based_model_access: + if requested_model and any_team_resolved: + raise HTTPException( + status_code=403, + detail=( + f"No team you are a member of has access to the requested " + f"model: {requested_model}. Check `/models` to see the models " + f"available to you." + ), + ) + raise HTTPException( + status_code=403, + detail=("User is not a member of any team. Add the user to a team via the LiteLLM UI or API."), + ) + return None, None, None + + @staticmethod + def _is_team_route_allowed( + route: str, + request_method: str | None, + jwt_handler: JWTHandler, + ) -> bool: + """ + Whether a team-role caller may reach `route` per the JWT config's + `team_allowed_routes`. Auth-enforced passthrough routes are exempt + here; their team's `allowed_passthrough_routes` gate runs separately. + """ + normalized_method = request_method.upper() if isinstance(request_method, str) else None + return RouteChecks.is_auth_enforced_pass_through_route( + route=route, method=normalized_method + ) or allowed_routes_check( + user_role=LitellmUserRoles.TEAM, + user_route=route, + litellm_proxy_roles=jwt_handler.litellm_jwtauth, + ) + + @staticmethod + def _raise_header_team_membership_denial(team_id: str) -> NoReturn: + """ + The single denial shape for a provisional x-litellm-team-id header, + raised identically for nonexistent teams and for teams the user is not + a member of, so the response does not reveal whether a team id exists. + """ + raise HTTPException( + status_code=403, + detail=(f"Team '{team_id}' (from x-litellm-team-id header) is not in your team memberships."), + ) + + @staticmethod + def _validate_header_team_in_db_membership( + team_id: str, + user_object: LiteLLM_UserTable | None, + ) -> None: + """ + A provisional team_id from the x-litellm-team-id header (accepted without + JWT-team validation when the JWT carries no team claims) must exist in the + user's DB team memberships before it becomes request context. + """ + user_team_ids = user_object.teams if user_object else [] + if team_id in user_team_ids: + return + JWTAuthManager._raise_header_team_membership_denial(team_id) + @staticmethod async def auth_builder( api_key: str, @@ -1911,24 +2087,49 @@ class JWTAuthManager: ## Check if team_id is specified via x-litellm-team-id header all_team_ids = JWTAuthManager.get_all_team_ids(jwt_handler, jwt_valid_token) specific_team_id = jwt_handler.get_team_id(token=jwt_valid_token, default_value=None) - if specific_team_id: + + # The DB fallback only applies when the token carries no team identity at + # all. `get_all_jwt_team_ids` ignores `team_id_default` so a configured + # default does not hide a claimless token, `get_team_alias` covers + # alias-only tokens so the alias still resolves via + # `find_and_validate_specific_team_id`, and `team_id is None` excludes + # the RBAC team-role path (which already set `team_id`); otherwise a + # provisional x-litellm-team-id header could override an RBAC-asserted team. + db_team_fallback = ( + jwt_handler.litellm_jwtauth.fallback_to_db_teams + and not jwt_handler.get_all_jwt_team_ids(token=jwt_valid_token) + and not jwt_handler.get_team_alias(token=jwt_valid_token, default_value=None) + and team_id is None + ) + if specific_team_id and not db_team_fallback: all_team_ids.add(specific_team_id) header_team_id = JWTAuthManager.get_team_id_from_header( request_headers=request_headers, allowed_team_ids=all_team_ids, + fallback_to_db_teams=db_team_fallback, ) if header_team_id: team_id = header_team_id - team_object = await get_team_object( - team_id=team_id, - prisma_client=prisma_client, - user_api_key_cache=user_api_key_cache, - parent_otel_span=parent_otel_span, - proxy_logging_obj=proxy_logging_obj, - team_id_upsert=jwt_handler.litellm_jwtauth.team_id_upsert, - ) - elif not team_id: + # A provisional header team (accepted only because the JWT carries no + # team claims) is validated against DB membership further down; never + # upsert it here or an attacker-supplied x-litellm-team-id would create + # an orphaned team row before that check runs. A genuine membership team + # already exists, so suppressing the upsert in that case costs nothing. + try: + team_object = await get_team_object( + team_id=team_id, + prisma_client=prisma_client, + user_api_key_cache=user_api_key_cache, + parent_otel_span=parent_otel_span, + proxy_logging_obj=proxy_logging_obj, + team_id_upsert=(jwt_handler.litellm_jwtauth.team_id_upsert and not db_team_fallback), + ) + except HTTPException: + if not db_team_fallback: + raise + JWTAuthManager._raise_header_team_membership_denial(team_id) + elif not team_id and not db_team_fallback: ## SPECIFIC TEAM ID ( team_id, @@ -2020,8 +2221,36 @@ class JWTAuthManager: user_api_key_cache=user_api_key_cache, ) - # If JWT did not resolve team_id, attempt single-team DB fallback. - if team_id is None: + # If JWT did not resolve team_id, attempt a team fallback. + if team_id is None and db_team_fallback: + ( + team_id, + team_object, + team_membership_object, + ) = await JWTAuthManager._resolve_db_team_fallback( + user_object=user_object, + user_id=user_id, + requested_model=request_data.get("model"), + route=route, + jwt_handler=jwt_handler, + enforce_team_based_model_access=jwt_handler.litellm_jwtauth.enforce_team_based_model_access, + team_id_upsert=jwt_handler.litellm_jwtauth.team_id_upsert, + prisma_client=prisma_client, + user_api_key_cache=user_api_key_cache, + parent_otel_span=parent_otel_span, + proxy_logging_obj=proxy_logging_obj, + request_method=request_method, + ) + # The earlier passthrough gate ran when team_id was None; re-check + # against the DB-resolved team so a fallback-selected team must also + # pass the auth-enforced passthrough allowlist. + if team_id and not JWTAuthManager._team_has_passthrough_route_access( + team_object=team_object, + route=route, + request_method=request_method, + ): + JWTAuthManager._raise_team_passthrough_route_denial(route=route) + elif team_id is None: ( team_id, team_object, @@ -2035,6 +2264,22 @@ class JWTAuthManager: proxy_logging_obj=proxy_logging_obj, team_id_upsert=jwt_handler.litellm_jwtauth.team_id_upsert, ) + elif db_team_fallback and team_id == header_team_id: + JWTAuthManager._validate_header_team_in_db_membership( + team_id=team_id, + user_object=user_object, + ) + if not JWTAuthManager._is_team_route_allowed( + route=route, + request_method=request_method, + jwt_handler=jwt_handler, + ): + raise HTTPException( + status_code=403, + detail=( + f"Team '{team_id}' (from x-litellm-team-id header) is not allowed to access route '{route}'." + ), + ) ## MAP USER TO TEAMS await JWTAuthManager.map_user_to_teams( diff --git a/litellm/proxy/auth/user_api_key_auth.py b/litellm/proxy/auth/user_api_key_auth.py index 7944bb54d67..2613510bd0c 100644 --- a/litellm/proxy/auth/user_api_key_auth.py +++ b/litellm/proxy/auth/user_api_key_auth.py @@ -2004,6 +2004,11 @@ async def _user_api_key_auth_builder( valid_token_dict = valid_token.model_dump(exclude_none=True) valid_token_dict.pop("token", None) + # budget_throttle_pct is excluded from model_dump (it must not leak + # into serialized responses), so carry the request-scoped decision + # forward by hand to the auth object the rate limiter receives. + if valid_token.budget_throttle_pct is not None: + valid_token_dict["budget_throttle_pct"] = valid_token.budget_throttle_pct if _end_user_object is not None: valid_token_dict.update(end_user_params) diff --git a/litellm/proxy/common_request_processing.py b/litellm/proxy/common_request_processing.py index 97f7d51970c..02bb66388ca 100644 --- a/litellm/proxy/common_request_processing.py +++ b/litellm/proxy/common_request_processing.py @@ -52,6 +52,7 @@ from litellm.proxy.dd_span_tagger import DDSpanTagger from litellm.proxy.route_llm_request import route_request from litellm.proxy.utils import ProxyLogging from litellm.router import Router +from litellm.router_utils.add_retry_fallback_headers import get_hidden_params_dict from litellm.types.guardrails import GuardrailEventHooks from litellm.types.router import RouterRateLimitError from litellm.types.utils import ServerToolUse @@ -90,7 +91,9 @@ _CLIENT_DISCONNECTED_ERROR_INFORMATION: StandardLoggingPayloadErrorInformation = } -def _apply_client_disconnect_metadata(target_metadata: dict[str, object]) -> None: +def _apply_client_disconnect_metadata(target_metadata: Optional[dict[str, object]]) -> None: + if target_metadata is None: + return target_metadata["client_disconnected"] = True target_metadata["error_information"] = dict(_CLIENT_DISCONNECTED_ERROR_INFORMATION) @@ -113,12 +116,33 @@ async def _record_streaming_client_disconnect_if_needed( logging_obj = request_data.get("litellm_logging_obj") if logging_obj is not None: litellm_params = logging_obj.model_call_details.setdefault("litellm_params", {}) - _apply_client_disconnect_metadata(litellm_params.setdefault("metadata", {})) - _apply_client_disconnect_metadata(logging_obj.model_call_details.setdefault("metadata", {})) + _lp_metadata = litellm_params.get("metadata") + if _lp_metadata is None: + _lp_metadata = {} + litellm_params["metadata"] = _lp_metadata + _apply_client_disconnect_metadata(_lp_metadata) - _apply_client_disconnect_metadata(request_data.setdefault("metadata", {})) - litellm_params = request_data.setdefault("litellm_params", {}) - _apply_client_disconnect_metadata(litellm_params.setdefault("metadata", {})) + _mcd_metadata = logging_obj.model_call_details.get("metadata") + if _mcd_metadata is None: + _mcd_metadata = {} + logging_obj.model_call_details["metadata"] = _mcd_metadata + _apply_client_disconnect_metadata(_mcd_metadata) + + _rd_metadata = request_data.get("metadata") + if _rd_metadata is None: + _rd_metadata = {} + request_data["metadata"] = _rd_metadata + _apply_client_disconnect_metadata(_rd_metadata) + + _rd_litellm_params = request_data.get("litellm_params") + if _rd_litellm_params is None: + _rd_litellm_params = {} + request_data["litellm_params"] = _rd_litellm_params + _rd_lp_metadata = _rd_litellm_params.get("metadata") + if _rd_lp_metadata is None: + _rd_lp_metadata = {} + _rd_litellm_params["metadata"] = _rd_lp_metadata + _apply_client_disconnect_metadata(_rd_lp_metadata) verbose_proxy_logger.debug( "Recorded streaming client disconnect with error_code=499 for litellm_call_id=%s", @@ -600,7 +624,7 @@ def _override_openai_response_model( if not requested_model: return - hidden_params = getattr(response_obj, "_hidden_params", {}) or {} + hidden_params = get_hidden_params_dict(response_obj) if isinstance(hidden_params, dict): # Check if a fallback occurred - if so, preserve the actual model used fallback_headers = hidden_params.get("additional_headers", {}) or {} @@ -896,7 +920,7 @@ class ProxyBaseLLMRequestProcessing: (e.g. Google native :generateContent) instead of base_process_llm_request. """ if isinstance(response, dict): - hidden_params = response.get("_hidden_params") or {} + hidden_params = get_hidden_params_dict(response) else: hidden_params = getattr(response, "_hidden_params", None) or {} if not isinstance(hidden_params, dict): @@ -1174,6 +1198,120 @@ class ProxyBaseLLMRequestProcessing: return self.data, logging_obj + async def _pre_call_with_fallbacks( + self, + request: Request, + general_settings: dict, + proxy_logging_obj: ProxyLogging, + user_api_key_dict: UserAPIKeyAuth, + version: Optional[str], + proxy_config: ProxyConfig, + user_model: Optional[str], + user_temperature: Optional[float], + user_request_timeout: Optional[float], + user_max_tokens: Optional[int], + user_api_base: Optional[str], + model: Optional[str], + route_type: str, + llm_router: Optional[Router], + ) -> tuple[dict, LiteLLMLoggingObj]: + from litellm.proxy.common_utils.proxy_rate_limit_error import ProxyRateLimitError + + try: + return await self.common_processing_pre_call_logic( + request=request, + general_settings=general_settings, + proxy_logging_obj=proxy_logging_obj, + user_api_key_dict=user_api_key_dict, + version=version, + proxy_config=proxy_config, + user_model=user_model, + user_temperature=user_temperature, + user_request_timeout=user_request_timeout, + user_max_tokens=user_max_tokens, + user_api_base=user_api_base, + model=model, + route_type=route_type, + llm_router=llm_router, + ) + except ProxyRateLimitError as original_exc: + original_model = self.data.get("model") + if not original_model or not llm_router or self.data.get("disable_fallbacks"): + raise + + fallback_models = self._resolve_fallback_models( + model=original_model, + llm_router=llm_router, + user_api_key_dict=user_api_key_dict, + ) + if not fallback_models: + raise + + verbose_proxy_logger.info( + "Local rate limit hit for model=%s, attempting fallbacks: %s", + original_model, + fallback_models, + ) + + try: + for fallback_model in fallback_models: + if fallback_model == original_model: + continue + self.data["model"] = fallback_model + try: + return await self.common_processing_pre_call_logic( + request=request, + general_settings=general_settings, + proxy_logging_obj=proxy_logging_obj, + user_api_key_dict=user_api_key_dict, + version=version, + proxy_config=proxy_config, + user_model=user_model, + user_temperature=user_temperature, + user_request_timeout=user_request_timeout, + user_max_tokens=user_max_tokens, + user_api_base=user_api_base, + model=fallback_model, + route_type=route_type, + llm_router=llm_router, + ) + except ProxyRateLimitError: + continue + except BaseException: + self.data["model"] = original_model + raise + + self.data["model"] = original_model + raise original_exc + + def _resolve_fallback_models( + self, + model: str, + llm_router: Router, + user_api_key_dict: UserAPIKeyAuth, + ) -> Optional[list]: + from litellm.router_utils.fallback_event_handlers import get_fallback_model_group + + fallbacks = None + + key_router_settings = user_api_key_dict.router_settings + if isinstance(key_router_settings, dict) and "fallbacks" in key_router_settings: + fallbacks = key_router_settings["fallbacks"] + + if fallbacks is None: + fallbacks = llm_router.fallbacks + + if not fallbacks: + return None + + fallback_model_group, generic_fallback_idx = get_fallback_model_group( + fallbacks=fallbacks, + model_group=model, + ) + if fallback_model_group is None and generic_fallback_idx is not None: + fallback_model_group = fallbacks[generic_fallback_idx]["*"] + return fallback_model_group + @staticmethod def _get_model_id_from_response(hidden_params: dict, data: dict) -> str: """Extract model_id from hidden_params with fallback to litellm_metadata.""" @@ -1349,7 +1487,7 @@ class ProxyBaseLLMRequestProcessing: "Ensure common_processing_pre_call_logic was called before using this parameter." ) else: - self.data, logging_obj = await self.common_processing_pre_call_logic( + self.data, logging_obj = await self._pre_call_with_fallbacks( request=request, general_settings=general_settings, proxy_logging_obj=proxy_logging_obj, @@ -1429,7 +1567,7 @@ class ProxyBaseLLMRequestProcessing: _exception_raised = False try: - hidden_params = getattr(response, "_hidden_params", {}) or {} + hidden_params = get_hidden_params_dict(response) model_id = self._get_model_id_from_response(hidden_params, self.data) cache_key, api_base, response_cost = ( @@ -1704,7 +1842,7 @@ class ProxyBaseLLMRequestProcessing: log_context=f"litellm_call_id={logging_obj.litellm_call_id}", ) - hidden_params = getattr(response, "_hidden_params", {}) or {} # get any updated response headers + hidden_params = get_hidden_params_dict(response) # get any updated response headers additional_headers = hidden_params.get("additional_headers", {}) or {} recover_response_cost = not response_cost and hidden_params.get("response_cost") is None @@ -1732,6 +1870,9 @@ class ProxyBaseLLMRequestProcessing: ) ) + if isinstance(response, dict): + response.pop("_hidden_params", None) + # Call response headers hook for non-streaming success callback_headers = await proxy_logging_obj.post_call_response_headers_hook( data=self.data, diff --git a/litellm/proxy/common_utils/cache_pydantic_utils.py b/litellm/proxy/common_utils/cache_pydantic_utils.py index 25d33a0aa52..f57f6a299ae 100644 --- a/litellm/proxy/common_utils/cache_pydantic_utils.py +++ b/litellm/proxy/common_utils/cache_pydantic_utils.py @@ -42,7 +42,7 @@ class CacheCodec: Encode a value for DualCache / Redis (``json.dumps``-safe). If ``model_type`` is set, the payload is validated with that model, then - ``model_dump(mode="json", exclude_none=True)`` — symmetric with ``deserialize``. + ``model_dump(mode="json")`` — symmetric with ``deserialize``. If the value is already an instance of ``model_type`` (or a subclass), ``model_validate`` is skipped to avoid an unnecessary Pydantic copy — the @@ -54,12 +54,12 @@ class CacheCodec: if model_type is not None: if isinstance(value, model_type): # Already the right type: dump directly, skip re-validation copy. - return value.model_dump(mode="json", exclude_none=True) + return value.model_dump(mode="json") if isinstance(value, (dict, BaseModel)): - return model_type.model_validate(value).model_dump(mode="json", exclude_none=True) + return model_type.model_validate(value).model_dump(mode="json") return value if isinstance(value, BaseModel): - return value.model_dump(mode="json", exclude_none=True) + return value.model_dump(mode="json") return value @staticmethod diff --git a/litellm/proxy/db/db_transaction_queue/redis_update_buffer.py b/litellm/proxy/db/db_transaction_queue/redis_update_buffer.py index 15d8c6c5d1e..c924448669d 100644 --- a/litellm/proxy/db/db_transaction_queue/redis_update_buffer.py +++ b/litellm/proxy/db/db_transaction_queue/redis_update_buffer.py @@ -525,10 +525,11 @@ class RedisUpdateBuffer: # Slots 1-5: daily spend categories daily_results: List[Optional[Dict[str, Any]]] = [] for slot in range(1, 6): - if raw_results[slot] is None: + slot_result = raw_results[slot] + if slot_result is None: daily_results.append(None) else: - list_of_daily = [json.loads(t) for t in raw_results[slot]] # type: ignore + list_of_daily = [json.loads(t) for t in slot_result] aggregated = DailySpendUpdateQueue.get_aggregated_daily_spend_update_transactions(list_of_daily) daily_results.append(aggregated) diff --git a/litellm/proxy/db/prisma_client.py b/litellm/proxy/db/prisma_client.py index 4042755f80d..fbccb8a726c 100644 --- a/litellm/proxy/db/prisma_client.py +++ b/litellm/proxy/db/prisma_client.py @@ -137,8 +137,17 @@ class PrismaWrapper: self.on_engine_replaced: Callable[[], None] | None = None def _get_engine_pid(self) -> int: - """Get the PID of the current Prisma engine subprocess, or 0 if unavailable.""" + """Get the PID of the current Prisma engine subprocess, or 0 if unavailable. + + Must never raise: it runs inside the reconnect path, where the client + may be in any broken state. Prisma's ``_engine`` is a property that + raises ``ClientNotConnectedError`` on a disconnected client; if that + escaped here, ``recreate_prisma_client`` would fail before it could + build a replacement client and the reconnect loop could never recover. + """ try: + if self._original_prisma.is_connected() is not True: + return 0 engine = self._original_prisma._engine process = getattr(engine, "process", None) if engine is not None else None if process is not None: diff --git a/litellm/proxy/guardrails/guardrail_hooks/crowdstrike_aidr/crowdstrike_aidr.py b/litellm/proxy/guardrails/guardrail_hooks/crowdstrike_aidr/crowdstrike_aidr.py index 84b0a3b8eba..eeb3623977e 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/crowdstrike_aidr/crowdstrike_aidr.py +++ b/litellm/proxy/guardrails/guardrail_hooks/crowdstrike_aidr/crowdstrike_aidr.py @@ -1,17 +1,29 @@ -from collections.abc import Mapping, Sequence import json import os -from typing import TYPE_CHECKING, Annotated, Literal, Optional, Type, Union, cast -from pydantic import BaseModel, ConfigDict, Field -from typing_extensions import Any, override +from collections.abc import Mapping, Sequence +from typing import ( + TYPE_CHECKING, + Annotated, + Literal, + NamedTuple, + Optional, + Union, + cast, +) from fastapi import HTTPException +from pydantic import BaseModel, ConfigDict, Field +from typing_extensions import Any, override from litellm._logging import verbose_proxy_logger from litellm.integrations.custom_guardrail import ( CustomGuardrail, log_guardrail_information, ) +from litellm.llms.base_llm.guardrail_translation.utils import ( + effective_skip_system_message_for_guardrail, + effective_skip_tool_message_for_guardrail, +) from litellm.llms.custom_httpx.http_handler import ( get_async_httpx_client, httpxSpecialProvider, @@ -19,7 +31,7 @@ from litellm.llms.custom_httpx.http_handler import ( from litellm.proxy.common_utils.callback_utils import ( add_guardrail_to_applied_guardrails_header, ) -from litellm.types.llms.openai import OpenAIChatCompletionToolParam +from litellm.types.llms.openai import AllMessageValues, OpenAIChatCompletionToolParam from litellm.types.utils import GenericGuardrailAPIInputs if TYPE_CHECKING: @@ -64,6 +76,37 @@ class _GuardInput(BaseModel): tools: Optional[Sequence[OpenAIChatCompletionToolParam]] = None +class _GuardChatCompletionsResult(BaseModel): + guard_output: Optional[_GuardInput] = None + """Updated structured prompt.""" + blocked: Optional[bool] = None + """Whether or not the prompt triggered a block detection.""" + transformed: Optional[bool] = None + """Whether or not the original input was transformed.""" + detectors: Optional[dict[str, Any]] = None + """Result of the policy analyzing and input prompt.""" + + +class _GuardChatCompletionsResponse(BaseModel): + result: Optional[_GuardChatCompletionsResult] = None + + +class _FilteredMessages(NamedTuple): + """Subset of a conversation selected for guardrail analysis.""" + + messages: list[AllMessageValues] + """Messages subset.""" + indices: tuple[int, ...] + """Positions of the subset's messages in the original list.""" + + +class _GuardInputWithIndices(NamedTuple): + guard_input: _GuardInput + """Guard API payload.""" + sent_indices: tuple[int, ...] + """Positions of the guard input's messages in the original list.""" + + def _normalize_content(raw: object) -> str | list[_ContentPart] | None: if raw is None: return None @@ -99,7 +142,16 @@ def _extract_text_from_content(content: object) -> str: return "" -def _merge_metadata_bags(request_data: Mapping[str, Any]) -> Optional[dict[str, Any]]: +def _extract_text_from_message(message: _Message) -> str: + content = message.content + if isinstance(content, str): + return content + if content is None: + return "" + return "\n".join(part.text for part in content if isinstance(part, _TextContentPart)) + + +def _merge_metadata_bags(request_data: Mapping[str, Any]) -> dict[str, Any] | None: merged: dict[str, Any] = {} present = False for bag in (request_data.get("metadata"), request_data.get("litellm_metadata")): @@ -109,6 +161,75 @@ def _merge_metadata_bags(request_data: Mapping[str, Any]) -> Optional[dict[str, return merged if present else None +def _messages_since_last_assistant( + messages: list[AllMessageValues], +) -> _FilteredMessages: + if not messages: + return _FilteredMessages([], ()) + + if messages[-1]["role"] == "assistant": + indices = tuple(i for i, m in enumerate(messages) if m["role"] == "system") + (len(messages) - 1,) + return _FilteredMessages([messages[i] for i in indices], indices) + + last_assistant_idx = -1 + for i in range(len(messages) - 1, -1, -1): + if messages[i]["role"] == "assistant": + last_assistant_idx = i + break + + system_indices = tuple(i for i in range(last_assistant_idx + 1) if messages[i]["role"] == "system") + tail_indices = tuple(range(last_assistant_idx + 1, len(messages))) + indices = system_indices + tail_indices + return _FilteredMessages([messages[i] for i in indices], indices) + + +def _merge_request_transforms( + guard_output: _GuardInput, + structured_messages: list[AllMessageValues] | None, + texts: list[str], + sent_indices: tuple[int, ...], +) -> list[str]: + returned_texts = [_extract_text_from_message(msg) for msg in guard_output.messages] + original_texts = ( + [_extract_text_from_content(m.get("content")) for m in structured_messages] if structured_messages else texts + ) + replacements = { + idx: returned_texts[pos] + for pos, idx in enumerate(sent_indices) + if pos < len(returned_texts) and idx < len(original_texts) + } + return [replacements.get(idx, original) for idx, original in enumerate(original_texts)] + + +def _apply_message_redaction(original: AllMessageValues, redacted: _Message) -> AllMessageValues: + content = original.get("content") + if isinstance(content, str): + return cast(AllMessageValues, {**original, "content": _extract_text_from_message(redacted)}) + if isinstance(content, list) and _extract_text_from_content(content): + redacted_content = redacted.content + new_content = ( + [part.model_dump() for part in redacted_content] if isinstance(redacted_content, list) else redacted_content + ) + return cast(AllMessageValues, {**original, "content": new_content}) + return original + + +def _redacted_messages( + processed_messages: list[AllMessageValues], + guard_output: _GuardInput, + sent_indices: tuple[int, ...], + full_messages: list[AllMessageValues], +) -> list[AllMessageValues] | None: + redactions = { + id(processed_messages[idx]): _apply_message_redaction(processed_messages[idx], guard_output.messages[pos]) + for pos, idx in enumerate(sent_indices) + if pos < len(guard_output.messages) and idx < len(processed_messages) + } + if not redactions.keys() <= {id(message) for message in full_messages}: + return None + return [redactions.get(id(message), message) for message in full_messages] + + class CrowdStrikeAIDRHandler(CustomGuardrail): """ CrowdStrike AIDR AI Guardrail handler to interact with the CrowdStrike AIDR @@ -118,17 +239,17 @@ class CrowdStrikeAIDRHandler(CustomGuardrail): def __init__( self, guardrail_name: str, - api_key: Optional[str] = None, - api_base: Optional[str] = None, + api_key: str | None = None, + api_base: str | None = None, **kwargs, - ): + ) -> None: """ Initializes the CrowdStrikeAIDRHandler. Args: guardrail_name (str): The name of the guardrail instance. - api_key (Optional[str]): The CrowdStrike AIDR API key. Reads from CS_AIDR_TOKEN env var if None. - api_base (Optional[str]): The CrowdStrike AIDR API base URL. Reads from CS_AIDR_BASE_URL env var if None. + api_key (str | None): The CrowdStrike AIDR API key. Reads from CS_AIDR_TOKEN env var if None. + api_base (str | None): The CrowdStrike AIDR API base URL. Reads from CS_AIDR_BASE_URL env var if None. **kwargs: Additional arguments passed to the CustomGuardrail base class. """ self.async_handler = get_async_httpx_client(llm_provider=httpxSpecialProvider.GuardrailCallback) @@ -151,7 +272,9 @@ class CrowdStrikeAIDRHandler(CustomGuardrail): f"Initialized CrowdStrike AIDR Guardrail: name={guardrail_name}, api_base={self.api_base}" ) - async def _call_crowdstrike_aidr_guard(self, payload: dict[str, Any], hook_name: str) -> dict[str, Any]: + async def _call_crowdstrike_aidr_guard( + self, payload: dict[str, Any], hook_name: str + ) -> _GuardChatCompletionsResult: """ Makes the API call to the CrowdStrike AIDR AI Guard endpoint. The function itself will raise an error if a response should be blocked, @@ -167,7 +290,7 @@ class CrowdStrikeAIDRHandler(CustomGuardrail): Exception: For other API call failures. Returns: - dict: The API response body + The parsed `result` body of the API response. """ endpoint = f"{self.api_base}/v1/guard_chat_completions" @@ -181,11 +304,12 @@ class CrowdStrikeAIDRHandler(CustomGuardrail): ) response = await self.async_handler.post(url=endpoint, json=payload, headers=headers) + assert response is not None response.raise_for_status() - result: dict[str, Any] = response.json() + result = _GuardChatCompletionsResponse.model_validate(response.json()).result or _GuardChatCompletionsResult() - if result.get("result", {}).get("blocked"): + if result.blocked: verbose_proxy_logger.warning( f"CrowdStrike AIDR Guardrail ({hook_name}): Request blocked. Response: {result}" ) @@ -197,25 +321,28 @@ class CrowdStrikeAIDRHandler(CustomGuardrail): }, ) verbose_proxy_logger.debug( - f"CrowdStrike AIDR Guardrail ({hook_name}): Request passed. Response: {result.get('result', {}).get('detectors')}" + f"CrowdStrike AIDR Guardrail ({hook_name}): Request passed. Response: {result.detectors}" ) return result - def _build_guard_input_for_request(self, inputs: GenericGuardrailAPIInputs) -> Optional[_GuardInput]: + def _build_guard_input_for_request(self, inputs: GenericGuardrailAPIInputs) -> _GuardInputWithIndices | None: guard_input = _GuardInput(messages=[], tools=[]) structured_messages = inputs.get("structured_messages") texts = inputs.get("texts", []) tools = inputs.get("tools") if structured_messages: - for message in structured_messages: + filtered = _messages_since_last_assistant(structured_messages) + for message in filtered.messages: content = _normalize_content(message.get("content")) if content is None or len(content) == 0: content = "" guard_input.messages.append(_Message(role=message["role"], content=content)) + indices = filtered.indices elif texts: guard_input.messages = [_Message(role="user", content=text) for text in texts] + indices = tuple(range(len(texts))) else: verbose_proxy_logger.warning("CrowdStrike AIDR Guardrail: No messages or texts provided for input request") return None @@ -223,37 +350,36 @@ class CrowdStrikeAIDRHandler(CustomGuardrail): if tools: guard_input.tools = tools - return guard_input + return _GuardInputWithIndices(guard_input, indices) - def _build_guard_input_for_response( - self, inputs: GenericGuardrailAPIInputs, request_data: Mapping[str, Any] - ) -> Optional[_GuardInput]: + def _build_guard_input_for_response(self, inputs: GenericGuardrailAPIInputs) -> _GuardInput: output_texts: list[str] = inputs.get("texts", []) - if len(output_texts) == 0: - verbose_proxy_logger.warning("CrowdStrike AIDR Guardrail: No text in output response.") - return None - - input_messages = request_data.get("messages", []) - return _GuardInput( - messages=[ - _Message(role=role, content=content) - for (role, content) in ( - (message["role"], _normalize_content(message.get("content"))) for message in input_messages - ) - if content is not None and len(content) > 0 - ] - + [_Message(role="assistant", content=text) for text in output_texts] + messages=[_Message(role="assistant", content=text) for text in output_texts], + tools=inputs.get("tools", []), ) - def _extract_transformed_texts( + def _extract_transformed_texts(self, guard_output: _GuardInput, num_assistant_messages: int) -> list[str]: + tail = guard_output.messages[-num_assistant_messages:] if num_assistant_messages > 0 else [] + return [_extract_text_from_message(msg) for msg in tail] + + def _writeback_messages( self, - guard_output: Mapping[str, Any], - num_assistant_messages: int, - ) -> list[str]: - transformed_messages = guard_output.get("messages", []) - tail = transformed_messages[-num_assistant_messages:] if num_assistant_messages > 0 else [] - return [(_extract_text_from_content(msg.get("content")) if isinstance(msg, dict) else "") for msg in tail] + structured_messages: list[AllMessageValues], + guard_output: _GuardInput, + sent_indices: tuple[int, ...], + request_data: dict, + ) -> list[AllMessageValues] | None: + if effective_skip_system_message_for_guardrail(self) or effective_skip_tool_message_for_guardrail(self): + request_messages = request_data.get("messages") + full_messages = ( + cast("list[AllMessageValues]", request_messages) + if isinstance(request_messages, list) + else structured_messages + ) + else: + full_messages = structured_messages + return _redacted_messages(structured_messages, guard_output, sent_indices, full_messages) @log_guardrail_information @override @@ -273,15 +399,18 @@ class CrowdStrikeAIDRHandler(CustomGuardrail): tool_calls = inputs.get("tool_calls") # Build guard_input based on input_type + sent_indices: tuple[int, ...] = () if input_type == "request": - guard_input = self._build_guard_input_for_request(inputs) - if guard_input is None: + request_result = self._build_guard_input_for_request(inputs) + if request_result is None: return inputs + guard_input = request_result.guard_input + sent_indices = request_result.sent_indices event_type = "input" hook_name = "apply_guardrail (request)" else: - guard_input = self._build_guard_input_for_response(inputs, request_data) - if guard_input is None: + guard_input = self._build_guard_input_for_response(inputs) + if len(guard_input.messages) == 0: return inputs event_type = "output" hook_name = "apply_guardrail (response)" @@ -307,29 +436,20 @@ class CrowdStrikeAIDRHandler(CustomGuardrail): extra_info["user_name"] = user_email ai_guard_payload["extra_info"] = extra_info - ai_guard_response = await self._call_crowdstrike_aidr_guard(ai_guard_payload, hook_name) + result = await self._call_crowdstrike_aidr_guard(ai_guard_payload, hook_name) if "body" in request_data or "messages" in request_data: add_guardrail_to_applied_guardrails_header(request_data=request_data, guardrail_name=self.guardrail_name) - result = ai_guard_response.get("result", {}) - if not result.get("transformed"): + if not result.transformed or result.guard_output is None: return inputs - guard_output = result.get("guard_output", {}) + guard_output = result.guard_output if input_type == "request": - # For requests, all messages were in the guard_input. Extract texts - # for every message in guard_output. - all_messages = guard_output.get("messages", []) - transformed_texts = [ - _extract_text_from_content(msg.get("content") if isinstance(msg, dict) else "") for msg in all_messages - ] + transformed_texts = _merge_request_transforms(guard_output, structured_messages, texts, sent_indices) else: - # For responses, guard_input contained history + assistant messages - # appended at the end. Extract only the assistant tail. - num_assistant = len(texts) - transformed_texts = self._extract_transformed_texts(guard_output, num_assistant) + transformed_texts = self._extract_transformed_texts(guard_output, len(texts)) result_inputs: GenericGuardrailAPIInputs = {"texts": transformed_texts} if tools: @@ -337,13 +457,18 @@ class CrowdStrikeAIDRHandler(CustomGuardrail): if tool_calls: result_inputs["tool_calls"] = tool_calls if structured_messages: - result_inputs["structured_messages"] = structured_messages + rebuilt = ( + self._writeback_messages(structured_messages, guard_output, sent_indices, request_data) + if input_type == "request" + else None + ) + result_inputs["structured_messages"] = rebuilt if rebuilt is not None else structured_messages return result_inputs @override @staticmethod - def get_config_model() -> Optional[Type["GuardrailConfigModel"]]: + def get_config_model() -> type["GuardrailConfigModel"] | None: from litellm.types.proxy.guardrails.guardrail_hooks.crowdstrike_aidr import ( CrowdStrikeAIDRGuardrailConfigModel, ) diff --git a/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py b/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py index 37863e0e356..84d03fe7144 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py +++ b/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py @@ -282,7 +282,7 @@ class HeadroomGuardrail(CustomGuardrail): self, messages: list[dict[str, object]], model: str | None, - ) -> tuple[list[dict[str, object]], bool]: + ) -> tuple[list[dict[str, object]], bool, dict[str, object]]: payload: dict[str, object] = {"messages": messages} if model: payload["model"] = model @@ -294,62 +294,94 @@ class HeadroomGuardrail(CustomGuardrail): headers=self._request_headers(), ) except httpx.HTTPStatusError as e: - return self._handle_compress_failure( - messages, - "Headroom compression service returned an error", - {"status_code": e.response.status_code, "body": e.response.text}, - ), False - except (httpx.ConnectError, httpx.TimeoutException, httpx.TransportError, litellm.Timeout) as e: - return self._handle_compress_failure( - messages, - "Headroom compression service unreachable", - {"detail": str(e)}, - ), False - if raw_response is None: - return self._handle_compress_failure( - messages, - "Headroom compression service returned no response", + return ( + self._handle_compress_failure( + messages, + "Headroom compression service returned an error", + {"status_code": e.response.status_code, "body": e.response.text}, + ), + False, {}, - ), False + ) + except (httpx.ConnectError, httpx.TimeoutException, httpx.TransportError, litellm.Timeout) as e: + return ( + self._handle_compress_failure( + messages, + "Headroom compression service unreachable", + {"detail": str(e)}, + ), + False, + {}, + ) + if raw_response is None: + return ( + self._handle_compress_failure( + messages, + "Headroom compression service returned no response", + {}, + ), + False, + {}, + ) response: HttpxResponse = raw_response if response.status_code != 200: - return self._handle_compress_failure( - messages, - "Headroom compression service returned an error", - {"status_code": response.status_code, "body": response.text}, - ), False + return ( + self._handle_compress_failure( + messages, + "Headroom compression service returned an error", + {"status_code": response.status_code, "body": response.text}, + ), + False, + {}, + ) try: body: object = response.json() except ValueError: - return self._handle_compress_failure( - messages, - "Headroom compression service returned non-JSON response", - {"body": response.text[:500]}, - ), False + return ( + self._handle_compress_failure( + messages, + "Headroom compression service returned non-JSON response", + {"body": response.text[:500]}, + ), + False, + {}, + ) if not _is_str_object_dict(body): - return self._handle_compress_failure( - messages, - "Headroom compression service returned unexpected response shape", - {"body": response.text[:500]}, - ), False + return ( + self._handle_compress_failure( + messages, + "Headroom compression service returned unexpected response shape", + {"body": response.text[:500]}, + ), + False, + {}, + ) compressed_messages = body.get("messages") if not _is_object_list(compressed_messages): - return self._handle_compress_failure( - messages, - "Headroom compression service response missing 'messages'", - {"body": response.text}, - ), False + return ( + self._handle_compress_failure( + messages, + "Headroom compression service response missing 'messages'", + {"body": response.text}, + ), + False, + {}, + ) filtered = [item for item in compressed_messages if _is_str_object_dict(item)] if not filtered: - return self._handle_compress_failure( - messages, - "Headroom compression service returned empty message list", - {"body": response.text}, - ), False + return ( + self._handle_compress_failure( + messages, + "Headroom compression service returned empty message list", + {"body": response.text}, + ), + False, + {}, + ) verbose_proxy_logger.debug( "Headroom: compressed %s tokens -> %s tokens (ratio %.2f)", @@ -357,7 +389,19 @@ class HeadroomGuardrail(CustomGuardrail): body.get("tokens_after", "?"), body.get("compression_ratio", 0), ) - return filtered, True + + stats = { + key: body[key] + for key in ( + "tokens_before", + "tokens_after", + "tokens_saved", + "compression_ratio", + "transforms_applied", + ) + if key in body + } + return filtered, True, stats async def _call_retrieve(self, hash_value: str, query: str | None = None) -> str: params: dict[str, str] = {} @@ -421,14 +465,26 @@ class HeadroomGuardrail(CustomGuardrail): return inputs model = self.headroom_model or request_data.get("model") - compressed, compression_succeeded = await self._call_compress( + start_time = time.time() + compressed, compression_succeeded, stats = await self._call_compress( messages=messages, model=model if isinstance(model, str) else None, ) + end_time = time.time() if not compression_succeeded: return {**inputs, "structured_messages": compressed} # pyright: ignore[reportReturnType] + self.add_standard_logging_guardrail_information_to_request_data( + guardrail_json_response=stats, + request_data=request_data, + guardrail_status="success", + guardrail_provider="headroom", + start_time=start_time, + end_time=end_time, + duration=end_time - start_time, + ) + hashes = extract_hashes_from_messages(compressed) if not hashes: return {**inputs, "structured_messages": compressed} # pyright: ignore[reportReturnType] diff --git a/litellm/proxy/guardrails/guardrail_hooks/model_armor/model_armor.py b/litellm/proxy/guardrails/guardrail_hooks/model_armor/model_armor.py index bebd9b28745..8a4e79b31ab 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/model_armor/model_armor.py +++ b/litellm/proxy/guardrails/guardrail_hooks/model_armor/model_armor.py @@ -39,6 +39,7 @@ from litellm.proxy.guardrails.guardrail_hooks.model_armor.file_scanning import ( from litellm.types.guardrails import GuardrailEventHooks from litellm.types.llms.openai import AllMessageValues from litellm.types.utils import ( + CallTypes, CallTypesLiteral, Choices, GuardrailStatus, @@ -477,6 +478,8 @@ class ModelArmorGuardrail(CustomGuardrail, VertexBase): ) event_type = GuardrailEventHooks.pre_call + if call_type == CallTypes.call_mcp_tool.value: + event_type = GuardrailEventHooks.pre_mcp_call if self.should_run_guardrail(data=data, event_type=event_type) is not True: return data @@ -574,6 +577,8 @@ class ModelArmorGuardrail(CustomGuardrail, VertexBase): ) event_type = GuardrailEventHooks.during_call + if call_type == CallTypes.call_mcp_tool.value: + event_type = GuardrailEventHooks.during_mcp_call if self.should_run_guardrail(data=data, event_type=event_type) is not True: return data diff --git a/litellm/proxy/guardrails/guardrail_hooks/semantic_guard/semantic_guard.py b/litellm/proxy/guardrails/guardrail_hooks/semantic_guard/semantic_guard.py index 5840480f0da..3f802485a8e 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/semantic_guard/semantic_guard.py +++ b/litellm/proxy/guardrails/guardrail_hooks/semantic_guard/semantic_guard.py @@ -225,7 +225,7 @@ def _handle_match( detection_info=detection_info, ) else: - raise HTTPException( # type: ignore[reportOptionalCall] + raise HTTPException( # pyright: ignore[reportOptionalCall] # fastapi is installed wherever this proxy hook runs status_code=400, detail={ "error": violation_msg, diff --git a/litellm/proxy/health_endpoints/_health_endpoints.py b/litellm/proxy/health_endpoints/_health_endpoints.py index 2b3ec231ac8..4250b9668ff 100644 --- a/litellm/proxy/health_endpoints/_health_endpoints.py +++ b/litellm/proxy/health_endpoints/_health_endpoints.py @@ -6,7 +6,7 @@ import secrets import time import traceback from datetime import datetime, timedelta -from typing import Any, Dict, Iterable, Literal, Optional, Union, cast +from typing import Any, Dict, Iterable, Literal, Optional, TypedDict, Union, cast import fastapi from fastapi import APIRouter, Depends, HTTPException, Request, Response, status @@ -1271,7 +1271,12 @@ async def health_license_endpoint( } -db_health_cache = {"status": "unknown", "last_updated": datetime.now()} +class DBHealthCache(TypedDict): + status: str + last_updated: datetime + + +db_health_cache: DBHealthCache = {"status": "unknown", "last_updated": datetime.now()} async def _db_health_readiness_check(): diff --git a/litellm/proxy/hooks/dynamic_rate_limiter.py b/litellm/proxy/hooks/dynamic_rate_limiter.py index f2e31b77761..83161fca5bb 100644 --- a/litellm/proxy/hooks/dynamic_rate_limiter.py +++ b/litellm/proxy/hooks/dynamic_rate_limiter.py @@ -4,7 +4,8 @@ import asyncio import os -from typing import List, Optional, Tuple, Union +from datetime import datetime +from typing import Callable, List, Optional, Tuple, Union import litellm from litellm import ModelResponse, Router @@ -30,12 +31,13 @@ class DynamicRateLimiterCache: Track number of active projects calling a model. """ - def __init__(self, cache: DualCache) -> None: + def __init__(self, cache: DualCache, time_fn: Callable[[], datetime] = get_utc_datetime) -> None: self.cache = cache self.ttl = 60 # 1 min ttl + self.time_fn = time_fn async def async_get_cache(self, model: str) -> Optional[int]: - dt = get_utc_datetime() + dt = self.time_fn() current_minute = dt.strftime("%H-%M") key_name = "{}:{}".format(current_minute, model) _response = await self.cache.async_get_cache(key=key_name) @@ -59,7 +61,7 @@ class DynamicRateLimiterCache: - Exception, if unable to connect to cache client (if redis caching enabled) """ try: - dt = get_utc_datetime() + dt = self.time_fn() current_minute = dt.strftime("%H-%M") key_name = "{}:{}".format(current_minute, model) @@ -75,8 +77,8 @@ class DynamicRateLimiterCache: class _PROXY_DynamicRateLimitHandler(CustomLogger): # Class variables or attributes - def __init__(self, internal_usage_cache: DualCache): - self.internal_usage_cache = DynamicRateLimiterCache(cache=internal_usage_cache) + def __init__(self, internal_usage_cache: DualCache, time_fn: Callable[[], datetime] = get_utc_datetime): + self.internal_usage_cache = DynamicRateLimiterCache(cache=internal_usage_cache, time_fn=time_fn) def update_variables(self, llm_router: Router): self.llm_router = llm_router diff --git a/litellm/proxy/hooks/mcp_semantic_filter/hook.py b/litellm/proxy/hooks/mcp_semantic_filter/hook.py index 92b74848188..7379096bf9b 100644 --- a/litellm/proxy/hooks/mcp_semantic_filter/hook.py +++ b/litellm/proxy/hooks/mcp_semantic_filter/hook.py @@ -24,6 +24,18 @@ if TYPE_CHECKING: from litellm.router import Router +def _truncate_csv_at_tool_name_boundary(tool_names_csv: str, max_length: int) -> str: + """Cap a CSV of tool names to max_length, dropping any name that does not fit whole.""" + if len(tool_names_csv) <= max_length: + return tool_names_csv + + head = tool_names_csv[: max_length + 1] + if "," not in head: + return "" + + return head.rsplit(",", 1)[0] + + class SemanticToolFilterHook(CustomLogger): """ Pre-call hook that filters MCP tools semantically. @@ -117,6 +129,27 @@ class SemanticToolFilterHook(CustomLogger): return openai_tools_as_dicts + async def _filter_expanded_tools( + self, + data: dict, + expanded_tools: list[dict[str, Any]], + ) -> list[dict[str, Any]]: + """ + Apply the semantic filter to expanded MCP tool definitions. + + Expanded tools are flat OpenAI function dicts with a top-level + "name" (see transform_mcp_tool_to_openai_responses_api_tool), so + filter_tools can name-match them against the semantic router. + """ + raw_messages = data.get("messages") or data.get("input") or [] + messages = [{"role": "user", "content": raw_messages}] if isinstance(raw_messages, str) else raw_messages + user_query = self.filter.extract_user_query(messages) + if not user_query: + verbose_proxy_logger.debug("No user query found, skipping semantic filter on expanded MCP tools") + return expanded_tools + + return await self.filter.filter_tools(query=user_query, available_tools=expanded_tools) + def _is_mcp_tool(self, tool: object) -> bool: """ Check whether *tool* is registered in the MCP semantic router. @@ -172,6 +205,32 @@ class SemanticToolFilterHook(CustomLogger): f"Semantic tool filter: all {len(native_tools)} tools are native, no MCP filtering applied" ) + def _emit_filter_metadata_safe( + self, + data: dict, + mcp_tools: list[object], + filtered_mcp_tools: list[object], + native_tools: list[object], + filtered_tools: list[object], + ) -> None: + """ + Emit filter metadata without letting an emission failure abort the + already-filtered request. + """ + try: + self._emit_filter_metadata( + data=data, + mcp_tools=mcp_tools, + filtered_mcp_tools=filtered_mcp_tools, + native_tools=native_tools, + filtered_tools=filtered_tools, + ) + except Exception as e: + verbose_proxy_logger.warning( + f"Failed to emit semantic filter metadata: {e}", + exc_info=True, + ) + async def async_pre_call_hook( self, user_api_key_dict: "UserAPIKeyAuth", @@ -194,9 +253,6 @@ class SemanticToolFilterHook(CustomLogger): verbose_proxy_logger.debug("No tools in request, skipping semantic filter") return None - # Expanded MCP tools are in OpenAI nested format which - # filter_tools/_extract_tool_info cannot name-match, so we skip - # semantic filtering and return early. if self._should_expand_mcp_tools(tools): verbose_proxy_logger.debug("Detected litellm_proxy MCP references, expanding before semantic filtering") @@ -215,11 +271,26 @@ class SemanticToolFilterHook(CustomLogger): verbose_proxy_logger.warning("No tools expanded from MCP references") return None - data["tools"] = native_tools_before_expand + expanded_tools + if not self.filter.enabled: + data["tools"] = native_tools_before_expand + expanded_tools + verbose_proxy_logger.debug("Semantic filter disabled, forwarding expanded MCP tools unfiltered") + return data + + filtered_expanded_tools = await self._filter_expanded_tools(data=data, expanded_tools=expanded_tools) + + combined_tools = native_tools_before_expand + filtered_expanded_tools + data["tools"] = combined_tools + self._emit_filter_metadata_safe( + data=data, + mcp_tools=expanded_tools, + filtered_mcp_tools=filtered_expanded_tools, + native_tools=native_tools_before_expand, + filtered_tools=combined_tools, + ) verbose_proxy_logger.info( f"Expanded MCP references to {len(expanded_tools)} tools " f"({len(native_tools_before_expand)} native preserved), " - f"skipping semantic filter (OpenAI nested format)" + f"semantic filter selected {len(filtered_expanded_tools)}" ) return data @@ -285,19 +356,13 @@ class SemanticToolFilterHook(CustomLogger): data["tools"] = filtered_tools - try: - self._emit_filter_metadata( - data=data, - mcp_tools=mcp_tools, - filtered_mcp_tools=filtered_mcp_tools, - native_tools=native_tools, - filtered_tools=filtered_tools, - ) - except Exception as e: - verbose_proxy_logger.warning( - f"Failed to emit semantic filter metadata: {e}", - exc_info=True, - ) + self._emit_filter_metadata_safe( + data=data, + mcp_tools=mcp_tools, + filtered_mcp_tools=filtered_mcp_tools, + native_tools=native_tools, + filtered_tools=filtered_tools, + ) return data @@ -327,11 +392,12 @@ class SemanticToolFilterHook(CustomLogger): # Add CSV of filtered tool names (nginx-safe length) tool_names_csv = metadata.get("litellm_semantic_filter_tools", "") - if tool_names_csv: - if len(tool_names_csv) > MAX_MCP_SEMANTIC_FILTER_TOOLS_HEADER_LENGTH: - tool_names_csv = tool_names_csv[: MAX_MCP_SEMANTIC_FILTER_TOOLS_HEADER_LENGTH - 3] + "..." - - headers["x-litellm-semantic-filter-tools"] = tool_names_csv + header_safe_csv = _truncate_csv_at_tool_name_boundary( + tool_names_csv=tool_names_csv, + max_length=MAX_MCP_SEMANTIC_FILTER_TOOLS_HEADER_LENGTH, + ) + if header_safe_csv: + headers["x-litellm-semantic-filter-tools"] = header_safe_csv return headers diff --git a/litellm/proxy/hooks/parallel_request_limiter.py b/litellm/proxy/hooks/parallel_request_limiter.py index 1ed76d5b1e3..ee6abb13d6b 100644 --- a/litellm/proxy/hooks/parallel_request_limiter.py +++ b/litellm/proxy/hooks/parallel_request_limiter.py @@ -17,6 +17,7 @@ from litellm.proxy.auth.auth_utils import ( get_key_model_rpm_limit, get_key_model_tpm_limit, ) +from litellm.proxy.auth.budget_throttle import throttled_limit from litellm.proxy.common_utils.proxy_rate_limit_error import ProxyRateLimitError from litellm.proxy.hooks.rate_limiter_utils import resolve_llm_provider_for_rate_limit @@ -248,10 +249,11 @@ class _PROXY_MaxParallelRequestsHandler(CustomLogger): if data is None: data = {} global_max_parallel_requests = data.get("metadata", {}).get("global_max_parallel_requests", None) - tpm_limit = getattr(user_api_key_dict, "tpm_limit", sys.maxsize) + throttle_pct = getattr(user_api_key_dict, "budget_throttle_pct", None) + tpm_limit = throttled_limit(getattr(user_api_key_dict, "tpm_limit", sys.maxsize), throttle_pct) if tpm_limit is None: tpm_limit = sys.maxsize - rpm_limit = getattr(user_api_key_dict, "rpm_limit", sys.maxsize) + rpm_limit = throttled_limit(getattr(user_api_key_dict, "rpm_limit", sys.maxsize), throttle_pct) if rpm_limit is None: rpm_limit = sys.maxsize diff --git a/litellm/proxy/hooks/parallel_request_limiter_v3.py b/litellm/proxy/hooks/parallel_request_limiter_v3.py index 8522294d120..ee0a0e1789d 100644 --- a/litellm/proxy/hooks/parallel_request_limiter_v3.py +++ b/litellm/proxy/hooks/parallel_request_limiter_v3.py @@ -32,6 +32,7 @@ from litellm.litellm_core_utils.prompt_templates.common_utils import ( ) from litellm.proxy._types import UserAPIKeyAuth from litellm.proxy.auth.auth_utils import get_model_rate_limit_from_metadata +from litellm.proxy.auth.budget_throttle import throttled_limit from litellm.proxy.common_utils.proxy_rate_limit_error import ( ProxyRateLimitError, map_v3_rate_limit_type, @@ -956,7 +957,7 @@ class _PROXY_MaxParallelRequestsHandler_v3(CustomLogger): for _idx, (keys, args, meta) in enumerate(descriptor_groups): try: - raw = await self.check_and_increment_by_n_script( + raw = await self.check_and_increment_by_n_script( # pyright: ignore[reportOptionalCall] # sole caller guards it is not None keys=keys, args=args, ) @@ -1549,18 +1550,19 @@ class _PROXY_MaxParallelRequestsHandler_v3(CustomLogger): or user_api_key_dict.tpm_limit is not None or user_api_key_dict.max_parallel_requests is not None ): + throttle_pct = user_api_key_dict.budget_throttle_pct descriptors.append( RateLimitDescriptor( key="api_key", value=user_api_key_dict.api_key, rate_limit={ "requests_per_unit": self._get_enforced_limit( - limit_value=user_api_key_dict.rpm_limit, + limit_value=throttled_limit(user_api_key_dict.rpm_limit, throttle_pct), limit_type=rpm_limit_type, model_has_failures=model_has_failures, ), "tokens_per_unit": self._get_enforced_limit( - limit_value=user_api_key_dict.tpm_limit, + limit_value=throttled_limit(user_api_key_dict.tpm_limit, throttle_pct), limit_type=tpm_limit_type, model_has_failures=model_has_failures, ), diff --git a/litellm/proxy/hooks/prompt_injection_detection.py b/litellm/proxy/hooks/prompt_injection_detection.py index 390074bf005..2d55a644bb2 100644 --- a/litellm/proxy/hooks/prompt_injection_detection.py +++ b/litellm/proxy/hooks/prompt_injection_detection.py @@ -250,7 +250,9 @@ class _OPTIONAL_PromptInjectionDetection(CustomLogger): self.print_verbose(f"Received LLM Moderation response: {response}") self.print_verbose(f"llm_api_fail_call_string: {self.prompt_injection_params.llm_api_fail_call_string}") if isinstance(response, litellm.ModelResponse) and isinstance(response.choices[0], litellm.Choices): - if self.prompt_injection_params.llm_api_fail_call_string in response.choices[0].message.content: # type: ignore + fail_call_string = self.prompt_injection_params.llm_api_fail_call_string + content = response.choices[0].message.content + if fail_call_string is not None and content is not None and fail_call_string in content: is_prompt_attack = True if is_prompt_attack is True: diff --git a/litellm/proxy/hooks/responses_id_security.py b/litellm/proxy/hooks/responses_id_security.py index a7b05c57dac..cba58506a00 100644 --- a/litellm/proxy/hooks/responses_id_security.py +++ b/litellm/proxy/hooks/responses_id_security.py @@ -44,6 +44,7 @@ class ResponsesIDSecurity(CustomLogger): "aget_responses", "adelete_responses", "acancel_responses", + "alist_input_items", } if call_type not in responses_api_call_types: return None @@ -54,7 +55,7 @@ class ResponsesIDSecurity(CustomLogger): original_response_id, user_id, team_id = self._decrypt_response_id(previous_response_id) self.check_user_access_to_response_id(user_id, team_id, user_api_key_dict) data["previous_response_id"] = original_response_id - elif call_type in {"aget_responses", "adelete_responses", "acancel_responses"}: + elif call_type in {"aget_responses", "adelete_responses", "acancel_responses", "alist_input_items"}: response_id = data.get("response_id") if response_id and self._is_encrypted_response_id(response_id): diff --git a/litellm/proxy/management_endpoints/cache_settings_endpoints.py b/litellm/proxy/management_endpoints/cache_settings_endpoints.py index 6aa3dbbf902..9f45cb619aa 100644 --- a/litellm/proxy/management_endpoints/cache_settings_endpoints.py +++ b/litellm/proxy/management_endpoints/cache_settings_endpoints.py @@ -38,13 +38,35 @@ from litellm.types.management_endpoints import ( router = APIRouter() # Cache fields holding credentials. Masked on read so plaintext Redis / -# Sentinel passwords never leave the server in a GET response. -_CACHE_SENSITIVE_FIELDS: set = {"password", "sentinel_password"} +# Sentinel passwords never leave the server in a GET response. `url` is here +# because a Redis/Valkey URL can embed a password inline +# (e.g. redis://:secret@host:6379/1). +_CACHE_SENSITIVE_FIELDS: set = {"password", "sentinel_password", "url"} _REDACTED_VALUE = "***REDACTED***" +_URL_OVERRIDDEN_CONNECTION_FIELDS: frozenset = frozenset({"host", "port", "db", "password", "username"}) + + +def _resolve_cache_url_precedence(settings: Mapping[str, Any]) -> dict[str, Any]: + """Return cache settings with the url-vs-discrete-fields ambiguity resolved. + + When a full ``url`` is supplied it wins: the discrete + host/port/db/password/username fields are dropped so the persisted config + is unambiguous and matches runtime resolution in ``litellm._redis`` + (``redis.Redis.from_url`` ignores them). Cluster mode + (``redis_startup_nodes``) is exempt because it authenticates via the + discrete fields rather than a url. + """ + url = settings.get("url") + has_url = isinstance(url, str) and url.strip() != "" + if not has_url or settings.get("redis_startup_nodes"): + return dict(settings) + return {k: v for k, v in settings.items() if k not in _URL_OVERRIDDEN_CONNECTION_FIELDS} + + def _redact_settings(settings: Optional[Mapping[str, Any]]) -> Dict[str, Any]: """Replace every value in a settings map with a fixed marker. @@ -311,7 +333,7 @@ async def test_cache_connection( from litellm import Cache try: - cache_settings = request.cache_settings.copy() + cache_settings = _resolve_cache_url_precedence(request.cache_settings) verbose_proxy_logger.debug("Testing cache connection with settings: %s", cache_settings) # Only support Redis for now @@ -378,7 +400,7 @@ async def update_cache_settings( ) try: - cache_settings = request.cache_settings.copy() + cache_settings = _resolve_cache_url_precedence(request.cache_settings) # Snapshot the prior settings (key set only — values get redacted in # the audit row) so the audit-log entry shows which fields changed. diff --git a/litellm/proxy/management_endpoints/key_management_endpoints.py b/litellm/proxy/management_endpoints/key_management_endpoints.py index ff849240292..71cf2db3dfb 100644 --- a/litellm/proxy/management_endpoints/key_management_endpoints.py +++ b/litellm/proxy/management_endpoints/key_management_endpoints.py @@ -343,7 +343,7 @@ def _personal_key_membership_check( if user_api_key_dict.user_role not in personal_key_generation["allowed_user_roles"]: raise HTTPException( status_code=400, - detail=f"Personal key creation has been restricted by admin. Allowed roles={litellm.key_generation_settings['personal_key_generation']['allowed_user_roles']}. Your role={user_api_key_dict.user_role}", # type: ignore + detail=f"Personal key creation has been restricted by admin. Allowed roles={personal_key_generation['allowed_user_roles']}. Your role={user_api_key_dict.user_role}", ) return True @@ -758,6 +758,12 @@ async def _common_key_generation_helper( premium_user=premium_user, ) + if data.throttle_on_budget_exceeded is True and user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN.value: + raise HTTPException( + status_code=403, + detail={"error": "Only proxy admins can enable throttle_on_budget_exceeded on a key."}, + ) + if data.metadata is not None and data.metadata.get("service_account_id") is not None and data.team_id is None: await validate_team_id_used_in_service_account_request( team_id=data.team_id, @@ -1483,6 +1489,7 @@ async def generate_key_fn( - guardrails: Optional[List[str]] - List of active guardrails for the key - policies: Optional[List[str]] - List of policy names to apply to the key. Policies define guardrails, conditions, and inheritance rules. - disable_global_guardrails: Optional[bool] - Whether to disable global guardrails for the key. + - throttle_on_budget_exceeded: Optional[bool] - When the key exceeds its max_budget, throttle its tpm/rpm to the global budget_exceeded_throttle_percentage instead of blocking the key entirely. - permissions: Optional[dict] - key-specific permissions. Currently just used for turning off pii masking (if connected). Example - {"pii": false} - model_max_budget: Optional[Dict[str, BudgetConfig]] - Model-specific budgets {"gpt-4": {"budget_limit": 0.0005, "time_period": "30d"}}}. IF null or {} then no model specific budget. - budget_fallbacks: Optional[Dict[str, List[str]]] - Per-model fallback chain tried in order when that model's own `model_max_budget` is exceeded, e.g. {"gpt-4o": ["gpt-4o-mini"]}. @@ -2317,6 +2324,16 @@ async def _validate_update_key_data( or "budget_limits" in data.model_fields_set ) + _existing_metadata = getattr(existing_key_row, "metadata", None) + _existing_throttle = ( + _existing_metadata.get("throttle_on_budget_exceeded") if isinstance(_existing_metadata, dict) else None + ) + if data.throttle_on_budget_exceeded is True and _existing_throttle is not True and not _is_proxy_admin: + raise HTTPException( + status_code=403, + detail={"error": "Only proxy admins can enable throttle_on_budget_exceeded on a key."}, + ) + # Personal-key bypass: the caller both created the key AND still owns it # (user_id == caller). Checking only created_by would let a demoted admin # who originally created a key for another user continue editing it without @@ -2507,6 +2524,7 @@ async def update_key_fn( - guardrails: Optional[List[str]] - List of active guardrails for the key - policies: Optional[List[str]] - List of policy names to apply to the key. Policies define guardrails, conditions, and inheritance rules. - disable_global_guardrails: Optional[bool] - Whether to disable global guardrails for the key. + - throttle_on_budget_exceeded: Optional[bool] - When the key exceeds its max_budget, throttle its tpm/rpm to the global budget_exceeded_throttle_percentage instead of blocking the key entirely. - prompts: Optional[List[str]] - List of prompts that the key is allowed to use. - blocked: Optional[bool] - Whether the key is blocked - aliases: Optional[dict] - Model aliases for the key - [Docs](https://litellm.vercel.app/docs/proxy/virtual_keys#model-aliases) diff --git a/litellm/proxy/management_endpoints/mcp_management_endpoints.py b/litellm/proxy/management_endpoints/mcp_management_endpoints.py index 3597e75404a..a66e2fcf618 100644 --- a/litellm/proxy/management_endpoints/mcp_management_endpoints.py +++ b/litellm/proxy/management_endpoints/mcp_management_endpoints.py @@ -215,6 +215,34 @@ if MCP_AVAILABLE: _base_validate_and_normalize_mcp_server_payload(payload) _validate_mcp_server_name_fields(payload) + def stamp_omitted_oauth2_flow(payload: NewMCPServerRequest) -> None: + """Fallback only: fill in oauth2_flow when an oauth2 create omits it. + + An explicit oauth2_flow from the caller (the dashboard's flow selector, a REST + body, config.yaml) always wins and is never touched. The shape check below runs + solely for oauth2 creates that leave the field unset, so those rows still + persist a flow instead of relying on read-time inference. + + The create payload carries the plaintext credentials, so the M2M-vs-interactive + decision is reliable here in a way it is not at read time (credentials are + encrypted at rest and redacted in responses). The client_credentials shape + mirrors the legacy inference in MCPServerManager._resolve_oauth2_flow; every + other oauth2 configuration is the authorization_code grant, including + delegate_auth_to_upstream, where the client runs that grant upstream. + """ + if payload.auth_type != MCPAuth.oauth2: + return + if payload.oauth2_flow: + return + credentials = payload.credentials or {} + has_m2m_shape = bool( + payload.token_url + and credentials.get("client_id") + and credentials.get("client_secret") + and not payload.authorization_url + ) + payload.oauth2_flow = "client_credentials" if has_m2m_shape else "authorization_code" + _VALID_MCP_REQUIRED_FIELDS: frozenset = frozenset(NewMCPServerRequest.model_fields) def _validate_mcp_required_fields(payload: Any) -> None: @@ -509,6 +537,10 @@ if MCP_AVAILABLE: sanitized.authorization_url = None sanitized.token_url = None sanitized.registration_url = None + sanitized.token_exchange_endpoint = None + sanitized.audience = None + sanitized.subject_token_type = None + sanitized.token_exchange_profile = None # Drop env vars entirely rather than only blanking global values: the # names alone (DB_PASSWORD, GITHUB_API_KEY, ...) leak what secrets the # admin configured. Non-admins get the per-user vars they must fill in @@ -550,6 +582,10 @@ if MCP_AVAILABLE: sanitized.authorization_url = None sanitized.token_url = None sanitized.registration_url = None + sanitized.token_exchange_endpoint = None + sanitized.audience = None + sanitized.subject_token_type = None + sanitized.token_exchange_profile = None sanitized.health_check_error = None sanitized.last_health_check = None @@ -1057,6 +1093,7 @@ if MCP_AVAILABLE: prisma_client = get_prisma_client_or_throw("Database not connected. Connect a database to your proxy") validate_and_normalize_mcp_server_payload(payload) + stamp_omitted_oauth2_flow(payload) _validate_mcp_required_fields(payload) payload.approval_status = MCPApprovalStatus.pending_review @@ -1322,6 +1359,7 @@ if MCP_AVAILABLE: # Validate and normalize payload fields validate_and_normalize_mcp_server_payload(payload) + stamp_omitted_oauth2_flow(payload) # AuthZ - restrict only proxy admins to create mcp servers if LitellmUserRoles.PROXY_ADMIN != user_api_key_dict.user_role: @@ -1413,6 +1451,7 @@ if MCP_AVAILABLE: # Validate and normalize payload fields (alias/server name rules) validate_and_normalize_mcp_server_payload(payload) + stamp_omitted_oauth2_flow(payload) # Restrict to proxy admins similar to the persistent create endpoint if LitellmUserRoles.PROXY_ADMIN != user_api_key_dict.user_role: diff --git a/litellm/proxy/management_endpoints/team_endpoints.py b/litellm/proxy/management_endpoints/team_endpoints.py index 5c41c60fcb1..8ec7ec707a2 100644 --- a/litellm/proxy/management_endpoints/team_endpoints.py +++ b/litellm/proxy/management_endpoints/team_endpoints.py @@ -26,6 +26,7 @@ from litellm._uuid import uuid from litellm.integrations.prometheus import PrometheusLogger from litellm.litellm_core_utils.safe_json_dumps import safe_dumps from litellm.proxy._types import ( + UI_TEAM_ID, BlockTeamRequest, CommonProxyErrors, DeleteTeamRequest, @@ -1046,6 +1047,13 @@ async def new_team( if data.team_id is None: data.team_id = str(uuid.uuid4()) else: + if data.team_id == UI_TEAM_ID: + raise HTTPException( + status_code=400, + detail={ + "error": f"team_id '{UI_TEAM_ID}' is reserved for LiteLLM UI dashboard sessions and cannot be used for a real team. Please use a different team id." + }, + ) # Check if team_id exists already _existing_team_id = await prisma_client.get_data( team_id=data.team_id, table_name="team", query_type="find_unique" diff --git a/litellm/proxy/openai_files_endpoints/files_endpoints.py b/litellm/proxy/openai_files_endpoints/files_endpoints.py index 34ca43c603f..0c9aa667751 100644 --- a/litellm/proxy/openai_files_endpoints/files_endpoints.py +++ b/litellm/proxy/openai_files_endpoints/files_endpoints.py @@ -771,7 +771,7 @@ async def get_file_content( version=version, ) - if should_route: + if should_route and credentials is not None: # Use model-based routing with credentials from config prepare_data_with_credentials( data=data, @@ -1129,7 +1129,7 @@ async def delete_file( check_file_id_encoding=True, ) - if should_route: + if should_route and credentials is not None: # Use model-based routing with credentials from config prepare_data_with_credentials( data=data, @@ -1308,7 +1308,7 @@ async def list_files( check_file_id_encoding=False, ) - if should_route: + if should_route and credentials is not None: # Use model-based routing with credentials from config data.update(credentials) # type: ignore response = await litellm.afile_list( diff --git a/litellm/proxy/pass_through_endpoints/streaming_handler.py b/litellm/proxy/pass_through_endpoints/streaming_handler.py index af61281243d..4dc1e0e70dd 100644 --- a/litellm/proxy/pass_through_endpoints/streaming_handler.py +++ b/litellm/proxy/pass_through_endpoints/streaming_handler.py @@ -25,6 +25,11 @@ from .success_handler import PassThroughEndpointLogging class PassThroughStreamingHandler: + @staticmethod + def _stamp_first_chunk_if_needed(litellm_logging_obj: LiteLLMLoggingObj) -> None: + if litellm_logging_obj.completion_start_time is None: + litellm_logging_obj._update_completion_start_time(completion_start_time=datetime.now()) + @staticmethod async def chunk_processor( response: httpx.Response, @@ -58,6 +63,7 @@ class PassThroughStreamingHandler: # Hot path: just buffer for end-of-stream logging and forward. async for chunk in response.aiter_bytes(): raw_bytes.append(chunk) + PassThroughStreamingHandler._stamp_first_chunk_if_needed(litellm_logging_obj) yield chunk else: # ``cost_injection_active`` already requires ``model_name`` to @@ -67,6 +73,7 @@ class PassThroughStreamingHandler: resolved_model_name: str = model_name async for chunk in response.aiter_bytes(): raw_bytes.append(chunk) + PassThroughStreamingHandler._stamp_first_chunk_if_needed(litellm_logging_obj) if endpoint_type == EndpointType.VERTEX_AI: if "streamRawPredict" in url_route or "rawPredict" in url_route: modified_chunk = ProxyBaseLLMRequestProcessing._process_chunk_with_cost_injection( diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 9f4c610dc47..38788d140e9 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -1125,6 +1125,18 @@ _DB_LITELLM_PARAM_ENV_REF_KEYS = frozenset( "vertex_ai_credentials", "aws_access_key_id", "aws_secret_access_key", + "aws_session_token", + "aws_region_name", + "aws_session_name", + "aws_profile_name", + "aws_role_name", + "aws_web_identity_token", + "aws_sts_endpoint", + "aws_external_id", + "aws_bedrock_runtime_endpoint", + "aws_bedrock_project_id", + "aws_batch_role_arn", + "aws_workspace_id", } ) @@ -3554,6 +3566,28 @@ def _scrub_db_overlay_remote_module_loads(section: str, db_value: Any) -> Any: return sanitized +def _normalize_user_url_validation(value: object) -> Optional[bool]: + if value is None: + return None + if isinstance(value, str): + return str_to_bool(value) + return bool(value) + + +def _apply_ssrf_general_settings(settings: Mapping[str, object]) -> None: + if "user_url_allowed_hosts" in settings: + litellm.user_url_allowed_hosts = cast(list[str], settings["user_url_allowed_hosts"]) + + user_url_validation = _normalize_user_url_validation(settings.get("user_url_validation")) + if user_url_validation is not None: + litellm.user_url_validation = user_url_validation + + if "provider_url_destination_allowed_hosts" in settings: + litellm.provider_url_destination_allowed_hosts = cast( + list[str], settings["provider_url_destination_allowed_hosts"] + ) + + class ProxyConfig: """ Abstraction class on top of config loading/updating logic. Gives us one place to control all config updating logic. @@ -4541,6 +4575,9 @@ class ProxyConfig: RoleBasedPermissions(**role_permission) for role_permission in rbac_role_permissions ] + ### SSRF URL VALIDATION SETTINGS ### + _apply_ssrf_general_settings(general_settings) + ## check if user has set a premium feature in general_settings if general_settings.get("enforced_params") is not None and premium_user is not True: raise ValueError("Trying to use `enforced_params`" + CommonProxyErrors.not_premium_user.value) @@ -5578,6 +5615,15 @@ class ProxyConfig: if old_value != new_value: await self._reschedule_spend_log_cleanup_job() + for key in ( + "user_url_allowed_hosts", + "user_url_validation", + "provider_url_destination_allowed_hosts", + ): + if key in _general_settings: + general_settings[key] = _general_settings[key] + _apply_ssrf_general_settings(_general_settings) + def _update_config_fields( self, current_config: dict, @@ -6354,6 +6400,17 @@ class ProxyConfig: from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( global_mcp_server_manager, ) + from litellm.proxy._experimental.mcp_server.oauth2_flow_backfill import ( + backfill_null_oauth2_flows, + ) + + try: + if prisma_client is not None: + await backfill_null_oauth2_flows(prisma_client) + except Exception as e: # noqa: BLE001 + verbose_proxy_logger.exception( + "litellm.proxy.proxy_server.py::ProxyConfig:_init_mcp_servers_in_db backfill - {}".format(str(e)) + ) try: await global_mcp_server_manager.reload_servers_from_database() @@ -6382,7 +6439,6 @@ class ProxyConfig: async def _init_search_tools_in_db(self, prisma_client: PrismaClient): """ Initialize search tools from database into the router on startup. - Only updates router if there are tools in the database, otherwise preserves config-loaded tools. """ global llm_router @@ -6392,26 +6448,29 @@ class ProxyConfig: from litellm.router_utils.search_api_router import SearchAPIRouter try: - search_tools = await SearchToolRegistry.get_all_search_tools_from_db(prisma_client=prisma_client) + db_search_tools = await SearchToolRegistry.get_all_search_tools_from_db(prisma_client=prisma_client) - verbose_proxy_logger.info(f"Loading {len(search_tools)} search tool(s) from database into router") + parsed_tools = self.parse_search_tools(self.get_config_state()) + config_search_tools = parsed_tools or [] - # Only update router if there are tools in the database - # This prevents overwriting config-loaded tools with an empty list - if len(search_tools) > 0: - if llm_router is not None: - # Add search tools to the router - await SearchAPIRouter.update_router_search_tools( - router_instance=llm_router, search_tools=search_tools - ) - verbose_proxy_logger.info(f"Successfully loaded {len(search_tools)} search tool(s) into router") - else: - verbose_proxy_logger.debug( - "Router not initialized yet, search tools will be added when router is created" - ) + search_tools = self._merge_config_and_db_search_tools( + config_search_tools=config_search_tools, + db_search_tools=[dict(tool) for tool in db_search_tools], + ) + + verbose_proxy_logger.info( + f"Loading {len(search_tools)} search tool(s) into router " + f"({len(config_search_tools)} from config, {len(db_search_tools)} from database)" + ) + + if llm_router is not None and search_tools: + await SearchAPIRouter.update_router_search_tools(router_instance=llm_router, search_tools=search_tools) + verbose_proxy_logger.info(f"Successfully loaded {len(search_tools)} search tool(s) into router") + elif llm_router is not None: + verbose_proxy_logger.debug("No search tools found in config or database, skipping router update") else: verbose_proxy_logger.debug( - "No search tools found in database, keeping config-loaded search tools (if any)" + "Router not initialized yet, search tools will be added when router is created" ) except Exception as e: @@ -6419,6 +6478,21 @@ class ProxyConfig: "litellm.proxy.proxy_server.py::ProxyConfig:_init_search_tools_in_db - {}".format(str(e)) ) + @staticmethod + def _merge_config_and_db_search_tools( + config_search_tools: list[SearchToolTypedDict], + db_search_tools: list[dict[str, Any]], + ) -> list[dict[str, Any]]: + db_tool_names = {tool.get("search_tool_name") for tool in db_search_tools} + return [ + *[ + dict(config_search_tool) + for config_search_tool in config_search_tools + if config_search_tool.get("search_tool_name") not in db_tool_names + ], + *db_search_tools, + ] + async def _init_pass_through_endpoints_in_db(self): from litellm.proxy.pass_through_endpoints.pass_through_endpoints import ( initialize_pass_through_endpoints_in_db, @@ -14211,6 +14285,9 @@ async def update_config_general_settings( detail={"error": CommonProxyErrors.not_allowed_access.value}, ) + if data.field_name in _GENERAL_SETTINGS_UI_LITELLM_FIELDS: + return await _persist_general_settings_ui_litellm_field(data.field_name, data.field_value, user_api_key_dict) + if data.field_name not in ConfigGeneralSettings.model_fields: raise HTTPException( status_code=400, @@ -14269,6 +14346,7 @@ async def update_config_general_settings( if data.field_name == "plugins": register_plugins_from_config(general_settings) + _apply_ssrf_general_settings(general_settings) return response @@ -14475,6 +14553,55 @@ async def get_config_general_settings( ) +_GENERAL_SETTINGS_UI_LITELLM_FIELDS: dict[str, dict[str, str]] = { + "budget_exceeded_throttle_percentage": { + "type": "Float", + "description": ( + "Fraction (0, 1] of a key's configured TPM/RPM that an over-budget key with " + "'Throttle on budget exceeded' enabled keeps serving at. Leave empty to hard-block " + "over-budget keys." + ), + }, +} + + +def _validate_general_settings_ui_litellm_value(field_name: str, value: Any) -> Optional[float]: + if value is None or value == "": + return None + if isinstance(value, bool) or not isinstance(value, (int, float)) or not (0 < float(value) <= 1): + raise HTTPException( + status_code=400, + detail={"error": f"{field_name} must be a number in (0, 1] or empty"}, + ) + return float(value) + + +async def _persist_general_settings_ui_litellm_field( + field_name: str, value: Any, user_api_key_dict: UserAPIKeyAuth +) -> dict: + validated = _validate_general_settings_ui_litellm_value(field_name, value) + config = await proxy_config.get_config() + before_value = config.get("litellm_settings", {}).get(field_name) + setattr(litellm, field_name, validated) + if "litellm_settings" not in config: + config["litellm_settings"] = {} + config["litellm_settings"][field_name] = validated + await proxy_config.save_config(new_config=config) + asyncio.create_task(create_config_audit_log(field_name, "updated", before_value, validated, user_api_key_dict)) + return {"message": f"Field {field_name} updated", "status": "success"} + + +async def _reset_general_settings_ui_litellm_field(field_name: str, user_api_key_dict: UserAPIKeyAuth) -> dict: + config = await proxy_config.get_config() + before_value = config.get("litellm_settings", {}).get(field_name) + setattr(litellm, field_name, None) + if "litellm_settings" in config: + config["litellm_settings"].pop(field_name, None) + await proxy_config.save_config(new_config=config) + asyncio.create_task(create_config_audit_log(field_name, "deleted", before_value, None, user_api_key_dict)) + return {"message": f"Field {field_name} reset", "status": "success"} + + @router.get( "/config/list", tags=["config.yaml"], @@ -14628,6 +14755,35 @@ async def get_config_list( ) return_val.append(_response_obj) + db_litellm_settings_row = await ConfigRepository(prisma_client).table.find_first( + where={"param_name": "litellm_settings"} + ) + db_litellm_settings: dict = ( + dict(db_litellm_settings_row.param_value) + if db_litellm_settings_row is not None and db_litellm_settings_row.param_value is not None + else {} + ) + for litellm_field_name, spec in _GENERAL_SETTINGS_UI_LITELLM_FIELDS.items(): + current_value: Optional[float] = getattr(litellm, litellm_field_name, None) + stored_in_db_litellm: Optional[bool] + if litellm_field_name in db_litellm_settings: + stored_in_db_litellm = True + elif current_value is not None: + stored_in_db_litellm = False + else: + stored_in_db_litellm = None + return_val.append( + ConfigList( + field_name=litellm_field_name, + field_type=spec["type"], + field_description=spec["description"], + field_value=current_value, + stored_in_db=stored_in_db_litellm, + field_default_value=None, + nested_fields=None, + ) + ) + return return_val @@ -14668,6 +14824,9 @@ async def delete_config_general_settings( }, ) + if data.field_name in _GENERAL_SETTINGS_UI_LITELLM_FIELDS: + return await _reset_general_settings_ui_litellm_field(data.field_name, user_api_key_dict) + if data.field_name not in ConfigGeneralSettings.model_fields: raise HTTPException( status_code=400, diff --git a/litellm/proxy/schema.prisma b/litellm/proxy/schema.prisma index f9ab5e6aefd..6d87102a3f3 100644 --- a/litellm/proxy/schema.prisma +++ b/litellm/proxy/schema.prisma @@ -329,6 +329,12 @@ model LiteLLM_MCPServerTable { token_url String? registration_url String? oauth2_flow String? + token_exchange_endpoint String? + // Named for the RFC 8693 "audience" token-exchange request parameter (that flow only). + // RFC 8707 resource indicators are a separate concept, named "resource" in the v2 egress types. + audience String? + subject_token_type String? + token_exchange_profile String? allow_all_keys Boolean @default(false) available_on_public_internet Boolean @default(true) delegate_auth_to_upstream Boolean @default(false) diff --git a/litellm/proxy/spend_tracking/budget_reservation.py b/litellm/proxy/spend_tracking/budget_reservation.py index ca6c2e86789..b577513fc0e 100644 --- a/litellm/proxy/spend_tracking/budget_reservation.py +++ b/litellm/proxy/spend_tracking/budget_reservation.py @@ -17,6 +17,7 @@ from litellm.proxy._types import ( UserAPIKeyAuth, ) from litellm.proxy.auth.auth_utils import get_model_from_request +from litellm.proxy.auth.budget_throttle import should_throttle_budget_exceeded from litellm.proxy.auth.route_checks import RouteChecks from litellm.proxy.utils import PrismaClient, ProxyLogging from litellm.router import Router @@ -54,6 +55,61 @@ def get_reserved_counter_keys(budget_reservation: Optional[dict]) -> set: } +def _key_reservation_should_release_for_throttle(counter_key: str, valid_token: Optional[UserAPIKeyAuth]) -> bool: + """ + Whether an over-budget key's own ``max_budget`` reservation should be + released rather than blocked, because the key opted into throttling: the + rate limiter slows it instead. Only the key's own ``max_budget`` counter is + exempt; team/user/window counters still enforce normally, and under-budget + requests never reach this branch so their concurrent-overspend protection is + untouched. + """ + if valid_token is None: + return False + return counter_key == f"spend:key:{valid_token.token}" and should_throttle_budget_exceeded(valid_token) + + +async def _apply_over_budget_reservation_policy( + counter: _BudgetCounter, + valid_token: Optional[UserAPIKeyAuth], + entry: dict[str, Any], + applied_entries: list[dict[str, Any]], + reservation_cost: float, + current_spend: float, +) -> float: + """ + Decide what to do when a counter is over budget, and return the reservation + cost to carry into the next counter. Three outcomes: an over-budget key that + opted into throttling releases its own reservation (the rate limiter slows + it) and keeps the cost; a partially-remaining budget resizes the reservation + down to what is left; anything else hard-blocks by raising. + """ + if _key_reservation_should_release_for_throttle(counter.counter_key, valid_token): + await _release_applied_entries_best_effort(entries=[entry], default_reserved_cost=reservation_cost) + applied_entries.remove(entry) + return reservation_cost + + remaining_before_reservation = counter.max_budget - (current_spend - reservation_cost) + if remaining_before_reservation > 1e-12: + await _resize_applied_reservation( + entries=applied_entries, + current_reserved_cost=reservation_cost, + new_reserved_cost=remaining_before_reservation, + ) + return remaining_before_reservation + + raise litellm.BudgetExceededError( + current_cost=current_spend, + max_budget=counter.max_budget, + message=( + "Budget has been exceeded! " + f"{counter.entity_type}={counter.entity_id} " + f"Current cost: {current_spend}, " + f"Max budget: {counter.max_budget}" + ), + ) + + async def reserve_budget_for_request( request_body: dict, route: str, @@ -130,25 +186,15 @@ async def reserve_budget_for_request( cached_spend = await _get_current_counter_value(counter=counter) current_spend = cached_spend + reservation_cost if current_spend > counter.max_budget: - remaining_before_reservation = counter.max_budget - (current_spend - reservation_cost) - if remaining_before_reservation > 1e-12: - await _resize_applied_reservation( - entries=applied_entries, - current_reserved_cost=reservation_cost, - new_reserved_cost=remaining_before_reservation, - ) - reservation_cost = remaining_before_reservation - continue - raise litellm.BudgetExceededError( - current_cost=current_spend, - max_budget=counter.max_budget, - message=( - "Budget has been exceeded! " - f"{counter.entity_type}={counter.entity_id} " - f"Current cost: {current_spend}, " - f"Max budget: {counter.max_budget}" - ), + reservation_cost = await _apply_over_budget_reservation_policy( + counter=counter, + valid_token=valid_token, + entry=entry, + applied_entries=applied_entries, + reservation_cost=reservation_cost, + current_spend=current_spend, ) + continue except Exception: await _release_applied_entries_best_effort( entries=applied_entries, diff --git a/litellm/proxy/spend_tracking/spend_management_endpoints.py b/litellm/proxy/spend_tracking/spend_management_endpoints.py index 9c152e30c52..8f530e3b8ce 100644 --- a/litellm/proxy/spend_tracking/spend_management_endpoints.py +++ b/litellm/proxy/spend_tracking/spend_management_endpoints.py @@ -27,6 +27,7 @@ from litellm.proxy.auth.user_api_key_auth import user_api_key_auth # module while common_utils may pull proxy_server during init, which can leave # those names undefined. Import the helpers locally where they are used. from litellm.proxy.spend_tracking.spend_tracking_utils import ( + get_spend_by_team, get_spend_by_team_and_customer, ) from litellm.proxy.utils import handle_exception_on_proxy @@ -44,6 +45,8 @@ else: router = APIRouter() +SPEND_LOGS_PAGINATION_COUNT_CAP = 10000 + @router.get( "/spend/keys", @@ -1131,68 +1134,7 @@ async def get_global_spend_report( start_date_obj, end_date_obj, team_id, customer_id, prisma_client ) if group_by == "team": - # first get data from spend logs -> SpendByModelApiKey - # then read data from "SpendByModelApiKey" to format the response obj - sql_query = """ - - WITH SpendByModelApiKey AS ( - SELECT - date_trunc('day', sl."startTime") AS group_by_day, - COALESCE(tt.team_alias, 'Unassigned Team') AS team_name, - sl.model, - sl.api_key, - SUM(sl.spend) AS model_api_spend, - SUM(sl.total_tokens) AS model_api_tokens - FROM - "LiteLLM_SpendLogs" sl - LEFT JOIN - "LiteLLM_TeamTable" tt - ON - sl.team_id = tt.team_id - WHERE - sl."startTime" >= ($1::timestamptz AT TIME ZONE 'UTC') - AND sl."startTime" < (($2::timestamptz + INTERVAL '1 day') AT TIME ZONE 'UTC') - GROUP BY - date_trunc('day', sl."startTime"), - tt.team_alias, - sl.model, - sl.api_key - ) - SELECT - group_by_day, - jsonb_agg(jsonb_build_object( - 'team_name', team_name, - 'total_spend', total_spend, - 'metadata', metadata - )) AS teams - FROM ( - SELECT - group_by_day, - team_name, - SUM(model_api_spend) AS total_spend, - jsonb_agg(jsonb_build_object( - 'model', model, - 'api_key', api_key, - 'spend', model_api_spend, - 'total_tokens', model_api_tokens - )) AS metadata - FROM - SpendByModelApiKey - GROUP BY - group_by_day, - team_name - ) AS aggregated - GROUP BY - group_by_day - ORDER BY - group_by_day; - """ - - db_response = await prisma_client.db.query_raw(sql_query, start_date_obj, end_date_obj) - if db_response is None: - return [] - - return db_response + return await get_spend_by_team(start_date_obj, end_date_obj, team_id, prisma_client) elif group_by == "customer": sql_query = """ @@ -2018,6 +1960,20 @@ async def ui_view_spend_logs( else: _order_expr = order_column + count_query = f""" + SELECT COUNT(*) AS total_count + FROM ( + SELECT 1 + FROM "LiteLLM_SpendLogs" + WHERE {" AND ".join(sql_conditions)} + LIMIT ${p} + ) AS bounded_matches + """ + count_rows = await prisma_client.db.query_raw(count_query, *sql_params, SPEND_LOGS_PAGINATION_COUNT_CAP + 1) + raw_total = int(count_rows[0]["total_count"]) if count_rows else 0 + total_is_capped = raw_total > SPEND_LOGS_PAGINATION_COUNT_CAP + total_records = SPEND_LOGS_PAGINATION_COUNT_CAP if total_is_capped else raw_total + sql_query = f""" SELECT request_id, call_type, api_key, spend, total_tokens, @@ -2027,8 +1983,7 @@ async def ui_view_spend_logs( cache_hit, cache_key, request_tags, team_id, organization_id, end_user, requester_ip_address, session_id, status, mcp_namespaced_tool_name, agent_id, - COALESCE(request_duration_ms, (EXTRACT(EPOCH FROM ("endTime" - "startTime")) * 1000)::INTEGER) AS request_duration_ms, - COUNT(*) OVER () AS total_count + COALESCE(request_duration_ms, (EXTRACT(EPOCH FROM ("endTime" - "startTime")) * 1000)::INTEGER) AS request_duration_ms FROM "LiteLLM_SpendLogs" WHERE {" AND ".join(sql_conditions)} ORDER BY {_order_expr} {_sql_dir}{_nulls_clause} @@ -2038,34 +1993,13 @@ async def ui_view_spend_logs( data = await prisma_client.db.query_raw(sql_query, *sql_params) - # `COUNT(*) OVER ()` folds the total-match count into the same scan as the - # page data; a standalone `COUNT(*)` is a distributed RPC on sharded - # engines like YugabyteDB that contacts every tablet and times out - # regardless of row count (LIT-4027). The hot path (page 1 and in-range - # pages) always carries the count on its rows, so the count round trip is - # gone there. Only an out-of-range page overshoots the last row and comes - # back empty; fall back to a direct count there so total/total_pages stay - # accurate rather than collapsing to zero. - if data: - total_records = int(data[0]["total_count"]) - elif page > 1: - total_records = int( - await SpendLogsRepository(prisma_client).table.count( - where=where_conditions, - ) - ) - else: - total_records = 0 - # query_raw returns the JSONB `metadata` column as a string (the Prisma # serialiser bypasses the model-layer JSON hydration we get on the ORM # path). The UI reads `metadata.status` / `metadata.error_information` # as object fields, so failure rows looked like successes (#29674). - # Re-hydrate to dict here. Also drop the window-function `total_count` - # helper column so it does not leak into the serialised rows. + # Re-hydrate to dict here. for row in data: if isinstance(row, dict): - row.pop("total_count", None) md = row.get("metadata") if isinstance(md, str): try: @@ -2086,6 +2020,7 @@ async def ui_view_spend_logs( page_size, total_pages, enrich_session_counts=not is_v2, + total_is_capped=total_is_capped, ) except Exception as e: verbose_proxy_logger.exception(f"Error in ui_view_spend_logs: {e}") @@ -3394,6 +3329,7 @@ async def _build_ui_spend_logs_response( page_size: int, total_pages: int, enrich_session_counts: bool = True, + total_is_capped: bool = False, ) -> dict: """ Build the paginated response for the UI spend-logs endpoint. @@ -3418,10 +3354,12 @@ async def _build_ui_spend_logs_response( total_pages: Total number of pages. enrich_session_counts: Whether to add ``session_total_count`` to each row. Defaults to ``True``. + total_is_capped: Whether ``total_records`` was clamped to the + pagination count cap (there are more matching rows than the cap). Returns: A dict with ``data`` (enriched rows), ``total``, ``page``, - ``page_size``, and ``total_pages``. + ``page_size``, ``total_pages``, and ``total_is_capped``. """ count_map: dict[str, int] = {} if enrich_session_counts: @@ -3511,6 +3449,7 @@ async def _build_ui_spend_logs_response( "page": page, "page_size": page_size, "total_pages": total_pages, + "total_is_capped": total_is_capped, } diff --git a/litellm/proxy/spend_tracking/spend_tracking_utils.py b/litellm/proxy/spend_tracking/spend_tracking_utils.py index 22f97feecd9..d3642cb12a4 100644 --- a/litellm/proxy/spend_tracking/spend_tracking_utils.py +++ b/litellm/proxy/spend_tracking/spend_tracking_utils.py @@ -55,6 +55,13 @@ def _get_max_string_length_prompt_in_db() -> int: return DEFAULT_MAX_STRING_LENGTH_PROMPT_IN_DB +def _hash_api_key_for_spend_log(api_key: str) -> str: + stripped = api_key[7:] if api_key[:7].lower() == "bearer " else api_key + if stripped.startswith("sk-"): + return hash_token(stripped) + return stripped + + def _is_master_key(api_key: Optional[str], _master_key: Optional[str]) -> bool: """ Raw-only constant-time master-key comparison. The hashed form is never @@ -120,6 +127,9 @@ def _get_spend_logs_metadata( key: metadata.get(key) for key in SpendLogsMetadata.__annotations__.keys() } ) + raw_user_api_key = clean_metadata.get("user_api_key") + if raw_user_api_key is not None and isinstance(raw_user_api_key, str): + clean_metadata["user_api_key"] = _hash_api_key_for_spend_log(raw_user_api_key) clean_metadata["applied_guardrails"] = applied_guardrails clean_metadata["batch_models"] = batch_models clean_metadata["mcp_tool_call_metadata"] = mcp_tool_call_metadata @@ -281,9 +291,7 @@ def get_logging_payload(kwargs, response_obj, start_time, end_time) -> SpendLogs standard_logging_completion_tokens = standard_logging_payload.get("completion_tokens", 0) standard_logging_total_tokens = standard_logging_payload.get("total_tokens", 0) if api_key is not None and isinstance(api_key, str): - if api_key.startswith("sk-"): - # hash the api_key - api_key = hash_token(api_key) + api_key = _hash_api_key_for_spend_log(api_key) if ( standard_logging_payload is not None @@ -485,6 +493,74 @@ def _ensure_datetime_utc(timestamp: datetime) -> datetime: return timestamp +async def get_spend_by_team( + start_date: dt, + end_date: dt, + team_id: Optional[str], + prisma_client: PrismaClient, +): + sql_query = """ + WITH SpendByModelApiKey AS ( + SELECT + date_trunc('day', sl."startTime") AS group_by_day, + COALESCE(tt.team_alias, 'Unassigned Team') AS team_name, + sl.model, + sl.api_key, + SUM(sl.spend) AS model_api_spend, + SUM(sl.total_tokens) AS model_api_tokens + FROM + "LiteLLM_SpendLogs" sl + LEFT JOIN + "LiteLLM_TeamTable" tt + ON + sl.team_id = tt.team_id + WHERE + sl."startTime" >= ($1::timestamptz AT TIME ZONE 'UTC') + AND sl."startTime" < (($2::timestamptz + INTERVAL '1 day') AT TIME ZONE 'UTC') + AND ($3::text IS NULL OR sl.team_id = $3) + GROUP BY + date_trunc('day', sl."startTime"), + tt.team_alias, + sl.model, + sl.api_key + ) + SELECT + group_by_day, + jsonb_agg(jsonb_build_object( + 'team_name', team_name, + 'total_spend', total_spend, + 'metadata', metadata + )) AS teams + FROM ( + SELECT + group_by_day, + team_name, + SUM(model_api_spend) AS total_spend, + jsonb_agg(jsonb_build_object( + 'model', model, + 'api_key', api_key, + 'spend', model_api_spend, + 'total_tokens', model_api_tokens + )) AS metadata + FROM + SpendByModelApiKey + GROUP BY + group_by_day, + team_name + ) AS aggregated + GROUP BY + group_by_day + ORDER BY + group_by_day; + """ + + db_response = await prisma_client.db.query_raw(sql_query, start_date, end_date, team_id) + if db_response is None: + return [] + + return db_response + + async def get_spend_by_team_and_customer( start_date: dt, end_date: dt, diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py index 2880eef6908..d7649b524aa 100644 --- a/litellm/proxy/utils.py +++ b/litellm/proxy/utils.py @@ -4097,11 +4097,22 @@ class PrismaClient: raise e def _get_engine_pid(self) -> int: + """Get the PID of the writer's engine subprocess, or 0 if unavailable. + + Must never raise: prisma's ``_engine`` property raises + ``ClientNotConnectedError`` on a disconnected client, and an exception + escaping from the reconnect path would leave it unable to recover. + """ try: - engine = self.db._original_prisma._engine # type: ignore[attr-defined] + prisma_obj = self.writer_db._original_prisma + if prisma_obj.is_connected() is not True: + return 0 + engine = prisma_obj._engine process = getattr(engine, "process", None) if engine is not None else None if process is not None: - return process.pid + pid = process.pid + if isinstance(pid, int): + return pid except (AttributeError, TypeError): pass return 0 diff --git a/litellm/responses/litellm_completion_transformation/custom_tools.py b/litellm/responses/litellm_completion_transformation/custom_tools.py new file mode 100644 index 00000000000..2417bf5cf2e --- /dev/null +++ b/litellm/responses/litellm_completion_transformation/custom_tools.py @@ -0,0 +1,163 @@ +""" +Utilities for handling OpenAI Responses API 'custom' tools (freeform/grammar tools) +when bridging to Chat Completions providers. + +Custom tools are defined with ``type: "custom"`` and a grammar/format specification. +Since most Chat Completions providers only support standard ``function`` tools, +the bridge converts them to ``function`` tools with a single ``content`` string +parameter. When the model responds with a ``function_call`` for such a tool, this +module converts it back to the ``custom_tool_call`` format expected by clients like +Codex CLI. + +The forward direction (custom -> function) and reverse direction (function_call -> +custom_tool_call) are both handled here so future custom tool types can be added by +extending this module without touching the streaming iterator or transformation +logic. +""" + +import json +from collections.abc import Mapping +from typing import Any + +from pydantic import BaseModel, TypeAdapter, ValidationError + +from litellm.types.llms.openai import ( + ChatCompletionToolParam, + ChatCompletionToolParamFunctionChunk, +) + +_MAX_ARGUMENTS_LEN = 1_000_000 + + +def extract_custom_tool_names(tools: list[Any] | None) -> set[str]: + """Extract names of tools originally defined as ``type: "custom"``.""" + if not tools: + return set() + names: set[str] = set() + for tool in tools: + if isinstance(tool, dict) and tool.get("type") == "custom" and "name" in tool: + names.add(tool["name"]) + return names + + +def is_custom_tool_call(tool_name: str, custom_tool_names: set[str]) -> bool: + """Check if a tool call name corresponds to a custom tool.""" + return tool_name in custom_tool_names + + +def unwrap_custom_tool_arguments(arguments: str) -> str: + """Extract the raw content string from JSON-wrapped arguments. + + The bridge converts custom tools to function tools with schema + ``{"properties": {"content": {"type": "string"}}}``, so the model returns + arguments like ``{"content": "*** Begin Patch\\n..."}``. This function + extracts just the content string. If the arguments are not valid JSON or do + not contain a ``content`` key, the original string is returned unchanged. + """ + if not arguments: + return "" + if len(arguments) > _MAX_ARGUMENTS_LEN: + return arguments + try: + parsed = json.loads(arguments) + if isinstance(parsed, dict) and "content" in parsed: + return str(parsed["content"]) + except (json.JSONDecodeError, TypeError, ValueError): + pass + return arguments + + +def build_tool_call_item_kwargs( + call_id: str, + name: str, + arguments_or_input: str, + status: str, + custom_tool_names: set[str], +) -> dict[str, Any]: + """Build kwargs for an output item dict that is either a ``function_call`` + or a ``custom_tool_call`` depending on whether *name* is in + *custom_tool_names*. + + For custom tools the ``arguments`` JSON is unwrapped into the ``input`` + field. For regular function tools the raw ``arguments`` string is kept. + + This centralises the branching logic so the streaming iterator and the + non-streaming transformation share a single code path. + """ + custom = is_custom_tool_call(name, custom_tool_names) + item_type = "custom_tool_call" if custom else "function_call" + kwargs: dict[str, Any] = { + "type": item_type, + "id": call_id, + "call_id": call_id, + "name": name, + "status": status, + } + if custom: + if status == "completed": + kwargs["input"] = unwrap_custom_tool_arguments(arguments_or_input) + else: + kwargs["input"] = "" + else: + kwargs["arguments"] = arguments_or_input + return kwargs + + +class _CustomToolFormat(BaseModel): + syntax: str = "" + definition: str = "" + + +_ALLOWED_CALLERS_ADAPTER = TypeAdapter(list[str] | None) + + +def _validated_allowed_callers(value: object) -> list[str] | None: + try: + return _ALLOWED_CALLERS_ADAPTER.validate_python(value, strict=True) + except ValidationError as exc: + raise ValueError("allowed_callers must be a list of strings") from exc + + +def _grammar_suffix(fmt: object) -> str: + try: + parsed = _CustomToolFormat.model_validate(fmt) + except ValidationError: + return "" + if not parsed.definition: + return "" + return f"\n\nFormat:\n```{parsed.syntax}\n{parsed.definition}\n```" + + +def convert_custom_tool_to_function_tool(tool: Mapping[str, object]) -> ChatCompletionToolParam | None: + """Convert a Responses API ``custom`` tool to a Chat Completions ``function`` + tool. + + The grammar definition is embedded in the description so the model can + produce correctly-formatted output. Returns ``None`` if the tool is not a + custom tool. Raises ``ValueError`` if ``allowed_callers`` is not a list of + strings. + """ + if tool.get("type") != "custom": + return None + raw_name = tool.get("name") + name = raw_name if isinstance(raw_name, str) else "" + raw_description = tool.get("description") + description = (raw_description if isinstance(raw_description, str) else "") + _grammar_suffix(tool.get("format")) + allowed_callers = _validated_allowed_callers(tool.get("allowed_callers")) + function_chunk = ChatCompletionToolParamFunctionChunk( + name=name, + description=description, + parameters={ + "type": "object", + "properties": { + "content": { + "type": "string", + "description": f"The {name} content following the specified format", + } + }, + "required": ["content"], + }, + ) + if allowed_callers is None: + return ChatCompletionToolParam(type="function", function=function_chunk) + return ChatCompletionToolParam(type="function", function=function_chunk, allowed_callers=allowed_callers) diff --git a/litellm/responses/litellm_completion_transformation/streaming_iterator.py b/litellm/responses/litellm_completion_transformation/streaming_iterator.py index b1198780bac..cf69654d15d 100644 --- a/litellm/responses/litellm_completion_transformation/streaming_iterator.py +++ b/litellm/responses/litellm_completion_transformation/streaming_iterator.py @@ -1,9 +1,13 @@ import time import uuid -from typing import Any, Dict, List, Optional, Union, cast +from typing import Any, cast import litellm from litellm.main import stream_chunk_builder +from litellm.responses.litellm_completion_transformation.custom_tools import ( + build_tool_call_item_kwargs, + extract_custom_tool_names, +) from litellm.responses.litellm_completion_transformation.transformation import ( LiteLLMCompletionResponsesConfig, ) @@ -53,20 +57,20 @@ class LiteLLMCompletionStreamingIterator(ResponsesAPIStreamingIterator): self, model: str, litellm_custom_stream_wrapper: litellm.CustomStreamWrapper, - request_input: Union[str, ResponseInputParam], + request_input: str | ResponseInputParam, responses_api_request: ResponsesAPIOptionalRequestParams, - custom_llm_provider: Optional[str] = None, - litellm_metadata: Optional[dict] = None, + custom_llm_provider: str | None = None, + litellm_metadata: dict | None = None, ): self.model: str = model self.litellm_custom_stream_wrapper: litellm.CustomStreamWrapper = litellm_custom_stream_wrapper - self.request_input: Union[str, ResponseInputParam] = request_input + self.request_input: str | ResponseInputParam = request_input self.responses_api_request: ResponsesAPIOptionalRequestParams = responses_api_request - self.custom_llm_provider: Optional[str] = custom_llm_provider - self.litellm_metadata: Optional[dict] = litellm_metadata or {} + self.custom_llm_provider: str | None = custom_llm_provider + self.litellm_metadata: dict | None = litellm_metadata or {} # Store lightweight dict snapshots for stream_chunk_builder to reduce # repeated Pydantic attribute access in end-of-stream assembly. - self.collected_chat_completion_chunks: List[Dict[str, Any]] = [] + self.collected_chat_completion_chunks: list[dict[str, Any]] = [] self.finished: bool = False self.litellm_logging_obj = litellm_custom_stream_wrapper.logging_obj self.sent_response_created_event: bool = False @@ -77,11 +81,11 @@ class LiteLLMCompletionStreamingIterator(ResponsesAPIStreamingIterator): self.sent_output_content_part_done_event: bool = False self.sent_output_item_done_event: bool = False self.sent_annotation_events: bool = False - self.litellm_model_response: Optional[Union[ModelResponse, TextCompletionResponse]] = None + self.litellm_model_response: ModelResponse | TextCompletionResponse | None = None self.final_text: str = "" - self._cached_item_id: Optional[str] = None - self._cached_response_id: Optional[str] = None - self._pending_tool_events: List[BaseLiteLLMOpenAIResponseObject] = [] + self._cached_item_id: str | None = None + self._cached_response_id: str | None = None + self._pending_tool_events: list[BaseLiteLLMOpenAIResponseObject] = [] self._tool_output_index_by_call_id: dict[str, int] = {} self._tool_args_by_call_id: dict[str, str] = {} self._tool_call_id_by_index: dict[int, str] = {} @@ -89,17 +93,18 @@ class LiteLLMCompletionStreamingIterator(ResponsesAPIStreamingIterator): self._next_tool_output_index: int = 1 # output_index=0 reserved for the message item self._final_tool_events_queued: bool = False self._sequence_number: int = 0 - self._cached_reasoning_item_id: Optional[str] = None + self._cached_reasoning_item_id: str | None = None self._sent_reasoning_summary_text_done_event: bool = False self._sent_reasoning_summary_part_done_event: bool = False self._reasoning_summary_text: str = "" # -- GENERIC RESPONSE-EVENTS PENDING QUEUE as required by fix -- - self._pending_response_events: List[BaseLiteLLMOpenAIResponseObject] = [] + self._pending_response_events: list[BaseLiteLLMOpenAIResponseObject] = [] self._reasoning_active = False self._reasoning_done_emitted = False - self._reasoning_item_id: Optional[str] = None - self._accumulated_reasoning_content_parts: List[str] = [] - self._accumulated_provider_specific_fields: Dict[str, Any] = {} + self._reasoning_item_id: str | None = None + self._accumulated_reasoning_content_parts: list[str] = [] + self._accumulated_provider_specific_fields: dict[str, Any] = {} + self._custom_tool_names: set[str] = extract_custom_tool_names(self.responses_api_request.get("tools")) def _get_or_assign_tool_output_index(self, call_id: str) -> int: existing = self._tool_output_index_by_call_id.get(call_id) @@ -110,7 +115,7 @@ class LiteLLMCompletionStreamingIterator(ResponsesAPIStreamingIterator): self._tool_output_index_by_call_id[call_id] = idx return idx - def _normalize_tool_call_index(self, tool_call: object) -> Optional[int]: + def _normalize_tool_call_index(self, tool_call: object) -> int | None: idx_raw = tool_call.get("index") if isinstance(tool_call, dict) else getattr(tool_call, "index", None) if idx_raw is None: return None @@ -183,19 +188,11 @@ class LiteLLMCompletionStreamingIterator(ResponsesAPIStreamingIterator): if call_id not in self._tool_args_by_call_id: self._tool_args_by_call_id[call_id] = "" self._sequence_number += 1 + item_kwargs = build_tool_call_item_kwargs(call_id, fn_name, "", "in_progress", self._custom_tool_names) event = OutputItemAddedEvent( type=ResponsesAPIStreamEvents.OUTPUT_ITEM_ADDED, output_index=output_index, - item=BaseLiteLLMOpenAIResponseObject( - **{ - "type": "function_call", - "id": call_id, - "call_id": call_id, - "name": fn_name, - "arguments": "", - "status": "in_progress", - } - ), + item=BaseLiteLLMOpenAIResponseObject(**item_kwargs), ) event.__dict__["sequence_number"] = self._sequence_number self._pending_tool_events.append(event) @@ -260,19 +257,11 @@ class LiteLLMCompletionStreamingIterator(ResponsesAPIStreamingIterator): if is_new_tool_call: self._tool_args_by_call_id[call_id] = "" self._sequence_number += 1 + item_kwargs = build_tool_call_item_kwargs(call_id, fn_name, "", "in_progress", self._custom_tool_names) event = OutputItemAddedEvent( type=ResponsesAPIStreamEvents.OUTPUT_ITEM_ADDED, output_index=output_index, - item=BaseLiteLLMOpenAIResponseObject( - **{ - "type": "function_call", - "id": call_id, - "call_id": call_id, - "name": fn_name, - "arguments": "", - "status": "in_progress", - } - ), + item=BaseLiteLLMOpenAIResponseObject(**item_kwargs), ) event.__dict__["sequence_number"] = self._sequence_number self._pending_tool_events.append(event) @@ -310,20 +299,14 @@ class LiteLLMCompletionStreamingIterator(ResponsesAPIStreamingIterator): self._pending_tool_events.append(done_event) self._sequence_number += 1 + item_kwargs = build_tool_call_item_kwargs( + call_id, fn_name, final_args, "completed", self._custom_tool_names + ) item_done_event = OutputItemDoneEvent( type=ResponsesAPIStreamEvents.OUTPUT_ITEM_DONE, output_index=output_index, sequence_number=self._sequence_number, - item=BaseLiteLLMOpenAIResponseObject( - **{ - "type": "function_call", - "id": call_id, - "call_id": call_id, - "name": fn_name, - "arguments": final_args, - "status": "completed", - } - ), + item=BaseLiteLLMOpenAIResponseObject(**item_kwargs), ) self._pending_tool_events.append(item_done_event) @@ -449,9 +432,9 @@ class LiteLLMCompletionStreamingIterator(ResponsesAPIStreamingIterator): for key, val in src.items(): self._accumulated_provider_specific_fields[key] = val - def create_litellm_model_response(self) -> Optional[ModelResponse]: + def create_litellm_model_response(self) -> ModelResponse | None: response = cast( - Optional[ModelResponse], + ModelResponse | None, stream_chunk_builder( chunks=self.collected_chat_completion_chunks, logging_obj=self.litellm_logging_obj, @@ -468,7 +451,7 @@ class LiteLLMCompletionStreamingIterator(ResponsesAPIStreamingIterator): @staticmethod def _snapshot_chunk_for_stream_chunk_builder( chunk: ModelResponseStream, - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """ Convert a streaming chunk into a plain dict for end-of-stream assembly. Keep _hidden_params so downstream usage/header behavior is preserved. @@ -564,7 +547,7 @@ class LiteLLMCompletionStreamingIterator(ResponsesAPIStreamingIterator): reasoning_content = getattr(litellm_complete_object.choices[0].message, "reasoning_content", "") or "" # type: ignore annotations = getattr(litellm_complete_object.choices[0].message, "annotations", None) # type: ignore - part: Optional[PART_UNION_TYPES] = None + part: PART_UNION_TYPES | None = None if reasoning_content: part = ContentPartDonePartReasoningText( type="reasoning_text", @@ -671,7 +654,7 @@ class LiteLLMCompletionStreamingIterator(ResponsesAPIStreamingIterator): def return_default_done_events( self, litellm_complete_object: ModelResponse - ) -> Optional[BaseLiteLLMOpenAIResponseObject]: + ) -> BaseLiteLLMOpenAIResponseObject | None: if self.sent_output_text_done_event is False: self.sent_output_text_done_event = True return self.create_output_text_done_event(litellm_complete_object) @@ -685,7 +668,7 @@ class LiteLLMCompletionStreamingIterator(ResponsesAPIStreamingIterator): def return_default_initial_events( self, - ) -> Optional[BaseLiteLLMOpenAIResponseObject]: + ) -> BaseLiteLLMOpenAIResponseObject | None: if self.sent_response_created_event is False: self.sent_response_created_event = True return self.create_response_created_event() @@ -725,6 +708,9 @@ class LiteLLMCompletionStreamingIterator(ResponsesAPIStreamingIterator): self.finished = self.is_stream_finished() response_completed_event = self._emit_response_completed_event(self.litellm_model_response) if response_completed_event: + # Latch so wrappers (FallbackResponsesStreamWrapper) + proxy + # container-ownership hook can read completed_response. + self.completed_response = response_completed_event return response_completed_event else: if sync_mode: @@ -800,11 +786,7 @@ class LiteLLMCompletionStreamingIterator(ResponsesAPIStreamingIterator): async def __anext__( self, - ) -> Union[ - ResponsesAPIStreamingResponse, - ResponseCompletedEvent, - BaseLiteLLMOpenAIResponseObject, - ]: + ) -> ResponsesAPIStreamingResponse | ResponseCompletedEvent | BaseLiteLLMOpenAIResponseObject: try: while True: if self.finished is True: @@ -906,11 +888,7 @@ class LiteLLMCompletionStreamingIterator(ResponsesAPIStreamingIterator): def __next__( self, - ) -> Union[ - ResponsesAPIStreamingResponse, - ResponseCompletedEvent, - BaseLiteLLMOpenAIResponseObject, - ]: + ) -> ResponsesAPIStreamingResponse | ResponseCompletedEvent | BaseLiteLLMOpenAIResponseObject: try: while True: if self.finished is True: @@ -961,7 +939,7 @@ class LiteLLMCompletionStreamingIterator(ResponsesAPIStreamingIterator): def _transform_chat_completion_chunk_to_response_api_chunk( self, chunk: ModelResponseStream - ) -> Optional[ResponsesAPIStreamingResponse]: + ) -> ResponsesAPIStreamingResponse | None: """ Transform a chat completion chunk to a response API chunk. @@ -1047,7 +1025,7 @@ class LiteLLMCompletionStreamingIterator(ResponsesAPIStreamingIterator): return None - def _get_delta_string_from_streaming_choices(self, choices: List[StreamingChoices]) -> str: + def _get_delta_string_from_streaming_choices(self, choices: list[StreamingChoices]) -> str: """ Get the delta string from the streaming choices @@ -1059,7 +1037,7 @@ class LiteLLMCompletionStreamingIterator(ResponsesAPIStreamingIterator): chat_completion_delta: ChatCompletionDelta = choice.delta return chat_completion_delta.content or "" - def _emit_response_completed_event(self, litellm_model_response: ModelResponse) -> Optional[ResponseCompletedEvent]: + def _emit_response_completed_event(self, litellm_model_response: ModelResponse) -> ResponseCompletedEvent | None: if litellm_model_response: # Add cost to usage object if include_cost_in_streaming_usage is True if litellm.include_cost_in_streaming_usage and self.litellm_logging_obj is not None: diff --git a/litellm/responses/litellm_completion_transformation/transformation.py b/litellm/responses/litellm_completion_transformation/transformation.py index 866698b1f96..1aaa38cea14 100644 --- a/litellm/responses/litellm_completion_transformation/transformation.py +++ b/litellm/responses/litellm_completion_transformation/transformation.py @@ -2,15 +2,17 @@ Handles transforming from Responses API -> LiteLLM completion (Chat Completion API) """ +import json import re from collections.abc import Sequence -from typing import Any, Dict, List, Literal, Optional, Set, Tuple, Union, cast +from typing import Any, Literal, cast from openai.types.responses import ResponseFunctionToolCall from openai.types.responses.response_create_params import ResponseInputParam from openai.types.responses.tool_param import FunctionToolParam from typing_extensions import TypedDict +from litellm._logging import verbose_logger from litellm.caching import InMemoryCache from litellm.litellm_core_utils.get_supported_openai_params import ( get_supported_openai_params, @@ -45,6 +47,7 @@ from litellm.types.llms.openai import ( ValidChatCompletionMessageContentTypesLiteral, ) from litellm.types.responses.main import ( + CustomToolCallOutputItem, GenericResponseOutputItem, GenericResponseOutputItemContentAnnotation, OutputCodeInterpreterCall, @@ -62,21 +65,26 @@ from litellm.types.utils import ( Usage, ) +from .custom_tools import ( + convert_custom_tool_to_function_tool, + extract_custom_tool_names, + is_custom_tool_call, + unwrap_custom_tool_arguments, +) + ########### Initialize Classes used for Responses API ########### TOOL_CALLS_CACHE = InMemoryCache() class ChatCompletionSession(TypedDict, total=False): - messages: List[ - Union[ - AllMessageValues, - GenericChatCompletionMessage, - ChatCompletionMessageToolCall, - ChatCompletionResponseMessage, - Message, - ] + messages: list[ + AllMessageValues + | GenericChatCompletionMessage + | ChatCompletionMessageToolCall + | ChatCompletionResponseMessage + | Message ] - litellm_session_id: Optional[str] + litellm_session_id: str | None ########### End of Initialize Classes used for Responses API ########### @@ -109,7 +117,7 @@ class LiteLLMCompletionResponsesConfig: @staticmethod def _transform_tool_choice( tool_choice: Any, - ) -> Optional[Union[str, Dict[str, Any]]]: + ) -> str | dict[str, Any] | None: """ Transform tool_choice from various formats to OpenAI Chat Completion format. @@ -159,7 +167,7 @@ class LiteLLMCompletionResponsesConfig: return tool_choice @staticmethod - def _should_drop_derived_web_search_options(model: str, custom_llm_provider: Optional[str]) -> bool: + def _should_drop_derived_web_search_options(model: str, custom_llm_provider: str | None) -> bool: """ A Responses ``web_search`` built-in tool is derived into a ``web_search_options`` param. When the resolved provider/model does not support it (e.g. Bedrock Anthropic, where only @@ -169,7 +177,7 @@ class LiteLLMCompletionResponsesConfig: Support is read from each provider's own ``get_supported_openai_params`` so this bridge stays provider-agnostic; an unmapped provider (``None``) is treated as "keep". """ - supported_params: Optional[List[str]] = get_supported_openai_params( + supported_params: list[str] | None = get_supported_openai_params( model=model, custom_llm_provider=custom_llm_provider ) return supported_params is not None and "web_search_options" not in supported_params @@ -177,11 +185,11 @@ class LiteLLMCompletionResponsesConfig: @staticmethod def transform_responses_api_request_to_chat_completion_request( model: str, - input: Union[str, ResponseInputParam], + input: str | ResponseInputParam, responses_api_request: ResponsesAPIOptionalRequestParams, - custom_llm_provider: Optional[str] = None, - stream: Optional[bool] = None, - extra_headers: Optional[Dict[str, Any]] = None, + custom_llm_provider: str | None = None, + stream: bool | None = None, + extra_headers: dict[str, Any] | None = None, **kwargs, ) -> dict: """ @@ -205,7 +213,7 @@ class LiteLLMCompletionResponsesConfig: response_format = LiteLLMCompletionResponsesConfig._transform_text_format_to_response_format(text_param) # Extract reasoning_effort from reasoning parameter - reasoning_effort: Optional[Union[Reasoning, str]] = None + reasoning_effort: Reasoning | str | None = None reasoning_param = responses_api_request.get("reasoning") if reasoning_param: if isinstance(reasoning_param, dict): @@ -255,7 +263,7 @@ class LiteLLMCompletionResponsesConfig: "include_usage": True, } litellm_completion_request["stream_options"] = stream_options - litellm_logging_obj: Optional[LiteLLMLoggingObj] = kwargs.get("litellm_logging_obj") + litellm_logging_obj: LiteLLMLoggingObj | None = kwargs.get("litellm_logging_obj") if litellm_logging_obj: litellm_logging_obj.stream_options = stream_options @@ -265,28 +273,24 @@ class LiteLLMCompletionResponsesConfig: @staticmethod def transform_responses_api_input_to_messages( - input: Union[str, ResponseInputParam], - responses_api_request: Union[ResponsesAPIOptionalRequestParams, dict], - ) -> List[ - Union[ - AllMessageValues, - GenericChatCompletionMessage, - ChatCompletionMessageToolCall, - ChatCompletionResponseMessage, - Message, - ] + input: str | ResponseInputParam, + responses_api_request: ResponsesAPIOptionalRequestParams | dict, + ) -> list[ + AllMessageValues + | GenericChatCompletionMessage + | ChatCompletionMessageToolCall + | ChatCompletionResponseMessage + | Message ]: """ Transform a Responses API input into a list of messages """ - messages: List[ - Union[ - AllMessageValues, - GenericChatCompletionMessage, - ChatCompletionMessageToolCall, - ChatCompletionResponseMessage, - Message, - ] + messages: list[ + AllMessageValues + | GenericChatCompletionMessage + | ChatCompletionMessageToolCall + | ChatCompletionResponseMessage + | Message ] = [] if responses_api_request.get("instructions"): messages.append( @@ -373,31 +377,24 @@ class LiteLLMCompletionResponsesConfig: @staticmethod def _transform_response_input_param_to_chat_completion_message( - input: Union[str, ResponseInputParam], - ) -> List[ - Union[ - AllMessageValues, - GenericChatCompletionMessage, - ChatCompletionMessageToolCall, - ChatCompletionResponseMessage, - ] + input: str | ResponseInputParam, + ) -> list[ + AllMessageValues | GenericChatCompletionMessage | ChatCompletionMessageToolCall | ChatCompletionResponseMessage ]: """ Transform a ResponseInputParam into a Chat Completion message """ - messages: List[ - Union[ - AllMessageValues, - GenericChatCompletionMessage, - ChatCompletionMessageToolCall, - ChatCompletionResponseMessage, - ] + messages: list[ + AllMessageValues + | GenericChatCompletionMessage + | ChatCompletionMessageToolCall + | ChatCompletionResponseMessage ] = [] if isinstance(input, str): messages.append(ChatCompletionUserMessage(role="user", content=input)) elif isinstance(input, list): - existing_tool_call_ids: Set[str] = set() + existing_tool_call_ids: set[str] = set() for _input in input: chat_completion_messages = ( LiteLLMCompletionResponsesConfig._transform_responses_api_input_item_to_chat_completion_message( @@ -449,7 +446,7 @@ class LiteLLMCompletionResponsesConfig: if not chat_completion_messages: continue - deduped_in_place: List[Any] = [] + deduped_in_place: list[Any] = [] for m in chat_completion_messages: role = "" if isinstance(m, dict): @@ -491,36 +488,27 @@ class LiteLLMCompletionResponsesConfig: @staticmethod def _deduplicate_tool_call_output_messages( - tool_call_output_messages: List[ - Union[ - AllMessageValues, - GenericChatCompletionMessage, - ChatCompletionMessageToolCall, - ChatCompletionResponseMessage, - ] + tool_call_output_messages: list[ + AllMessageValues + | GenericChatCompletionMessage + | ChatCompletionMessageToolCall + | ChatCompletionResponseMessage ], - existing_tool_call_ids: Set[str], - ) -> List[ - Union[ - AllMessageValues, - GenericChatCompletionMessage, - ChatCompletionMessageToolCall, - ChatCompletionResponseMessage, - ] + existing_tool_call_ids: set[str], + ) -> list[ + AllMessageValues | GenericChatCompletionMessage | ChatCompletionMessageToolCall | ChatCompletionResponseMessage ]: """Return tool call outputs after dropping assistant entries with duplicate call_ids.""" if not tool_call_output_messages: return [] - filtered_messages: List[ - Union[ - AllMessageValues, - GenericChatCompletionMessage, - ChatCompletionMessageToolCall, - ChatCompletionResponseMessage, - ] + filtered_messages: list[ + AllMessageValues + | GenericChatCompletionMessage + | ChatCompletionMessageToolCall + | ChatCompletionResponseMessage ] = [] - seen_tool_call_ids: Set[str] = set(existing_tool_call_ids) + seen_tool_call_ids: set[str] = set(existing_tool_call_ids) for tool_call_message in tool_call_output_messages: if isinstance(tool_call_message, dict): @@ -563,7 +551,7 @@ class LiteLLMCompletionResponsesConfig: @staticmethod def _ensure_tool_call_output_has_corresponding_tool_call( - messages: List[Union[AllMessageValues, GenericChatCompletionMessage]], + messages: list[AllMessageValues | GenericChatCompletionMessage], ) -> bool: """ If any tool call output is present, ensure there is a corresponding tool call/tool_use block @@ -574,7 +562,7 @@ class LiteLLMCompletionResponsesConfig: return False @staticmethod - def _find_previous_assistant_idx(messages: List[Any], current_idx: int) -> Optional[int]: + def _find_previous_assistant_idx(messages: list[Any], current_idx: int) -> int | None: """Find the index of the previous assistant message.""" for j in range(current_idx - 1, -1, -1): if messages[j].get("role") == "assistant": @@ -600,7 +588,7 @@ class LiteLLMCompletionResponsesConfig: return "" @staticmethod - def _get_tool_calls_list(assistant_message: Any) -> List[Any]: + def _get_tool_calls_list(assistant_message: Any) -> list[Any]: """Extract tool_calls as a list from assistant message.""" tool_calls_raw = ( assistant_message.get("tool_calls") @@ -616,10 +604,10 @@ class LiteLLMCompletionResponsesConfig: return [] @staticmethod - def _check_tool_call_exists(tool_calls: List[Any], tool_call_id: str) -> bool: + def _check_tool_call_exists(tool_calls: list[Any], tool_call_id: str) -> bool: """Check if a tool_call with the given ID exists in the list.""" for tool_call in tool_calls: - tool_call_id_to_check: Optional[str] = None + tool_call_id_to_check: str | None = None if isinstance(tool_call, dict): tool_call_id_to_check = tool_call.get("id") elif hasattr(tool_call, "id"): @@ -629,7 +617,7 @@ class LiteLLMCompletionResponsesConfig: return False @staticmethod - def _reconstruct_tool_call_from_tools(tool_call_id: str, tools: List[Any]) -> Optional[Dict[str, Any]]: + def _reconstruct_tool_call_from_tools(tool_call_id: str, tools: list[Any]) -> dict[str, Any] | None: """Reconstruct a minimal tool_call definition from tools list.""" for tool in tools: if isinstance(tool, dict): @@ -668,13 +656,13 @@ class LiteLLMCompletionResponsesConfig: @staticmethod def _create_tool_call_chunk( - tool_use_definition: Dict[str, Any], tool_call_id: str, index: int + tool_use_definition: dict[str, Any], tool_call_id: str, index: int ) -> ChatCompletionToolCallChunk: """Create a ChatCompletionToolCallChunk from tool_use_definition.""" function_raw = LiteLLMCompletionResponsesConfig._get_mapping_or_attr_value(tool_use_definition, "function") function_name_raw = LiteLLMCompletionResponsesConfig._get_mapping_or_attr_value(function_raw, "name") function_arguments_raw = LiteLLMCompletionResponsesConfig._get_mapping_or_attr_value(function_raw, "arguments") - function: Dict[str, Any] = { + function: dict[str, Any] = { "name": function_name_raw or "", "arguments": function_arguments_raw or "{}", } @@ -693,7 +681,7 @@ class LiteLLMCompletionResponsesConfig: ) @staticmethod - def _normalize_tool_use_definition(tool_use_definition: Any, tool_call_id: str) -> Optional[Dict[str, Any]]: + def _normalize_tool_use_definition(tool_use_definition: Any, tool_call_id: str) -> dict[str, Any] | None: """ Normalize cached tool_call definitions to a dict-like shape consumed by _create_tool_call_chunk. """ @@ -701,7 +689,7 @@ class LiteLLMCompletionResponsesConfig: return None if isinstance(tool_use_definition, dict): - normalized_definition: Dict[str, Any] = dict(tool_use_definition) + normalized_definition: dict[str, Any] = dict(tool_use_definition) else: tool_use_id_raw = LiteLLMCompletionResponsesConfig._get_mapping_or_attr_value(tool_use_definition, "id") tool_use_type_raw = LiteLLMCompletionResponsesConfig._get_mapping_or_attr_value(tool_use_definition, "type") @@ -737,7 +725,7 @@ class LiteLLMCompletionResponsesConfig: def _add_tool_call_to_assistant(assistant_message: Any, tool_call_chunk: ChatCompletionToolCallChunk) -> None: """Add a tool_call to an assistant message.""" if isinstance(assistant_message, dict): - prev_assistant_dict = cast(Dict[str, Any], assistant_message) + prev_assistant_dict = cast(dict[str, Any], assistant_message) if "tool_calls" not in prev_assistant_dict: prev_assistant_dict["tool_calls"] = [] tool_calls_list = prev_assistant_dict["tool_calls"] @@ -752,23 +740,19 @@ class LiteLLMCompletionResponsesConfig: @staticmethod def _ensure_tool_results_have_corresponding_tool_calls( messages: Sequence[ - Union[ - AllMessageValues, - GenericChatCompletionMessage, - ChatCompletionResponseMessage, - ChatCompletionMessageToolCall, - Message, - ] + AllMessageValues + | GenericChatCompletionMessage + | ChatCompletionResponseMessage + | ChatCompletionMessageToolCall + | Message ], - tools: Optional[List[Any]] = None, - ) -> List[ - Union[ - AllMessageValues, - GenericChatCompletionMessage, - ChatCompletionResponseMessage, - ChatCompletionMessageToolCall, - Message, - ] + tools: list[Any] | None = None, + ) -> list[ + AllMessageValues + | GenericChatCompletionMessage + | ChatCompletionResponseMessage + | ChatCompletionMessageToolCall + | Message ]: """ Ensure that tool_result messages have corresponding tool_calls in the previous assistant message. @@ -789,14 +773,12 @@ class LiteLLMCompletionResponsesConfig: # Create a deep copy to avoid modifying the original (use list() so we can mutate and return List) import copy - fixed_messages: List[ - Union[ - AllMessageValues, - GenericChatCompletionMessage, - ChatCompletionResponseMessage, - ChatCompletionMessageToolCall, - Message, - ] + fixed_messages: list[ + AllMessageValues + | GenericChatCompletionMessage + | ChatCompletionResponseMessage + | ChatCompletionMessageToolCall + | Message ] = list(copy.deepcopy(messages)) messages_to_remove = [] @@ -828,7 +810,7 @@ class LiteLLMCompletionResponsesConfig: # Type-safe way to set tool_call_id on tool message if isinstance(message, dict): # Cast to dict to allow setting tool_call_id - message_dict = cast(Dict[str, Any], message) + message_dict = cast(dict[str, Any], message) message_dict["tool_call_id"] = tool_call_id elif hasattr(message, "tool_call_id"): setattr(message, "tool_call_id", tool_call_id) @@ -881,13 +863,7 @@ class LiteLLMCompletionResponsesConfig: @staticmethod def _transform_responses_api_input_item_to_chat_completion_message( input_item: Any, - ) -> List[ - Union[ - AllMessageValues, - GenericChatCompletionMessage, - ChatCompletionResponseMessage, - ] - ]: + ) -> list[AllMessageValues | GenericChatCompletionMessage | ChatCompletionResponseMessage]: """ Transform a Responses API input item into a Chat Completion message @@ -937,6 +913,7 @@ class LiteLLMCompletionResponsesConfig: """ return input_item.get("type") in [ "function_call_output", + "custom_tool_call_output", "web_search_call", "computer_call_output", "tool_result", # Anthropic/MCP format @@ -945,20 +922,16 @@ class LiteLLMCompletionResponsesConfig: @staticmethod def _is_input_item_function_call(input_item: Any) -> bool: """ - Check if the input item is a function call + Check if the input item is a function call or custom tool call. + Both need to be reconstructed as assistant tool_calls for Chat + Completions providers. """ - return input_item.get("type") == "function_call" + return input_item.get("type") in ("function_call", "custom_tool_call") @staticmethod def _transform_responses_api_tool_call_output_to_chat_completion_message( - tool_call_output: Dict[str, Any], - ) -> List[ - Union[ - AllMessageValues, - GenericChatCompletionMessage, - ChatCompletionResponseMessage, - ] - ]: + tool_call_output: dict[str, Any], + ) -> list[AllMessageValues | GenericChatCompletionMessage | ChatCompletionResponseMessage]: """ ChatCompletionToolMessage is used to indicate the output from a tool call """ @@ -992,8 +965,8 @@ class LiteLLMCompletionResponsesConfig: # Some adapters represent tool output as a list of "input_*" parts if isinstance(output, list): - normalized_blocks: List[Dict[str, Any]] = [] - text_acc: List[str] = [] + normalized_blocks: list[dict[str, Any]] = [] + text_acc: list[str] = [] for part in output: if not isinstance(part, dict): continue @@ -1093,14 +1066,8 @@ class LiteLLMCompletionResponsesConfig: @staticmethod def _transform_responses_api_function_call_to_chat_completion_message( - function_call: Dict[str, Any], - ) -> List[ - Union[ - AllMessageValues, - GenericChatCompletionMessage, - ChatCompletionResponseMessage, - ] - ]: + function_call: dict[str, Any], + ) -> list[AllMessageValues | GenericChatCompletionMessage | ChatCompletionResponseMessage]: """ Transform a Responses API function_call into a Chat Completion message with tool calls @@ -1117,13 +1084,19 @@ class LiteLLMCompletionResponsesConfig: } ``` """ - # Create a tool call for the function call + # Create a tool call for the function call. Custom tool calls + # store their payload in "input" (raw string) rather than + # "arguments" (JSON string), so normalize to arguments here. + raw_arguments = function_call.get("arguments") + if not raw_arguments and function_call.get("type") == "custom_tool_call": + raw_input = function_call.get("input") or "" + raw_arguments = json.dumps({"content": raw_input}) if raw_input else "" tool_call = ChatCompletionToolCallChunk( id=function_call.get("call_id") or function_call.get("id") or "", type="function", function=ChatCompletionToolCallFunctionChunk( name=function_call.get("name") or "", - arguments=str(function_call.get("arguments") or ""), + arguments=str(raw_arguments or ""), ), index=0, ) @@ -1138,7 +1111,7 @@ class LiteLLMCompletionResponsesConfig: return [chat_completion_response_message] @staticmethod - def _resolve_file_id(item: Dict[str, Any]) -> Optional[str]: + def _resolve_file_id(item: dict[str, Any]) -> str | None: """ Return the effective file_id for a Responses API input_file item. Explicit file_id takes precedence; file_url is used as fallback so @@ -1147,7 +1120,7 @@ class LiteLLMCompletionResponsesConfig: return item.get("file_id") or item.get("file_url") or None @staticmethod - def _transform_input_file_item_to_file_item(item: Dict[str, Any]) -> Dict[str, Any]: + def _transform_input_file_item_to_file_item(item: dict[str, Any]) -> dict[str, Any]: """ Transform a Responses API input_file item to a Chat Completion file item @@ -1157,21 +1130,21 @@ class LiteLLMCompletionResponsesConfig: Returns: Dictionary with transformed file structure for Chat Completion """ - file_dict: Dict[str, Any] = {} + file_dict: dict[str, Any] = {} file_id = LiteLLMCompletionResponsesConfig._resolve_file_id(item) if file_id: file_dict["file_id"] = file_id if item.get("file_data"): file_dict["file_data"] = item["file_data"] - new_item: Dict[str, Any] = {"type": "file", "file": file_dict} + new_item: dict[str, Any] = {"type": "file", "file": file_dict} if "cache_control" in item: new_item["cache_control"] = item["cache_control"] return new_item @staticmethod def _transform_input_image_item_to_image_item( - item: Dict[str, Any], + item: dict[str, Any], ) -> ChatCompletionImageObject: """ Transform a Responses API input_image item to a Chat Completion image item @@ -1185,7 +1158,7 @@ class LiteLLMCompletionResponsesConfig: @staticmethod def _transform_responses_api_content_to_chat_completion_content( content: Any, - ) -> Union[str, List[Union[str, Dict[str, Any]]]]: + ) -> str | list[str | dict[str, Any]]: """ Transform a Responses API content into a Chat Completion content @@ -1199,7 +1172,7 @@ class LiteLLMCompletionResponsesConfig: elif isinstance(content, str): return content elif isinstance(content, list): - content_list: List[Union[str, Dict[str, Any]]] = [] + content_list: list[str | dict[str, Any]] = [] for item in content: if isinstance(item, str): content_list.append(item) @@ -1220,7 +1193,7 @@ class LiteLLMCompletionResponsesConfig: text_value = item.get("text") if text_value is None: continue - content_block: Dict[str, Any] = { + content_block: dict[str, Any] = { "type": LiteLLMCompletionResponsesConfig._get_chat_completion_request_content_type( item.get("type") or "text" ), @@ -1268,7 +1241,7 @@ class LiteLLMCompletionResponsesConfig: @staticmethod def transform_instructions_to_system_message( - instructions: Optional[str], + instructions: str | None, ) -> ChatCompletionSystemMessage: """ Transform a Instructions into a system message @@ -1277,18 +1250,18 @@ class LiteLLMCompletionResponsesConfig: @staticmethod def transform_responses_api_tools_to_chat_completion_tools( - tools: Optional[List[Union[FunctionToolParam, OpenAIMcpServerTool]]], - ) -> Tuple[ - List[Union[ChatCompletionToolParam, OpenAIMcpServerTool]], - Optional[OpenAIWebSearchOptions], + tools: list[FunctionToolParam | OpenAIMcpServerTool] | None, + ) -> tuple[ + list[ChatCompletionToolParam | OpenAIMcpServerTool], + OpenAIWebSearchOptions | None, ]: """ Transform a Responses API tools into a Chat Completion tools """ if tools is None: return [], None - chat_completion_tools: List[Union[ChatCompletionToolParam, OpenAIMcpServerTool]] = [] - web_search_options: Optional[OpenAIWebSearchOptions] = None + chat_completion_tools: list[ChatCompletionToolParam | OpenAIMcpServerTool] = [] + web_search_options: OpenAIWebSearchOptions | None = None for tool in tools: if tool.get("type") == "mcp": chat_completion_tools.append(cast(OpenAIMcpServerTool, tool)) @@ -1296,8 +1269,8 @@ class LiteLLMCompletionResponsesConfig: _search_context_size: Literal["low", "medium", "high"] = cast( Literal["low", "medium", "high"], tool.get("search_context_size") ) - _user_location: Optional[OpenAIWebSearchUserLocation] = cast( - Optional[OpenAIWebSearchUserLocation], + _user_location: OpenAIWebSearchUserLocation | None = cast( + OpenAIWebSearchUserLocation | None, tool.get("user_location") or None, ) web_search_options = OpenAIWebSearchOptions( @@ -1310,7 +1283,7 @@ class LiteLLMCompletionResponsesConfig: parameters = dict(typed_tool.get("parameters", {}) or {}) if not parameters or "type" not in parameters: parameters["type"] = "object" - chat_completion_tool: Dict[str, Any] = { + chat_completion_tool: dict[str, Any] = { "type": "function", "function": { "name": typed_tool.get("name") or "", @@ -1328,14 +1301,30 @@ class LiteLLMCompletionResponsesConfig: if tool.get("input_examples"): chat_completion_tool["input_examples"] = tool.get("input_examples") # type: ignore chat_completion_tools.append(cast(ChatCompletionToolParam, chat_completion_tool)) + elif tool.get("type") == "custom": + converted = convert_custom_tool_to_function_tool(tool) + if converted is not None: + chat_completion_tools.append(converted) else: - chat_completion_tools.append(cast(Union[ChatCompletionToolParam, OpenAIMcpServerTool], tool)) + _tool_type = tool.get("type") + if _tool_type in ("computer_use", "image_generation", "namespace", "shell"): + # Drop unsupported Responses-API-only tool types that have no + # Chat Completions equivalent. Passing them through verbatim + # causes providers to reject the request with "'function' is a + # required property". + verbose_logger.warning( + "Dropping Responses API tool of type '%s': it has no Chat Completions " + "equivalent and the target provider would reject the request.", + _tool_type, + ) + continue + chat_completion_tools.append(cast(ChatCompletionToolParam | OpenAIMcpServerTool, tool)) return chat_completion_tools, web_search_options @staticmethod def transform_chat_completion_tool_params_to_responses_api_tools( - chat_completion_tools: Optional[List[Union[ChatCompletionToolParam, OpenAIMcpServerTool]]], - ) -> List[Dict[str, Any]]: + chat_completion_tools: list[ChatCompletionToolParam | OpenAIMcpServerTool] | None, + ) -> list[dict[str, Any]]: """ Transform Chat Completion tool params (e.g. from guardrail output) back to Responses API request tool format. Inverse of @@ -1343,17 +1332,17 @@ class LiteLLMCompletionResponsesConfig: """ if chat_completion_tools is None or not chat_completion_tools: return [] - result: List[Dict[str, Any]] = [] + result: list[dict[str, Any]] = [] for tool in chat_completion_tools: if not isinstance(tool, dict): result.append(tool) # type: ignore continue if tool.get("type") == "function": - fn = cast(Dict[str, Any], tool.get("function") or {}) + fn = cast(dict[str, Any], tool.get("function") or {}) parameters = dict(fn.get("parameters", {}) or {}) if not parameters or "type" not in parameters: parameters["type"] = "object" - responses_tool: Dict[str, Any] = { + responses_tool: dict[str, Any] = { "type": "function", "name": fn.get("name") or "", "description": fn.get("description") or "", @@ -1377,11 +1366,16 @@ class LiteLLMCompletionResponsesConfig: @staticmethod def transform_chat_completion_tools_to_responses_tools( chat_completion_response: ModelResponse, - ) -> List[ResponseFunctionToolCall]: + responses_api_request: ResponsesAPIOptionalRequestParams | None = None, + ) -> list[ResponseFunctionToolCall | CustomToolCallOutputItem]: """ - Transform a Chat Completion tools into a Responses API tools + Transform a Chat Completion tools into a Responses API tools. + + For custom tools (e.g. apply_patch), returns CustomToolCallOutputItem + with ``type="custom_tool_call"``. For regular function tools, returns + ``ResponseFunctionToolCall`` with ``type="function_call"``. """ - all_chat_completion_tools: List[ChatCompletionMessageToolCall] = [] + all_chat_completion_tools: list[ChatCompletionMessageToolCall] = [] for choice in chat_completion_response.choices: if isinstance(choice, Choices): if choice.message.tool_calls: @@ -1392,53 +1386,77 @@ class LiteLLMCompletionResponsesConfig: value=tool_call, ) - responses_tools: List[ResponseFunctionToolCall] = [] + # Extract custom tool names from the original request + custom_tool_names: set[str] = set() + if responses_api_request and "tools" in responses_api_request: + custom_tool_names = extract_custom_tool_names(responses_api_request["tools"]) + + responses_tools: list[ResponseFunctionToolCall | CustomToolCallOutputItem] = [] for tool in all_chat_completion_tools: if tool.type == "function": function_definition = tool.function - provider_specific_fields: Optional[Dict] = None - if hasattr(tool, "provider_specific_fields") and getattr(tool, "provider_specific_fields", None): - provider_specific_fields = getattr(tool, "provider_specific_fields") - if not isinstance(provider_specific_fields, dict): - provider_specific_fields = ( - dict(provider_specific_fields) # type: ignore - if hasattr(provider_specific_fields, "__dict__") - else {} - ) - elif hasattr(function_definition, "provider_specific_fields") and getattr( - function_definition, "provider_specific_fields", None - ): - provider_specific_fields = getattr(function_definition, "provider_specific_fields") - if not isinstance(provider_specific_fields, dict): - provider_specific_fields = ( - dict(provider_specific_fields) # type: ignore - if hasattr(provider_specific_fields, "__dict__") - else {} - ) + tool_name = function_definition.name or "" + tool_id = tool.id or "" + tool_arguments = function_definition.get("arguments") or "" - output_tool_call: ResponseFunctionToolCall = ResponseFunctionToolCall( - name=function_definition.name or "", - arguments=function_definition.get("arguments") or "", - call_id=tool.id or "", - id=tool.id or "", - type="function_call", # critical this is "function_call" to work with tools like openai codex - status=function_definition.get("status") or "completed", - ) + # Check if this is a custom tool + if is_custom_tool_call(tool_name, custom_tool_names): + # Build custom_tool_call output item + input_str = unwrap_custom_tool_arguments(tool_arguments) + custom_item = CustomToolCallOutputItem( + type="custom_tool_call", + call_id=tool_id, + id=tool_id, + name=tool_name, + input=input_str, + status=function_definition.get("status") or "completed", + ) + responses_tools.append(custom_item) + else: + # Build regular function_call output item + provider_specific_fields: dict | None = None + if hasattr(tool, "provider_specific_fields") and getattr(tool, "provider_specific_fields", None): + provider_specific_fields = getattr(tool, "provider_specific_fields") + if not isinstance(provider_specific_fields, dict): + provider_specific_fields = ( + dict(provider_specific_fields) # type: ignore + if hasattr(provider_specific_fields, "__dict__") + else {} + ) + elif hasattr(function_definition, "provider_specific_fields") and getattr( + function_definition, "provider_specific_fields", None + ): + provider_specific_fields = getattr(function_definition, "provider_specific_fields") + if not isinstance(provider_specific_fields, dict): + provider_specific_fields = ( + dict(provider_specific_fields) # type: ignore + if hasattr(provider_specific_fields, "__dict__") + else {} + ) - # Pass through provider_specific_fields as-is if present - if provider_specific_fields: - setattr( - output_tool_call, - "provider_specific_fields", - provider_specific_fields, - ) # type: ignore + output_tool_call: ResponseFunctionToolCall = ResponseFunctionToolCall( + name=tool_name, + arguments=tool_arguments, + call_id=tool_id, + id=tool_id, + type="function_call", + status=function_definition.get("status") or "completed", + ) - responses_tools.append(output_tool_call) + # Pass through provider_specific_fields as-is if present + if provider_specific_fields: + setattr( + output_tool_call, + "provider_specific_fields", + provider_specific_fields, + ) # type: ignore + + responses_tools.append(output_tool_call) return responses_tools @staticmethod def _map_chat_completion_finish_reason_to_responses_status( - finish_reason: Optional[str], + finish_reason: str | None, ) -> ResponsesAPIStatus: """ Map chat completion finish_reason to responses API status. @@ -1465,7 +1483,7 @@ class LiteLLMCompletionResponsesConfig: return "completed" @staticmethod - def _tool_call_id_from_responses_item(item_id: Optional[str], call_id: Optional[str]) -> str: + def _tool_call_id_from_responses_item(item_id: str | None, call_id: str | None) -> str: """Bedrock Mantle returns a non-unique, index-based ``call_id`` (``call_0``, ``call_1``, ... that resets every response) alongside a unique ``id`` (``fc_...``). ``call_id`` is the canonical Responses API correlation key, so @@ -1480,7 +1498,7 @@ class LiteLLMCompletionResponsesConfig: def convert_response_function_tool_call_to_chat_completion_tool_call( tool_call_item: Any, index: int = 0, - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """ Convert ResponseFunctionToolCall to ChatCompletionToolCallChunk format. @@ -1510,7 +1528,7 @@ class LiteLLMCompletionResponsesConfig: ) ) - function_dict: Dict[str, Any] = { + function_dict: dict[str, Any] = { "name": tool_call_item.name, "arguments": tool_call_item.arguments, } @@ -1518,7 +1536,7 @@ class LiteLLMCompletionResponsesConfig: if provider_specific_fields: function_dict["provider_specific_fields"] = provider_specific_fields - tool_call_dict: Dict[str, Any] = { + tool_call_dict: dict[str, Any] = { "id": LiteLLMCompletionResponsesConfig._tool_call_id_from_responses_item( getattr(tool_call_item, "id", None), getattr(tool_call_item, "call_id", None), @@ -1537,7 +1555,7 @@ class LiteLLMCompletionResponsesConfig: def convert_apply_patch_tool_call_to_chat_completion_tool_call( tool_call_item: Any, index: int = 0, - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """ Convert ResponseApplyPatchToolCall to ChatCompletionToolCallChunk format. @@ -1555,7 +1573,7 @@ class LiteLLMCompletionResponsesConfig: import json operation_dict = tool_call_item.operation.model_dump() - tool_call_dict: Dict[str, Any] = { + tool_call_dict: dict[str, Any] = { "id": tool_call_item.call_id, "function": { "name": "apply_patch", @@ -1568,9 +1586,9 @@ class LiteLLMCompletionResponsesConfig: @staticmethod def transform_chat_completion_response_to_responses_api_response( - request_input: Union[str, ResponseInputParam], + request_input: str | ResponseInputParam, responses_api_request: ResponsesAPIOptionalRequestParams, - chat_completion_response: Union[ModelResponse, dict], + chat_completion_response: ModelResponse | dict, ) -> ResponsesAPIResponse: """ Transform a Chat Completion response into a Responses API response @@ -1578,8 +1596,8 @@ class LiteLLMCompletionResponsesConfig: if isinstance(chat_completion_response, dict): chat_completion_response = ModelResponse(**chat_completion_response) # Get finish_reason from the first choice to determine overall status - finish_reason: Optional[str] = None - choices: List[Choices] = getattr(chat_completion_response, "choices", []) + finish_reason: str | None = None + choices: list[Choices] = getattr(chat_completion_response, "choices", []) if choices and len(choices) > 0: finish_reason = choices[0].finish_reason @@ -1595,6 +1613,7 @@ class LiteLLMCompletionResponsesConfig: output=LiteLLMCompletionResponsesConfig._transform_chat_completion_choices_to_responses_output( chat_completion_response=chat_completion_response, choices=getattr(chat_completion_response, "choices", []), + responses_api_request=responses_api_request, ), parallel_tool_calls=getattr(chat_completion_response, "parallel_tool_calls", False), temperature=getattr(chat_completion_response, "temperature", 0), @@ -1626,24 +1645,23 @@ class LiteLLMCompletionResponsesConfig: @staticmethod def _transform_chat_completion_choices_to_responses_output( chat_completion_response: ModelResponse, - choices: List[Choices], - ) -> List[ - Union[ - GenericResponseOutputItem, - OutputCodeInterpreterCall, - OutputFunctionToolCall, - OutputImageGenerationCall, - ResponseFunctionToolCall, - ] + choices: list[Choices], + responses_api_request: ResponsesAPIOptionalRequestParams | None = None, + ) -> list[ + GenericResponseOutputItem + | OutputCodeInterpreterCall + | OutputFunctionToolCall + | OutputImageGenerationCall + | ResponseFunctionToolCall + | CustomToolCallOutputItem ]: - responses_output: List[ - Union[ - GenericResponseOutputItem, - OutputCodeInterpreterCall, - OutputFunctionToolCall, - OutputImageGenerationCall, - ResponseFunctionToolCall, - ] + responses_output: list[ + GenericResponseOutputItem + | OutputCodeInterpreterCall + | OutputFunctionToolCall + | OutputImageGenerationCall + | ResponseFunctionToolCall + | CustomToolCallOutputItem ] = [] responses_output.extend( @@ -1654,7 +1672,8 @@ class LiteLLMCompletionResponsesConfig: ) responses_output.extend( LiteLLMCompletionResponsesConfig.transform_chat_completion_tools_to_responses_tools( - chat_completion_response=chat_completion_response + chat_completion_response=chat_completion_response, + responses_api_request=responses_api_request, ) ) @@ -1713,8 +1732,8 @@ class LiteLLMCompletionResponsesConfig: @staticmethod def _extract_reasoning_output_items( chat_completion_response: ModelResponse, - choices: List[Choices], - ) -> List[GenericResponseOutputItem]: + choices: list[Choices], + ) -> list[GenericResponseOutputItem]: for choice in choices: if hasattr(choice, "message") and choice.message: message = choice.message @@ -1743,7 +1762,7 @@ class LiteLLMCompletionResponsesConfig: def _extract_image_generation_output_items( chat_completion_response: ModelResponse, choice: Choices, - ) -> List[OutputImageGenerationCall]: + ) -> list[OutputImageGenerationCall]: """ Extract image generation outputs from a choice that contains images. @@ -1762,7 +1781,7 @@ class LiteLLMCompletionResponsesConfig: 'result': 'iVBORw0...' # Pure base64 without data: prefix } """ - image_generation_items: List[OutputImageGenerationCall] = [] + image_generation_items: list[OutputImageGenerationCall] = [] images = getattr(choice.message, "images", []) if not images: @@ -1789,7 +1808,7 @@ class LiteLLMCompletionResponsesConfig: @staticmethod def _map_finish_reason_to_image_generation_status( - finish_reason: Optional[str], + finish_reason: str | None, ) -> Literal["in_progress", "completed", "incomplete", "failed"]: """ Map finish_reason to image generation status. @@ -1808,7 +1827,7 @@ class LiteLLMCompletionResponsesConfig: return "completed" @staticmethod - def _extract_base64_from_data_url(data_url: str) -> Optional[str]: + def _extract_base64_from_data_url(data_url: str) -> str | None: """ Extract pure base64 string from a data URL. @@ -1834,9 +1853,9 @@ class LiteLLMCompletionResponsesConfig: @staticmethod def _extract_message_output_items( chat_completion_response: ModelResponse, - choices: List[Choices], - ) -> List[Union[GenericResponseOutputItem, OutputImageGenerationCall]]: - message_output_items: List[Union[GenericResponseOutputItem, OutputImageGenerationCall]] = [] + choices: list[Choices], + ) -> list[GenericResponseOutputItem | OutputImageGenerationCall]: + message_output_items: list[GenericResponseOutputItem | OutputImageGenerationCall] = [] for choice in choices: # Check if message has images (image generation) if hasattr(choice.message, "images") and choice.message.images: @@ -1868,20 +1887,8 @@ class LiteLLMCompletionResponsesConfig: @staticmethod def _transform_responses_api_outputs_to_chat_completion_messages( responses_api_output: ResponsesAPIResponse, - ) -> List[ - Union[ - AllMessageValues, - GenericChatCompletionMessage, - ChatCompletionMessageToolCall, - ] - ]: - messages: List[ - Union[ - AllMessageValues, - GenericChatCompletionMessage, - ChatCompletionMessageToolCall, - ] - ] = [] + ) -> list[AllMessageValues | GenericChatCompletionMessage | ChatCompletionMessageToolCall]: + messages: list[AllMessageValues | GenericChatCompletionMessage | ChatCompletionMessageToolCall] = [] output_items = responses_api_output.output for _output_item in output_items: output_item: dict = dict(_output_item) @@ -1939,9 +1946,9 @@ class LiteLLMCompletionResponsesConfig: @staticmethod def _transform_chat_completion_annotations_to_response_output_annotations( - annotations: Optional[List[ChatCompletionAnnotation]], - ) -> List[GenericResponseOutputItemContentAnnotation]: - response_output_annotations: List[GenericResponseOutputItemContentAnnotation] = [] + annotations: list[ChatCompletionAnnotation] | None, + ) -> list[GenericResponseOutputItemContentAnnotation]: + response_output_annotations: list[GenericResponseOutputItemContentAnnotation] = [] if annotations is None: return response_output_annotations @@ -1965,10 +1972,10 @@ class LiteLLMCompletionResponsesConfig: @staticmethod def _transform_chat_completion_usage_to_responses_usage( - chat_completion_response: Union[ModelResponse, Usage], + chat_completion_response: ModelResponse | Usage, ) -> ResponseAPIUsage: if isinstance(chat_completion_response, ModelResponse): - usage: Optional[Usage] = getattr(chat_completion_response, "usage", None) + usage: Usage | None = getattr(chat_completion_response, "usage", None) else: usage = chat_completion_response if usage is None: @@ -1991,7 +1998,7 @@ class LiteLLMCompletionResponsesConfig: # Translate prompt_tokens_details to input_tokens_details if hasattr(usage, "prompt_tokens_details") and usage.prompt_tokens_details is not None: prompt_details = usage.prompt_tokens_details - input_details_dict: Dict[str, int] = {} + input_details_dict: dict[str, int] = {} if hasattr(prompt_details, "cached_tokens") and prompt_details.cached_tokens is not None: input_details_dict["cached_tokens"] = prompt_details.cached_tokens @@ -2010,7 +2017,7 @@ class LiteLLMCompletionResponsesConfig: # Translate completion_tokens_details to output_tokens_details if hasattr(usage, "completion_tokens_details") and usage.completion_tokens_details is not None: completion_details = usage.completion_tokens_details - output_details_dict: Dict[str, int] = {} + output_details_dict: dict[str, int] = {} if hasattr(completion_details, "reasoning_tokens") and completion_details.reasoning_tokens is not None: output_details_dict["reasoning_tokens"] = completion_details.reasoning_tokens else: @@ -2029,8 +2036,8 @@ class LiteLLMCompletionResponsesConfig: @staticmethod def _transform_text_format_to_response_format( - text_param: Union[Dict[str, Any], Any], - ) -> Optional[Dict[str, Any]]: + text_param: dict[str, Any] | Any, + ) -> dict[str, Any] | None: """ Transform Responses API text.format parameter to Chat Completion response_format parameter. diff --git a/litellm/responses/mcp/litellm_proxy_mcp_handler.py b/litellm/responses/mcp/litellm_proxy_mcp_handler.py index 999945b3823..e03f0296109 100644 --- a/litellm/responses/mcp/litellm_proxy_mcp_handler.py +++ b/litellm/responses/mcp/litellm_proxy_mcp_handler.py @@ -16,8 +16,10 @@ from typing import ( from litellm._logging import verbose_logger from litellm.constants import MAXIMUM_TRACEBACK_LINES_TO_LOG from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj -from litellm.proxy._experimental.mcp_server.utils import split_server_prefix_from_name -from litellm.proxy.litellm_pre_call_utils import LiteLLMProxyRequestSetup +from litellm.proxy._experimental.mcp_server.utils import ( + split_server_prefix_from_name, + strip_known_server_prefix, +) from litellm.responses.main import aresponses from litellm.responses.streaming_iterator import BaseResponsesAPIStreamingIterator from litellm.types.llms.openai import ResponsesAPIResponse @@ -628,6 +630,9 @@ class LiteLLM_Proxy_MCP_Handler: from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( global_mcp_server_manager, ) + from litellm.proxy._experimental.mcp_server.server import ( + _resolve_display_name_to_original, + ) from litellm.proxy.proxy_server import proxy_logging_obj tool_results = [] @@ -654,11 +659,13 @@ class LiteLLM_Proxy_MCP_Handler: server_name = tool_server_map[tool_name] - # Remove the server name prefix if the tool name includes it. - sanitized_tool_name = tool_name - unprefixed_name, prefixed_server_name = split_server_prefix_from_name(tool_name) - if prefixed_server_name and prefixed_server_name == server_name and unprefixed_name: - sanitized_tool_name = unprefixed_name + mcp_server = global_mcp_server_manager.get_mcp_server_by_name( + server_name + ) or global_mcp_server_manager._get_mcp_server_from_tool_name(tool_name) + resolved_tool_name = ( + _resolve_display_name_to_original(tool_name, [mcp_server]) if mcp_server else tool_name + ) + sanitized_tool_name = strip_known_server_prefix(resolved_tool_name, mcp_server) start_time = datetime.now() logging_input = [ @@ -697,6 +704,10 @@ class LiteLLM_Proxy_MCP_Handler: if request_tags: logging_request_data["metadata"]["tags"] = request_tags if user_api_key_auth is not None: + from litellm.proxy.litellm_pre_call_utils import ( + LiteLLMProxyRequestSetup, + ) + LiteLLMProxyRequestSetup.add_user_api_key_auth_to_request_metadata( data=logging_request_data, user_api_key_dict=user_api_key_auth, @@ -741,7 +752,6 @@ class LiteLLM_Proxy_MCP_Handler: "arguments": parsed_arguments, "namespaced_tool_name": tool_name, } - mcp_server = global_mcp_server_manager._get_mcp_server_from_tool_name(tool_name) if mcp_server: mcp_info = mcp_server.mcp_info or {} standard_logging_mcp_tool_call["mcp_server_name"] = ( diff --git a/litellm/responses/streaming_iterator.py b/litellm/responses/streaming_iterator.py index 6df544dee3e..3618331f0f5 100644 --- a/litellm/responses/streaming_iterator.py +++ b/litellm/responses/streaming_iterator.py @@ -128,6 +128,9 @@ class BaseResponsesAPIStreamingIterator: self.finished = True return None + if self.logging_obj.completion_start_time is None: + self.logging_obj._update_completion_start_time(completion_start_time=datetime.now()) + try: # Parse the JSON chunk parsed_chunk = json.loads(chunk) @@ -284,11 +287,12 @@ class BaseResponsesAPIStreamingIterator: end_time = datetime.now() if is_async: asyncio.create_task( - self.logging_obj.async_success_handler( - result=logging_response, + self.logging_obj.dispatch_success_handlers( + logging_response, start_time=self.start_time, end_time=end_time, cache_hit=self._completed_response_cache_hit, + prefer_async_handlers=True, ) ) else: @@ -299,14 +303,13 @@ class BaseResponsesAPIStreamingIterator: end_time=end_time, cache_hit=self._completed_response_cache_hit, ) - - executor.submit( - self.logging_obj.success_handler, - result=logging_response, - cache_hit=self._completed_response_cache_hit, - start_time=self.start_time, - end_time=end_time, - ) + executor.submit( + self.logging_obj.success_handler, + result=logging_response, + cache_hit=self._completed_response_cache_hit, + start_time=self.start_time, + end_time=end_time, + ) self._run_post_success_hooks(end_time=end_time) def _handle_logging_completed_response(self): @@ -1133,7 +1136,6 @@ def _build_synthetic_response_events( # --------------------------------------------------------------------------- from litellm._logging import verbose_logger -from litellm.litellm_core_utils.thread_pool_executor import executor as _ws_executor RESPONSES_WS_LOGGED_EVENT_TYPES = [ "response.created", @@ -1248,8 +1250,7 @@ class ResponsesWebSocketStreaming: if self.input_messages: self.logging_obj.model_call_details["messages"] = self.input_messages if self.messages: - asyncio.create_task(self.logging_obj.async_success_handler(self.messages)) - _ws_executor.submit(self.logging_obj.success_handler, self.messages) + asyncio.create_task(self.logging_obj.dispatch_success_handlers(self.messages, prefer_async_handlers=True)) async def backend_to_client(self) -> None: """Forward events from backend WebSocket to the client.""" diff --git a/litellm/router.py b/litellm/router.py index 8c1f2021501..5ffe60c2da0 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -90,7 +90,12 @@ from litellm.router_utils.add_retry_fallback_headers import ( _HiddenParamsHost, add_fallback_headers_to_response, add_retry_headers_to_response, + apply_quality_router_decision_headers, + apply_remaining_usage_headers, + ensure_response_additional_headers, get_hidden_params_dict, + prepare_response_for_header_attachment, + response_in_flight_token_count, ) from litellm.router_utils.batch_utils import ( _get_router_metadata_variable_name, @@ -133,6 +138,12 @@ from litellm.router_utils.pre_call_checks.deployment_affinity_check import ( from litellm.router_utils.pre_call_checks.model_rate_limit_check import ( ModelRateLimitingCheck, ) +from litellm.router_utils.pre_call_checks.io_token_rate_limit_check import ( + build_io_token_rate_limit_headers, + deployment_has_io_token_limits, + refund_stale_reservation_before_retry, + set_io_token_rate_limit_request_kwargs, +) from litellm.router_utils.pre_call_checks.prompt_caching_deployment_check import ( PromptCachingDeploymentCheck, ) @@ -845,6 +856,8 @@ class Router: router_cache=self.cache, routing_args={}, ) + case _: + pass if selector is not None and register_callbacks and isinstance(litellm.callbacks, list): litellm.logging_callback_manager.add_litellm_callback(selector) # type: ignore @@ -1647,6 +1660,7 @@ class Router: ) thread.start() + kwargs.setdefault("messages", messages) self._update_kwargs_with_deployment(deployment=deployment, kwargs=kwargs) kwargs.pop("silent_model", None) # Ensure it's not in kwargs either model_name = litellm_params["model"] @@ -2347,7 +2361,20 @@ class Router: return self async def __anext__(self): - chunk = await self._async_generator.__anext__() + try: + chunk = await self._async_generator.__anext__() + except StopAsyncIteration: + # The inner generator is exhausted. If we never sniffed a + # terminal event off a chunk (the bridge path emits the + # final response.completed via common_done_event_logic, + # which raises StopAsyncIteration after returning it), + # fall back to whatever the source iterator latched so + # the proxy's container-ownership hook still sees a + # completed_response instead of logging a spurious + # "no completed_response" warning. + if self.completed_response is None: + self.completed_response = getattr(source_iterator, "completed_response", None) + raise # Sniff the terminal stream event off each forwarded chunk # so ``self.completed_response`` is populated regardless of # which inner iterator produced it (source_iterator, @@ -2670,6 +2697,7 @@ class Router: ) ) + kwargs.setdefault("messages", messages) self._update_kwargs_with_deployment(deployment=deployment, kwargs=kwargs) kwargs.pop("silent_model", None) # Ensure it's not in kwargs either @@ -2944,6 +2972,13 @@ class Router: } ) + # A retry/fallback reuses this same kwargs dict for the next deployment. + # Refund and clear any reservation the previous deployment attempt left + # here before it's wiped below, instead of relying on that attempt's + # (possibly still-pending) failure event to do it. + refund_stale_reservation_before_retry(self.cache, kwargs) + set_io_token_rate_limit_request_kwargs(kwargs) + ## DEPLOYMENT-LEVEL TAGS deployment_tags = deployment.get("litellm_params", {}).get("tags") if deployment_tags: @@ -6751,7 +6786,13 @@ class Router: deployment_id=id, ) - ## if all are none, return - no need to track current tpm/rpm usage for models with no tpm/rpm set + deployment_dict = deployment_info if isinstance(deployment_info, dict) else deployment_info.model_dump() + has_io_token_limits = deployment_has_io_token_limits(deployment_dict) + + ## Nothing to track only when neither tpm/rpm nor itpm/otpm limits are + ## set. IO deployments still record TPM/RPM usage here so TPM-aware + ## routing strategies see their real load in mixed model groups; their + ## itpm/otpm enforcement runs separately in ModelRateLimitingCheck. if ( tpm is None and rpm is None @@ -6759,6 +6800,7 @@ class Router: and rpm_litellm_params is None and tpm_model_info is None and rpm_model_info is None + and not has_io_token_limits ): return @@ -7352,8 +7394,7 @@ class Router: # deployment sharing the same backend model name. # Each deployment's full pricing is already stored under its # unique model_id above. - _custom_pricing_fields = CustomPricingLiteLLMParams.model_fields.keys() - _shared_model_info = {k: v for k, v in _model_info.items() if k not in _custom_pricing_fields} + _shared_model_info = CustomPricingLiteLLMParams.strip_custom_pricing_fields(_model_info) _existing_shared_mode = (cast(Optional[dict], litellm.model_cost.get(_model_name, {})) or {}).get("mode") _deployment_mode = _shared_model_info.get("mode") # Keep the built-in bridge mode stable for shared backend keys. @@ -8017,8 +8058,7 @@ class Router: # deployment sharing the same backend model name. # Each deployment's full pricing is already stored under its # unique model_id above (when present). - _custom_pricing_fields = CustomPricingLiteLLMParams.model_fields.keys() - _shared_model_info = {k: v for k, v in _model_info_dict.items() if k not in _custom_pricing_fields} + _shared_model_info = CustomPricingLiteLLMParams.strip_custom_pricing_fields(_model_info_dict) _backend_alias_cost = {_model_name: _shared_model_info} if "responses/" in _model_name: _stripped_model_name = _model_name.replace("responses/", "") @@ -8595,6 +8635,8 @@ class Router: total_tpm: Optional[int] = None total_rpm: Optional[int] = None + total_itpm: Optional[int] = None + total_otpm: Optional[int] = None configurable_clientside_auth_params: CONFIGURABLE_CLIENTSIDE_AUTH_PARAMS = None model_list = self.get_model_list(model_name=model_group) if model_list is None: @@ -8635,6 +8677,18 @@ class Router: if _deployment_rpm is None: _deployment_rpm = model_info_dict.get("rpm", None) # type: ignore + _deployment_itpm: Optional[int] = model.get("itpm") # type: ignore + if _deployment_itpm is None: + _deployment_itpm = model_litellm_params.get("itpm", None) # type: ignore + if _deployment_itpm is None: + _deployment_itpm = model_info_dict.get("itpm", None) # type: ignore + + _deployment_otpm: Optional[int] = model.get("otpm") # type: ignore + if _deployment_otpm is None: + _deployment_otpm = model_litellm_params.get("otpm", None) # type: ignore + if _deployment_otpm is None: + _deployment_otpm = model_info_dict.get("otpm", None) # type: ignore + # get model info try: model_id = model_info_dict.get("id", None) @@ -8775,6 +8829,16 @@ class Router: if total_rpm is None: total_rpm = 0 total_rpm += _deployment_rpm # type: ignore + + if _deployment_itpm is not None: + if total_itpm is None: + total_itpm = 0 + total_itpm += _deployment_itpm # type: ignore + + if _deployment_otpm is not None: + if total_otpm is None: + total_otpm = 0 + total_otpm += _deployment_otpm # type: ignore if model_group_info is not None: ## UPDATE WITH TOTAL TPM/RPM FOR MODEL GROUP if total_tpm is not None: @@ -8783,6 +8847,12 @@ class Router: if total_rpm is not None: model_group_info.rpm = total_rpm + if total_itpm is not None: + model_group_info.itpm = total_itpm + + if total_otpm is not None: + model_group_info.otpm = total_otpm + ## UPDATE WITH CONFIGURABLE CLIENTSIDE AUTH PARAMS FOR MODEL GROUP if configurable_clientside_auth_params is not None: model_group_info.configurable_clientside_auth_params = configurable_clientside_auth_params @@ -8885,6 +8955,58 @@ class Router: rpm_usage += t return tpm_usage, rpm_usage + async def get_model_group_io_token_usage(self, model_group: str) -> tuple[Optional[int], Optional[int]]: + """ + Returns current ITPM/OTPM usage for a model group (sum across deployments). + """ + dt = get_utc_datetime() + current_minute = dt.strftime("%H-%M") + itpm_keys: list[str] = [] + otpm_keys: list[str] = [] + + model_list = self.get_model_list(model_name=model_group) + if model_list is None: + return None, None + + for model in model_list: + model_id: Optional[str] = model.get("model_info", {}).get("id") # type: ignore + litellm_model: Optional[str] = model["litellm_params"].get("model") + if model_id is None or litellm_model is None: + continue + itpm_keys.append( + RouterCacheEnum.ITPM.value.format( + id=model_id, + model=litellm_model, + current_minute=current_minute, + ) + ) + otpm_keys.append( + RouterCacheEnum.OTPM.value.format( + id=model_id, + model=litellm_model, + current_minute=current_minute, + ) + ) + + combined_values = await self.cache.async_batch_get_cache(keys=itpm_keys + otpm_keys) + if combined_values is None: + return None, None + + itpm_values = combined_values[: len(itpm_keys)] + otpm_values = combined_values[len(itpm_keys) :] + + total_itpm: Optional[int] = None + for value in itpm_values: + if isinstance(value, int): + total_itpm = (total_itpm or 0) + value + + total_otpm: Optional[int] = None + for value in otpm_values: + if isinstance(value, int): + total_otpm = (total_otpm or 0) + value + + return total_itpm, total_otpm + @lru_cache(maxsize=DEFAULT_MAX_LRU_CACHE_SIZE) def _cached_get_model_group_info(self, model_group: str) -> Optional[ModelGroupInfo]: """ @@ -8894,25 +9016,33 @@ class Router: """ return self.get_model_group_info(model_group) - async def get_remaining_model_group_usage(self, model_group: str) -> Dict[str, int]: + async def get_remaining_model_group_usage(self, model_group: str) -> dict[str, int]: model_group_info = self._cached_get_model_group_info(model_group) - if model_group_info is not None and model_group_info.tpm is not None: - tpm_limit = model_group_info.tpm - else: - tpm_limit = None + returned_dict: dict[str, int] = {} - if model_group_info is not None and model_group_info.rpm is not None: - rpm_limit = model_group_info.rpm - else: - rpm_limit = None + # ITPM/OTPM groups emit input/output token headers, but they may also set + # tpm/rpm, so build both sets rather than returning early - clients and + # prometheus gauges that read the standard headers still get data. + if model_group_info is not None and (model_group_info.itpm is not None or model_group_info.otpm is not None): + current_itpm, current_otpm = await self.get_model_group_io_token_usage(model_group) + returned_dict.update( + build_io_token_rate_limit_headers( + itpm_limit=model_group_info.itpm, + otpm_limit=model_group_info.otpm, + current_itpm=current_itpm, + current_otpm=current_otpm, + ) + ) + + tpm_limit = model_group_info.tpm if model_group_info is not None else None + rpm_limit = model_group_info.rpm if model_group_info is not None else None if tpm_limit is None and rpm_limit is None: - return {} + return returned_dict current_tpm, current_rpm = await self.get_model_group_usage(model_group) - returned_dict = {} if tpm_limit is not None: returned_dict["x-ratelimit-remaining-tokens"] = tpm_limit - (current_tpm or 0) returned_dict["x-ratelimit-limit-tokens"] = tpm_limit @@ -8935,69 +9065,25 @@ class Router: # - if healthy_deployments > 1, return model group rate limit headers # - else return the model's rate limit headers """ - if response is not None and hasattr(response, "_hidden_params"): - hidden_params = getattr(response, "_hidden_params", {}) or {} - if hasattr(hidden_params, "model_dump"): - hidden_params = hidden_params.model_dump() - if not isinstance(hidden_params, dict): - return response - response._hidden_params = hidden_params + response = prepare_response_for_header_attachment(response) + if response is None: + return response - additional_headers = hidden_params.get("additional_headers") - if not isinstance(additional_headers, dict): - additional_headers = {} - hidden_params["additional_headers"] = additional_headers - additional_headers["x-litellm-model-group"] = model_group + additional_headers = ensure_response_additional_headers(response) + additional_headers["x-litellm-model-group"] = model_group + apply_quality_router_decision_headers(additional_headers, request_kwargs) - # Lift QualityRouter routing decision into response headers for - # transparency. The decision is stashed in request_kwargs.metadata - # by QualityRouter.async_pre_routing_hook. - metadata = (request_kwargs.get("metadata") or {}) if isinstance(request_kwargs, dict) else {} - decision = metadata.get("quality_router_decision") if isinstance(metadata, dict) else None - if isinstance(decision, dict): - # Only emit headers for fields that have a meaningful value. - # `complexity_tier` and `matched_keyword` are mutually exclusive - # (the keyword path short-circuits classification), so each - # request emits one or the other but not both. - if decision.get("routed_model") is not None: - additional_headers["x-litellm-quality-router-model"] = str(decision["routed_model"]) - if decision.get("quality_tier") is not None: - additional_headers["x-litellm-quality-router-tier"] = str(decision["quality_tier"]) - if decision.get("routed_via") is not None: - additional_headers["x-litellm-quality-router-via"] = str(decision["routed_via"]) - if decision.get("matched_keyword") is not None: - additional_headers["x-litellm-quality-router-keyword"] = str(decision["matched_keyword"]) - if decision.get("complexity_tier") is not None: - additional_headers["x-litellm-quality-router-complexity"] = str(decision["complexity_tier"]) - - if ( - "x-ratelimit-remaining-tokens" not in additional_headers - and "x-ratelimit-remaining-requests" not in additional_headers - and model_group is not None - ): - remaining_usage = await self.get_remaining_model_group_usage(model_group) - - # get_remaining_model_group_usage reads the router's TPM/RPM - # counter, which is incremented post-response by - # deployment_callback_on_success. So the values returned here - # are pre-decrement for the current request, while vendor - # headers (OpenAI/Anthropic/Azure) are post-decrement. Replay - # the in-flight increment so router-derived headers match - # vendor-derived semantics — for both the HTTP response sent - # to the client and the prometheus gauges that read these - # headers downstream (LIT-2719). - in_flight_tokens = 0 - usage = getattr(response, "usage", None) - if usage is not None: - in_flight_tokens = getattr(usage, "total_tokens", 0) or 0 - in_flight_delta = { - "x-ratelimit-remaining-tokens": in_flight_tokens, - "x-ratelimit-remaining-requests": 1, - } - - for header, value in remaining_usage.items(): - if value is not None: - additional_headers[header] = value - in_flight_delta.get(header, 0) + if model_group is not None: + remaining_usage = await self.get_remaining_model_group_usage(model_group) + # get_remaining_model_group_usage reads the router's TPM/RPM counter, + # which is incremented post-response by deployment_callback_on_success. + # Replay the in-flight increment for TPM/RPM only (LIT-2719); ITPM/OTPM + # counters are incremented at reservation time and must not be adjusted. + apply_remaining_usage_headers( + additional_headers, + remaining_usage, + response_in_flight_token_count(response), + ) return response def _build_model_name_index(self, model_list: list) -> None: diff --git a/litellm/router_strategy/auto_router/auto_router.py b/litellm/router_strategy/auto_router/auto_router.py index dc24623f505..c01e2f10d2c 100644 --- a/litellm/router_strategy/auto_router/auto_router.py +++ b/litellm/router_strategy/auto_router/auto_router.py @@ -130,11 +130,12 @@ class AutoRouter(CustomLogger): # do nothing, return same inputs return None - if self.routelayer is None: + routelayer = self.routelayer + if routelayer is None: ####################### # Create the route layer ####################### - self.routelayer = SemanticRouter( + routelayer = SemanticRouter( routes=self.loaded_routes, encoder=LiteLLMRouterEncoder( litellm_router_instance=self.litellm_router_instance, @@ -142,9 +143,10 @@ class AutoRouter(CustomLogger): ), auto_sync=self.auto_sync_value, ) + self.routelayer = routelayer message_content = self._extract_text_from_messages(messages) - route_choice: Optional[Union[RouteChoice, List[RouteChoice]]] = self.routelayer(text=message_content) + route_choice: Optional[Union[RouteChoice, List[RouteChoice]]] = routelayer(text=message_content) verbose_router_logger.debug(f"route_choice: {route_choice}") if isinstance(route_choice, RouteChoice): model = route_choice.name or self.default_model diff --git a/litellm/router_strategy/complexity_router/complexity_router.py b/litellm/router_strategy/complexity_router/complexity_router.py index 9a1d845dd65..4f9135ad9ed 100644 --- a/litellm/router_strategy/complexity_router/complexity_router.py +++ b/litellm/router_strategy/complexity_router/complexity_router.py @@ -32,6 +32,14 @@ else: PreRoutingHookResponse = Any +def _append_custom_keywords(base_keywords: list[str], custom_keywords: Optional[list[str]]) -> list[str]: + if not custom_keywords: + return base_keywords + base_lowered = frozenset(keyword.lower() for keyword in base_keywords) + deduped_custom = {keyword.lower(): keyword for keyword in custom_keywords if keyword.lower() not in base_lowered} + return [*base_keywords, *deduped_custom.values()] + + class DimensionScore: """Represents a score for a single dimension with optional signal.""" @@ -90,7 +98,10 @@ class ComplexityRouter(CustomLogger): # Build effective keyword lists (use config overrides or defaults) self.code_keywords = self.config.code_keywords or DEFAULT_CODE_KEYWORDS self.reasoning_keywords = self.config.reasoning_keywords or DEFAULT_REASONING_KEYWORDS - self.technical_keywords = self.config.technical_keywords or DEFAULT_TECHNICAL_KEYWORDS + self.technical_keywords = _append_custom_keywords( + self.config.technical_keywords or DEFAULT_TECHNICAL_KEYWORDS, + self.config.custom_technical_keywords, + ) self.simple_keywords = self.config.simple_keywords or DEFAULT_SIMPLE_KEYWORDS # Pre-compile regex patterns for efficiency diff --git a/litellm/router_strategy/complexity_router/config.py b/litellm/router_strategy/complexity_router/config.py index a8a21e3f30b..ae04d5fc5d4 100644 --- a/litellm/router_strategy/complexity_router/config.py +++ b/litellm/router_strategy/complexity_router/config.py @@ -237,6 +237,15 @@ class ComplexityRouterConfig(BaseModel): default=None, description="Keywords indicating technical content", ) + custom_technical_keywords: Optional[list[str]] = Field( + default=None, + description=( + "Domain-specific technical keywords appended to the effective base list " + "(technical_keywords if set, otherwise DEFAULT_TECHNICAL_KEYWORDS). " + "Order is preserved; duplicates are removed case-insensitively against " + "the base list and within this list." + ), + ) simple_keywords: Optional[List[str]] = Field( default=None, description="Keywords indicating simple/basic queries", diff --git a/litellm/router_utils/add_retry_fallback_headers.py b/litellm/router_utils/add_retry_fallback_headers.py index 0b927714ca9..e2204e18a3c 100644 --- a/litellm/router_utils/add_retry_fallback_headers.py +++ b/litellm/router_utils/add_retry_fallback_headers.py @@ -1,5 +1,5 @@ import json -from typing import Protocol, TypedDict, cast +from typing import Any, Protocol, TypedDict, cast from pydantic import BaseModel @@ -15,8 +15,98 @@ class _HiddenParamsHost(Protocol): _hidden_params: dict[str, object] -def get_hidden_params_dict(response: object) -> dict[str, object]: - hidden_params: object = cast(object, getattr(response, "_hidden_params", None)) +class HiddenParamsAsyncIteratorWrapper: + """ + Wraps a bare async generator/iterator (e.g. a provider's raw SSE + streaming response) that cannot itself hold a ``_hidden_params`` + attribute, so router-derived headers (ITPM/OTPM, model-group, retry, + fallback) can attach to a streaming response the same way they attach + to object-based responses (e.g. ``CustomStreamWrapper``). + """ + + def __init__(self, inner: object) -> None: + self._inner = inner + self._hidden_params: dict[str, object] = {} + + def __aiter__(self) -> "HiddenParamsAsyncIteratorWrapper": + return self + + async def __anext__(self) -> object: + return await cast(Any, self._inner).__anext__() + + async def aclose(self) -> None: + aclose = getattr(self._inner, "aclose", None) + if callable(aclose): + await aclose() + + +def prepare_response_for_header_attachment(response: object) -> object | None: + if response is None: + return None + if isinstance(response, dict) or hasattr(response, "_hidden_params"): + return response + if hasattr(response, "__anext__"): + return HiddenParamsAsyncIteratorWrapper(response) + return response + + +def ensure_response_additional_headers(response: object) -> dict[str, object]: + hidden_params = get_hidden_params_dict(response, create=isinstance(response, dict)) + _write_hidden_params(response, hidden_params) + additional_headers = hidden_params.get("additional_headers") + if not isinstance(additional_headers, dict): + additional_headers = {} + hidden_params["additional_headers"] = additional_headers + return additional_headers + + +def apply_quality_router_decision_headers( + additional_headers: dict[str, object], + request_kwargs: object, +) -> None: + metadata = (request_kwargs.get("metadata") or {}) if isinstance(request_kwargs, dict) else {} + decision = metadata.get("quality_router_decision") if isinstance(metadata, dict) else None + if not isinstance(decision, dict): + return + quality_header_fields = ( + ("routed_model", "x-litellm-quality-router-model"), + ("quality_tier", "x-litellm-quality-router-tier"), + ("routed_via", "x-litellm-quality-router-via"), + ("matched_keyword", "x-litellm-quality-router-keyword"), + ("complexity_tier", "x-litellm-quality-router-complexity"), + ) + for field, header in quality_header_fields: + if decision.get(field) is not None: + additional_headers[header] = str(decision[field]) + + +def response_in_flight_token_count(response: object) -> int: + usage = response.get("usage") if isinstance(response, dict) else getattr(response, "usage", None) + if usage is None: + return 0 + if isinstance(usage, dict): + total = int(usage.get("total_tokens") or 0) + if total: + return total + return int(usage.get("input_tokens") or 0) + int(usage.get("output_tokens") or 0) + return int(getattr(usage, "total_tokens", 0) or 0) + + +def apply_remaining_usage_headers( + additional_headers: dict[str, object], + remaining_usage: dict[str, int], + in_flight_tokens: int, +) -> None: + in_flight_delta = { + "x-ratelimit-remaining-tokens": in_flight_tokens, + "x-ratelimit-remaining-requests": 1, + } + for header, value in remaining_usage.items(): + if value is not None and header not in additional_headers: + additional_headers[header] = value - in_flight_delta.get(header, 0) + + +def _normalize_hidden_params(hidden_params: object) -> dict[str, object]: if isinstance(hidden_params, BaseModel): return cast("dict[str, object]", hidden_params.model_dump()) if isinstance(hidden_params, dict): @@ -24,6 +114,29 @@ def get_hidden_params_dict(response: object) -> dict[str, object]: return {} +def get_hidden_params_dict( + response: object, + *, + create: bool = False, +) -> dict[str, object]: + if isinstance(response, dict): + hidden_params = _normalize_hidden_params(response.get("_hidden_params")) + if not hidden_params and create: + hidden_params = {} + response["_hidden_params"] = hidden_params + return hidden_params + + hidden_params = _normalize_hidden_params(cast(object, getattr(response, "_hidden_params", None))) + return hidden_params + + +def _write_hidden_params(response: object, hidden_params: dict[str, object]) -> None: + if isinstance(response, dict): + response["_hidden_params"] = hidden_params + elif hasattr(response, "_hidden_params"): + cast(_HiddenParamsHost, response)._hidden_params = hidden_params + + def _ensure_additional_headers_dict( hidden_params: dict[str, object], ) -> dict[str, object]: @@ -73,15 +186,19 @@ def _add_headers_to_response(response: object, headers: dict[str, object]) -> ob if response is None: return response - if not isinstance(response, BaseModel) and not hasattr(response, "_hidden_params"): + if ( + not isinstance(response, BaseModel) + and not isinstance(response, dict) + and not hasattr(response, "_hidden_params") + ): return response - hidden_params = get_hidden_params_dict(response) + hidden_params = get_hidden_params_dict(response, create=isinstance(response, dict)) additional_headers = _ensure_additional_headers_dict(hidden_params) additional_headers.update(headers) hidden_params["additional_headers"] = additional_headers - cast(_HiddenParamsHost, response)._hidden_params = hidden_params + _write_hidden_params(response, hidden_params) return response @@ -127,12 +244,12 @@ def add_fallback_headers_to_response( if fallback_errors is None or response is None: return response - hidden_params = get_hidden_params_dict(response) + hidden_params = get_hidden_params_dict(response, create=isinstance(response, dict)) additional_headers = _ensure_additional_headers_dict(hidden_params) merged_errors = get_fallback_errors_from_headers(additional_headers) + [ cast("dict[str, object]", error) for error in fallback_errors ] additional_headers["x-litellm-fallback-errors"] = json.dumps(merged_errors) hidden_params["additional_headers"] = additional_headers - cast(_HiddenParamsHost, response)._hidden_params = hidden_params + _write_hidden_params(response, hidden_params) return response diff --git a/litellm/router_utils/pre_call_checks/io_token_rate_limit_check.py b/litellm/router_utils/pre_call_checks/io_token_rate_limit_check.py new file mode 100644 index 00000000000..62c99a1c9f6 --- /dev/null +++ b/litellm/router_utils/pre_call_checks/io_token_rate_limit_check.py @@ -0,0 +1,711 @@ +""" +Separate ITPM/OTPM (input/output tokens per minute) deployment rate limits. + +- Pre-call: atomically reserve estimated_input against ITPM and max_tokens against OTPM +- Post-call: reconcile ITPM to actual input tokens and OTPM to actual output tokens +- Cached prompt-read tokens are excluded from ITPM post-call accounting + +Used by ModelRateLimitingCheck when a deployment sets itpm/otpm. +""" + +from __future__ import annotations + +import contextlib +import contextvars +from typing import TYPE_CHECKING, Any, Optional + +import httpx + +import litellm +from litellm import token_counter +from litellm._logging import verbose_router_logger +from litellm.caching.dual_cache import DualCache +from litellm.types.router import RouterCacheEnum, RouterErrors +from litellm.utils import get_utc_datetime + +if TYPE_CHECKING: + from opentelemetry.trace import Span as _Span + + Span = _Span | Any +else: + Span = Any + +RoutingArgsTTL = 60 + +_io_token_rate_limit_request_kwargs: contextvars.ContextVar[Optional[dict[str, Any]]] = contextvars.ContextVar( + "io_token_rate_limit_request_kwargs", + default=None, +) + +ITPM_RESERVED_KEY = "_litellm_itpm_reserved" +OTPM_RESERVED_KEY = "_litellm_otpm_reserved" +ITPM_CACHE_KEY = "_litellm_itpm_cache_key" +OTPM_CACHE_KEY = "_litellm_otpm_cache_key" + + +def set_io_token_rate_limit_request_kwargs(kwargs: Optional[dict[str, Any]]) -> None: + # The reservation sentinels are server-only, but `metadata` is caller + # controlled on proxy requests. Strip any client-supplied copies here (this + # runs before the router stashes its own reservation) so a forged + # reservation can't drive the post-call reconcile/refund against an + # arbitrary counter and bypass the configured limits. + _clear_reservation_from_kwargs(kwargs) + _io_token_rate_limit_request_kwargs.set(kwargs) + + +def get_io_token_rate_limit_request_kwargs() -> Optional[dict[str, Any]]: + return _io_token_rate_limit_request_kwargs.get() + + +def seconds_until_minute_reset() -> int: + dt = get_utc_datetime() + return max(1, 60 - dt.second) + + +def get_deployment_io_token_limits( + deployment: dict, +) -> tuple[Optional[int], Optional[int]]: + itpm = deployment.get("itpm") + otpm = deployment.get("otpm") + litellm_params = deployment.get("litellm_params") or {} + model_info = deployment.get("model_info") or {} + if itpm is None: + itpm = litellm_params.get("itpm") + if otpm is None: + otpm = litellm_params.get("otpm") + if itpm is None: + itpm = model_info.get("itpm") + if otpm is None: + otpm = model_info.get("otpm") + return itpm, otpm + + +def deployment_has_io_token_limits(deployment: dict) -> bool: + itpm, otpm = get_deployment_io_token_limits(deployment) + return itpm is not None or otpm is not None + + +def _get_cache_keys(deployment: dict, current_minute: str) -> Optional[tuple[str, str]]: + model_id = deployment.get("model_info", {}).get("id") + deployment_name = deployment.get("litellm_params", {}).get("model") + # Without both a deployment id and model name the key would collapse to a + # shared "None:None" bucket across misconfigured deployments, so bail out. + if model_id is None or deployment_name is None: + return None + itpm_key = RouterCacheEnum.ITPM.value.format(id=model_id, model=deployment_name, current_minute=current_minute) + otpm_key = RouterCacheEnum.OTPM.value.format(id=model_id, model=deployment_name, current_minute=current_minute) + return itpm_key, otpm_key + + +def _estimate_input_tokens(request_kwargs: Optional[dict[str, Any]], model: str = "") -> int: + if not request_kwargs: + return 0 + messages = request_kwargs.get("messages") + prompt = request_kwargs.get("prompt") + input_text = request_kwargs.get("input") + # token_counter can raise from any of its tokenizer backends; this is a + # best-effort estimate for the ITPM reservation and must never fail the + # underlying request. Passing the deployment model name uses a model-specific + # tokenizer when available, reducing the reservation over/under-estimate window + # between pre-call and post-call reconcile. + with contextlib.suppress(Exception): + return max(0, int(token_counter(model=model, messages=messages, text=prompt or input_text))) + return 0 + + +def _model_max_output_tokens(model_name: str) -> Optional[int]: + # litellm.get_model_info raises a bare Exception for an unrecognized model; + # this lookup is a fallback default and must never fail the request. + with contextlib.suppress(Exception): + info = litellm.get_model_info(model=model_name) + model_max = info.get("max_output_tokens") or info.get("max_tokens") + if model_max is not None: + return max(0, int(model_max)) + return None + + +def _resolve_max_tokens(request_kwargs: Optional[dict[str, Any]], deployment: dict) -> int: + if request_kwargs: + # An explicit max_tokens=0 must be honored, not treated as absent and + # replaced by the model default. + explicit = request_kwargs.get("max_tokens") + if explicit is None: + explicit = request_kwargs.get("max_completion_tokens") + if explicit is None: + explicit = request_kwargs.get("max_output_tokens") + if explicit is not None: + return max(0, int(explicit)) + + model_name = (deployment.get("litellm_params") or {}).get("model") + if model_name: + model_max = _model_max_output_tokens(model_name) + if model_max is not None: + return model_max + return 4096 + + +def _get_usage_tokens(usage: Any) -> tuple[int, int, int]: + if usage is None: + return 0, 0, 0 + if hasattr(usage, "prompt_tokens") or hasattr(usage, "input_tokens"): + prompt = int(getattr(usage, "prompt_tokens", None) or getattr(usage, "input_tokens", 0) or 0) + completion = int(getattr(usage, "completion_tokens", None) or getattr(usage, "output_tokens", 0) or 0) + cached = 0 + details = getattr(usage, "prompt_tokens_details", None) + if details is not None: + cached = int(getattr(details, "cached_tokens", 0) or 0) + if not cached: + cached = int(getattr(usage, "cache_read_input_tokens", 0) or 0) + return prompt, completion, cached + if isinstance(usage, dict): + prompt = int(usage.get("prompt_tokens") or usage.get("input_tokens") or 0) + completion = int(usage.get("completion_tokens") or usage.get("output_tokens") or 0) + details = usage.get("prompt_tokens_details") or {} + cached = int(details.get("cached_tokens", 0) or 0) if isinstance(details, dict) else 0 + if not cached: + cached = int(usage.get("cache_read_input_tokens") or 0) + return prompt, completion, cached + return 0, 0, 0 + + +def _extract_response_usage(response_obj: Any) -> Any: + if isinstance(response_obj, dict): + return response_obj.get("usage") + return getattr(response_obj, "usage", None) + + +def _usage_is_present(usage: Any) -> bool: + """ + True only if usage carries an actual input/output breakdown. + + ``total_tokens`` alone is deliberately excluded: ``_get_usage_tokens`` has + no way to split a bare total into input vs. output, so treating it as + "present" would resolve to (0, 0) and refund the full reservation as if + zero tokens were used. + """ + if usage is None: + return False + fields = ("prompt_tokens", "completion_tokens", "input_tokens", "output_tokens") + if isinstance(usage, dict): + return any(key in usage for key in fields) + return any(hasattr(usage, key) for key in fields) + + +def _resolve_reconcile_usage_tokens( + kwargs: Any, + response_obj: Any, +) -> tuple[int, int, bool]: + """ + Resolve billable input and output tokens for post-call reconcile. + + Prefer the response usage object; fall back to standard_logging_object token + fields. When usage cannot be resolved, return ``usage_resolved=False`` so + callers keep the pre-call reservation instead of refunding it as zero usage. + """ + usage = _extract_response_usage(response_obj) + if _usage_is_present(usage): + prompt_tokens, completion_tokens, cached_tokens = _get_usage_tokens(usage) + return max(0, prompt_tokens - cached_tokens), completion_tokens, True + + if isinstance(kwargs, dict): + standard_logging_object = kwargs.get("standard_logging_object") + if isinstance(standard_logging_object, dict): + prompt_tokens = int(standard_logging_object.get("prompt_tokens") or 0) + completion_tokens = int(standard_logging_object.get("completion_tokens") or 0) + cached_tokens = 0 + metadata = standard_logging_object.get("metadata") + if isinstance(metadata, dict): + cached_tokens = int(metadata.get("cache_read_input_tokens") or 0) + # Same rationale as _usage_is_present: a bare total_tokens with no + # prompt/completion breakdown can't be split, so it isn't treated + # as resolved usage - the reservation is kept instead of refunded. + if prompt_tokens or completion_tokens: + return max(0, prompt_tokens - cached_tokens), completion_tokens, True + + return 0, 0, False + + +def _stash_reservation_in_metadata( + request_kwargs: Optional[dict[str, Any]], + *, + itpm_reserved: int, + otpm_reserved: int, + itpm_cache_key: Optional[str], + otpm_cache_key: Optional[str], +) -> None: + if not request_kwargs: + return + reservation = { + ITPM_RESERVED_KEY: itpm_reserved, + OTPM_RESERVED_KEY: otpm_reserved, + ITPM_CACHE_KEY: itpm_cache_key, + OTPM_CACHE_KEY: otpm_cache_key, + } + for channel in ("metadata", "litellm_metadata"): + existing = request_kwargs.get(channel) + if isinstance(existing, dict): + existing.update(reservation) + elif channel == "metadata": + request_kwargs[channel] = dict(reservation) + + +def _extract_reservation(reservation: dict[str, Any]) -> tuple[int, int, Optional[str], Optional[str]]: + itpm_cache_key = reservation.get(ITPM_CACHE_KEY) + otpm_cache_key = reservation.get(OTPM_CACHE_KEY) + return ( + int(reservation.get(ITPM_RESERVED_KEY, 0) or 0), + int(reservation.get(OTPM_RESERVED_KEY, 0) or 0), + itpm_cache_key if isinstance(itpm_cache_key, str) else None, + otpm_cache_key if isinstance(otpm_cache_key, str) else None, + ) + + +def _reservation_channels(kwargs: Any) -> tuple[Any, ...]: + """ + Places a reservation may live, in priority order: the top-level metadata + channels win over litellm_params.metadata (so a top-level stash is never + shadowed), which win over the standard_logging_object copy. + """ + if not isinstance(kwargs, dict): + return () + channels = [kwargs.get("metadata"), kwargs.get("litellm_metadata")] + litellm_params = kwargs.get("litellm_params") + if isinstance(litellm_params, dict): + channels.append(litellm_params.get("metadata")) + standard_logging_object = kwargs.get("standard_logging_object") + if isinstance(standard_logging_object, dict): + channels.append(standard_logging_object.get("metadata")) + return tuple(channels) + + +def _read_reservation_from_kwargs(kwargs: Any) -> tuple[int, int, Optional[str], Optional[str]]: + for channel_dict in _reservation_channels(kwargs): + if isinstance(channel_dict, dict) and ITPM_RESERVED_KEY in channel_dict: + return _extract_reservation(channel_dict) + return 0, 0, None, None + + +def _clear_reservation_from_kwargs(kwargs: Any) -> None: + """ + Remove the stashed reservation so a retry on a different (e.g. non-IO) + deployment does not re-process the already-reconciled/refunded reservation. + """ + for channel_dict in _reservation_channels(kwargs): + if isinstance(channel_dict, dict): + for key in (ITPM_RESERVED_KEY, OTPM_RESERVED_KEY, ITPM_CACHE_KEY, OTPM_CACHE_KEY): + channel_dict.pop(key, None) + + +def _reservation_value(value: int, limit: Optional[int]) -> int: + if limit is None: + return 0 + if value > 0: + return value + # Estimation failed (empty messages, unsupported model, tokenizer error). + # Reserve a minimal 1-token slot rather than the full limit: the latter + # would let one request whose estimate failed fill the entire bucket, + # serializing every concurrent request to the deployment until it + # completes and reconciles against actual usage. + return 1 + + +def _rate_limit_error(limit_label: str, limit: int, current: float) -> litellm.RateLimitError: + return litellm.RateLimitError( + message=f"Model rate limit exceeded. {limit_label} limit={limit}, current usage={current}", + llm_provider="", + model="", + response=httpx.Response( + status_code=429, + content=( + f"{RouterErrors.user_defined_ratelimit_error.value} " + f"{limit_label} limit={limit}. current usage={current}." + ), + headers={"retry-after": str(RoutingArgsTTL)}, + request=httpx.Request( + method="io_token_rate_limit_check", + url="https://github.com/BerriAI/litellm", + ), + ), + num_retries=0, + ) + + +def _sync_increment_with_rollback( + dual_cache: DualCache, + key: str, + value: int, + limit: Optional[int], + *, + limit_label: str, +) -> None: + if value <= 0 or limit is None: + return + current = dual_cache.increment_cache( + key=key, + value=value, + ttl=RoutingArgsTTL, + ) + if current is not None and current > limit: + dual_cache.increment_cache( + key=key, + value=-value, + ttl=RoutingArgsTTL, + ) + raise _rate_limit_error(limit_label, limit, current) + + +async def _increment_with_rollback( + dual_cache: DualCache, + key: str, + value: int, + limit: Optional[int], + *, + parent_otel_span: Optional[Span] = None, + limit_label: str, +) -> None: + if value <= 0 or limit is None: + return + current = await dual_cache.async_increment_cache( + key=key, + value=value, + ttl=RoutingArgsTTL, + parent_otel_span=parent_otel_span, + ) + if current is not None and current > limit: + await dual_cache.async_increment_cache( + key=key, + value=-value, + ttl=RoutingArgsTTL, + parent_otel_span=parent_otel_span, + ) + raise _rate_limit_error(limit_label, limit, current) + + +def io_token_pre_call_check( + dual_cache: DualCache, + deployment: dict, +) -> Optional[dict]: + itpm_limit, otpm_limit = get_deployment_io_token_limits(deployment) + if itpm_limit is None and otpm_limit is None: + return deployment + + request_kwargs = get_io_token_rate_limit_request_kwargs() + _model = (deployment.get("litellm_params") or {}).get("model") or "" + estimated_input = _estimate_input_tokens(request_kwargs, model=_model) + max_tokens = _resolve_max_tokens(request_kwargs, deployment) + + dt = get_utc_datetime() + current_minute = dt.strftime("%H-%M") + cache_keys = _get_cache_keys(deployment, current_minute) + if cache_keys is None: + return deployment + itpm_key, otpm_key = cache_keys + + itpm_reserved = 0 + otpm_reserved = 0 + + if itpm_limit is not None: + itpm_reserved = _reservation_value(estimated_input, itpm_limit) + _sync_increment_with_rollback( + dual_cache, + itpm_key, + itpm_reserved, + itpm_limit, + limit_label="ITPM", + ) + + if otpm_limit is not None: + otpm_reserved = 0 if max_tokens == 0 else _reservation_value(max_tokens, otpm_limit) + try: + _sync_increment_with_rollback( + dual_cache, + otpm_key, + otpm_reserved, + otpm_limit, + limit_label="OTPM", + ) + except Exception: + if itpm_reserved > 0: + dual_cache.increment_cache( + key=itpm_key, + value=-itpm_reserved, + ttl=RoutingArgsTTL, + ) + raise + + _stash_reservation_in_metadata( + request_kwargs, + itpm_reserved=itpm_reserved, + otpm_reserved=otpm_reserved, + itpm_cache_key=itpm_key if itpm_limit is not None else None, + otpm_cache_key=otpm_key if otpm_limit is not None else None, + ) + return deployment + + +async def async_io_token_pre_call_check( + dual_cache: DualCache, + deployment: dict, + parent_otel_span: Optional[Span] = None, +) -> Optional[dict]: + itpm_limit, otpm_limit = get_deployment_io_token_limits(deployment) + if itpm_limit is None and otpm_limit is None: + return deployment + + request_kwargs = get_io_token_rate_limit_request_kwargs() + _model = (deployment.get("litellm_params") or {}).get("model") or "" + estimated_input = _estimate_input_tokens(request_kwargs, model=_model) + max_tokens = _resolve_max_tokens(request_kwargs, deployment) + + dt = get_utc_datetime() + current_minute = dt.strftime("%H-%M") + cache_keys = _get_cache_keys(deployment, current_minute) + if cache_keys is None: + return deployment + itpm_key, otpm_key = cache_keys + + itpm_reserved = 0 + otpm_reserved = 0 + + if itpm_limit is not None: + itpm_reserved = _reservation_value(estimated_input, itpm_limit) + await _increment_with_rollback( + dual_cache, + itpm_key, + itpm_reserved, + itpm_limit, + parent_otel_span=parent_otel_span, + limit_label="ITPM", + ) + + if otpm_limit is not None: + otpm_reserved = 0 if max_tokens == 0 else _reservation_value(max_tokens, otpm_limit) + try: + await _increment_with_rollback( + dual_cache, + otpm_key, + otpm_reserved, + otpm_limit, + parent_otel_span=parent_otel_span, + limit_label="OTPM", + ) + except Exception: + # Any failure reserving OTPM (a 429 or a transient cache error) must + # release the ITPM reservation already made, or it stays inflated + # until the TTL expires. + if itpm_reserved > 0: + await dual_cache.async_increment_cache( + key=itpm_key, + value=-itpm_reserved, + ttl=RoutingArgsTTL, + parent_otel_span=parent_otel_span, + ) + raise + + _stash_reservation_in_metadata( + request_kwargs, + itpm_reserved=itpm_reserved, + otpm_reserved=otpm_reserved, + itpm_cache_key=itpm_key if itpm_limit is not None else None, + otpm_cache_key=otpm_key if otpm_limit is not None else None, + ) + return deployment + + +def io_token_reconcile_success( + dual_cache: DualCache, + kwargs: Any, + response_obj: Any, +) -> None: + itpm_reserved, otpm_reserved, itpm_key, otpm_key = _read_reservation_from_kwargs(kwargs) + if itpm_key is None and otpm_key is None: + return + + billable_input, completion_tokens, usage_resolved = _resolve_reconcile_usage_tokens(kwargs, response_obj) + + try: + if usage_resolved: + if itpm_key is not None: + itpm_delta = billable_input - itpm_reserved + if itpm_delta != 0: + dual_cache.increment_cache( + key=itpm_key, + value=itpm_delta, + ttl=RoutingArgsTTL, + ) + + if otpm_key is not None: + otpm_delta = completion_tokens - otpm_reserved + if otpm_delta != 0: + dual_cache.increment_cache( + key=otpm_key, + value=otpm_delta, + ttl=RoutingArgsTTL, + ) + else: + verbose_router_logger.debug( + "[IO TOKEN LIMIT] usage missing; keeping reservation " + f"(itpm_reserved={itpm_reserved}, otpm_reserved={otpm_reserved})" + ) + finally: + _clear_reservation_from_kwargs(kwargs) + + verbose_router_logger.debug( + f"[IO TOKEN LIMIT] reconciled " + f"(usage_resolved={usage_resolved}, itpm_reserved={itpm_reserved}, " + f"billable_input={billable_input}, otpm_reserved={otpm_reserved}, output={completion_tokens})" + ) + + +async def async_io_token_reconcile_success( + dual_cache: DualCache, + kwargs: Any, + response_obj: Any, + *, + parent_otel_span: Optional[Span] = None, +) -> None: + itpm_reserved, otpm_reserved, itpm_key, otpm_key = _read_reservation_from_kwargs(kwargs) + if itpm_key is None and otpm_key is None: + return + + billable_input, completion_tokens, usage_resolved = _resolve_reconcile_usage_tokens(kwargs, response_obj) + + # Reconcile against the exact key that held the reservation (which encodes + # the reservation's minute), not a key recomputed at response time. This + # tracks actual usage even when the pre-call estimate was 0, and avoids a + # minute-boundary mismatch for calls that span into the next minute. Always + # clear the stash afterwards (even if an increment throws) so a retry or a + # duplicate success event can't re-process it. + try: + if usage_resolved: + if itpm_key is not None: + itpm_delta = billable_input - itpm_reserved + if itpm_delta != 0: + await dual_cache.async_increment_cache( + key=itpm_key, + value=itpm_delta, + ttl=RoutingArgsTTL, + parent_otel_span=parent_otel_span, + ) + + if otpm_key is not None: + otpm_delta = completion_tokens - otpm_reserved + if otpm_delta != 0: + await dual_cache.async_increment_cache( + key=otpm_key, + value=otpm_delta, + ttl=RoutingArgsTTL, + parent_otel_span=parent_otel_span, + ) + else: + verbose_router_logger.debug( + "[IO TOKEN LIMIT] usage missing; keeping reservation " + f"(itpm_reserved={itpm_reserved}, otpm_reserved={otpm_reserved})" + ) + finally: + _clear_reservation_from_kwargs(kwargs) + + verbose_router_logger.debug( + f"[IO TOKEN LIMIT] reconciled " + f"(usage_resolved={usage_resolved}, itpm_reserved={itpm_reserved}, " + f"billable_input={billable_input}, otpm_reserved={otpm_reserved}, output={completion_tokens})" + ) + + +def io_token_refund_failure( + dual_cache: DualCache, + kwargs: Any, +) -> None: + itpm_reserved, otpm_reserved, itpm_key, otpm_key = _read_reservation_from_kwargs(kwargs) + if itpm_key is None and otpm_key is None: + return + if itpm_key is not None and itpm_reserved > 0: + dual_cache.increment_cache( + key=itpm_key, + value=-itpm_reserved, + ttl=RoutingArgsTTL, + ) + if otpm_key is not None and otpm_reserved > 0: + dual_cache.increment_cache( + key=otpm_key, + value=-otpm_reserved, + ttl=RoutingArgsTTL, + ) + _clear_reservation_from_kwargs(kwargs) + verbose_router_logger.debug(f"[IO TOKEN LIMIT] refunded ITPM={itpm_reserved} OTPM={otpm_reserved}") + + +def refund_stale_reservation_before_retry(dual_cache: DualCache, kwargs: Optional[dict[str, Any]]) -> None: + """ + Synchronously refund and clear any reservation a previous deployment + attempt stashed in ``kwargs``, before it's overwritten for the next + attempt (retry/fallback). + + ``set_io_token_rate_limit_request_kwargs`` strips reservation sentinels + from ``kwargs`` on every deployment pick (a security measure so a + caller-forged reservation can't be replayed). Without this refund, a + retry after a non-RateLimitError failure (e.g. an upstream 500) would + wipe deployment A's still-unreconciled reservation before its failure + event - which may be scheduled as a background task - gets a chance to + refund it, permanently stranding the reservation until its TTL expires + and causing false rate-limit errors for subsequent requests. + + ponytail: uses sync ``DualCache.increment_cache`` which issues a blocking + Redis INCR when a Redis backend is configured. This only triggers on + streaming mid-stream retries (non-streaming failures await their failure + handler before retrying, so the sentinels are already cleared). Upgrade + path: make ``_update_kwargs_with_deployment`` async and switch to + ``async_io_token_refund_failure`` — requires touching all callers. + """ + if not kwargs: + return + io_token_refund_failure(dual_cache, kwargs) + + +async def async_io_token_refund_failure( + dual_cache: DualCache, + kwargs: Any, + *, + parent_otel_span: Optional[Span] = None, +) -> None: + itpm_reserved, otpm_reserved, itpm_key, otpm_key = _read_reservation_from_kwargs(kwargs) + if itpm_key is None and otpm_key is None: + return + if itpm_key is not None and itpm_reserved > 0: + await dual_cache.async_increment_cache( + key=itpm_key, + value=-itpm_reserved, + ttl=RoutingArgsTTL, + parent_otel_span=parent_otel_span, + ) + if otpm_key is not None and otpm_reserved > 0: + await dual_cache.async_increment_cache( + key=otpm_key, + value=-otpm_reserved, + ttl=RoutingArgsTTL, + parent_otel_span=parent_otel_span, + ) + _clear_reservation_from_kwargs(kwargs) + verbose_router_logger.debug(f"[IO TOKEN LIMIT] refunded ITPM={itpm_reserved} OTPM={otpm_reserved}") + + +def build_io_token_rate_limit_headers( + *, + itpm_limit: Optional[int], + otpm_limit: Optional[int], + current_itpm: Optional[int], + current_otpm: Optional[int], +) -> dict[str, int]: + headers: dict[str, int] = {} + reset = seconds_until_minute_reset() + if itpm_limit is not None: + usage = current_itpm or 0 + headers["x-ratelimit-limit-input-tokens"] = itpm_limit + headers["x-ratelimit-remaining-input-tokens"] = max(0, itpm_limit - usage) + headers["x-ratelimit-reset-input-tokens"] = reset + if otpm_limit is not None: + usage = current_otpm or 0 + headers["x-ratelimit-limit-output-tokens"] = otpm_limit + headers["x-ratelimit-remaining-output-tokens"] = max(0, otpm_limit - usage) + headers["x-ratelimit-reset-output-tokens"] = reset + return headers diff --git a/litellm/router_utils/pre_call_checks/model_rate_limit_check.py b/litellm/router_utils/pre_call_checks/model_rate_limit_check.py index 0c6450b191f..373563ca442 100644 --- a/litellm/router_utils/pre_call_checks/model_rate_limit_check.py +++ b/litellm/router_utils/pre_call_checks/model_rate_limit_check.py @@ -1,13 +1,16 @@ """ -Enforce TPM/RPM rate limits set on model deployments. +Enforce TPM/RPM or separate ITPM/OTPM rate limits set on model deployments. -This pre-call check ensures that model-level TPM/RPM limits are enforced -across all requests, regardless of routing strategy. +When enabled via router_settings.optional_pre_call_checks: ["enforce_model_rate_limits"] -When enabled via `enforce_model_rate_limits: true` in litellm_settings, -requests that exceed the configured TPM/RPM limits will receive a 429 error. +- tpm/rpm: combined TPM + optional RPM (legacy) +- itpm/otpm: separate input/output tokens per minute + +When a deployment sets both itpm/otpm and tpm/rpm, both are enforced. A warning +is logged the first time such a deployment is seen. """ +import contextlib from typing import TYPE_CHECKING, Any, Dict, Optional, Union import httpx @@ -16,6 +19,17 @@ import litellm from litellm._logging import verbose_router_logger from litellm.caching.dual_cache import DualCache from litellm.integrations.custom_logger import CustomLogger +from litellm.router_utils.pre_call_checks.io_token_rate_limit_check import ( + async_io_token_pre_call_check, + async_io_token_reconcile_success, + async_io_token_refund_failure, + deployment_has_io_token_limits, + get_io_token_rate_limit_request_kwargs, + io_token_pre_call_check, + io_token_reconcile_success, + io_token_refund_failure, + ITPM_RESERVED_KEY, +) from litellm.types.router import RouterErrors from litellm.types.utils import StandardLoggingPayload from litellm.utils import get_utc_datetime @@ -34,7 +48,7 @@ class RoutingArgs: class ModelRateLimitingCheck(CustomLogger): """ - Pre-call check that enforces TPM/RPM limits on model deployments. + Pre-call check that enforces TPM/RPM or ITPM/OTPM limits on model deployments. This check runs before each request and raises a RateLimitError if the deployment has exceeded its configured TPM or RPM limits. @@ -45,6 +59,42 @@ class ModelRateLimitingCheck(CustomLogger): def __init__(self, dual_cache: DualCache): self.dual_cache = dual_cache + # model_ids already warned about itpm/otpm + tpm/rpm on the same deployment, + # so the warning is logged once per deployment rather than per request. + self._io_token_conflict_warned_ids: set[str] = set() + + def _warn_io_token_and_tpm_rpm_coexist_once(self, deployment: dict) -> None: + tpm_limit, rpm_limit = self._get_deployment_limits(deployment) + if tpm_limit is None and rpm_limit is None: + return + model_id = deployment.get("model_info", {}).get("id") + # Dedup per deployment id; if there is no id (degenerate config) don't + # collapse every such deployment onto one key - warn each time instead. + if model_id is not None: + if model_id in self._io_token_conflict_warned_ids: + return + self._io_token_conflict_warned_ids.add(str(model_id)) + verbose_router_logger.warning( + f"Deployment '{model_id}' configures itpm/otpm alongside tpm/rpm; " + "both limit types are enforced on this deployment" + ) + + def _refund_io_token_reservation_if_any(self) -> None: + request_kwargs = get_io_token_rate_limit_request_kwargs() + if request_kwargs is not None: + io_token_refund_failure(self.dual_cache, request_kwargs) + + async def _async_refund_io_token_reservation_if_any( + self, + parent_otel_span: Optional[Span] = None, + ) -> None: + request_kwargs = get_io_token_rate_limit_request_kwargs() + if request_kwargs is not None: + await async_io_token_refund_failure( + self.dual_cache, + request_kwargs, + parent_otel_span=parent_otel_span, + ) def _get_deployment_limits(self, deployment: Dict) -> tuple[Optional[int], Optional[int]]: """ @@ -93,6 +143,15 @@ class ModelRateLimitingCheck(CustomLogger): Raises RateLimitError if deployment exceeds TPM/RPM limits. """ try: + io_reservation_made = False + if deployment_has_io_token_limits(deployment): + self._warn_io_token_and_tpm_rpm_coexist_once(deployment) + io_token_pre_call_check( + self.dual_cache, + deployment, + ) + io_reservation_made = True + tpm_limit, rpm_limit = self._get_deployment_limits(deployment) # If no limits are set, allow the request @@ -149,6 +208,8 @@ class ModelRateLimitingCheck(CustomLogger): return deployment except litellm.RateLimitError: + if io_reservation_made: + self._refund_io_token_reservation_if_any() raise except Exception as e: verbose_router_logger.debug(f"Error in ModelRateLimitingCheck.pre_call_check: {str(e)}") @@ -159,9 +220,19 @@ class ModelRateLimitingCheck(CustomLogger): """ Async pre-call check for model rate limits. - Raises RateLimitError if deployment exceeds TPM/RPM limits. + Raises RateLimitError if deployment exceeds TPM/RPM or ITPM/OTPM limits. """ try: + io_reservation_made = False + if deployment_has_io_token_limits(deployment): + self._warn_io_token_and_tpm_rpm_coexist_once(deployment) + await async_io_token_pre_call_check( + self.dual_cache, + deployment, + parent_otel_span=parent_otel_span, + ) + io_reservation_made = True + tpm_limit, rpm_limit = self._get_deployment_limits(deployment) # If no limits are set, allow the request @@ -225,6 +296,8 @@ class ModelRateLimitingCheck(CustomLogger): return deployment except litellm.RateLimitError: + if io_reservation_made: + await self._async_refund_io_token_reservation_if_any(parent_otel_span=parent_otel_span) raise except Exception as e: verbose_router_logger.debug(f"Error in ModelRateLimitingCheck.async_pre_call_check: {str(e)}") @@ -232,14 +305,31 @@ class ModelRateLimitingCheck(CustomLogger): return deployment async def async_log_success_event(self, kwargs, response_obj, start_time, end_time): - """ - Track TPM usage after successful request. + from litellm.litellm_core_utils.core_helpers import ( + _get_parent_otel_span_from_kwargs, + ) - This updates the TPM counter with the actual tokens used. - Always tracks tokens - the pre-call check handles enforcement. - """ try: standard_logging_object: Optional[StandardLoggingPayload] = kwargs.get("standard_logging_object") + + # IO token reconciliation works purely from the cache keys stashed in + # kwargs/metadata, so it must run before the model_id guard below + # (which only the TPM-tracking path needs). Otherwise a request whose + # standard_logging_object lacks model_id would never return its + # reservation, leaving the counter elevated until the TTL expires. + slo_metadata = (standard_logging_object.get("metadata") or {}) if standard_logging_object else {} + kwargs_metadata = kwargs.get("metadata") or {} + if ITPM_RESERVED_KEY in slo_metadata or ITPM_RESERVED_KEY in kwargs_metadata: + await async_io_token_reconcile_success( + self.dual_cache, + kwargs, + response_obj, + parent_otel_span=_get_parent_otel_span_from_kwargs(kwargs), + ) + # Fall through: a deployment can also configure tpm/rpm alongside + # itpm/otpm, and that path's pre-call check reads the tpm_key + # counter tracked below, so it must still be incremented here. + if standard_logging_object is None: return @@ -272,6 +362,19 @@ class ModelRateLimitingCheck(CustomLogger): except Exception as e: verbose_router_logger.debug(f"Error in ModelRateLimitingCheck.async_log_success_event: {str(e)}") + async def async_log_failure_event(self, kwargs, response_obj, start_time, end_time): + from litellm.litellm_core_utils.core_helpers import ( + _get_parent_otel_span_from_kwargs, + ) + + # Never fail the primary logging pipeline over an io-token refund error. + with contextlib.suppress(Exception): + await async_io_token_refund_failure( + self.dual_cache, + kwargs, + parent_otel_span=_get_parent_otel_span_from_kwargs(kwargs), + ) + def log_success_event(self, kwargs, response_obj, start_time, end_time): """ Sync version of tracking TPM usage after successful request. @@ -279,6 +382,18 @@ class ModelRateLimitingCheck(CustomLogger): """ try: standard_logging_object: Optional[StandardLoggingPayload] = kwargs.get("standard_logging_object") + slo_metadata = (standard_logging_object.get("metadata") or {}) if standard_logging_object else {} + kwargs_metadata = kwargs.get("metadata") or {} + if ITPM_RESERVED_KEY in slo_metadata or ITPM_RESERVED_KEY in kwargs_metadata: + io_token_reconcile_success( + self.dual_cache, + kwargs, + response_obj, + ) + # Fall through: a deployment can also configure tpm/rpm alongside + # itpm/otpm, and that path's pre-call check reads the tpm_key + # counter tracked below, so it must still be incremented here. + if standard_logging_object is None: return @@ -304,3 +419,10 @@ class ModelRateLimitingCheck(CustomLogger): except Exception as e: verbose_router_logger.debug(f"Error in ModelRateLimitingCheck.log_success_event: {str(e)}") + + def log_failure_event(self, kwargs, response_obj, start_time, end_time): + with contextlib.suppress(Exception): + io_token_refund_failure( + self.dual_cache, + kwargs, + ) diff --git a/litellm/types/llms/openai.py b/litellm/types/llms/openai.py index 4c656b32081..3ab5a7b736e 100644 --- a/litellm/types/llms/openai.py +++ b/litellm/types/llms/openai.py @@ -90,6 +90,7 @@ from typing_extensions import ( from litellm.types.llms.base import BaseLiteLLMOpenAIResponseObject from litellm.types.responses.main import ( + CustomToolCallOutputItem, GenericResponseOutputItem, OutputCodeInterpreterCall, OutputFunctionToolCall, @@ -914,6 +915,7 @@ class OpenAIChatCompletionToolParam(TypedDict): class ChatCompletionToolParam(OpenAIChatCompletionToolParam, total=False): cache_control: ChatCompletionCachedContent + allowed_callers: List[str] class Function(TypedDict, total=False): @@ -1253,6 +1255,7 @@ class ResponsesAPIResponse(BaseLiteLLMOpenAIResponseObject): OutputFunctionToolCall, OutputImageGenerationCall, ResponseFunctionToolCall, + CustomToolCallOutputItem, ] ], ] diff --git a/litellm/types/llms/vertex_ai_speech_to_text.py b/litellm/types/llms/vertex_ai_speech_to_text.py new file mode 100644 index 00000000000..8995d98385b --- /dev/null +++ b/litellm/types/llms/vertex_ai_speech_to_text.py @@ -0,0 +1,40 @@ +from pydantic import BaseModel +from typing_extensions import TypedDict + + +class VertexSpeechToTextAutoDecodingConfig(TypedDict): + pass + + +class VertexSpeechToTextRecognitionFeatures(TypedDict): + enableAutomaticPunctuation: bool + + +class VertexSpeechToTextRecognitionConfig(TypedDict): + model: str + languageCodes: list[str] + features: VertexSpeechToTextRecognitionFeatures + autoDecodingConfig: VertexSpeechToTextAutoDecodingConfig + + +class VertexSpeechToTextRecognizeRequest(TypedDict): + config: VertexSpeechToTextRecognitionConfig + content: str + + +class VertexSpeechToTextAlternative(BaseModel): + transcript: str | None = None + + +class VertexSpeechToTextResult(BaseModel): + alternatives: list[VertexSpeechToTextAlternative] = [] + languageCode: str | None = None + + +class VertexSpeechToTextResponseMetadata(BaseModel): + totalBilledDuration: str | None = None + + +class VertexSpeechToTextRecognizeResponse(BaseModel): + results: list[VertexSpeechToTextResult] = [] + metadata: VertexSpeechToTextResponseMetadata | None = None diff --git a/litellm/types/management_endpoints/cache_settings_endpoints.py b/litellm/types/management_endpoints/cache_settings_endpoints.py index 9bccfed7c14..32ae70ac92e 100644 --- a/litellm/types/management_endpoints/cache_settings_endpoints.py +++ b/litellm/types/management_endpoints/cache_settings_endpoints.py @@ -40,6 +40,15 @@ CACHE_SETTINGS_FIELDS: List[CacheSettingsField] = [ redis_type=None, ), # Common fields for all Redis types + CacheSettingsField( + field_name="url", + field_type="String", + field_value=None, + field_description="Full Redis/Valkey connection URL (e.g. redis://:password@host:6379/1). When set, it takes precedence over Host, Port, Username, Password, and Database Index.", + field_default=None, + ui_field_name="Redis URL", + redis_type=None, + ), CacheSettingsField( field_name="host", field_type="String", @@ -58,6 +67,15 @@ CACHE_SETTINGS_FIELDS: List[CacheSettingsField] = [ ui_field_name="Port", redis_type=None, ), + CacheSettingsField( + field_name="db", + field_type="Integer", + field_value=None, + field_description="Logical database index to isolate the cache (e.g. 1 for redis://host:6379/1)", + field_default=None, + ui_field_name="Database Index", + redis_type=None, + ), CacheSettingsField( field_name="password", field_type="String", diff --git a/litellm/types/mcp.py b/litellm/types/mcp.py index e9a6bfa602e..9c564a3c7a6 100644 --- a/litellm/types/mcp.py +++ b/litellm/types/mcp.py @@ -40,6 +40,12 @@ class MCPAuth(str, enum.Enum): oauth2_token_exchange = "oauth2_token_exchange" +# RFC 8693 default subject_token_type. A NULL column / omitted config key means +# "use this default"; it is applied at every egress build site via this single +# constant rather than a DB-level DEFAULT (Prisma writes explicit values on +# insert, so a column default would rarely apply anyway). +DEFAULT_SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token" + # MCP Literals MCPTransportType = Literal[MCPTransport.sse, MCPTransport.http, MCPTransport.stdio] MCPSpecVersionType = Literal[MCPSpecVersion.nov_2024, MCPSpecVersion.mar_2025, MCPSpecVersion.jun_2025] @@ -122,18 +128,31 @@ class MCPCredentials(TypedDict, total=False): audience: Optional[str] """ - Target audience for OAuth 2.0 Token Exchange (RFC 8693) + Target audience for OAuth 2.0 Token Exchange (RFC 8693). + + Legacy input shape: this setting has a dedicated ``audience`` column, which is + authoritative. A value sent here is accepted for back-compat (the pre-column + REST shape, released since 2026-05), lifted into the column on write, and + stripped from the stored blob. Prefer the top-level request field. """ token_exchange_endpoint: Optional[str] """ - IDP token endpoint for OAuth 2.0 Token Exchange (RFC 8693) + IDP token endpoint for OAuth 2.0 Token Exchange (RFC 8693). + + Legacy input shape: lifted into the dedicated ``token_exchange_endpoint`` + column on write and stripped from the stored blob; the column is + authoritative. Prefer the top-level request field. """ subject_token_type: Optional[str] """ Subject token type for OAuth 2.0 Token Exchange (RFC 8693). - Default: urn:ietf:params:oauth:token-type:access_token + Default: DEFAULT_SUBJECT_TOKEN_TYPE (urn:ietf:params:oauth:token-type:access_token). + + Legacy input shape: lifted into the dedicated ``subject_token_type`` column on + write and stripped from the stored blob; the column is authoritative. Prefer + the top-level request field. """ token_endpoint_auth_method: Optional[MCPTokenEndpointAuthMethod] @@ -142,6 +161,17 @@ class MCPCredentials(TypedDict, total=False): sends HTTP Basic; defaults to "client_secret_post" when unset. """ + token_exchange_profile: Optional[str] + """ + Token exchange wire dialect: "rfc8693" (default, the standard token-exchange grant) or + "entra_obo" (Microsoft Entra On-Behalf-Of, the RFC 7523 jwt-bearer grant + requested_token_use + extension). Not a secret; stored unencrypted. + + Legacy input shape: lifted into the dedicated ``token_exchange_profile`` column on + write and stripped from the stored blob; the column is authoritative. Prefer the + top-level request field. + """ + class MCPServerCostInfo(TypedDict, total=False): default_cost_per_query: Optional[float] diff --git a/litellm/types/mcp_server/mcp_server_manager.py b/litellm/types/mcp_server/mcp_server_manager.py index c4e37b236ae..522a6f09165 100644 --- a/litellm/types/mcp_server/mcp_server_manager.py +++ b/litellm/types/mcp_server/mcp_server_manager.py @@ -4,6 +4,7 @@ from typing import Any, Dict, List, Literal, Optional from pydantic import BaseModel, ConfigDict from litellm.types.mcp import ( + DEFAULT_SUBJECT_TOKEN_TYPE, MCPAuth, MCPAuthType, MCPTokenEndpointAuthMethod, @@ -65,10 +66,13 @@ class MCPServer(BaseModel): aws_service_name: Optional[str] = None # defaults to "bedrock-agentcore" aws_role_name: Optional[str] = None # IAM role ARN for STS AssumeRole aws_session_name: Optional[str] = None # session name for CloudTrail auditing - # Token Exchange (OBO) fields — RFC 8693 + # Token Exchange (OBO) fields token_exchange_endpoint: Optional[str] = None audience: Optional[str] = None - subject_token_type: str = "urn:ietf:params:oauth:token-type:access_token" + subject_token_type: str = DEFAULT_SUBJECT_TOKEN_TYPE + # Wire dialect: "rfc8693" (standard token-exchange grant) or "entra_obo" (Microsoft Entra + # On-Behalf-Of, the RFC 7523 jwt-bearer grant + requested_token_use extension) + token_exchange_profile: str = "rfc8693" # Stdio-specific fields command: Optional[str] = None args: Optional[List[str]] = None diff --git a/litellm/types/responses/main.py b/litellm/types/responses/main.py index ebd2ad5b5a8..32e07f9e52f 100644 --- a/litellm/types/responses/main.py +++ b/litellm/types/responses/main.py @@ -85,6 +85,22 @@ def build_code_interpreter_log_outputs( return [OutputCodeInterpreterCallLog(type="logs", logs=logs)] if logs else None +class CustomToolCallOutputItem(BaseLiteLLMOpenAIResponseObject): + """A custom/freeform tool call output item (e.g. apply_patch). + + Mirrors the ``custom_tool_call`` variant of OpenAI's Responses API output. + Unlike ``OutputFunctionToolCall`` which uses ``arguments`` (JSON string), + this uses ``input`` (raw string) for the tool payload. + """ + + type: Literal["custom_tool_call"] + call_id: str + id: Optional[str] = None + name: str + input: str + status: Optional[Literal["in_progress", "completed", "incomplete"]] = None + + class GenericResponseOutputItem(BaseLiteLLMOpenAIResponseObject): """ Generic response API output item diff --git a/litellm/types/router.py b/litellm/types/router.py index 0c3485deae7..4bac9358392 100644 --- a/litellm/types/router.py +++ b/litellm/types/router.py @@ -214,6 +214,8 @@ class GenericLiteLLMParams(CredentialLiteLLMParams, CustomPricingLiteLLMParams): custom_llm_provider: Optional[str] = None tpm: Optional[int] = None rpm: Optional[int] = None + itpm: Optional[int] = None + otpm: Optional[int] = None timeout: Optional[Union[float, str, httpx.Timeout]] = None # if str, pass in as os.environ/ stream_timeout: Optional[Union[float, str]] = ( None # timeout when making stream=True calls, if str, pass in as os.environ/ @@ -359,6 +361,8 @@ class LiteLLMParamsTypedDict(TypedDict, total=False): custom_llm_provider: Optional[str] tpm: Optional[int] rpm: Optional[int] + itpm: Optional[int] + otpm: Optional[int] order: Optional[int] weight: Optional[int] max_parallel_requests: Optional[int] @@ -552,6 +556,8 @@ class ModelGroupInfo(BaseModel): ] = Field(default="chat") tpm: Optional[int] = None rpm: Optional[int] = None + itpm: Optional[int] = None + otpm: Optional[int] = None supports_parallel_function_calling: bool = Field(default=False) supports_vision: bool = Field(default=False) supports_web_search: bool = Field(default=False) @@ -749,6 +755,8 @@ class RoutingStrategy(enum.Enum): class RouterCacheEnum(enum.Enum): TPM = "global_router:{id}:{model}:tpm:{current_minute}" RPM = "global_router:{id}:{model}:rpm:{current_minute}" + ITPM = "global_router:{id}:{model}:itpm:{current_minute}" + OTPM = "global_router:{id}:{model}:otpm:{current_minute}" class GenericBudgetWindowDetails(BaseModel): diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 2e0eceaddd9..908f5b76424 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -3048,6 +3048,17 @@ class CustomPricingLiteLLMParams(BaseModel): regional_processing_uplift_multiplier_eu: Optional[float] = None regional_processing_uplift_multiplier_us: Optional[float] = None + @classmethod + def strip_custom_pricing_fields(cls, model_info: Dict[str, Any]) -> Dict[str, Any]: + """Return a copy of ``model_info`` without per-deployment custom pricing fields. + + Used when registering a deployment's info under the shared + ``{provider}/{model}`` key in ``litellm.model_cost``, so one deployment's + pricing overrides don't pollute sibling deployments that share the same + backend model. Full pricing stays under the deployment's unique model id. + """ + return {k: v for k, v in model_info.items() if k not in cls.model_fields} + # Server-controlled fields that bound or drive an interceptor's agentic loop # (depth, cycle fingerprints, ceiling, code-interpreter sandbox state). Listed @@ -3120,6 +3131,8 @@ all_litellm_params = ( "client", "rpm", "tpm", + "itpm", + "otpm", "max_parallel_requests", "input_cost_per_token", "output_cost_per_token", diff --git a/litellm/utils.py b/litellm/utils.py index 83d129339a4..f9bb84101e7 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -8097,6 +8097,12 @@ class ProviderConfigManager: ) return SonioxAudioTranscriptionConfig() + elif litellm.LlmProviders.VERTEX_AI == provider: + from litellm.llms.vertex_ai.audio_transcription.transformation import ( + VertexAIAudioTranscriptionConfig, + ) + + return VertexAIAudioTranscriptionConfig() return None @staticmethod diff --git a/migrations/Dockerfile b/migrations/Dockerfile index caca280cbfc..b20284df000 100644 --- a/migrations/Dockerfile +++ b/migrations/Dockerfile @@ -1,5 +1,5 @@ -ARG LITELLM_BUILD_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:c61ac6919b811ea53c4782d69f1fe05218ba3c25d53f01b6ab7892e621bd4370 -ARG LITELLM_RUNTIME_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:c61ac6919b811ea53c4782d69f1fe05218ba3c25d53f01b6ab7892e621bd4370 +ARG LITELLM_BUILD_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:42df77a9974d6ec8b17a5ee8bc23b532600a44d705acef2409e0933c1251b45f +ARG LITELLM_RUNTIME_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:42df77a9974d6ec8b17a5ee8bc23b532600a44d705acef2409e0933c1251b45f ARG UV_IMAGE=ghcr.io/astral-sh/uv:0.11.7@sha256:240fb85ab0f263ef12f492d8476aa3a2e4e1e333f7d67fbdd923d00a506a516a FROM $UV_IMAGE AS uvbin diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index a3dcdaed3f7..6cfa7c9e8be 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -5761,6 +5761,76 @@ "supports_tool_choice": true, "supports_vision": true }, + "azure/us/gpt-5.4": { + "cache_read_input_token_cost": 2.8e-07, + "cache_read_input_token_cost_priority": 5.5e-07, + "input_cost_per_token": 2.75e-06, + "input_cost_per_token_priority": 5.5e-06, + "output_cost_per_token": 1.65e-05, + "output_cost_per_token_priority": 3.3e-05, + "litellm_provider": "azure", + "max_input_tokens": 1050000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/batch", + "/v1/responses" + ], + "supported_modalities": [ + "text", + "image" + ], + "supported_output_modalities": [ + "text" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_parallel_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": true + }, + "azure/eu/gpt-5.4": { + "cache_read_input_token_cost": 2.8e-07, + "cache_read_input_token_cost_priority": 5.5e-07, + "input_cost_per_token": 2.75e-06, + "input_cost_per_token_priority": 5.5e-06, + "output_cost_per_token": 1.65e-05, + "output_cost_per_token_priority": 3.3e-05, + "litellm_provider": "azure", + "max_input_tokens": 1050000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/batch", + "/v1/responses" + ], + "supported_modalities": [ + "text", + "image" + ], + "supported_output_modalities": [ + "text" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_parallel_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": true + }, "azure/gpt-5.4-2026-03-05": { "cache_read_input_token_cost": 2.5e-07, "cache_read_input_token_cost_above_272k_tokens": 5e-07, @@ -5802,6 +5872,76 @@ "supports_tool_choice": true, "supports_vision": true }, + "azure/us/gpt-5.4-2026-03-05": { + "cache_read_input_token_cost": 2.8e-07, + "cache_read_input_token_cost_priority": 5.5e-07, + "input_cost_per_token": 2.75e-06, + "input_cost_per_token_priority": 5.5e-06, + "output_cost_per_token": 1.65e-05, + "output_cost_per_token_priority": 3.3e-05, + "litellm_provider": "azure", + "max_input_tokens": 1050000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/batch", + "/v1/responses" + ], + "supported_modalities": [ + "text", + "image" + ], + "supported_output_modalities": [ + "text" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_parallel_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": true + }, + "azure/eu/gpt-5.4-2026-03-05": { + "cache_read_input_token_cost": 2.8e-07, + "cache_read_input_token_cost_priority": 5.5e-07, + "input_cost_per_token": 2.75e-06, + "input_cost_per_token_priority": 5.5e-06, + "output_cost_per_token": 1.65e-05, + "output_cost_per_token_priority": 3.3e-05, + "litellm_provider": "azure", + "max_input_tokens": 1050000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/batch", + "/v1/responses" + ], + "supported_modalities": [ + "text", + "image" + ], + "supported_output_modalities": [ + "text" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_parallel_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": true + }, "azure/gpt-5.4-pro": { "cache_read_input_token_cost": 3e-06, "cache_read_input_token_cost_above_272k_tokens": 6e-06, @@ -5917,6 +6057,90 @@ "supports_xhigh_reasoning_effort": true, "supports_minimal_reasoning_effort": false }, + "azure/us/gpt-5.5": { + "cache_read_input_token_cost": 5.5e-07, + "cache_read_input_token_cost_above_272k_tokens": 1.1e-06, + "cache_read_input_token_cost_priority": 1.38e-06, + "input_cost_per_token": 5.5e-06, + "input_cost_per_token_above_272k_tokens": 1.1e-05, + "input_cost_per_token_priority": 1.375e-05, + "output_cost_per_token": 3.3e-05, + "output_cost_per_token_above_272k_tokens": 4.95e-05, + "output_cost_per_token_priority": 8.25e-05, + "litellm_provider": "azure", + "max_input_tokens": 1050000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/batch", + "/v1/responses" + ], + "supported_modalities": [ + "text", + "image" + ], + "supported_output_modalities": [ + "text" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_parallel_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": true, + "supports_web_search": true, + "supports_none_reasoning_effort": true, + "supports_xhigh_reasoning_effort": true, + "supports_minimal_reasoning_effort": false + }, + "azure/eu/gpt-5.5": { + "cache_read_input_token_cost": 5.5e-07, + "cache_read_input_token_cost_above_272k_tokens": 1.1e-06, + "cache_read_input_token_cost_priority": 1.38e-06, + "input_cost_per_token": 5.5e-06, + "input_cost_per_token_above_272k_tokens": 1.1e-05, + "input_cost_per_token_priority": 1.375e-05, + "output_cost_per_token": 3.3e-05, + "output_cost_per_token_above_272k_tokens": 4.95e-05, + "output_cost_per_token_priority": 8.25e-05, + "litellm_provider": "azure", + "max_input_tokens": 1050000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/batch", + "/v1/responses" + ], + "supported_modalities": [ + "text", + "image" + ], + "supported_output_modalities": [ + "text" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_parallel_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": true, + "supports_web_search": true, + "supports_none_reasoning_effort": true, + "supports_xhigh_reasoning_effort": true, + "supports_minimal_reasoning_effort": false + }, "azure/gpt-5.5-2026-04-23": { "cache_read_input_token_cost": 5e-07, "cache_read_input_token_cost_above_272k_tokens": 1e-06, @@ -5959,6 +6183,84 @@ "supports_vision": true, "supports_web_search": true }, + "azure/us/gpt-5.5-2026-04-23": { + "cache_read_input_token_cost": 5.5e-07, + "cache_read_input_token_cost_above_272k_tokens": 1.1e-06, + "cache_read_input_token_cost_priority": 1.38e-06, + "input_cost_per_token": 5.5e-06, + "input_cost_per_token_above_272k_tokens": 1.1e-05, + "input_cost_per_token_priority": 1.375e-05, + "output_cost_per_token": 3.3e-05, + "output_cost_per_token_above_272k_tokens": 4.95e-05, + "output_cost_per_token_priority": 8.25e-05, + "litellm_provider": "azure", + "max_input_tokens": 1050000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/batch", + "/v1/responses" + ], + "supported_modalities": [ + "text", + "image" + ], + "supported_output_modalities": [ + "text" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_parallel_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": true, + "supports_web_search": true + }, + "azure/eu/gpt-5.5-2026-04-23": { + "cache_read_input_token_cost": 5.5e-07, + "cache_read_input_token_cost_above_272k_tokens": 1.1e-06, + "cache_read_input_token_cost_priority": 1.38e-06, + "input_cost_per_token": 5.5e-06, + "input_cost_per_token_above_272k_tokens": 1.1e-05, + "input_cost_per_token_priority": 1.375e-05, + "output_cost_per_token": 3.3e-05, + "output_cost_per_token_above_272k_tokens": 4.95e-05, + "output_cost_per_token_priority": 8.25e-05, + "litellm_provider": "azure", + "max_input_tokens": 1050000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/batch", + "/v1/responses" + ], + "supported_modalities": [ + "text", + "image" + ], + "supported_output_modalities": [ + "text" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_parallel_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_vision": true, + "supports_web_search": true + }, "azure/gpt-5.5-pro": { "cache_read_input_token_cost": 3e-06, "cache_read_input_token_cost_above_272k_tokens": 6e-06, @@ -34829,6 +35131,19 @@ "/v1/audio/speech" ] }, + "vertex_ai/chirp_3": { + "input_cost_per_second": 0.00026667, + "litellm_provider": "vertex_ai", + "metadata": { + "calculation": "$0.016/60 seconds = $0.00026667 per second", + "original_pricing_per_minute": 0.016 + }, + "mode": "audio_transcription", + "source": "https://cloud.google.com/speech-to-text/pricing", + "supported_endpoints": [ + "/v1/audio/transcriptions" + ] + }, "vertex_ai/claude-3-5-haiku": { "input_cost_per_token": 1e-06, "litellm_provider": "vertex_ai-anthropic_models", diff --git a/pyproject.toml b/pyproject.toml index d1f22224c79..3f5458c5494 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "litellm" -version = "1.92.0" +version = "1.93.0" description = "Library to easily interface with LLM API providers" readme = "README.md" requires-python = ">=3.10, <3.14" @@ -63,7 +63,7 @@ proxy = [ "azure-storage-blob>=12.28.0,<13.0", "mcp>=1.26.0,<2.0", "litellm-proxy-extras==0.4.75", - "litellm-enterprise==0.1.47", + "litellm-enterprise==0.1.48", "RestrictedPython>=8.1,<9.0", "rich>=13.9.4,<14.0", "polars>=1.38.1,<2.0", @@ -279,7 +279,7 @@ members = ["enterprise", "litellm-proxy-extras"] profile = "black" [tool.commitizen] -version = "1.92.0" +version = "1.93.0" version_files = [ "pyproject.toml:^version", ] diff --git a/ruff-strict-budget.json b/ruff-strict-budget.json index dcf6e9868b7..7750ac6628a 100644 --- a/ruff-strict-budget.json +++ b/ruff-strict-budget.json @@ -264,7 +264,7 @@ "limit": 63 }, "SIM102": { - "limit": 325 + "limit": 324 }, "SIM103": { "limit": 129 @@ -324,7 +324,7 @@ "limit": 883 }, "UP006": { - "limit": 12870 + "limit": 12869 }, "UP007": { "limit": 2570 diff --git a/schema.prisma b/schema.prisma index f9ab5e6aefd..6d87102a3f3 100644 --- a/schema.prisma +++ b/schema.prisma @@ -329,6 +329,12 @@ model LiteLLM_MCPServerTable { token_url String? registration_url String? oauth2_flow String? + token_exchange_endpoint String? + // Named for the RFC 8693 "audience" token-exchange request parameter (that flow only). + // RFC 8707 resource indicators are a separate concept, named "resource" in the v2 egress types. + audience String? + subject_token_type String? + token_exchange_profile String? allow_all_keys Boolean @default(false) available_on_public_internet Boolean @default(true) delegate_auth_to_upstream Boolean @default(false) diff --git a/scripts/check_type_discipline.py b/scripts/check_type_discipline.py index 6e152541863..809dc141eb8 100644 --- a/scripts/check_type_discipline.py +++ b/scripts/check_type_discipline.py @@ -23,7 +23,7 @@ LIT002 Mutable-collection *construction*: a list/dict/set literal or comprehens str]`) are exempt. Suppress with `# mutable-ok: `. LIT003 noqa suppression without rule codes or without a reason. Required shape: `# noqa: TID251 # ` -LIT004 type/pyright/mypy ignore without bracketed codes or without a reason. +LIT004 pyright/mypy ignore without bracketed codes or without a reason. Required shape: `# pyright: ignore[reportArgumentType] # ` LIT005 A `# mutable-ok` / `# cast-ok` / `# guard-ok` / `# kwargs-ok` suppression without a reason. @@ -38,6 +38,10 @@ LIT008 `**kwargs` parameter. The keyword contract is erased and everything it c is effectively Any. ruff can force it to be typed (ANN003) but can't ban the syntax. Declare explicit keyword params, or accept one frozen payload. `*args`, by contrast, is fine when typed (it's just a tuple). Suppress: `# kwargs-ok: `. +LIT009 `# type: ignore` in any shape (bare, with codes, with a reason). + pyrightconfig.json sets enableTypeIgnoreComments to false, so basedpyright + never honors it: the comment is inert dead syntax that suppresses nothing. + Use `# pyright: ignore[ruleName] # ` instead. LIT000 Setup failure: a target file could not be read, or contains a syntax error. Reported as a violation rather than crashing the run. @@ -95,8 +99,9 @@ NOQA_RE = re.compile( r"(?P.*)", re.IGNORECASE, ) +TYPE_IGNORE_RE = re.compile(r"#\s*type:\s*ignore\b") IGNORE_RE = re.compile( - r"#\s*(?:type|pyright|mypy):\s*ignore(?P\[[^\]]*\])?(?P.*)" + r"#\s*(?:pyright|mypy):\s*ignore(?P\[[^\]]*\])?(?P.*)" ) MUTABLE_OK_RE = re.compile(r"#\s*mutable-ok(?::\s*(?P.*))?") CAST_OK_RE = re.compile(r"#\s*cast-ok(?::\s*(?P.*))?") @@ -161,6 +166,11 @@ def _comment_violations(path: Path, line_no: int, text: str) -> Iterator[Violati elif len(_reason_of(m.group("rest"))) < MIN_REASON_LEN: yield Violation(path, line_no, "LIT003", "noqa requires a reason: `# noqa: XXX123 # `") + if TYPE_IGNORE_RE.search(text): + yield Violation(path, line_no, "LIT009", + "`# type: ignore` is inert (enableTypeIgnoreComments is false, so " + "basedpyright never honors it); use `# pyright: ignore[ruleName] # `") + m = IGNORE_RE.search(text) if m: codes = m.group("codes") diff --git a/scripts/type_discipline_gate.py b/scripts/type_discipline_gate.py index bd63a42dcab..bd8d16553b1 100644 --- a/scripts/type_discipline_gate.py +++ b/scripts/type_discipline_gate.py @@ -9,12 +9,14 @@ adds, never for drift that already exists in the base. Rules not present in the budget are ignored, but today every rule the checker emits is gated: LIT001 (mutable collection in any annotation), LIT002 -(mutable-collection construction), LIT003/LIT004 (noqa / ignore without codes or -reason), LIT006 (cast), and LIT008 (`**kwargs`) carry limits above their current -count to ratchet down; LIT005 (`*-ok` suppression without a reason) is frozen at -limit 0 so any net-new reasonless suppression trips the gate; and LIT007 -(TypeGuard/TypeIs) is a hard zero. ``--update`` ratchets a limit down by the -violations this branch fixed relative to its branch point (the merge-base). +(mutable-collection construction), LIT003/LIT004 (noqa / pyright-mypy ignore +without codes or reason), LIT006 (cast), LIT008 (`**kwargs`), and LIT009 (inert +`# type: ignore`, dead syntax while enableTypeIgnoreComments is false) carry +limits at or above their current count to ratchet down; LIT005 (`*-ok` +suppression without a reason) is frozen at limit 0 so any net-new reasonless +suppression trips the gate; and LIT007 (TypeGuard/TypeIs) is a hard zero. +``--update`` ratchets a limit down by the violations this branch fixed relative +to its branch point (the merge-base). """ import argparse diff --git a/terraform/provider/.gitignore b/terraform/provider/.gitignore new file mode 100644 index 00000000000..7606b250a4c --- /dev/null +++ b/terraform/provider/.gitignore @@ -0,0 +1,71 @@ +# Local .terraform directories +**/.terraform/* +test_litellm/* + +# .tfstate files +*.tfstate +*.tfstate.* + +# Crash log files +crash.log +crash.*.log + +# Exclude all .tfvars files, which are likely to contain sensitive data +*.tfvars +!*.tfvars.example + +# Ignore override files as they are usually used to override resources locally +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Ignore CLI configuration files +.terraformrc +terraform.rc + +# Binary files +terraform-provider-litellm + +# IDE and editor files +.idea/ +*.swp +*.swo +.vscode/ +*.sublime-workspace +*.sublime-project + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Go specific +*.exe +*.exe~ +*.dll +*.so +*.dylib +*.test +*.out +go.work + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a + +# Log files +*.log + +# Environment files +.env diff --git a/terraform/provider/.goreleaser.yml b/terraform/provider/.goreleaser.yml new file mode 100644 index 00000000000..f41a29406b8 --- /dev/null +++ b/terraform/provider/.goreleaser.yml @@ -0,0 +1,81 @@ +# Visit https://goreleaser.com for documentation on how to customize this +# behavior. +version: 2 +before: + hooks: + # this is just an example and not a requirement for provider building/publishing + - go mod tidy +builds: +- env: + # goreleaser does not work with CGO, it could also complicate + # usage by users in CI/CD systems like HCP Terraform where + # they are unable to install libraries. + - CGO_ENABLED=0 + mod_timestamp: '{{ .CommitTimestamp }}' + flags: + - -trimpath + ldflags: + - '-s -w -X main.version={{.Version}} -X main.commit={{.Commit}}' + goos: + - freebsd + - windows + - linux + - darwin + goarch: + - amd64 + - '386' + - arm + - arm64 + ignore: + # macOS doesn't support 32-bit anymore + - goos: darwin + goarch: '386' + # Windows ARM is uncommon for Terraform usage + - goos: windows + goarch: arm + - goos: windows + goarch: arm64 + # FreeBSD ARM is rarely used + - goos: freebsd + goarch: arm + - goos: freebsd + goarch: arm64 + # This builds the following key targets for Terraform users: + # - linux/amd64 (most common CI/CD) + # - linux/arm64 (Graviton, ARM-based CI) + # - linux/386 (legacy 32-bit systems) + # - linux/arm (Raspberry Pi, etc.) + # - darwin/amd64 (Intel Macs) + # - darwin/arm64 (Apple Silicon Macs) + # - windows/amd64 (Windows desktops) + # - freebsd/amd64, freebsd/386 (FreeBSD servers) + binary: '{{ .ProjectName }}_v{{ .Version }}' +archives: +- format: zip + name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}' +checksum: + extra_files: + - glob: 'terraform-registry-manifest.json' + name_template: '{{ .ProjectName }}_{{ .Version }}_manifest.json' + name_template: '{{ .ProjectName }}_{{ .Version }}_SHA256SUMS' + algorithm: sha256 +signs: + - artifacts: checksum + args: + # if you are using this in a GitHub action or some other automated pipeline, you + # need to pass the batch flag to indicate its not interactive. + - "--batch" + - "--local-user" + - "{{ .Env.GPG_FINGERPRINT }}" # set this environment variable for your signing key + - "--output" + - "${signature}" + - "--detach-sign" + - "${artifact}" +release: + extra_files: + - glob: 'terraform-registry-manifest.json' + name_template: '{{ .ProjectName }}_{{ .Version }}_manifest.json' + # If you want to manually examine the release before its live, uncomment this line: + # draft: true +changelog: + disable: true \ No newline at end of file diff --git a/terraform/provider/CHANGELOG.md b/terraform/provider/CHANGELOG.md new file mode 100644 index 00000000000..101519c0b08 --- /dev/null +++ b/terraform/provider/CHANGELOG.md @@ -0,0 +1,294 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Fixed + +- **organization**: Send `PATCH` instead of `POST` to `/organization/update` and `/organization/member_update`, matching the methods the LiteLLM proxy serves; organization and organization member updates previously failed with a 405 + +### Changed + +- The provider source of truth moved to `terraform/provider/` in [BerriAI/litellm](https://github.com/BerriAI/litellm); this repository is now a release mirror. CI in the monorepo statically audits every endpoint the provider calls against the proxy's OpenAPI schema on every change + +## [0.2.2] - 2026-05-13 + +### Fixed + +- **key**: Include `tags` in `UpdateKey` payload so tag changes on an existing `litellm_key` are applied on update instead of being silently dropped (#41) + +## [0.2.1] - 2026-04-13 + +### Fixed + +- **team, organization**: Use pointer types for `tpm_limit`, `rpm_limit`, and `max_budget` to prevent zero-value diffs on every `terraform plan` when these fields are not configured (#31) + +## [0.2.0] - 2026-04-03 + +### ⚠️ Breaking Changes + +#### `litellm_key`: API keys are no longer stored in Terraform state + +**Why this change?** Storing raw API keys in Terraform state is a security risk — state files are often stored in S3, Terraform Cloud, or other backends where the key could be exposed even with encryption at rest. This release eliminates that risk entirely. + +**What changed:** +- The `key` attribute is now **write-only** — available during `terraform apply` so you can pipe it to a secrets manager, but never persisted to state +- The resource ID has changed from the raw key value to its **SHA-256 hash (`token_id`)** — safe to store in state, cannot be used to authenticate +- **Requires Terraform 1.11+** + +**Migration steps for existing `litellm_key` resources:** + +1. Find the `token_id` for each key via the LiteLLM UI or `GET /key/info?key=` +2. Remove the old resource from state: + ``` + terraform state rm litellm_key.example + ``` +3. Re-import using the token_id: + ``` + terraform import litellm_key.example + ``` + +> ⚠️ After upgrading, you cannot retrieve the raw key from state. Make sure you have the key value stored somewhere safe before migrating, or plan to rotate the key after re-import. + +**Security best practice:** Since the key is only available during the initial `terraform apply`, pipe it directly to a secrets manager: + +```hcl +resource "aws_ssm_parameter" "litellm_key" { + name = "/myapp/litellm-key" + type = "SecureString" + value = litellm_key.example.key +} +``` + +### Fixed + +- **key**: API key is no longer stored in Terraform state. The `key` attribute is now write-only and `token_id` is used as the resource ID (#27) +- **model**: Handle eventual consistency in model reads post-create (#26) + +## [0.1.2] - 2026-02-17 + +### Added +- **Documentation**: Added RELEASING.md with comprehensive release process documentation + - GPG key setup instructions + - Step-by-step release workflow + - Troubleshooting guide + - Security best practices + +## [0.1.1] - 2026-02-11 + +### Added +- **New Model Modes**: Added support for `audio_speech` and `rerank` model modes + - `audio_speech`: For text-to-speech models (e.g., Gemini TTS, OpenAI TTS) + - `rerank`: For reranking/semantic ranking models (e.g., Cohere Rerank, Vertex AI Semantic Ranker) + +### Fixed +- Implemented exponential backoff for credential reads +- Only include cost fields when explicitly set in model resource +- Added litellm_credential_name support + +## [0.3.14] - 2025-08-24 + +### Added +- **Enhanced JSON Parsing**: Added support for JSON string parsing in `additional_litellm_params` + - JSON objects and arrays (starting with `{` or `[`) are now automatically parsed + - Maintains backward compatibility with existing string-to-type conversion + - Enables complex nested parameter configurations +- **Parameter Dropping Feature**: Added `additional_drop_params` special parameter + - Allows removal of unwanted parameters from final `litellm_params` before API submission + - Specified as JSON array string: `"additional_drop_params" = "[\"reasoningEffort\"]"` + - Useful for overriding or removing built-in parameters when needed +- **Enhanced Examples**: Updated `examples/model_additional_params.tf` with comprehensive JSON parsing examples + - Demonstrates all supported value types (boolean, integer, float, string, JSON objects/arrays) + - Includes real-world Azure model configuration with parameter dropping + - Shows both simple and complex use cases + +### Changed +- **Documentation Enhancement**: Updated `docs/resources/model.md` with detailed JSON parsing documentation + - Added comprehensive explanation of conversion rules and behavior + - Included special `additional_drop_params` parameter documentation + - Enhanced examples showing all supported parameter types and JSON parsing capabilities + +### Technical Details +- Enhanced parameter processing logic in `createOrUpdateModel()` function +- Added JSON detection and parsing for string values starting with `[` or `{` +- Implemented parameter filtering system for `additional_drop_params` +- Maintains full backward compatibility with existing configurations + +## [0.3.13] - 2025-08-24 + +### Changed +- Documentation: Performed a documentation audit and improvements across resources and data-sources. Added missing argument references, clarified types/defaults, documented implementation behaviors (e.g., additional_litellm_params parsing and state-preservation), and added an `examples/` directory with runnable HCL examples (starting with `examples/model_additional_params.tf`). +- Docs: Updated `docs/resources/model.md` with missing fields (`vertex_*`, pixel/second cost fields, and `additional_litellm_params`) and added conversion rules and an example. +- Docs Index: Added references to the new `examples/` directory in `docs/index.md`. + +## [0.3.12] - 2025-08-13 + +### Added +- **New AWS Parameters**: Added `aws_session_name` and `aws_role_name` to model resource for cross-account access scenarios + - Support for AWS session names in cross-account access configurations + - Support for AWS IAM role names for cross-account access + - Enhanced AWS Bedrock integration capabilities + +### Changed +- **Documentation Overhaul**: Comprehensive update to all provider documentation + - Updated provider source references from `bitop/litellm` to `registry.terraform.io/ncecere/litellm` + - Consolidated all scattered example files into organized documentation structure + - Enhanced all resource documentation with multiple real-world examples + - Added comprehensive cross-resource integration examples +- **Vector Store Documentation**: Updated to reflect only officially supported LiteLLM providers + - Removed unsupported providers (Pinecone, Weaviate, Chroma, Qdrant, Milvus, FAISS) + - Added accurate examples for supported providers: AWS Bedrock Knowledge Bases, OpenAI Vector Stores, Azure Vector Stores, Vertex AI RAG Engine, PG Vector + - Updated provider-specific parameters with correct configurations + - Added references to official LiteLLM documentation +- **Project Organization**: Cleaned up project structure + - Removed scattered example files from root directory + - Consolidated all examples into comprehensive documentation + - Updated README.md to reflect current capabilities and structure + +### Fixed +- Corrected vector store provider documentation to match LiteLLM's official capabilities +- Updated all documentation links and references for accuracy + +## [0.3.11] - 2025-08-10 + +### Added +- **New Resource**: `litellm_credential` - Manage credentials for secure authentication + - Support for storing sensitive credential values (API keys, tokens, etc.) + - Non-sensitive credential information storage + - Model ID association for credentials + - Secure handling of sensitive data with Terraform's sensitive attribute +- **New Resource**: `litellm_vector_store` - Manage vector stores for embeddings and RAG + - Support for multiple vector store providers (Pinecone, Weaviate, Chroma, Qdrant, etc.) + - Integration with credential management for secure authentication + - Configurable metadata and provider-specific parameters + - Full CRUD operations for vector store lifecycle management +- **New Data Source**: `litellm_credential` - Retrieve information about existing credentials + - Read-only access to credential metadata (sensitive values excluded for security) + - Support for model ID filtering + - Cross-stack and cross-configuration referencing capabilities +- **New Data Source**: `litellm_vector_store` - Retrieve information about existing vector stores + - Complete vector store information retrieval + - Support for monitoring, validation, and cross-referencing use cases + - Metadata-based conditional logic support +- Enhanced API response handling for credential and vector store operations +- Comprehensive documentation and examples for new resources and data sources +- Example Terraform configurations for common use cases + +### Changed +- Extended `utils.go` with specialized API response handlers for credentials and vector stores +- Updated provider configuration to include new resources and data sources +- Enhanced error handling for credential and vector store not found scenarios + +## [0.3.10] - 2025-08-10 + +### Added +- **New Resource**: `litellm_mcp_server` - Manage MCP (Model Context Protocol) servers + - Support for HTTP, SSE, and stdio transport types + - Configurable authentication types (none, bearer, basic) + - MCP access groups for permission management + - Cost tracking configuration for MCP tools + - Environment variables and command arguments for stdio transport + - Health check status monitoring + - Comprehensive documentation and examples + +### Changed +- Updated provider to support MCP server management functionality +- Enhanced API response handling for MCP-specific operations + +## [0.3.9] - 2025-08-10 + +### Fixed +- Fixed issue where omitting `budget_duration` in key resource caused API error "Invalid duration format" +- Added missing `omitempty` JSON tag to `BudgetDuration` field in Key struct to prevent sending empty strings to API + +## [0.3.8] - 2025-08-08 + +### Added +- Added `additional_litellm_params` field to model resource for custom parameters beyond standard ones +- Support for passing custom parameters like `drop_params`, `timeout`, `max_retries`, `organization`, etc. +- Automatic type conversion for string values to appropriate types (boolean, integer, float) +- Full backward compatibility with existing model configurations +- Comprehensive example demonstrating various use cases with different providers + +## [0.3.7] - 2025-08-08 + +### Fixed +- Fixed issue where changing max_budget_in_team didn't update existing team members with new budget +- Added budget change detection using d.HasChange to update ALL existing members when budget changes +- Implemented tracking to avoid duplicate API calls for members already updated +- Enhanced debug logging for budget update operations + +## [0.3.6] - 2025-08-08 + +### Fixed +- Fixed issue where models deleted from LiteLLM proxy caused terraform plan to fail instead of planning recreation +- Enhanced ErrorResponse struct to properly parse LiteLLM proxy error format with Detail field +- Improved isModelNotFoundError function to detect "not found on litellm proxy" messages in Detail.Error field + +## [0.3.5] - 2025-08-08 + +### Fixed +- Fixed team member update behavior to use member_update endpoint instead of delete/re-add +- Restored team_member_permissions functionality to litellm_team resource +- Enhanced team resource with proper permissions management endpoints + +## [0.3.0] - 2025-04-23 + +### Fixed +- Implemented retry mechanism with exponential backoff for model read operations +- Added detailed logging for retry attempts +- Improved error handling for "model not found" errors + +## [0.2.9] - 2025-04-23 + +### Fixed +- Increased delay after model creation from 2 to 5 seconds to fix "model not found" errors +- Added logging to confirm delay is working properly + +## [0.2.8] - 2025-04-23 + +### Fixed +- Added delay after model creation to fix "model not found" errors when the LiteLLM proxy hasn't fully registered the model yet + +## [0.2.7] - 2025-04-23 + +### Fixed +- Fixed issue where `thinking_enabled` and `merge_reasoning_content_in_choices` values were not being preserved in state, causing Terraform to want to modify them on every run + +## [0.2.6] - 2025-03-13 + +### Added +- Added new `merge_reasoning_content_in_choices` option to model resource + +## [0.2.5] - 2025-03-13 + +### Fixed +- Fixed issue where `thinking_budget_tokens` was being added to models that don't have `thinking_enabled = true` + +## [0.2.4] - 2025-03-13 + +### Added +- Added new `thinking` capability to model resource with configurable parameters: + - `thinking_enabled` - Boolean to enable/disable thinking capability (default: false) + - `thinking_budget_tokens` - Integer to set token budget for thinking (default: 1024) + +## [0.2.2] - 2025-02-06 + +### Added +- Added new `reasoning_effort` parameter to model resource with values: "low", "medium", "high" +- Added "chat" mode to model resource + +### Changed +- Updated model mode options to: "completion", "embedding", "image_generation", "chat", "moderation", "audio_transcription" + +## [1.0.0] - 2024-01-17 + +### Added +- Initial release of the LiteLLM Terraform Provider +- Support for managing LiteLLM models +- Support for managing teams and team members +- Comprehensive documentation for all resources diff --git a/terraform/provider/LICENSE b/terraform/provider/LICENSE new file mode 100644 index 00000000000..967d4ac9b42 --- /dev/null +++ b/terraform/provider/LICENSE @@ -0,0 +1,35 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source diff --git a/terraform/provider/Makefile b/terraform/provider/Makefile new file mode 100644 index 00000000000..ddca16e1636 --- /dev/null +++ b/terraform/provider/Makefile @@ -0,0 +1,32 @@ +HOSTNAME=registry.terraform.io +NAMESPACE=local +NAME=litellm +VERSION=1.0.0 +OS_ARCH=darwin_amd64 + +default: install + +build: + go build -o terraform-provider-${NAME} + +install: build + mkdir -p ~/.terraform.d/plugins/${HOSTNAME}/${NAMESPACE}/${NAME}/${VERSION}/${OS_ARCH} + mv terraform-provider-${NAME} ~/.terraform.d/plugins/${HOSTNAME}/${NAMESPACE}/${NAME}/${VERSION}/${OS_ARCH}/terraform-provider-${NAME}_v${VERSION} + +test: + go test ./... + +fmt: + go fmt ./... + +vet: + go vet ./... + +lint: + golangci-lint run + +clean: + rm -f terraform-provider-${NAME} + rm -rf ~/.terraform.d/plugins/${HOSTNAME}/${NAMESPACE}/${NAME}/${VERSION} + +.PHONY: build install test fmt vet lint clean diff --git a/terraform/provider/README.md b/terraform/provider/README.md new file mode 100644 index 00000000000..3b59edd97c6 --- /dev/null +++ b/terraform/provider/README.md @@ -0,0 +1,223 @@ +# LiteLLM Terraform Provider + +This Terraform provider allows you to manage LiteLLM resources through Infrastructure as Code. It provides support for managing models, teams, team members, and API keys via the LiteLLM REST API. + +## Source of truth + +This directory (`terraform/provider/` in [BerriAI/litellm](https://github.com/BerriAI/litellm)) is the source of truth for the provider. [BerriAI/terraform-provider-litellm](https://github.com/BerriAI/terraform-provider-litellm) is a thin release mirror that the public Terraform Registry ingests from; do not open PRs there. Changes land here, where CI builds the provider, runs its tests, and statically audits every endpoint the provider calls against the proxy's generated OpenAPI schema (`tools/endpointaudit/`), so the provider cannot drift from the LiteLLM API silently. Releases are published by mirroring this directory into the split repo and tagging it, which triggers the goreleaser workflow there (see `RELEASING.md`) + +## Features + +- Manage LiteLLM model configurations +- Associate models with specific teams +- Create and manage teams +- Configure team members and their permissions +- Set usage limits and budgets +- Control access to specific models +- Specify model modes (e.g., completion, embedding, image generation) +- Manage API keys with fine-grained controls +- Support for reasoning effort configuration in the model resource + +## Requirements + +- [Terraform](https://www.terraform.io/downloads.html) >= 0.13.x +- [Go](https://golang.org/doc/install) >= 1.16 (for development) + +## Using the Provider + +To use the LiteLLM provider in your Terraform configuration, you need to declare it in the terraform block: + +```hcl +terraform { + required_providers { + litellm = { + source = "BerriAI/litellm" + version = "~> 0.1.1" #HERE UPDATE VERSION ACCORDINGLY + } + } +} + +provider "litellm" { + api_base = var.litellm_api_base + api_key = var.litellm_api_key +} +``` + +Then, you can use the provider to manage LiteLLM resources. Here's an example of creating a model configuration: + +```hcl +resource "litellm_model" "gpt4" { + model_name = "gpt-4-proxy" + custom_llm_provider = "openai" + model_api_key = var.openai_api_key + model_api_base = "https://api.openai.com/v1" + base_model = "gpt-4" + tier = "paid" + mode = "chat" + reasoning_effort = "medium" # Optional: "low", "medium", or "high" + + input_cost_per_million_tokens = 30.0 + output_cost_per_million_tokens = 60.0 +} +``` + +For full details on the litellm_model resource, see the [model resource documentation](docs/resources/model.md). + +Here's an example of creating an API key with various options: + +```hcl +resource "litellm_key" "example_key" { + models = ["gpt-4", "claude-3.5-sonnet"] + max_budget = 100.0 + user_id = "user123" + team_id = "team456" + max_parallel_requests = 5 + tpm_limit = 1000 + rpm_limit = 60 + budget_duration = "monthly" + key_alias = "prod-key-1" + duration = "30d" + metadata = { + environment = "production" + } + allowed_cache_controls = ["no-cache", "max-age=3600"] + soft_budget = 80.0 + aliases = { + "gpt-4" = "gpt4" + } + config = { + default_model = "gpt-4" + } + permissions = { + can_create_keys = "true" + } + model_max_budget = { + "gpt-4" = 50.0 + } + model_rpm_limit = { + "claude-3.5-sonnet" = 30 + } + model_tpm_limit = { + "gpt-4" = 500 + } + guardrails = ["content_filter", "token_limit"] + blocked = false + tags = ["production", "api"] +} +``` + +The litellm_key resource supports the following options: + +- models: List of allowed models for this key +- max_budget: Maximum budget for the key +- user_id and team_id: Associate the key with a user and team +- max_parallel_requests: Limit concurrent requests +- tpm_limit and rpm_limit: Set tokens and requests per minute limits +- budget_duration: Specify budget duration (e.g., "monthly", "weekly") +- key_alias: Set a friendly name for the key +- duration: Set the key's validity period +- metadata: Add custom metadata to the key +- allowed_cache_controls: Specify allowed cache control directives +- soft_budget: Set a soft budget limit +- aliases: Define model aliases +- config: Set configuration options +- permissions: Specify key permissions +- model_max_budget, model_rpm_limit, model_tpm_limit: Set per-model limits +- guardrails: Apply specific guardrails to the key +- blocked: Flag to block/unblock the key +- tags: Add tags for organization and filtering + +For full details on the litellm_key resource, see the [key resource documentation](docs/resources/key.md). + +### Available Resources + +- litellm_model: Manage model configurations. [Documentation](docs/resources/model.md) +- litellm_team: Manage teams. [Documentation](docs/resources/team.md) +- litellm_team_member: Manage team members. [Documentation](docs/resources/team_member.md) +- litellm_team_member_add: Add multiple members to teams. [Documentation](docs/resources/team_member_add.md) +- litellm_key: Manage API keys. [Documentation](docs/resources/key.md) +- litellm_mcp_server: Manage MCP (Model Context Protocol) servers. [Documentation](docs/resources/mcp_server.md) +- litellm_credential: Manage credentials for secure authentication. [Documentation](docs/resources/credential.md) +- litellm_vector_store: Manage vector stores for embeddings and RAG. [Documentation](docs/resources/vector_store.md) + +### Available Data Sources + +- litellm_credential: Retrieve information about existing credentials. [Documentation](docs/data-sources/credential.md) +- litellm_vector_store: Retrieve information about existing vector stores. [Documentation](docs/data-sources/vector_store.md) + +## Development + +### Project Structure + +The project is organized as follows: + +``` +terraform-provider-litellm/ +├── litellm/ +│ ├── provider.go +│ ├── resource_model.go +│ ├── resource_model_crud.go +│ ├── resource_team.go +│ ├── resource_team_member.go +│ ├── resource_key.go +│ ├── resource_key_utils.go +│ ├── types.go +│ └── utils.go +├── main.go +├── go.mod +├── go.sum +├── Makefile +└── ... +``` + +### Building the Provider + +1. Clone the repository: +```sh +git clone https://github.com/your-username/terraform-provider-litellm.git +``` + +2. Enter the repository directory: +```sh +cd terraform-provider-litellm +``` + +3. Build and install the provider: +```sh +make install +``` + +### Development Commands + +The Makefile provides several useful commands for development: + +- `make build`: Builds the provider +- `make install`: Builds and installs the provider +- `make test`: Runs the test suite +- `make fmt`: Formats the code +- `make vet`: Runs go vet +- `make lint`: Runs golangci-lint +- `make clean`: Removes build artifacts and installed provider + +### Testing + +To run the tests: +```sh +make test +``` + +### Contributing + +Contributions are welcome! Please read our [contributing guidelines](CONTRIBUTING.md) first. + +## License + +This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details. + +## Notes + +- Always use environment variables or secure secret management solutions to handle sensitive information like API keys and AWS credentials. +- Refer to the comprehensive documentation in the `docs/` directory for detailed usage examples and configuration options. +- Make sure to keep your provider version updated for the latest features and bug fixes. +- The provider now supports AWS cross-account access with `aws_session_name` and `aws_role_name` parameters in the model resource. +- All example configurations have been consolidated into the documentation for better organization and maintenance. diff --git a/terraform/provider/RELEASING.md b/terraform/provider/RELEASING.md new file mode 100644 index 00000000000..1dc296f29b8 --- /dev/null +++ b/terraform/provider/RELEASING.md @@ -0,0 +1,237 @@ +# Release Process + +This document describes the release process for the LiteLLM Terraform Provider. + +## Overview + +Releases are automated via GitHub Actions when a version tag is pushed. The workflow builds the provider for multiple platforms, signs the artifacts with GPG, and publishes them to GitHub Releases. + +## Prerequisites + +### GPG Key Setup (One-Time Setup for Repository Maintainers) + +The Terraform Registry requires all providers to be signed with a GPG key. This must be configured before the first release. + +#### 1. Generate a GPG Key + +If you don't already have a GPG key for provider signing: + +```bash +gpg --full-generate-key +``` + +Configuration: +- Key type: RSA and RSA (default) +- Key size: 4096 bits +- Expiration: No expiration (or set a long expiration period) +- Email: Use an email associated with your GitHub account +- Set a strong passphrase (or leave empty for CI/CD use) + +#### 2. Export the GPG Key + +```bash +# List your keys to get the key ID +gpg --list-secret-keys --keyid-format=long + +# Example output: +# sec rsa4096/ABCD1234EFGH5678 2024-01-01 [SC] +# 1234567890ABCDEF1234567890ABCDEF12345678 +# uid [ultimate] Your Name +# +# The key ID is: ABCD1234EFGH5678 +# The fingerprint is: 1234567890ABCDEF1234567890ABCDEF12345678 + +# Export the private key (ASCII-armored format) +gpg --armor --export-secret-keys ABCD1234EFGH5678 + +# Export the public key +gpg --armor --export ABCD1234EFGH5678 +``` + +#### 3. Configure GitHub Repository Secrets + +Add the following secrets to the repository at: **Settings → Secrets and variables → Actions → New repository secret** + +| Secret Name | Description | Value | +|-------------|-------------|-------| +| `GPG_PRIVATE_KEY` | The GPG private key for signing releases | Full output from `gpg --armor --export-secret-keys` (including `-----BEGIN PGP PRIVATE KEY BLOCK-----` and `-----END PGP PRIVATE KEY BLOCK-----`) | +| `PASSPHRASE` | The passphrase for the GPG key | Your GPG key passphrase (leave empty if no passphrase was set) | + +#### 4. Register Public Key with Terraform Registry + +Before publishing to the Terraform Registry: + +1. Go to https://registry.terraform.io/settings/gpg-keys +2. Click "Add a key" +3. Paste your public GPG key (output from `gpg --armor --export`) +4. Submit + +**Note**: The public key fingerprint must match the key used to sign the provider releases. + +## Release Steps + +### 1. Prepare the Release + +Before creating a release: + +1. **Update CHANGELOG.md** + - Move items from `[Unreleased]` section to a new version section + - Follow [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) format + - Use [Semantic Versioning](https://semver.org/spec/v2.0.0.html) for version numbers + - Include all notable changes since the last release + + Example: + ```markdown + ## [0.1.2] - 2026-02-20 + + ### Added + - New feature description + + ### Fixed + - Bug fix description + + ### Changed + - Changed behavior description + ``` + +2. **Verify tests pass** + ```bash + make test + ``` + +3. **Verify the build works locally** + ```bash + make build + ``` + +4. **Land the changes in BerriAI/litellm** + + Open a PR to `BerriAI/litellm` updating `terraform/provider/CHANGELOG.md` (and any source changes) and merge it. Note the merge commit SHA; the release workflow takes it as `git_ref` + +### 2. Mirror and Tag via project-releaser + +The provider source lives at `terraform/provider/` in `BerriAI/litellm`; `BerriAI/terraform-provider-litellm` is a thin release mirror. Do not commit or tag the mirror directly + +1. Go to `BerriAI/project-releaser` > **Actions** > `Publish Terraform provider` +2. Click **Run workflow**: + - `git_ref`: full 40-char commit SHA from `BerriAI/litellm` to release from + - `provider_version`: the new version without the `v` prefix (e.g. `0.3.0`) + - `dry_run`: optional; validates without pushing +3. The workflow rsyncs `terraform/provider/` into the mirror repo, commits, and pushes tag `v` +4. The tag push triggers the mirror's `Release` workflow (goreleaser), which is gated by the `production-release` environment approval + +**Important**: +- Tags must follow the format: `v..` (e.g., `v0.1.2`, `v1.0.0`) +- The workflow refuses to overwrite an existing tag; publish a new version instead + +### 3. Monitor the Release Workflow + +1. Go to: https://github.com/BerriAI/terraform-provider-litellm/actions +2. Find the "Release" workflow run for your tag +3. Monitor the progress and check for any errors + +The workflow will: +- Check out the code +- Set up Go +- Import the GPG key +- Run `go mod tidy` +- Build binaries for multiple platforms (Linux, macOS, Windows, FreeBSD) +- Create archives and checksums +- Sign the checksums with GPG +- Create a GitHub release +- Upload all artifacts + +### 4. Verify the Release + +After the workflow completes successfully: + +1. **Check the GitHub Release** + - Go to: https://github.com/BerriAI/terraform-provider-litellm/releases + - Verify the release was created with the correct version + - Confirm all artifacts are present: + - Binary archives for each platform + - SHA256SUMS file + - SHA256SUMS.sig (GPG signature) + - terraform-registry-manifest.json + +2. **Verify the signature** (optional) + ```bash + # Download the checksums and signature + wget https://github.com/BerriAI/terraform-provider-litellm/releases/download/v0.1.2/terraform-provider-litellm_0.1.2_SHA256SUMS + wget https://github.com/BerriAI/terraform-provider-litellm/releases/download/v0.1.2/terraform-provider-litellm_0.1.2_SHA256SUMS.sig + + # Verify the signature + gpg --verify terraform-provider-litellm_0.1.2_SHA256SUMS.sig terraform-provider-litellm_0.1.2_SHA256SUMS + ``` + +### 5. Publish to Terraform Registry (Optional) + +If this provider is published to the Terraform Registry: + +1. The registry should automatically detect the new release via the GitHub webhook +2. If not, you may need to manually trigger a sync on the Terraform Registry dashboard +3. Verify the new version appears at: https://registry.terraform.io/providers/BerriAI/litellm/latest + +## Troubleshooting + +### Release Workflow Fails with GPG Error + +**Error**: `Input required and not supplied: gpg_private_key` + +**Solution**: +- Verify that `GPG_PRIVATE_KEY` and `PASSPHRASE` secrets are configured in the repository +- Ensure the secrets are not expired +- Check that the secret names match exactly (case-sensitive) + +### GoReleaser Signing Fails + +**Error**: `gpg: signing failed: No secret key` + +**Solution**: +- Verify the `GPG_PRIVATE_KEY` secret contains the complete private key block +- Ensure the passphrase is correct +- Check that the key hasn't expired: `gpg --list-keys` + +### Build Fails + +**Error**: Build errors during compilation + +**Solution**: +- Run `make test` and `make build` locally first +- Ensure `go.mod` and `go.sum` are up to date +- Check that all dependencies are available + +### Tag Already Exists + +**Error**: The publish workflow refuses to push because the tag already exists on the mirror + +**Solution**: Tags are immutable by design. Re-run the workflow with a new patch version instead of deleting or moving an existing tag + +## Version Numbering + +This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html): + +- **MAJOR** version (1.0.0): Incompatible API changes +- **MINOR** version (0.1.0): New functionality in a backward-compatible manner +- **PATCH** version (0.0.1): Backward-compatible bug fixes + +For pre-1.0 releases: +- Breaking changes may occur in minor versions +- Patch versions should only contain bug fixes + +## Security Considerations + +1. **Never commit private keys**: The GPG private key should only be stored as a GitHub secret +2. **Protect repository secrets**: Limit who has access to manage repository secrets +3. **Use a dedicated key**: Consider using a separate GPG key specifically for provider signing +4. **Key rotation**: If the GPG key is compromised, generate a new key, update secrets, and register the new public key with the Terraform Registry +5. **Passphrase**: Use a strong passphrase for the GPG key, or use a passphrase-less key specifically for CI/CD + +## References + +- [GoReleaser Documentation](https://goreleaser.com/) +- [Terraform Provider Publishing](https://www.terraform.io/docs/registry/providers/publishing.html) +- [HashiCorp GPG Signing Requirements](https://www.terraform.io/docs/registry/providers/publishing.html#signing-releases) +- [GitHub Actions Secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets) +- [Semantic Versioning](https://semver.org/) +- [Keep a Changelog](https://keepachangelog.com/) diff --git a/terraform/provider/docs/data-sources/credential.md b/terraform/provider/docs/data-sources/credential.md new file mode 100644 index 00000000000..de4b9a8d9e4 --- /dev/null +++ b/terraform/provider/docs/data-sources/credential.md @@ -0,0 +1,153 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "litellm_credential Data Source - terraform-provider-litellm" +subcategory: "" +description: |- + Retrieves information about an existing LiteLLM credential. +--- + +# litellm_credential (Data Source) + +Retrieves information about an existing LiteLLM credential. This data source allows you to reference credentials that were created outside of Terraform or in other Terraform configurations. + +## Example Usage + +```terraform +# Retrieve an existing credential by name +data "litellm_credential" "existing_openai" { + credential_name = "openai-production-key" +} + +# Use the credential in a model resource +resource "litellm_model" "gpt4_with_existing_cred" { + model_name = "gpt-4-with-existing-cred" + custom_llm_provider = "openai" + base_model = "gpt-4" + tier = "paid" + mode = "chat" + + # Reference the existing credential's info + additional_litellm_params = { + credential_name = data.litellm_credential.existing_openai.credential_name + } +} +``` + +## Example Usage with Model ID + +```terraform +# Retrieve a credential associated with a specific model +data "litellm_credential" "model_specific_cred" { + credential_name = "claude-api-key" + model_id = "claude-3-sonnet" +} + +# Use in a vector store +resource "litellm_vector_store" "knowledge_base" { + vector_store_name = "claude-knowledge-base" + custom_llm_provider = "anthropic" + litellm_credential_name = data.litellm_credential.model_specific_cred.credential_name + + vector_store_description = "Knowledge base using Claude credentials" +} +``` + +## Example Usage for Cross-Reference + +```terraform +# Get credential info to use in other resources +data "litellm_credential" "shared_cred" { + credential_name = "shared-api-key" +} + +# Create multiple resources using the same credential +resource "litellm_vector_store" "store_1" { + vector_store_name = "store-1" + custom_llm_provider = "pinecone" + litellm_credential_name = data.litellm_credential.shared_cred.credential_name + + vector_store_description = "First store using shared credential" +} + +resource "litellm_vector_store" "store_2" { + vector_store_name = "store-2" + custom_llm_provider = "pinecone" + litellm_credential_name = data.litellm_credential.shared_cred.credential_name + + vector_store_description = "Second store using shared credential" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `credential_name` - (Required) Name of the credential to retrieve. +* `model_id` - (Optional) Model ID associated with this credential. Use this when the same credential name is used for different models. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `credential_info` - Map of additional non-sensitive information about the credential. + +## Security Note + +For security reasons, the `credential_values` (sensitive data like API keys) are not exposed through data sources. This prevents accidental exposure of sensitive information in Terraform plans and logs. If you need to access credential values, you should manage them through the resource directly or use external secret management systems. + +## Common Use Cases + +### 1. Cross-Stack References +Use data sources to reference credentials created in other Terraform configurations or stacks: + +```terraform +data "litellm_credential" "shared_openai" { + credential_name = "openai-shared-key" +} + +resource "litellm_model" "gpt4" { + model_name = "gpt-4-cross-stack" + custom_llm_provider = "openai" + base_model = "gpt-4" + + additional_litellm_params = { + credential_reference = data.litellm_credential.shared_openai.credential_name + } +} +``` + +### 2. Conditional Logic +Use credential information for conditional resource creation: + +```terraform +data "litellm_credential" "optional_cred" { + credential_name = var.credential_name +} + +resource "litellm_vector_store" "conditional_store" { + count = length(data.litellm_credential.optional_cred.credential_info) > 0 ? 1 : 0 + + vector_store_name = "conditional-store" + custom_llm_provider = "weaviate" + litellm_credential_name = data.litellm_credential.optional_cred.credential_name +} +``` + +### 3. Validation and Verification +Verify that required credentials exist before creating dependent resources: + +```terraform +data "litellm_credential" "required_cred" { + credential_name = "production-api-key" +} + +# This will fail if the credential doesn't exist +resource "litellm_model" "production_model" { + model_name = "production-gpt-4" + custom_llm_provider = "openai" + base_model = "gpt-4" + + additional_litellm_params = { + credential_name = data.litellm_credential.required_cred.credential_name + } +} diff --git a/terraform/provider/docs/data-sources/vector_store.md b/terraform/provider/docs/data-sources/vector_store.md new file mode 100644 index 00000000000..30bc26c163e --- /dev/null +++ b/terraform/provider/docs/data-sources/vector_store.md @@ -0,0 +1,225 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "litellm_vector_store Data Source - terraform-provider-litellm" +subcategory: "" +description: |- + Retrieves information about an existing LiteLLM vector store. +--- + +# litellm_vector_store (Data Source) + +Retrieves information about an existing LiteLLM vector store. This data source allows you to reference vector stores that were created outside of Terraform or in other Terraform configurations. + +## Example Usage + +```terraform +# Retrieve an existing vector store by ID +data "litellm_vector_store" "existing_store" { + vector_store_id = "vs-12345" +} + +# Use the vector store information in outputs +output "vector_store_info" { + value = { + name = data.litellm_vector_store.existing_store.vector_store_name + provider = data.litellm_vector_store.existing_store.custom_llm_provider + created_at = data.litellm_vector_store.existing_store.created_at + } +} +``` + +## Example Usage for Cross-Reference + +```terraform +# Get vector store info to reference in other configurations +data "litellm_vector_store" "shared_store" { + vector_store_id = var.shared_vector_store_id +} + +# Create a model that might use the same credential as the vector store +data "litellm_credential" "store_credential" { + credential_name = data.litellm_vector_store.shared_store.litellm_credential_name +} + +resource "litellm_model" "embedding_model" { + model_name = "embedding-model" + custom_llm_provider = "openai" + base_model = "text-embedding-ada-002" + mode = "embedding" + + additional_litellm_params = { + credential_name = data.litellm_credential.store_credential.credential_name + } +} +``` + +## Example Usage for Validation + +```terraform +# Verify vector store exists and get its configuration +data "litellm_vector_store" "production_store" { + vector_store_id = "production-vector-store-id" +} + +# Create resources only if the vector store is properly configured +resource "litellm_model" "rag_model" { + count = data.litellm_vector_store.production_store.custom_llm_provider == "pinecone" ? 1 : 0 + + model_name = "rag-enabled-model" + custom_llm_provider = "openai" + base_model = "gpt-4" + mode = "chat" + + additional_litellm_params = { + vector_store_id = data.litellm_vector_store.production_store.vector_store_id + } +} +``` + +## Example Usage for Monitoring + +```terraform +# Get vector store details for monitoring and alerting +data "litellm_vector_store" "monitored_stores" { + for_each = toset(var.vector_store_ids) + + vector_store_id = each.value +} + +# Output store information for monitoring systems +output "vector_store_status" { + value = { + for k, v in data.litellm_vector_store.monitored_stores : k => { + name = v.vector_store_name + provider = v.custom_llm_provider + created_at = v.created_at + updated_at = v.updated_at + metadata = v.vector_store_metadata + } + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `vector_store_id` - (Required) Unique identifier for the vector store to retrieve. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `vector_store_name` - Name of the vector store. +* `custom_llm_provider` - Custom LLM provider for the vector store. +* `vector_store_description` - Description of the vector store. +* `vector_store_metadata` - Map of metadata associated with the vector store. +* `litellm_credential_name` - Name of the LiteLLM credential used. +* `litellm_params` - Map of additional LiteLLM parameters. +* `created_at` - Timestamp when the vector store was created. +* `updated_at` - Timestamp when the vector store was last updated. + +## Common Use Cases + +### 1. Cross-Stack References +Reference vector stores created in other Terraform configurations: + +```terraform +data "litellm_vector_store" "shared_knowledge_base" { + vector_store_id = var.knowledge_base_id +} + +# Use the same credential for consistency +resource "litellm_model" "knowledge_model" { + model_name = "knowledge-retrieval-model" + custom_llm_provider = "openai" + base_model = "gpt-4" + + additional_litellm_params = { + vector_store_credential = data.litellm_vector_store.shared_knowledge_base.litellm_credential_name + } +} +``` + +### 2. Configuration Validation +Validate vector store configuration before creating dependent resources: + +```terraform +data "litellm_vector_store" "target_store" { + vector_store_id = var.target_vector_store_id +} + +# Ensure the vector store uses the expected provider +locals { + is_pinecone_store = data.litellm_vector_store.target_store.custom_llm_provider == "pinecone" +} + +resource "litellm_model" "pinecone_optimized_model" { + count = local.is_pinecone_store ? 1 : 0 + + model_name = "pinecone-optimized" + custom_llm_provider = "openai" + base_model = "text-embedding-ada-002" + mode = "embedding" +} +``` + +### 3. Metadata-Based Logic +Use vector store metadata for conditional resource creation: + +```terraform +data "litellm_vector_store" "environment_store" { + vector_store_id = var.vector_store_id +} + +# Create different resources based on environment metadata +resource "litellm_model" "production_model" { + count = lookup(data.litellm_vector_store.environment_store.vector_store_metadata, "environment", "") == "production" ? 1 : 0 + + model_name = "production-rag-model" + custom_llm_provider = "openai" + base_model = "gpt-4" + mode = "chat" +} + +resource "litellm_model" "development_model" { + count = lookup(data.litellm_vector_store.environment_store.vector_store_metadata, "environment", "") == "development" ? 1 : 0 + + model_name = "development-rag-model" + custom_llm_provider = "openai" + base_model = "gpt-3.5-turbo" + mode = "chat" +} +``` + +### 4. Audit and Compliance +Retrieve vector store information for audit and compliance reporting: + +```terraform +data "litellm_vector_store" "compliance_stores" { + for_each = toset(var.compliance_vector_store_ids) + + vector_store_id = each.value +} + +# Generate compliance report +output "compliance_report" { + value = { + for k, v in data.litellm_vector_store.compliance_stores : k => { + store_name = v.vector_store_name + provider = v.custom_llm_provider + credential = v.litellm_credential_name + created_date = v.created_at + last_updated = v.updated_at + metadata = v.vector_store_metadata + } + } +} +``` + +## Notes + +* Vector store IDs are unique identifiers assigned by the LiteLLM system. +* The data source will fail if the specified vector store ID does not exist. +* All computed attributes reflect the current state of the vector store in the LiteLLM system. +* Use this data source to integrate with existing vector stores or to reference stores created outside of Terraform. diff --git a/terraform/provider/docs/index.md b/terraform/provider/docs/index.md new file mode 100644 index 00000000000..c03071e7ed3 --- /dev/null +++ b/terraform/provider/docs/index.md @@ -0,0 +1,117 @@ +# LiteLLM Provider + +The LiteLLM provider allows Terraform to manage LiteLLM resources. LiteLLM is a proxy service that standardizes the input/output across different LLM APIs, providing a unified interface for various language model providers. + +## Example Usage + +```hcl +terraform { + required_providers { + litellm = { + source = "registry.terraform.io/BerriAI/litellm" + } + } +} + +provider "litellm" { + api_base = "https://your-litellm-proxy.com" + api_key = var.litellm_api_key +} + +# Basic model configuration +resource "litellm_model" "gpt4" { + model_name = "gpt-4-proxy" + custom_llm_provider = "openai" + model_api_key = var.openai_api_key + base_model = "gpt-4" + tier = "paid" + mode = "chat" + + input_cost_per_million_tokens = 30.0 + output_cost_per_million_tokens = 60.0 +} + +# Team configuration +resource "litellm_team" "dev_team" { + team_alias = "development-team" + models = [litellm_model.gpt4.model_name] + max_budget = 100.0 +} +``` + +## Available Resources + +The LiteLLM provider supports the following resources: + +* [`litellm_model`](./resources/model) - Manage LiteLLM model configurations +* [`litellm_team`](./resources/team) - Manage teams and their permissions +* [`litellm_team_member`](./resources/team_member) - Manage team member configurations +* [`litellm_team_member_add`](./resources/team_member_add) - Add members to teams +* [`litellm_key`](./resources/key) - Manage API keys +* [`litellm_mcp_server`](./resources/mcp_server) - Manage MCP (Model Context Protocol) servers +* [`litellm_credential`](./resources/credential) - Manage credentials for various providers +* [`litellm_vector_store`](./resources/vector_store) - Manage vector stores + +## Available Data Sources + +The LiteLLM provider supports the following data sources: + +* [`litellm_credential`](./data-sources/credential) - Retrieve credential information +* [`litellm_vector_store`](./data-sources/vector_store) - Retrieve vector store information + +## Authentication + +The LiteLLM provider requires an API key and base URL for authentication. These can be provided in the provider configuration block or via environment variables. + +### Environment Variables + +- `LITELLM_API_BASE` - The base URL of your LiteLLM instance +- `LITELLM_API_KEY` - Your LiteLLM API key + +### Example with Environment Variables + +```bash +export LITELLM_API_BASE="https://your-litellm-proxy.com" +export LITELLM_API_KEY="your-api-key" +``` + +```hcl +terraform { + required_providers { + litellm = { + source = "registry.terraform.io/BerriAI/litellm" + } + } +} + +# Provider will automatically use environment variables +provider "litellm" {} +``` + +## Provider Arguments + +The following arguments are supported in the provider block: + +* `api_base` - (Required) The base URL of your LiteLLM instance. This can also be provided via the `LITELLM_API_BASE` environment variable. +* `api_key` - (Required) The API key used to authenticate with LiteLLM. This can also be provided via the `LITELLM_API_KEY` environment variable. + +## Getting Started + +1. Install the provider by adding it to your Terraform configuration +2. Configure your LiteLLM instance URL and API key +3. Start creating resources like models, teams, and credentials +4. Use data sources to reference existing configurations + +For detailed examples and configuration options, see the individual resource and data source documentation pages. + +## Examples + +This repository includes an `examples/` directory with curated, ready-to-run HCL examples that demonstrate common and advanced usages of the provider. Examples are grouped by resource and illustrate provider-specific configuration, handling of sensitive values, and advanced options such as `additional_litellm_params`. + +See: +* `examples/model_additional_params.tf` — demonstrates how to use `additional_litellm_params` (booleans, integers, floats, and strings). +* Other example files will be added to `examples/` for credentials, vector stores, and MCP servers. + +You can reference these examples directly or copy snippets into your Terraform configurations for quick starts. + +For detailed examples and configuration options, see the individual resource and data source documentation pages. diff --git a/terraform/provider/docs/resources/credential.md b/terraform/provider/docs/resources/credential.md new file mode 100644 index 00000000000..554ac07c395 --- /dev/null +++ b/terraform/provider/docs/resources/credential.md @@ -0,0 +1,152 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "litellm_credential Resource - terraform-provider-litellm" +subcategory: "" +description: |- + Manages a LiteLLM credential for storing sensitive authentication information. +--- + +# litellm_credential (Resource) + +Manages a LiteLLM credential for storing sensitive authentication information. Credentials can be used to securely store API keys, tokens, and other sensitive data that can be referenced by models and vector stores. + +## Example Usage + +### Basic OpenAI Credential + +```terraform +resource "litellm_credential" "openai_cred" { + credential_name = "openai-api-key" + model_id = "gpt-4" + + credential_info = { + provider = "openai" + region = "us-east-1" + purpose = "chat-completions" + } + + credential_values = { + api_key = var.openai_api_key + org_id = var.openai_org_id + } +} +``` + +### Anthropic Credential + +```terraform +resource "litellm_credential" "anthropic_cred" { + credential_name = "anthropic-api-key" + + credential_info = { + provider = "anthropic" + purpose = "text-generation" + } + + credential_values = { + api_key = var.anthropic_api_key + } +} +``` + +### Pinecone Vector Store Credential + +```terraform +resource "litellm_credential" "pinecone_cred" { + credential_name = "pinecone-production" + + credential_info = { + provider = "pinecone" + environment = "production" + region = "us-east-1" + } + + credential_values = { + api_key = var.pinecone_api_key + index_name = "document-embeddings" + } +} +``` + +### Using Credentials with Vector Store + +```terraform +resource "litellm_vector_store" "example" { + vector_store_name = "my-vector-store" + custom_llm_provider = "pinecone" + litellm_credential_name = litellm_credential.pinecone_cred.credential_name + + vector_store_description = "Example vector store using Pinecone" + + vector_store_metadata = { + environment = "production" + team = "ai-team" + } +} +``` + +### Multiple Provider Credentials + +```terraform +# AWS Bedrock credential +resource "litellm_credential" "aws_bedrock" { + credential_name = "aws-bedrock-cred" + + credential_info = { + provider = "aws" + service = "bedrock" + region = "us-east-1" + } + + credential_values = { + aws_access_key_id = var.aws_access_key_id + aws_secret_access_key = var.aws_secret_access_key + aws_region = "us-east-1" + } +} + +# Azure OpenAI credential +resource "litellm_credential" "azure_openai" { + credential_name = "azure-openai-cred" + + credential_info = { + provider = "azure" + service = "openai" + } + + credential_values = { + api_key = var.azure_openai_key + api_base = var.azure_openai_endpoint + api_version = "2023-12-01-preview" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `credential_name` - (Required) Name of the credential. This will be used as the identifier for the credential. +* `credential_values` - (Required, Sensitive) Map of sensitive credential values such as API keys, tokens, etc. +* `model_id` - (Optional) Model ID associated with this credential. +* `credential_info` - (Optional) Map of additional non-sensitive information about the credential. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `credential_name` - The name of the credential. + +## Import + +Credentials can be imported using their name: + +```shell +terraform import litellm_credential.example "credential-name" +``` + +## Security Considerations + +* The `credential_values` field is marked as sensitive and will not be displayed in Terraform output or logs. +* Credential values are not read back from the API for security reasons, so they are preserved in the Terraform state. +* Like every Terraform attribute marked `Sensitive`, `credential_values` is still written in plaintext to the state file. Anyone with read access to the state (or state artifacts such as plan files) can recover the configured secrets. Use an encrypted remote backend with tight access controls, and prefer feeding secrets in via variables sourced from a secret manager rather than hardcoding them in configuration. diff --git a/terraform/provider/docs/resources/key.md b/terraform/provider/docs/resources/key.md new file mode 100644 index 00000000000..b48d3334c14 --- /dev/null +++ b/terraform/provider/docs/resources/key.md @@ -0,0 +1,116 @@ +# litellm_key Resource + +Manages a LiteLLM API key. + +## Example Usage + +```hcl +resource "litellm_key" "example" { + models = ["gpt-3.5-turbo", "gpt-4"] + max_budget = 100.0 + user_id = "user123" + team_id = "team456" + max_parallel_requests = 5 + metadata = { + "environment" = "production" + } + tpm_limit = 1000 + rpm_limit = 60 + budget_duration = "monthly" + allowed_cache_controls = ["no-cache", "max-age=3600"] + soft_budget = 80.0 + key_alias = "prod-key-1" + duration = "30d" + aliases = { + "gpt-3.5-turbo" = "chatgpt" + } + config = { + "default_model" = "gpt-3.5-turbo" + } + permissions = { + "can_create_keys" = "true" + } + model_max_budget = { + "gpt-4" = 50.0 + } + model_rpm_limit = { + "gpt-3.5-turbo" = 30 + } + model_tpm_limit = { + "gpt-4" = 500 + } + guardrails = ["content_filter", "token_limit"] + blocked = false + tags = ["production", "api"] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `models` - (Optional) List of models that can be used with this key. This restricts the key to only use the specified models. + +* `max_budget` - (Optional) Maximum budget for this key. This sets an upper limit on the total spend allowed for this key. + +* `user_id` - (Optional) User ID associated with this key. This links the key to a specific user in the LiteLLM system. + +* `team_id` - (Optional) Team ID associated with this key. This links the key to a specific team in the LiteLLM system. + +* `max_parallel_requests` - (Optional) Maximum number of parallel requests allowed for this key. This helps in controlling concurrent usage. + +* `metadata` - (Optional) Metadata associated with this key. This can be used to store additional, custom information about the key. + +* `tpm_limit` - (Optional) Tokens per minute limit for this key. This sets a rate limit based on the number of tokens processed. + +* `rpm_limit` - (Optional) Requests per minute limit for this key. This sets a rate limit based on the number of API calls. + +* `budget_duration` - (Optional) Duration for the budget (e.g., "monthly", "weekly"). This defines the time period for which the `max_budget` applies. + +* `allowed_cache_controls` - (Optional) List of allowed cache control directives. This can be used to control caching behavior for requests made with this key. + +* `soft_budget` - (Optional) Soft budget limit for this key. This can be used to set a warning threshold before reaching the `max_budget`. + +* `key_alias` - (Optional) Alias for this key. This provides a human-readable identifier for the key. + +* `duration` - (Optional) Duration for which this key is valid. This sets an expiration time for the key. + +* `aliases` - (Optional) Map of model aliases. This allows you to create custom names for models when using this key. + +* `config` - (Optional) Configuration options for this key. This can be used to set key-specific settings. + +* `permissions` - (Optional) Permissions associated with this key. This defines what actions are allowed with this key. + +* `model_max_budget` - (Optional) Maximum budget per model. This allows setting different budget limits for each model. + +* `model_rpm_limit` - (Optional) Requests per minute limit per model. This allows setting different RPM limits for each model. + +* `model_tpm_limit` - (Optional) Tokens per minute limit per model. This allows setting different TPM limits for each model. + +* `guardrails` - (Optional) List of guardrails applied to this key. This can be used to enforce certain safety or quality checks. + +* `blocked` - (Optional) Whether this key is blocked. If set to true, the key will be unable to make any requests. + +* `tags` - (Optional) List of tags associated with this key. This can be used for organization and filtering of keys. + +## Attribute Reference + +In addition to all arguments above, the following attributes are exported: + +* `key` - The generated API key. This is the actual key value that will be used for authentication. + +* `spend` - The current spend for this key. This reflects the total amount spent using this key so far. + +## State Management + +Recent updates have improved how the Key resource manages its state. The provider now ensures that all non-zero and non-empty values are correctly persisted in the Terraform state file. This means that any value you set will be accurately reflected in your state, preventing unnecessary updates and ensuring consistency between your configuration and the actual resource state. + +## Import + +LiteLLM keys can be imported using the `id`, e.g., + +``` +$ terraform import litellm_key.example 12345 +``` + +This allows you to import existing keys into your Terraform state, enabling management of keys that were created outside of Terraform. diff --git a/terraform/provider/docs/resources/mcp_server.md b/terraform/provider/docs/resources/mcp_server.md new file mode 100644 index 00000000000..77457a5ae55 --- /dev/null +++ b/terraform/provider/docs/resources/mcp_server.md @@ -0,0 +1,217 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "litellm_mcp_server Resource - terraform-provider-litellm" +subcategory: "" +description: |- + Manages an MCP (Model Context Protocol) server in LiteLLM. +--- + +# litellm_mcp_server (Resource) + +Manages an MCP (Model Context Protocol) server in LiteLLM. MCP servers provide tools and resources that can be used by LLMs through the LiteLLM proxy. + +## Example Usage + +### Basic HTTP MCP Server + +```terraform +resource "litellm_mcp_server" "github_server" { + server_name = "github-mcp-server" + alias = "github" + description = "GitHub MCP server for repository operations" + url = "https://api.github.com/mcp" + transport = "http" + auth_type = "bearer" + + mcp_access_groups = ["dev_team", "devops_team"] +} +``` + +### SSE MCP Server with Comprehensive Cost Tracking + +```terraform +resource "litellm_mcp_server" "zapier_server" { + server_name = "zapier-automation" + alias = "zapier" + description = "Zapier MCP server for workflow automation" + url = "https://actions.zapier.com/mcp/sk-xxxxx/sse" + transport = "sse" + auth_type = "bearer" + spec_version = "2024-11-05" + + mcp_access_groups = ["automation_team", "marketing_team"] + + mcp_info { + server_name = "Zapier Integration Server" + description = "Provides automation tools through Zapier's MCP interface" + logo_url = "https://zapier.com/assets/images/zapier-logo.png" + + mcp_server_cost_info { + default_cost_per_query = 0.01 + + tool_name_to_cost_per_query = { + "send_email" = 0.05 + "create_document" = 0.03 + "update_spreadsheet" = 0.02 + "post_to_slack" = 0.01 + "create_calendar_event" = 0.04 + } + } + } +} +``` + +### Stdio MCP Server for Local Development + +```terraform +resource "litellm_mcp_server" "local_dev_server" { + server_name = "local-development-tools" + alias = "local-dev" + description = "Local MCP server for development tools" + url = "stdio://local-dev" + transport = "stdio" + auth_type = "none" + + command = "python3" + args = ["/opt/mcp-servers/dev-tools/server.py", "--verbose"] + + env = { + "PYTHONPATH" = "/opt/mcp-servers/dev-tools" + "DEBUG" = "true" + "LOG_LEVEL" = "info" + "WORKSPACE_DIR" = "/workspace" + } + + mcp_access_groups = ["local_developers"] + + mcp_info { + server_name = "Development Tools" + description = "Local development utilities and tools" + + mcp_server_cost_info { + default_cost_per_query = 0.0 # Free for local development + } + } +} +``` + +### Enterprise MCP Server with Full Configuration + +```terraform +resource "litellm_mcp_server" "enterprise_api_server" { + server_name = "enterprise-api-gateway" + alias = "enterprise" + description = "Enterprise API gateway MCP server" + url = "https://api.enterprise.com/mcp/v1" + transport = "http" + auth_type = "bearer" + spec_version = "2024-11-05" + + mcp_access_groups = [ + "enterprise_users", + "api_consumers", + "integration_team" + ] + + mcp_info { + server_name = "Enterprise API Gateway" + description = "Provides access to enterprise APIs and services" + logo_url = "https://enterprise.com/logo.png" + + mcp_server_cost_info { + default_cost_per_query = 0.10 + + tool_name_to_cost_per_query = { + "query_database" = 0.25 + "generate_report" = 0.50 + "send_notification" = 0.05 + "create_user" = 0.15 + "update_permissions" = 0.20 + "audit_log_query" = 0.30 + } + } + } +} +``` + +## Argument Reference + +The following arguments are supported: + +### Required Arguments + +* `server_name` - (Required) Name of the MCP server. +* `url` - (Required) URL of the MCP server. For stdio transport, use `stdio://` prefix. +* `transport` - (Required) Transport type for the MCP server. Valid values: `http`, `sse`, `stdio`. + +### Optional Arguments + +* `alias` - (Optional) Alias for the MCP server. Used for easier reference. +* `description` - (Optional) Description of the MCP server. +* `spec_version` - (Optional) MCP specification version. Defaults to `2024-11-05`. +* `auth_type` - (Optional) Authentication type. Valid values: `none`, `bearer`, `basic`. Defaults to `none`. +* `mcp_access_groups` - (Optional) List of access groups that can use this MCP server. +* `command` - (Optional) Command to run for stdio transport. +* `args` - (Optional) List of arguments for the command (stdio transport only). Do not pass secrets as arguments; args are shown in plans, stored unencrypted in state, and visible in the server's process list. +* `env` - (Optional, Sensitive) Map of environment variables for the command (stdio transport only). Hidden from plan output but still stored unencrypted in state; secure your state backend when configuring tokens here. + +### MCP Info Block + +The `mcp_info` block supports: + +* `server_name` - (Optional) Server name in MCP info. +* `description` - (Optional) Description in MCP info. +* `logo_url` - (Optional) Logo URL for the MCP server. + +#### MCP Server Cost Info Block + +The `mcp_server_cost_info` block within `mcp_info` supports: + +* `default_cost_per_query` - (Optional) Default cost per query for all tools. +* `tool_name_to_cost_per_query` - (Optional) Map of specific tool names to their cost per query. + +## Attribute Reference + +In addition to all arguments above, the following attributes are exported: + +* `server_id` - Unique identifier for the MCP server. +* `created_at` - Timestamp when the server was created. +* `created_by` - User who created the server. +* `updated_at` - Timestamp when the server was last updated. +* `updated_by` - User who last updated the server. +* `status` - Current status of the MCP server. +* `last_health_check` - Timestamp of the last health check. +* `health_check_error` - Error message from the last health check, if any. + +## Import + +MCP servers can be imported using their server ID: + +```shell +terraform import litellm_mcp_server.example server-id-here +``` + +## Transport Types + +### HTTP Transport +- Standard HTTP/HTTPS communication +- Suitable for REST API-based MCP servers +- Supports authentication via `auth_type` + +### SSE (Server-Sent Events) Transport +- Real-time streaming communication +- Ideal for servers that need to push updates +- Commonly used with services like Zapier + +### Stdio Transport +- Standard input/output communication +- Used for local MCP servers or command-line tools +- Requires `command` and optionally `args` and `env` + +## Access Control + +Use `mcp_access_groups` to control which teams or users can access the MCP server tools. This integrates with LiteLLM's permission management system. + +## Cost Tracking + +Configure cost tracking through the `mcp_info.mcp_server_cost_info` block to monitor and control spending on MCP tool usage. diff --git a/terraform/provider/docs/resources/model.md b/terraform/provider/docs/resources/model.md new file mode 100644 index 00000000000..5a46fe2f073 --- /dev/null +++ b/terraform/provider/docs/resources/model.md @@ -0,0 +1,238 @@ +# litellm_model Resource + +Manages a LiteLLM model configuration. This resource allows you to create, update, and delete model configurations in your LiteLLM instance. + +## Example Usage + +### Basic OpenAI Model + +```hcl +resource "litellm_model" "gpt4" { + model_name = "gpt-4-proxy" + custom_llm_provider = "openai" + model_api_key = var.openai_api_key + base_model = "gpt-4" + tier = "paid" + mode = "chat" + + input_cost_per_million_tokens = 30.0 + output_cost_per_million_tokens = 60.0 +} +``` + +### Advanced Model with All Features + +```hcl +resource "litellm_model" "advanced_gpt4" { + model_name = "gpt-4-advanced" + custom_llm_provider = "openai" + model_api_key = var.openai_api_key + model_api_base = "https://api.openai.com/v1" + api_version = "2023-05-15" + base_model = "gpt-4" + tier = "paid" + team_id = "team-123" + mode = "chat" + reasoning_effort = "medium" + thinking_enabled = true + thinking_budget_tokens = 1024 + merge_reasoning_content_in_choices = true + tpm = 100000 + rpm = 1000 + + # Cost configuration (per million tokens) + input_cost_per_million_tokens = 30.0 # $0.03 per 1k tokens = $30 per million + output_cost_per_million_tokens = 60.0 # $0.06 per 1k tokens = $60 per million +} +``` + +### AWS Bedrock Model with Cross-Account Access + +```hcl +resource "litellm_model" "bedrock_claude" { + model_name = "bedrock-claude-proxy" + custom_llm_provider = "bedrock" + base_model = "anthropic.claude-3-sonnet-20240229-v1:0" + tier = "paid" + mode = "chat" + + # AWS configuration with cross-account access + aws_access_key_id = var.aws_access_key_id + aws_secret_access_key = var.aws_secret_access_key + aws_region_name = "us-east-1" + aws_session_name = "litellm-cross-account-session" + aws_role_name = "arn:aws:iam::123456789012:role/LiteLLMCrossAccountRole" + + input_cost_per_million_tokens = 3.0 + output_cost_per_million_tokens = 15.0 +} +``` + +### Anthropic Model + +```hcl +resource "litellm_model" "claude" { + model_name = "claude-proxy" + custom_llm_provider = "anthropic" + model_api_key = var.anthropic_api_key + base_model = "claude-3-sonnet-20240229" + tier = "paid" + mode = "chat" + + input_cost_per_million_tokens = 3.0 + output_cost_per_million_tokens = 15.0 +} +``` + +### Azure OpenAI Model + +```hcl +resource "litellm_model" "azure_gpt4" { + model_name = "azure-gpt4-proxy" + custom_llm_provider = "azure" + model_api_key = var.azure_openai_key + model_api_base = var.azure_openai_endpoint + api_version = "2023-12-01-preview" + base_model = "gpt-4" + tier = "paid" + mode = "chat" + + input_cost_per_million_tokens = 30.0 + output_cost_per_million_tokens = 60.0 +} +``` + +## Argument Reference + +The following arguments are supported: + +* `model_name` - (Required) string. The name of the model configuration used to identify the model in API calls. + +* `custom_llm_provider` - (Required) string. The LLM provider for this model (e.g., "openai", "anthropic", "azure", "bedrock"). + +* `model_api_key` - (Optional) string (Sensitive). The API key for the underlying model provider. Sensitive attributes are hidden from Terraform output but still stored in plaintext in the state file; prefer storing provider secrets in a `litellm_credential` and referencing it via `litellm_credential_name`, and secure your state backend. + +* `model_api_base` - (Optional) string. The base URL for the model provider's API. + +* `api_version` - (Optional) string. The API version to use for the model provider. + +* `base_model` - (Required) string. The actual model identifier from the provider (e.g., "gpt-4", "claude-2"). + +* `litellm_credential_name` - (Optional) string. Name of a LiteLLM credential to use for this model. + +* `tier` - (Optional) string. The usage tier for this model. Valid values are `"free"` or `"paid"`. Default: `"free"`. + +* `team_id` - (Optional) string. Associate the model with a specific team. + +* `mode` - (Optional) string. The intended use of the model. Valid values are: + * `completion` + * `embedding` + * `image_generation` + * `chat` + * `moderation` + * `audio_transcription` + * `audio_speech` + * `rerank` + +* `tpm` - (Optional) integer. Tokens per minute limit for this model. + +* `rpm` - (Optional) integer. Requests per minute limit for this model. + +* `reasoning_effort` - (Optional) string. Configures the model's reasoning effort level. Valid values are: + * `low` + * `medium` + * `high` + +* `thinking_enabled` - (Optional) boolean. Enables the model's thinking capability. Default: `false`. + +* `thinking_budget_tokens` - (Optional) integer. Sets the token budget for the model's thinking capability. Default: `1024`. Note: this field is only relevant when `thinking_enabled = true`. + +* `merge_reasoning_content_in_choices` - (Optional) boolean. When set to `true`, merges reasoning content into the model's choices. + +* `input_cost_per_million_tokens` - (Optional) float. Cost per million input tokens. The provider converts this to a per-token cost sent to the API. + +* `output_cost_per_million_tokens` - (Optional) float. Cost per million output tokens. The provider converts this to a per-token cost sent to the API. + +* `input_cost_per_pixel` - (Optional) float. Cost applied per input pixel for models that charge by image size. + +* `output_cost_per_pixel` - (Optional) float. Cost applied per output pixel for image-generation models. + +* `input_cost_per_second` - (Optional) float. Cost applied per input second for audio/transcription models. + +* `output_cost_per_second` - (Optional) float. Cost applied per output second for audio/transcription models. + +* `vertex_project` - (Optional) string. Vertex AI project id (for `custom_llm_provider = "vertex"`). + +* `vertex_location` - (Optional) string. Vertex AI location (e.g., `us-central1`). + +* `vertex_credentials` - (Optional) string. Vertex credentials (JSON string or path depending on your setup). + +* `additional_litellm_params` - (Optional) map(string). A map of arbitrary additional parameters that will be merged into the `litellm_params` object sent to the LiteLLM API. This is intended for provider-specific or experimental options not exposed as dedicated arguments. + + Conversion and behavior rules (how the provider handles values): + * When values in the map are strings the provider will attempt to coerce them: + * `"true"` / `"false"` (strings) -> boolean true / false + * Numeric strings are parsed first as integers; if integer parsing fails, parsed as floats (e.g., `"16384"` -> 16384, `"0.75"` -> 0.75) + * JSON strings (starting with `[` or `{`) are parsed as JSON objects/arrays + * Non-convertible strings remain strings + * Non-string map values (if supplied) are passed through unchanged. + * The provider merges these keys into the `litellm_params` payload sent to the API. + * Note: the remote API may not echo back all custom parameters; this provider preserves `additional_litellm_params` in state when present in configuration. + + **Special parameter: `additional_drop_params`** + * When `additional_drop_params` is provided as a JSON array string, it specifies parameters to remove from the final `litellm_params` before sending to the API + * This allows you to override or remove built-in parameters if needed + * The `additional_drop_params` key itself is not included in the final parameters + + Example showing booleans, integers, floats, strings, and parameter dropping: + + ```hcl + resource "litellm_model" "with_additional" { + model_name = "custom-model" + custom_llm_provider = "openai" + model_api_key = var.openai_api_key + base_model = "gpt-4" + mode = "chat" + + additional_litellm_params = { + "use_fine_tune" = "true" # becomes boolean true + "max_context" = "16384" # becomes integer 16384 + "scale" = "0.75" # becomes float 0.75 + "note" = "for testing" # stays string + "complex_config" = "{\"nested\": {\"value\": 42}}" # parsed as JSON object + "additional_drop_params" = "[\"reasoningEffort\"]" # removes reasoningEffort parameter + } + } + ``` + +### AWS-specific Configuration + +* `aws_access_key_id` - (Optional) string (Sensitive). AWS access key ID for AWS-based models. + +* `aws_secret_access_key` - (Optional) string (Sensitive). AWS secret access key for AWS-based models. As with `model_api_key`, the value is stored in plaintext in the state file; prefer a `litellm_credential` referenced via `litellm_credential_name` and secure your state backend. + +* `aws_region_name` - (Optional) string. AWS region name for AWS-based models. + +* `aws_session_name` - (Optional) string (Sensitive). AWS session name for cross-account access scenarios. + +* `aws_role_name` - (Optional) string (Sensitive). AWS IAM role name for cross-account access scenarios. + +## Attribute Reference + +In addition to the arguments above, the following attributes are exported: + +* `id` - The ID of the model configuration. + +## Import + +Model configurations can be imported using the model ID: + +```shell +terraform import litellm_model.gpt4 +``` + +Note: The model ID is generated when the model is created and is different from the `model_name`. + +## Security Note + +When using this resource, ensure that sensitive information such as API keys and AWS credentials are stored securely. It's recommended to use environment variables or a secure secret management solution rather than hardcoding these values in your Terraform configuration files. diff --git a/terraform/provider/docs/resources/team.md b/terraform/provider/docs/resources/team.md new file mode 100644 index 00000000000..68535309f10 --- /dev/null +++ b/terraform/provider/docs/resources/team.md @@ -0,0 +1,130 @@ +# litellm_team Resource + +Manages a team configuration in LiteLLM. Teams allow you to group users and manage their access to models and usage limits. + +## Example Usage + +### Basic Team Configuration + +```hcl +resource "litellm_team" "engineering" { + team_alias = "engineering-team" + models = ["gpt-4-proxy", "claude-2"] + max_budget = 1000.0 +} +``` + +### Team with Comprehensive Configuration + +```hcl +resource "litellm_team" "advanced_team" { + team_alias = "ai-research-team" + organization_id = "org_123456" + models = ["gpt-4-proxy", "claude-2", "gpt-3.5-turbo"] + + # Budget and rate limiting + max_budget = 1000.0 + budget_duration = "1mo" + tpm_limit = 500000 + rpm_limit = 5000 + blocked = false + + # Team member permissions + team_member_permissions = [ + "create_key", + "delete_key", + "view_spend", + "edit_team" + ] + + # Metadata for organization + metadata = { + department = "Engineering" + project = "AI Research" + cost_center = "R&D-001" + } +} +``` + +### Team with Model Dependencies + +```hcl +# First create models +resource "litellm_model" "gpt4" { + model_name = "gpt-4-proxy" + custom_llm_provider = "openai" + base_model = "gpt-4" + model_api_key = var.openai_api_key +} + +resource "litellm_model" "claude" { + model_name = "claude-proxy" + custom_llm_provider = "anthropic" + base_model = "claude-3-sonnet-20240229" + model_api_key = var.anthropic_api_key +} + +# Then create team with access to these models +resource "litellm_team" "model_dependent_team" { + team_alias = "model-users" + models = [ + litellm_model.gpt4.model_name, + litellm_model.claude.model_name + ] + + max_budget = 500.0 + budget_duration = "1mo" + + team_member_permissions = [ + "view_spend" + ] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `team_alias` - (Required) A human-readable identifier for the team. + +* `organization_id` - (Optional) The ID of the organization this team belongs to. + +* `models` - (Optional) List of model names that this team can access. + +* `metadata` - (Optional) A map of metadata key-value pairs associated with the team. + +* `blocked` - (Optional) Whether the team is blocked from making requests. Default is `false`. + +* `tpm_limit` - (Optional) Team-wide tokens per minute limit. + +* `rpm_limit` - (Optional) Team-wide requests per minute limit. + +* `max_budget` - (Optional) Maximum budget allocated to the team. + +* `budget_duration` - (Optional) Duration for the budget cycle. Valid values are: + * `daily` + * `weekly` + * `monthly` + * `yearly` + +* `team_member_permissions` - (Optional) List of permissions granted to team members. This controls what actions team members can perform within the team context. + +## Attribute Reference + +In addition to the arguments above, the following attributes are exported: + +* `id` - The unique identifier for the team. + +## Import + +Teams can be imported using the team ID: + +```shell +terraform import litellm_team.engineering +``` + +Note: The team ID is generated when the team is created and is different from the `team_alias`. + +## Note on Team Members + +Team members are managed through the separate `litellm_team_member` resource. This allows for more granular control over team membership and permissions. See the `litellm_team_member` resource documentation for details on managing team members. diff --git a/terraform/provider/docs/resources/team_member.md b/terraform/provider/docs/resources/team_member.md new file mode 100644 index 00000000000..426d8b8892f --- /dev/null +++ b/terraform/provider/docs/resources/team_member.md @@ -0,0 +1,54 @@ +# litellm_team_member Resource + +Manages individual team member configurations in LiteLLM. This resource allows you to add, update, and remove team members with specific permissions and budget limits. + +## Example Usage + +```hcl +resource "litellm_team_member" "engineer" { + team_id = litellm_team.engineering.id + user_id = "user_3" + user_email = "engineer@example.com" + role = "user" + max_budget_in_team = 200.0 +} +``` + +## Argument Reference + +The following arguments are supported: + +* `team_id` - (Required) The ID of the team this member belongs to. + +* `user_id` - (Required) Unique identifier for the user. + +* `user_email` - (Required) Email address of the user. + +* `role` - (Required) The role of the team member. Valid values are: + * `org_admin` + * `internal_user` + * `internal_user_viewer` + * `admin` + * `user` + +* `max_budget_in_team` - (Optional) Maximum budget allocated to this team member within the team's budget. + +## Attribute Reference + +In addition to the arguments above, the following attributes are exported: + +* `id` - The unique identifier for the team member configuration. This is typically a composite of the team_id and user_id. + +## Import + +Team members can be imported using the format `team_id:user_id`: + +```shell +terraform import litellm_team_member.engineer : +``` + +Note: The team_id and user_id should match the values used in the resource configuration. + +## Security Note + +Ensure that sensitive information such as user emails and IDs are handled securely. It's recommended to use variables or a secure secret management solution rather than hardcoding these values in your Terraform configuration files. diff --git a/terraform/provider/docs/resources/team_member_add.md b/terraform/provider/docs/resources/team_member_add.md new file mode 100644 index 00000000000..f5398e49d9c --- /dev/null +++ b/terraform/provider/docs/resources/team_member_add.md @@ -0,0 +1,161 @@ +# Resource: litellm_team_member_add + +Add multiple members to a team with a single resource. This resource efficiently manages team members by using the appropriate API endpoints for each operation: + +- **Adding new members**: Uses `/team/member_add` endpoint +- **Updating existing members**: Uses `/team/member_update` endpoint (preserves member identity) +- **Removing members**: Uses `/team/member_delete` endpoint + +When you modify an existing team member's attributes (like role), the resource will update the member in-place rather than deleting and re-adding them. + +## Example Usage + +### Basic Usage + +```hcl +resource "litellm_team_member_add" "example" { + team_id = "team-123" + + member { + user_id = "user-456" + role = "admin" + } + + member { + user_email = "user@example.com" + role = "user" + } + + max_budget_in_team = 100.0 +} +``` + +### Complete Team Setup with Members + +```hcl +# First create a team +resource "litellm_team" "development" { + team_alias = "development-team" + max_budget = 500.0 + models = ["gpt-4", "gpt-3.5-turbo"] + + team_member_permissions = [ + "create_key", + "view_spend" + ] +} + +# Add members to the team +resource "litellm_team_member_add" "dev_team_members" { + team_id = litellm_team.development.id + + # Team lead with admin role + member { + user_email = "team-lead@company.com" + role = "admin" + } + + # Regular developers + member { + user_email = "developer1@company.com" + role = "user" + } + + member { + user_email = "developer2@company.com" + role = "user" + } + + member { + user_id = "existing-user-123" + role = "user" + } + + # Budget per member + max_budget_in_team = 100.0 +} +``` + +### Dynamic Members Using Locals + +```hcl +locals { + team_members = [ + { + user_id = "user-123" + role = "admin" + }, + { + user_email = "developer1@company.com" + role = "user" + }, + { + user_email = "developer2@company.com" + role = "user" + } + ] +} + +resource "litellm_team_member_add" "dynamic_example" { + team_id = "team-456" + + dynamic "member" { + for_each = local.team_members + content { + user_id = lookup(member.value, "user_id", null) + user_email = lookup(member.value, "user_email", null) + role = member.value.role + } + } + + max_budget_in_team = 200.0 +} +``` + +### Budget Update Example + +```hcl +# This example demonstrates how budget updates work correctly +resource "litellm_team_member_add" "budget_example" { + team_id = litellm_team.example.id + + # Initial budget of $100 per member + max_budget_in_team = 100.0 + + member { + user_email = "user1@example.com" + role = "admin" + } + + member { + user_email = "user2@example.com" + role = "user" + } + + member { + user_id = "user123" + role = "user" + } +} + +# To update the budget: +# 1. Change max_budget_in_team from 100.0 to 120.0 +# 2. Run terraform plan - it will show the budget change +# 3. Run terraform apply - all existing members will be updated with the new budget +``` + +## Argument Reference + +* `team_id` - (Required) The ID of the team to add members to. +* `member` - (Required) One or more member blocks defining team members. Each block supports: + * `user_id` - (Optional) The ID of the user to add to the team. + * `user_email` - (Optional) The email of the user to add to the team. + * `role` - (Required) The role of the user in the team. Must be one of: "admin" or "user". +* `max_budget_in_team` - (Optional) The maximum budget allocated for the team members. + +## Import + +Team members can be imported using a composite ID of the team ID and user ID: + +```shell +terraform import litellm_team_member_add.example team-123:user-456 diff --git a/terraform/provider/docs/resources/vector_store.md b/terraform/provider/docs/resources/vector_store.md new file mode 100644 index 00000000000..b839b327429 --- /dev/null +++ b/terraform/provider/docs/resources/vector_store.md @@ -0,0 +1,274 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "litellm_vector_store Resource - terraform-provider-litellm" +subcategory: "" +description: |- + Manages a LiteLLM vector store for storing and retrieving vector embeddings. +--- + +# litellm_vector_store (Resource) + +Manages a LiteLLM vector store for storing and retrieving vector embeddings. Vector stores enable semantic search and retrieval-augmented generation (RAG) capabilities using officially supported providers including AWS Bedrock Knowledge Bases, OpenAI Vector Stores, Azure Vector Stores, Vertex AI RAG Engine, and PG Vector. + +## Example Usage + +### AWS Bedrock Knowledge Base + +```terraform +resource "litellm_credential" "bedrock_cred" { + credential_name = "bedrock-knowledge-base" + + credential_info = { + provider = "bedrock" + region = "us-east-1" + } + + credential_values = { + aws_access_key_id = var.aws_access_key_id + aws_secret_access_key = var.aws_secret_access_key + aws_region = "us-east-1" + } +} + +resource "litellm_vector_store" "bedrock_kb" { + vector_store_name = "bedrock-litellm-website-knowledgebase" + custom_llm_provider = "bedrock" + litellm_credential_name = litellm_credential.bedrock_cred.credential_name + + vector_store_description = "Bedrock vector store for the LiteLLM website knowledgebase" + + vector_store_metadata = { + source = "https://www.litellm.com/docs" + } + + litellm_params = { + vector_store_id = "T37J8R4WTM" + } +} +``` + +### OpenAI Vector Store + +```terraform +resource "litellm_credential" "openai_cred" { + credential_name = "openai-vector-store" + + credential_info = { + provider = "openai" + } + + credential_values = { + api_key = var.openai_api_key + } +} + +resource "litellm_vector_store" "openai_store" { + vector_store_name = "openai-knowledge-base" + custom_llm_provider = "openai" + litellm_credential_name = litellm_credential.openai_cred.credential_name + + vector_store_description = "OpenAI vector store for document search" + + vector_store_metadata = { + environment = "production" + purpose = "file-search" + } + + litellm_params = { + vector_store_id = "vs_687ae3b2439881918b433cb99d10662e" + } +} +``` + +### Azure Vector Store + +```terraform +resource "litellm_credential" "azure_cred" { + credential_name = "azure-vector-store" + + credential_info = { + provider = "azure" + } + + credential_values = { + api_key = var.azure_openai_key + api_base = var.azure_openai_endpoint + api_version = "2023-12-01-preview" + } +} + +resource "litellm_vector_store" "azure_store" { + vector_store_name = "azure-knowledge-base" + custom_llm_provider = "azure" + litellm_credential_name = litellm_credential.azure_cred.credential_name + + vector_store_description = "Azure vector store for enterprise search" + + vector_store_metadata = { + environment = "production" + team = "enterprise" + } + + litellm_params = { + vector_store_id = "vs_azure_example_id" + } +} +``` + +### Vertex AI RAG Engine + +```terraform +resource "litellm_credential" "vertex_cred" { + credential_name = "vertex-rag-engine" + + credential_info = { + provider = "vertex_ai" + project = "your-gcp-project" + } + + credential_values = { + service_account_key = var.gcp_service_account_key + } +} + +resource "litellm_vector_store" "vertex_rag" { + vector_store_name = "vertex-rag-corpus" + custom_llm_provider = "vertex_ai" + litellm_credential_name = litellm_credential.vertex_cred.credential_name + + vector_store_description = "Vertex AI RAG Engine for enterprise knowledge" + + vector_store_metadata = { + project = "your-gcp-project" + environment = "production" + } + + litellm_params = { + vector_store_id = "6917529027641081856" + } +} +``` + +### PG Vector Store + +```terraform +resource "litellm_credential" "pgvector_cred" { + credential_name = "pgvector-store" + + credential_info = { + provider = "pgvector" + host = "your-pgvector-host.com" + } + + credential_values = { + api_key = var.pgvector_api_key + api_base = "https://your-pgvector-host.com" + } +} + +resource "litellm_vector_store" "pgvector_store" { + vector_store_name = "postgres-vector-store" + custom_llm_provider = "pgvector" + litellm_credential_name = litellm_credential.pgvector_cred.credential_name + + vector_store_description = "PostgreSQL vector store with pgvector extension" + + vector_store_metadata = { + database = "vector_db" + table = "embeddings" + environment = "production" + } + + litellm_params = { + api_base = "https://your-pgvector-host.com" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `vector_store_name` - (Required) Name of the vector store. +* `custom_llm_provider` - (Required) The vector store provider. Supported values: "bedrock", "openai", "azure", "vertex_ai", "pgvector". +* `vector_store_description` - (Optional) Description of the vector store. +* `vector_store_metadata` - (Optional) Map of metadata associated with the vector store. +* `litellm_credential_name` - (Optional) Name of the LiteLLM credential to use for authentication. +* `litellm_params` - (Optional, Sensitive) Map of additional parameters specific to the vector store provider. Do not put API keys or other secrets here; this map is stored unencrypted in state. Store secrets in a `litellm_credential` and reference it via `litellm_credential_name`. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `vector_store_id` - The unique identifier of the vector store. +* `created_at` - Timestamp when the vector store was created. +* `updated_at` - Timestamp when the vector store was last updated. + +## Supported Providers + +The following vector store providers are officially supported by LiteLLM: + +* **AWS Bedrock Knowledge Bases** - Managed knowledge bases on AWS Bedrock +* **OpenAI Vector Stores** - OpenAI's native vector store service +* **Azure Vector Stores** - Azure OpenAI vector store integration +* **Vertex AI RAG Engine** - Google Cloud's RAG API for vector search +* **PG Vector** - PostgreSQL with pgvector extension + +## Provider-Specific Parameters + +### AWS Bedrock Knowledge Base + +```terraform +litellm_params = { + vector_store_id = "T37J8R4WTM" # Your Bedrock Knowledge Base ID +} +``` + +### OpenAI Vector Store + +```terraform +litellm_params = { + vector_store_id = "vs_687ae3b2439881918b433cb99d10662e" # Your OpenAI Vector Store ID +} +``` + +### Azure Vector Store + +```terraform +litellm_params = { + vector_store_id = "vs_azure_example_id" # Your Azure Vector Store ID +} +``` + +### Vertex AI RAG Engine + +```terraform +litellm_params = { + vector_store_id = "6917529027641081856" # Your Vertex AI RAG Engine ID +} +``` + +### PG Vector + +```terraform +litellm_params = { + api_base = "https://your-pgvector-host.com" +} +``` + +## Import + +Vector stores can be imported using their ID: + +```shell +terraform import litellm_vector_store.example "vector-store-id" +``` + +## Notes + +* Vector stores require appropriate credentials for the chosen provider. +* The `litellm_params` field allows provider-specific configuration. +* Some providers may require additional setup outside of Terraform (e.g., creating Knowledge Bases in AWS Bedrock, Vector Stores in OpenAI). +* Ensure your vector store provider is properly configured and accessible from your LiteLLM instance. +* Only the officially supported providers listed above are guaranteed to work with LiteLLM's vector store integration. +* For the most up-to-date list of supported providers, refer to the [LiteLLM documentation](https://docs.litellm.ai/docs/completion/knowledgebase). diff --git a/terraform/provider/examples/model_additional_params.tf b/terraform/provider/examples/model_additional_params.tf new file mode 100644 index 00000000000..fb4981ec6fb --- /dev/null +++ b/terraform/provider/examples/model_additional_params.tf @@ -0,0 +1,57 @@ +provider "litellm" { + api_base = "https://your-litellm-proxy.com" + api_key = var.litellm_api_key +} + +# Example: using additional_litellm_params to pass provider-specific options. +# Notes: +# - String values "true"/"false" will be coerced to booleans. +# - Numeric strings will be parsed to integer (if possible) otherwise float. +# - JSON strings (starting with [ or {) will be parsed as JSON objects/arrays. +# - Non-convertible strings remain strings. +# - Non-string map values are passed through unchanged. +# - Use "additional_drop_params" as a JSON array to remove parameters from the final request. + +resource "litellm_model" "with_additional" { + model_name = "custom-model" + custom_llm_provider = "openai" + model_api_key = var.openai_api_key + base_model = "gpt-4" + mode = "chat" + + # Additional parameters not exposed as first-class arguments + additional_litellm_params = { + "use_fine_tune" = "true" # becomes boolean true + "max_context" = "16384" # becomes integer 16384 + "temperature_scale" = "0.75" # becomes float 0.75 + "experimental_feature" = "enabled" # stays string "enabled" + "complex_config" = "{\"nested\": {\"value\": 42}}" # parsed as JSON object + "additional_drop_params" = "[\"reasoningEffort\"]" # removes reasoningEffort parameter + # You may also pass non-string values (they will be passed through unchanged) + # "raw_flag" = true + } + + # Cost configuration (optional) + input_cost_per_million_tokens = 30.0 + output_cost_per_million_tokens = 60.0 +} + +# Example: Azure model with parameter dropping +resource "litellm_model" "azure_with_drop_params" { + model_name = "gpt-5-mini-coder" + custom_llm_provider = "azure" + model_api_key = "your-azure-api-key" + model_api_base = "https://your-azure-endpoint.openai.azure.com/" + api_version = "2025-03-01-preview" + base_model = "gpt-5-mini" + tier = "paid" + mode = "completion" + + # Drop the reasoningEffort parameter that might be automatically added + additional_litellm_params = { + "additional_drop_params" = "[\"reasoningEffort\"]" + } + + input_cost_per_million_tokens = 0.25 + output_cost_per_million_tokens = 2.00 +} diff --git a/terraform/provider/go.mod b/terraform/provider/go.mod new file mode 100644 index 00000000000..899af1a6fbe --- /dev/null +++ b/terraform/provider/go.mod @@ -0,0 +1,61 @@ +module github.com/BerriAI/terraform-provider-litellm + +go 1.25.0 + +require ( + github.com/google/uuid v1.6.0 + github.com/hashicorp/terraform-plugin-sdk/v2 v2.40.0 +) + +require ( + github.com/ProtonMail/go-crypto v1.3.0 // indirect + github.com/agext/levenshtein v1.2.2 // indirect + github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect + github.com/cloudflare/circl v1.6.1 // indirect + github.com/fatih/color v1.16.0 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/hashicorp/go-checkpoint v0.5.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-cty v1.5.0 // indirect + github.com/hashicorp/go-hclog v1.6.3 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-plugin v1.7.0 // indirect + github.com/hashicorp/go-retryablehttp v0.7.8 // indirect + github.com/hashicorp/go-uuid v1.0.3 // indirect + github.com/hashicorp/go-version v1.8.0 // indirect + github.com/hashicorp/hc-install v0.9.3 // indirect + github.com/hashicorp/hcl/v2 v2.24.0 // indirect + github.com/hashicorp/logutils v1.0.0 // indirect + github.com/hashicorp/terraform-exec v0.25.0 // indirect + github.com/hashicorp/terraform-json v0.27.2 // indirect + github.com/hashicorp/terraform-plugin-go v0.31.0 // indirect + github.com/hashicorp/terraform-plugin-log v0.10.0 // indirect + github.com/hashicorp/terraform-registry-address v0.4.0 // indirect + github.com/hashicorp/terraform-svchost v0.1.1 // indirect + github.com/hashicorp/yamux v0.1.2 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/oklog/run v1.1.0 // indirect + github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect + github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/zclconf/go-cty v1.17.0 // indirect + golang.org/x/crypto v0.48.0 // indirect + golang.org/x/mod v0.33.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/text v0.34.0 // indirect + golang.org/x/tools v0.41.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect + google.golang.org/grpc v1.79.2 // indirect + google.golang.org/protobuf v1.36.11 // indirect +) diff --git a/terraform/provider/go.sum b/terraform/provider/go.sum new file mode 100644 index 00000000000..890703d4f8a --- /dev/null +++ b/terraform/provider/go.sum @@ -0,0 +1,239 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= +github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= +github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= +github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= +github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= +github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= +github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= +github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= +github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= +github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= +github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= +github.com/go-git/go-git/v5 v5.16.5 h1:mdkuqblwr57kVfXri5TTH+nMFLNUxIj9Z7F5ykFbw5s= +github.com/go-git/go-git/v5 v5.16.5/go.mod h1:QOMLpNf1qxuSY4StA/ArOdfFR2TrKEjJiye2kel2m+M= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= +github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-checkpoint v0.5.0 h1:MFYpPZCnQqQTE18jFwSII6eUQrD/oxMFp3mlgcqk5mU= +github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-cty v1.5.0 h1:EkQ/v+dDNUqnuVpmS5fPqyY71NXVgT5gf32+57xY8g0= +github.com/hashicorp/go-cty v1.5.0/go.mod h1:lFUCG5kd8exDobgSfyj4ONE/dc822kiYMguVKdHGMLM= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshfk+mA= +github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8= +github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= +github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= +github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/hc-install v0.9.3 h1:1H4dgmgzxEVwT6E/d/vIL5ORGVKz9twRwDw+qA5Hyho= +github.com/hashicorp/hc-install v0.9.3/go.mod h1:FQlQ5I3I/X409N/J1U4pPeQQz1R3BoV0IysB7aiaQE0= +github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE= +github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM= +github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/terraform-exec v0.25.0 h1:Bkt6m3VkJqYh+laFMrWIpy9KHYFITpOyzRMNI35rNaY= +github.com/hashicorp/terraform-exec v0.25.0/go.mod h1:dl9IwsCfklDU6I4wq9/StFDp7dNbH/h5AnfS1RmiUl8= +github.com/hashicorp/terraform-json v0.27.2 h1:BwGuzM6iUPqf9JYM/Z4AF1OJ5VVJEEzoKST/tRDBJKU= +github.com/hashicorp/terraform-json v0.27.2/go.mod h1:GzPLJ1PLdUG5xL6xn1OXWIjteQRT2CNT9o/6A9mi9hE= +github.com/hashicorp/terraform-plugin-go v0.31.0 h1:0Fz2r9DQ+kNNl6bx8HRxFd1TfMKUvnrOtvJPmp3Z0q8= +github.com/hashicorp/terraform-plugin-go v0.31.0/go.mod h1:A88bDhd/cW7FnwqxQRz3slT+QY6yzbHKc6AOTtmdeS8= +github.com/hashicorp/terraform-plugin-log v0.10.0 h1:eu2kW6/QBVdN4P3Ju2WiB2W3ObjkAsyfBsL3Wh1fj3g= +github.com/hashicorp/terraform-plugin-log v0.10.0/go.mod h1:/9RR5Cv2aAbrqcTSdNmY1NRHP4E3ekrXRGjqORpXyB0= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.40.0 h1:MKS/2URqeJRwJdbOfcbdsZCq/IRrNkqJNN0GtVIsuGs= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.40.0/go.mod h1:PuG4P97Ju3QXW6c6vRkRadWJbvnEu2Xh+oOuqcYOqX4= +github.com/hashicorp/terraform-registry-address v0.4.0 h1:S1yCGomj30Sao4l5BMPjTGZmCNzuv7/GDTDX99E9gTk= +github.com/hashicorp/terraform-registry-address v0.4.0/go.mod h1:LRS1Ay0+mAiRkUyltGT+UHWkIqTFvigGn/LbMshfflE= +github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= +github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc= +github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= +github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94= +github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= +github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= +github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= +github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= +github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= +github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zclconf/go-cty v1.17.0 h1:seZvECve6XX4tmnvRzWtJNHdscMtYEx5R7bnnVyd/d0= +github.com/zclconf/go-cty v1.17.0/go.mod h1:wqFzcImaLTI6A5HfsRwB0nj5n0MRZFwmey8YoFPPs3U= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= +golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU= +google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/terraform/provider/litellm/client.go b/terraform/provider/litellm/client.go new file mode 100644 index 00000000000..e0aba61477d --- /dev/null +++ b/terraform/provider/litellm/client.go @@ -0,0 +1,386 @@ +package litellm + +import ( + "bytes" + "crypto/tls" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "regexp" + "strings" +) + +type Client struct { + APIBase string + APIKey string + httpClient *http.Client + InsecureSkipVerify bool +} + +func NewClient(apiBase, apiKey string, insecureSkipVerify bool) *Client { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: insecureSkipVerify}, + } + + return &Client{ + APIBase: apiBase, + APIKey: apiKey, + httpClient: &http.Client{Transport: tr}, + InsecureSkipVerify: insecureSkipVerify, + } +} + +// Organization member methods +func (c *Client) AddOrganizationMember(data map[string]interface{}) (map[string]interface{}, error) { + return c.sendRequest("POST", "/organization/member_add", data) +} + +func (c *Client) UpdateOrganizationMember(data map[string]interface{}) (map[string]interface{}, error) { + return c.sendRequest("PATCH", "/organization/member_update", data) +} + +func (c *Client) DeleteOrganizationMember(data map[string]interface{}) (map[string]interface{}, error) { + return c.sendRequest("DELETE", "/organization/member_delete", data) +} + +// Key-related methods +func (c *Client) CreateKey(key *Key) (*Key, error) { + resp, err := c.sendRequest("POST", "/key/generate", key) + if err != nil { + return nil, err + } + + return c.parseKeyResponse(resp) +} + +func (c *Client) GetKey(keyID string) (*Key, error) { + resp, err := c.sendRequest("GET", fmt.Sprintf("/key/info?key=%s", keyID), nil) + if err != nil { + return nil, err + } + + return c.parseKeyResponse(resp) +} + +func (c *Client) UpdateKey(key *Key) (*Key, error) { + // Create a new map with only the fields that can be updated + updateData := map[string]interface{}{ + "key": key.Key, + "team_id": key.TeamID, + "metadata": key.Metadata, + "budget_duration": key.BudgetDuration, + "key_alias": key.KeyAlias, + "aliases": key.Aliases, + "permissions": key.Permissions, + "model_max_budget": key.ModelMaxBudget, + "model_rpm_limit": key.ModelRPMLimit, + "model_tpm_limit": key.ModelTPMLimit, + "blocked": key.Blocked, + } + + // Only add pointer fields if they are explicitly set + if key.MaxBudget != nil { + updateData["max_budget"] = *key.MaxBudget + } + if key.SoftBudget != nil { + updateData["soft_budget"] = *key.SoftBudget + } + if key.MaxParallelRequests != nil { + updateData["max_parallel_requests"] = *key.MaxParallelRequests + } + if key.TPMLimit != nil { + updateData["tpm_limit"] = *key.TPMLimit + } + if key.RPMLimit != nil { + updateData["rpm_limit"] = *key.RPMLimit + } + + // Only add array fields if they are non-empty + if len(key.Models) > 0 { + updateData["models"] = key.Models + } + if len(key.Guardrails) > 0 { + updateData["guardrails"] = key.Guardrails + } + if len(key.Tags) > 0 { + updateData["tags"] = key.Tags + } + + resp, err := c.sendRequest("POST", "/key/update", updateData) + if err != nil { + return nil, err + } + + return c.parseKeyResponse(resp) +} + +func (c *Client) DeleteKey(keyID string) error { + payload := map[string]interface{}{ + "keys": []string{keyID}, + } + _, err := c.sendRequest("POST", "/key/delete", payload) + return err +} + +func (c *Client) parseKeyResponse(resp map[string]interface{}) (*Key, error) { + if resp == nil { + return nil, fmt.Errorf("received nil response") + } + + createdKey := &Key{} + + for k, v := range resp { + if v == nil { + continue + } + + switch k { + case "key": + if s, ok := v.(string); ok { + createdKey.Key = s + } + case "token_id": + if s, ok := v.(string); ok { + createdKey.TokenID = s + } + case "models": + if models, ok := v.([]interface{}); ok { + createdKey.Models = make([]string, len(models)) + for i, model := range models { + if s, ok := model.(string); ok { + createdKey.Models[i] = s + } + } + } + case "spend": + if f, ok := v.(float64); ok { + createdKey.Spend = f + } + case "max_budget": + if f, ok := v.(float64); ok { + createdKey.MaxBudget = &f + } + case "user_id": + if s, ok := v.(string); ok { + createdKey.UserID = s + } + case "team_id": + if s, ok := v.(string); ok { + createdKey.TeamID = s + } + case "max_parallel_requests": + if i, ok := v.(float64); ok { + val := int(i) + createdKey.MaxParallelRequests = &val + } + case "metadata": + if m, ok := v.(map[string]interface{}); ok { + createdKey.Metadata = m + } + case "tpm_limit": + if i, ok := v.(float64); ok { + val := int(i) + createdKey.TPMLimit = &val + } + case "rpm_limit": + if i, ok := v.(float64); ok { + val := int(i) + createdKey.RPMLimit = &val + } + case "budget_duration": + if s, ok := v.(string); ok { + createdKey.BudgetDuration = s + } + case "soft_budget": + if f, ok := v.(float64); ok { + createdKey.SoftBudget = &f + } + case "key_alias": + if s, ok := v.(string); ok { + createdKey.KeyAlias = s + } + case "duration": + if s, ok := v.(string); ok { + createdKey.Duration = s + } + case "aliases": + if m, ok := v.(map[string]interface{}); ok { + createdKey.Aliases = m + } + case "config": + if m, ok := v.(map[string]interface{}); ok { + createdKey.Config = m + } + case "permissions": + if m, ok := v.(map[string]interface{}); ok { + createdKey.Permissions = m + } + case "model_max_budget": + if m, ok := v.(map[string]interface{}); ok { + createdKey.ModelMaxBudget = m + } + case "model_rpm_limit": + if m, ok := v.(map[string]interface{}); ok { + createdKey.ModelRPMLimit = m + } + case "model_tpm_limit": + if m, ok := v.(map[string]interface{}); ok { + createdKey.ModelTPMLimit = m + } + case "guardrails": + if guardrails, ok := v.([]interface{}); ok { + createdKey.Guardrails = make([]string, len(guardrails)) + for i, guardrail := range guardrails { + if s, ok := guardrail.(string); ok { + createdKey.Guardrails[i] = s + } + } + } + case "blocked": + if b, ok := v.(bool); ok { + createdKey.Blocked = b + } + case "tags": + if tags, ok := v.([]interface{}); ok { + createdKey.Tags = make([]string, len(tags)) + for i, tag := range tags { + if s, ok := tag.(string); ok { + createdKey.Tags[i] = s + } + } + } + } + } + + return createdKey, nil +} + +func (c *Client) sendRequest(method, path string, body interface{}) (map[string]interface{}, error) { + url := c.APIBase + path + + var req *http.Request + var err error + + if body != nil { + jsonBody, err := json.Marshal(body) + if err != nil { + return nil, fmt.Errorf("error marshaling request body: %v", err) + } + log.Printf("Making %s request to %s with body:\n%s", method, url, c.redactSensitiveData(string(jsonBody))) + req, err = http.NewRequest(method, url, bytes.NewBuffer(jsonBody)) + } else { + log.Printf("Making %s request to %s", method, url) + req, err = http.NewRequest(method, url, nil) + } + + if err != nil { + return nil, fmt.Errorf("error creating request: %v", err) + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("x-api-key", c.APIKey) + req.Header.Set("accept", "application/json") + + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, fmt.Errorf("error making request: %v", err) + } + defer resp.Body.Close() + + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("error reading response body: %v", err) + } + + log.Printf("Response status: %d", resp.StatusCode) + log.Printf("Response body: %s", c.redactSensitiveData(string(bodyBytes))) + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("API request failed with status code %d: %s", resp.StatusCode, string(bodyBytes)) + } + + var result map[string]interface{} + if err := json.Unmarshal(bodyBytes, &result); err != nil { + if (method == "POST" || method == "PATCH" || method == "PUT" || method == "DELETE") && + (len(bodyBytes) == 0 || string(bodyBytes) == "null") { + return make(map[string]interface{}), nil + } + return nil, fmt.Errorf("error parsing response JSON: %v\nResponse body: %s", err, string(bodyBytes)) + } + + return result, nil +} + +var sensitiveLogFields = map[string]bool{ + "api_key": true, + "key": true, + "token": true, + "password": true, + "secret": true, + "credential": true, + "auth": true, + "model_api_key": true, + "aws_access_key_id": true, + "aws_secret_access_key": true, + "vertex_credentials": true, + "x-api-key": true, + "credential_values": true, +} + +func redactJSONValue(value interface{}) interface{} { + switch typed := value.(type) { + case map[string]interface{}: + redacted := make(map[string]interface{}, len(typed)) + for k, v := range typed { + if sensitiveLogFields[k] { + redacted[k] = "[REDACTED]" + } else { + redacted[k] = redactJSONValue(v) + } + } + return redacted + case []interface{}: + redacted := make([]interface{}, len(typed)) + for i, v := range typed { + redacted[i] = redactJSONValue(v) + } + return redacted + default: + return value + } +} + +var sensitiveLogPatterns = []*regexp.Regexp{ + regexp.MustCompile(`"(api_key|key|token|password|secret|credential|auth)":\s*"[^"]*"`), + regexp.MustCompile(`"(model_api_key|aws_access_key_id|aws_secret_access_key|vertex_credentials)":\s*"[^"]*"`), + regexp.MustCompile(`"(x-api-key)":\s*"[^"]*"`), +} + +func redactWithPatterns(data string) string { + result := data + for _, re := range sensitiveLogPatterns { + result = re.ReplaceAllStringFunc(result, func(match string) string { + parts := strings.SplitN(match, ":", 2) + if len(parts) == 2 { + return parts[0] + `: "[REDACTED]"` + } + return "[REDACTED]" + }) + } + return result +} + +// redactSensitiveData masks sensitive information in logs +func (c *Client) redactSensitiveData(data string) string { + var parsed interface{} + if err := json.Unmarshal([]byte(data), &parsed); err != nil { + return redactWithPatterns(data) + } + redactedBytes, err := json.Marshal(redactJSONValue(parsed)) + if err != nil { + return redactWithPatterns(data) + } + return string(redactedBytes) +} diff --git a/terraform/provider/litellm/client_test.go b/terraform/provider/litellm/client_test.go new file mode 100644 index 00000000000..56f76565616 --- /dev/null +++ b/terraform/provider/litellm/client_test.go @@ -0,0 +1,71 @@ +package litellm + +import ( + "strings" + "testing" +) + +func TestRedactSensitiveDataNestedCredentialValues(t *testing.T) { + c := NewClient("http://localhost:4000", "sk-test", false) + + input := `{"credential_name":"azure-cred","credential_values":{"api_key":"sk-secret-123","config":{"region":"us-east-1","client_secret":"nested-secret"}}}` + got := c.redactSensitiveData(input) + + for _, leaked := range []string{"sk-secret-123", "us-east-1", "nested-secret"} { + if strings.Contains(got, leaked) { + t.Errorf("redacted output leaked %q: %s", leaked, got) + } + } + if !strings.Contains(got, `"credential_values":"[REDACTED]"`) { + t.Errorf("credential_values not redacted: %s", got) + } + if !strings.Contains(got, `"credential_name":"azure-cred"`) { + t.Errorf("non-sensitive field mangled: %s", got) + } +} + +func TestRedactSensitiveDataDeeplyNestedSensitiveKeys(t *testing.T) { + c := NewClient("http://localhost:4000", "sk-test", false) + + input := `{"data":[{"litellm_params":{"model":"gpt-4","api_key":"sk-deep-456","aws_secret_access_key":"aws-secret"}}]}` + got := c.redactSensitiveData(input) + + for _, leaked := range []string{"sk-deep-456", "aws-secret"} { + if strings.Contains(got, leaked) { + t.Errorf("redacted output leaked %q: %s", leaked, got) + } + } + if !strings.Contains(got, `"model":"gpt-4"`) { + t.Errorf("non-sensitive field mangled: %s", got) + } +} + +func TestRedactSensitiveDataTopLevelStringFields(t *testing.T) { + c := NewClient("http://localhost:4000", "sk-test", false) + + input := `{"model_api_key":"sk-top-789","vertex_credentials":"{\"type\":\"service_account\"}","team_alias":"eng"}` + got := c.redactSensitiveData(input) + + for _, leaked := range []string{"sk-top-789", "service_account"} { + if strings.Contains(got, leaked) { + t.Errorf("redacted output leaked %q: %s", leaked, got) + } + } + if !strings.Contains(got, `"team_alias":"eng"`) { + t.Errorf("non-sensitive field mangled: %s", got) + } +} + +func TestRedactSensitiveDataNonJSONFallback(t *testing.T) { + c := NewClient("http://localhost:4000", "sk-test", false) + + input := `error before "api_key": "sk-fallback-000" after` + got := c.redactSensitiveData(input) + + if strings.Contains(got, "sk-fallback-000") { + t.Errorf("fallback redaction leaked secret: %s", got) + } + if !strings.Contains(got, "[REDACTED]") { + t.Errorf("fallback redaction did not redact: %s", got) + } +} diff --git a/terraform/provider/litellm/data_source_credential.go b/terraform/provider/litellm/data_source_credential.go new file mode 100644 index 00000000000..e4533546a67 --- /dev/null +++ b/terraform/provider/litellm/data_source_credential.go @@ -0,0 +1,73 @@ +package litellm + +import ( + "fmt" + "net/http" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceLiteLLMCredential() *schema.Resource { + return &schema.Resource{ + Read: dataSourceLiteLLMCredentialRead, + + Schema: map[string]*schema.Schema{ + "credential_name": { + Type: schema.TypeString, + Required: true, + Description: "Name of the credential to retrieve", + }, + "model_id": { + Type: schema.TypeString, + Optional: true, + Description: "Model ID associated with this credential", + }, + "credential_info": { + Type: schema.TypeMap, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Description: "Additional information about the credential", + }, + // Note: credential_values are not exposed in data sources for security reasons + }, + } +} + +func dataSourceLiteLLMCredentialRead(d *schema.ResourceData, m interface{}) error { + client := m.(*Client) + credentialName := d.Get("credential_name").(string) + modelID := d.Get("model_id").(string) + + // Use the same endpoint as the resource read operation + endpoint := fmt.Sprintf("/credentials/by_name/%s", credentialName) + if modelID != "" { + endpoint += fmt.Sprintf("?model_id=%s", modelID) + } + + resp, err := MakeRequest(client, "GET", endpoint, nil) + if err != nil { + return fmt.Errorf("failed to read credential: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusNotFound { + return fmt.Errorf("credential '%s' not found", credentialName) + } + + var credentialResp CredentialResponse + err = handleCredentialAPIResponse(resp, &credentialResp, client) + if err != nil { + if err.Error() == "credential_not_found" { + return fmt.Errorf("credential '%s' not found", credentialName) + } + return fmt.Errorf("failed to read credential: %w", err) + } + + // Set the data source ID to the credential name + d.SetId(credentialResp.CredentialName) + d.Set("credential_name", credentialResp.CredentialName) + d.Set("credential_info", credentialResp.CredentialInfo) + // Note: We don't expose credential_values in data sources for security reasons + + return nil +} diff --git a/terraform/provider/litellm/data_source_vector_store.go b/terraform/provider/litellm/data_source_vector_store.go new file mode 100644 index 00000000000..d39a2f92af4 --- /dev/null +++ b/terraform/provider/litellm/data_source_vector_store.go @@ -0,0 +1,107 @@ +package litellm + +import ( + "fmt" + "net/http" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceLiteLLMVectorStore() *schema.Resource { + return &schema.Resource{ + Read: dataSourceLiteLLMVectorStoreRead, + + Schema: map[string]*schema.Schema{ + "vector_store_id": { + Type: schema.TypeString, + Required: true, + Description: "Unique identifier for the vector store to retrieve", + }, + "vector_store_name": { + Type: schema.TypeString, + Computed: true, + Description: "Name of the vector store", + }, + "custom_llm_provider": { + Type: schema.TypeString, + Computed: true, + Description: "Custom LLM provider for the vector store", + }, + "vector_store_description": { + Type: schema.TypeString, + Computed: true, + Description: "Description of the vector store", + }, + "vector_store_metadata": { + Type: schema.TypeMap, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Description: "Metadata associated with the vector store", + }, + "litellm_credential_name": { + Type: schema.TypeString, + Computed: true, + Description: "Name of the LiteLLM credential used", + }, + "litellm_params": { + Type: schema.TypeMap, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Description: "Additional LiteLLM parameters", + }, + "created_at": { + Type: schema.TypeString, + Computed: true, + Description: "Timestamp when the vector store was created", + }, + "updated_at": { + Type: schema.TypeString, + Computed: true, + Description: "Timestamp when the vector store was last updated", + }, + }, + } +} + +func dataSourceLiteLLMVectorStoreRead(d *schema.ResourceData, m interface{}) error { + client := m.(*Client) + vectorStoreID := d.Get("vector_store_id").(string) + + // Use the info endpoint to get vector store details + infoRequest := VectorStoreInfoRequest{ + VectorStoreID: vectorStoreID, + } + + resp, err := MakeRequest(client, "POST", "/vector_store/info", infoRequest) + if err != nil { + return fmt.Errorf("failed to read vector store: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusNotFound { + return fmt.Errorf("vector store '%s' not found", vectorStoreID) + } + + var vectorStoreResp VectorStoreResponse + err = handleVectorStoreAPIResponse(resp, &vectorStoreResp, client) + if err != nil { + if err.Error() == "vector_store_not_found" { + return fmt.Errorf("vector store '%s' not found", vectorStoreID) + } + return fmt.Errorf("failed to read vector store: %w", err) + } + + // Set the data source ID to the vector store ID + d.SetId(vectorStoreResp.VectorStoreID) + d.Set("vector_store_id", vectorStoreResp.VectorStoreID) + d.Set("vector_store_name", vectorStoreResp.VectorStoreName) + d.Set("custom_llm_provider", vectorStoreResp.CustomLLMProvider) + d.Set("vector_store_description", vectorStoreResp.VectorStoreDescription) + d.Set("vector_store_metadata", vectorStoreResp.VectorStoreMetadata) + d.Set("litellm_credential_name", vectorStoreResp.LiteLLMCredentialName) + d.Set("litellm_params", vectorStoreResp.LiteLLMParams) + d.Set("created_at", vectorStoreResp.CreatedAt) + d.Set("updated_at", vectorStoreResp.UpdatedAt) + + return nil +} diff --git a/terraform/provider/litellm/provider.go b/terraform/provider/litellm/provider.go new file mode 100644 index 00000000000..57f9cc24183 --- /dev/null +++ b/terraform/provider/litellm/provider.go @@ -0,0 +1,63 @@ +package litellm + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +// Provider returns a terraform.ResourceProvider. +func Provider() *schema.Provider { + return &schema.Provider{ + ResourcesMap: map[string]*schema.Resource{ + "litellm_model": resourceLiteLLMModel(), + "litellm_team": ResourceLiteLLMTeam(), + "litellm_organization": resourceLiteLLMOrganization(), + "litellm_organization_member": resourceLiteLLMOrganizationMember(), + "litellm_organization_member_add": resourceLiteLLMOrganizationMemberAdd(), + "litellm_team_member": resourceLiteLLMTeamMember(), + "litellm_team_member_add": resourceLiteLLMTeamMemberAdd(), + "litellm_key": resourceKey(), + "litellm_mcp_server": resourceLiteLLMMCPServer(), + "litellm_credential": resourceLiteLLMCredential(), + "litellm_vector_store": resourceLiteLLMVectorStore(), + }, + DataSourcesMap: map[string]*schema.Resource{ + "litellm_credential": dataSourceLiteLLMCredential(), + "litellm_vector_store": dataSourceLiteLLMVectorStore(), + }, + Schema: map[string]*schema.Schema{ + "api_base": { + Type: schema.TypeString, + Required: true, + Sensitive: false, + DefaultFunc: schema.EnvDefaultFunc("LITELLM_API_BASE", nil), + Description: "The base URL of the LiteLLM API", + }, + "api_key": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + DefaultFunc: schema.EnvDefaultFunc("LITELLM_API_KEY", nil), + Description: "The API key for authenticating with LiteLLM", + }, + "insecure_skip_verify": { + Type: schema.TypeBool, + Optional: true, + Default: false, + DefaultFunc: schema.EnvDefaultFunc("LITELLM_INSECURE_SKIP_VERIFY", false), + Description: "Skip TLS certificate verification. Only use for development or when using self-signed certificates", + }, + }, + ConfigureFunc: providerConfigure, + } +} + +// providerConfigure configures the provider with the given schema data. +func providerConfigure(d *schema.ResourceData) (interface{}, error) { + config := ProviderConfig{ + APIBase: d.Get("api_base").(string), + APIKey: d.Get("api_key").(string), + InsecureSkipVerify: d.Get("insecure_skip_verify").(bool), + } + + return NewClient(config.APIBase, config.APIKey, config.InsecureSkipVerify), nil +} diff --git a/terraform/provider/litellm/provider_test.go b/terraform/provider/litellm/provider_test.go new file mode 100644 index 00000000000..00817e7c410 --- /dev/null +++ b/terraform/provider/litellm/provider_test.go @@ -0,0 +1,83 @@ +package litellm + +import ( + "os" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var testAccProviders map[string]*schema.Provider +var testAccProvider *schema.Provider + +func init() { + testAccProvider = Provider() + testAccProviders = map[string]*schema.Provider{ + "litellm": testAccProvider, + } +} + +func TestProvider(t *testing.T) { + if err := Provider().InternalValidate(); err != nil { + t.Fatalf("err: %s", err) + } +} + +func TestProvider_impl(t *testing.T) { + var _ *schema.Provider = Provider() +} + +func testAccPreCheck(t *testing.T) { + if v := os.Getenv("LITELLM_API_BASE"); v == "" { + t.Fatal("LITELLM_API_BASE must be set for acceptance tests") + } + if v := os.Getenv("LITELLM_API_KEY"); v == "" { + t.Fatal("LITELLM_API_KEY must be set for acceptance tests") + } + + // Create test users needed for organization member tests + createTestUsers(t) +} + +func createTestUsers(t *testing.T) { + apiBase := os.Getenv("LITELLM_API_BASE") + apiKey := os.Getenv("LITELLM_API_KEY") + + if apiBase == "" || apiKey == "" { + return + } + + client := NewClient(apiBase, apiKey, false) + + // Create test users + users := []map[string]interface{}{ + { + "user_id": "test-user-1", + "user_email": "test-user-1@example.com", + "user_role": "internal_user", + }, + { + "user_id": "bulk-user-1", + "user_email": "bulk-user-1@example.com", + "user_role": "internal_user", + }, + { + "user_id": "bulk-user-2", + "user_email": "bulk-user-2@example.com", + "user_role": "internal_user", + }, + } + + for _, user := range users { + _, err := client.sendRequest("POST", "/user/new", user) + if err != nil { + // Silently ignore if user already exists (400 error) + // This is expected when running tests multiple times + errStr := err.Error() + if !strings.Contains(errStr, "400") && !strings.Contains(errStr, "already exists") { + t.Logf("Warning: Could not create user %s: %v", user["user_id"], err) + } + } + } +} diff --git a/terraform/provider/litellm/resource_credential.go b/terraform/provider/litellm/resource_credential.go new file mode 100644 index 00000000000..f668a46a324 --- /dev/null +++ b/terraform/provider/litellm/resource_credential.go @@ -0,0 +1,44 @@ +package litellm + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceLiteLLMCredential() *schema.Resource { + return &schema.Resource{ + Create: resourceLiteLLMCredentialCreate, + Read: resourceLiteLLMCredentialRead, + Update: resourceLiteLLMCredentialUpdate, + Delete: resourceLiteLLMCredentialDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "credential_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Name of the credential", + }, + "model_id": { + Type: schema.TypeString, + Optional: true, + Description: "Model ID associated with this credential", + }, + "credential_info": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Description: "Additional information about the credential", + }, + "credential_values": { + Type: schema.TypeMap, + Required: true, + Sensitive: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Description: "Sensitive credential values (API keys, tokens, etc.)", + }, + }, + } +} diff --git a/terraform/provider/litellm/resource_credential_crud.go b/terraform/provider/litellm/resource_credential_crud.go new file mode 100644 index 00000000000..dd9aef64f76 --- /dev/null +++ b/terraform/provider/litellm/resource_credential_crud.go @@ -0,0 +1,204 @@ +package litellm + +import ( + "fmt" + "log" + "net/http" + "strings" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +// retryCredentialRead attempts to read a credential with exponential backoff. +// If the read path clears the ID (e.g., transient 404 right after create), +// we treat it as retryable instead of accepting an empty state. +func retryCredentialRead(d *schema.ResourceData, m interface{}, maxRetries int) error { + var err error + delay := 1 * time.Second + maxDelay := 10 * time.Second + origID := d.Id() + + for i := 0; i < maxRetries; i++ { + log.Printf("[INFO] Attempting to read credential (attempt %d/%d)", i+1, maxRetries) + + err = resourceLiteLLMCredentialRead(d, m) + // If read succeeded but wiped the ID, treat as not found so we retry. + if err == nil && d.Id() == "" { + d.SetId(origID) + err = fmt.Errorf("credential_not_found") + } + + if err == nil { + log.Printf("[INFO] Successfully read credential after %d attempts", i+1) + return nil + } + + if !strings.Contains(err.Error(), "credential_not_found") { + return err + } + + if i < maxRetries-1 { + log.Printf("[INFO] Credential not found yet, retrying in %v...", delay) + time.Sleep(delay) + + delay *= 2 + if delay > maxDelay { + delay = maxDelay + } + } + } + + log.Printf("[WARN] Failed to read credential after %d attempts: %v", maxRetries, err) + return err +} + +func resourceLiteLLMCredentialCreate(d *schema.ResourceData, m interface{}) error { + client := m.(*Client) + + credentialName := d.Get("credential_name").(string) + modelID := d.Get("model_id").(string) + credentialInfo := d.Get("credential_info").(map[string]interface{}) + credentialValues := d.Get("credential_values").(map[string]interface{}) + + // Convert credential_info to map[string]interface{} for JSON + credInfoMap := make(map[string]interface{}) + for k, v := range credentialInfo { + credInfoMap[k] = v + } + + // Convert credential_values to map[string]interface{} for JSON + credValuesMap := make(map[string]interface{}) + for k, v := range credentialValues { + credValuesMap[k] = v + } + + credentialRequest := CredentialRequest{ + CredentialName: credentialName, + ModelID: modelID, + CredentialInfo: credInfoMap, + CredentialValues: credValuesMap, + } + + resp, err := MakeRequest(client, "POST", "/credentials", credentialRequest) + if err != nil { + return fmt.Errorf("failed to create credential: %w", err) + } + defer resp.Body.Close() + + err = handleCredentialAPIResponse(resp, nil, client) + if err != nil { + return fmt.Errorf("failed to create credential: %w", err) + } + + // Set the resource ID to the credential name + d.SetId(credentialName) + + log.Printf("[INFO] Credential created with name %s. Starting retry mechanism to read the credential...", credentialName) + return retryCredentialRead(d, m, 5) +} + +func resourceLiteLLMCredentialRead(d *schema.ResourceData, m interface{}) error { + client := m.(*Client) + credentialName := d.Id() + + // Try to get credential by name first + modelID := d.Get("model_id").(string) + endpoint := fmt.Sprintf("/credentials/by_name/%s", credentialName) + if modelID != "" { + endpoint += fmt.Sprintf("?model_id=%s", modelID) + } + + resp, err := MakeRequest(client, "GET", endpoint, nil) + if err != nil { + return fmt.Errorf("failed to read credential: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusNotFound { + d.SetId("") + return nil + } + + var credentialResp CredentialResponse + err = handleCredentialAPIResponse(resp, &credentialResp, client) + if err != nil { + if err.Error() == "credential_not_found" { + d.SetId("") + return nil + } + return fmt.Errorf("failed to read credential: %w", err) + } + + d.Set("credential_name", credentialResp.CredentialName) + d.Set("credential_info", credentialResp.CredentialInfo) + // Note: We don't set credential_values from the response for security reasons + // The API might not return sensitive values, and we want to preserve what's in state + + return nil +} + +func resourceLiteLLMCredentialUpdate(d *schema.ResourceData, m interface{}) error { + client := m.(*Client) + credentialName := d.Id() + + credentialInfo := d.Get("credential_info").(map[string]interface{}) + credentialValues := d.Get("credential_values").(map[string]interface{}) + + // Convert credential_info to map[string]interface{} for JSON + credInfoMap := make(map[string]interface{}) + for k, v := range credentialInfo { + credInfoMap[k] = v + } + + // Convert credential_values to map[string]interface{} for JSON + credValuesMap := make(map[string]interface{}) + for k, v := range credentialValues { + credValuesMap[k] = v + } + + credentialRequest := CredentialRequest{ + CredentialName: credentialName, + CredentialInfo: credInfoMap, + CredentialValues: credValuesMap, + } + + endpoint := fmt.Sprintf("/credentials/%s", credentialName) + resp, err := MakeRequest(client, "PATCH", endpoint, credentialRequest) + if err != nil { + return fmt.Errorf("failed to update credential: %w", err) + } + defer resp.Body.Close() + + err = handleCredentialAPIResponse(resp, nil, client) + if err != nil { + return fmt.Errorf("failed to update credential: %w", err) + } + + log.Printf("[INFO] Credential updated with name %s. Starting retry mechanism to read the credential...", credentialName) + return retryCredentialRead(d, m, 5) +} + +func resourceLiteLLMCredentialDelete(d *schema.ResourceData, m interface{}) error { + client := m.(*Client) + credentialName := d.Id() + + endpoint := fmt.Sprintf("/credentials/%s", credentialName) + resp, err := MakeRequest(client, "DELETE", endpoint, nil) + if err != nil { + return fmt.Errorf("failed to delete credential: %w", err) + } + defer resp.Body.Close() + + err = handleCredentialAPIResponse(resp, nil, client) + if err != nil { + if err.Error() == "credential_not_found" { + d.SetId("") + return nil + } + return fmt.Errorf("failed to delete credential: %w", err) + } + + d.SetId("") + return nil +} diff --git a/terraform/provider/litellm/resource_credential_crud_test.go b/terraform/provider/litellm/resource_credential_crud_test.go new file mode 100644 index 00000000000..3398e58dd13 --- /dev/null +++ b/terraform/provider/litellm/resource_credential_crud_test.go @@ -0,0 +1,201 @@ +package litellm + +import ( + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "sync/atomic" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +// newTestResourceData creates a *schema.ResourceData with the credential schema, +// sets the ID and populates the required fields. +func newTestResourceData(t *testing.T, id string) *schema.ResourceData { + t.Helper() + d := schema.TestResourceDataRaw(t, resourceLiteLLMCredential().Schema, map[string]interface{}{ + "credential_name": id, + "model_id": "", + "credential_info": map[string]interface{}{}, + "credential_values": map[string]interface{}{"key": "val"}, + }) + d.SetId(id) + return d +} + +func TestRetryCredentialRead_SuccessOnFirstAttempt(t *testing.T) { + resp := CredentialResponse{ + CredentialName: "test-cred", + CredentialInfo: map[string]interface{}{"provider": "aws"}, + } + body, _ := json.Marshal(resp) + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write(body) + })) + defer srv.Close() + + client := NewClient(srv.URL, "test-key", true) + d := newTestResourceData(t, "test-cred") + + err := retryCredentialRead(d, client, 3) + if err != nil { + t.Fatalf("expected nil error, got: %v", err) + } + if d.Id() != "test-cred" { + t.Fatalf("expected ID 'test-cred', got %q", d.Id()) + } +} + +func TestRetryCredentialRead_SuccessAfterRetries(t *testing.T) { + resp := CredentialResponse{ + CredentialName: "test-cred", + CredentialInfo: map[string]interface{}{"provider": "aws"}, + } + body, _ := json.Marshal(resp) + + var callCount int32 + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + n := atomic.AddInt32(&callCount, 1) + w.Header().Set("Content-Type", "application/json") + if n <= 2 { + // First two calls return 404, triggering retry + w.WriteHeader(http.StatusNotFound) + return + } + w.WriteHeader(http.StatusOK) + w.Write(body) + })) + defer srv.Close() + + client := NewClient(srv.URL, "test-key", true) + d := newTestResourceData(t, "test-cred") + + err := retryCredentialRead(d, client, 3) + if err != nil { + t.Fatalf("expected nil error, got: %v", err) + } + if d.Id() != "test-cred" { + t.Fatalf("expected ID 'test-cred', got %q", d.Id()) + } + if atomic.LoadInt32(&callCount) != 3 { + t.Fatalf("expected 3 HTTP calls, got %d", callCount) + } +} + +func TestRetryCredentialRead_ExhaustsRetries(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + })) + defer srv.Close() + + client := NewClient(srv.URL, "test-key", true) + d := newTestResourceData(t, "test-cred") + + err := retryCredentialRead(d, client, 2) + if err == nil { + t.Fatal("expected error after exhausting retries, got nil") + } + if err.Error() != "credential_not_found" { + t.Fatalf("expected 'credential_not_found' error, got: %v", err) + } + // ID should still be restored (not wiped) + if d.Id() != "test-cred" { + t.Fatalf("expected ID to be restored to 'test-cred', got %q", d.Id()) + } +} + +func TestRetryCredentialRead_NonRetryableError(t *testing.T) { + var callCount int32 + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + atomic.AddInt32(&callCount, 1) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(`{"error": "internal server error"}`)) + })) + defer srv.Close() + + client := NewClient(srv.URL, "test-key", true) + d := newTestResourceData(t, "test-cred") + + err := retryCredentialRead(d, client, 3) + if err == nil { + t.Fatal("expected error for 500 response, got nil") + } + // Should fail on first attempt without retrying + if atomic.LoadInt32(&callCount) != 1 { + t.Fatalf("expected 1 HTTP call (no retries for non-retryable error), got %d", callCount) + } +} + +func TestRetryCredentialRead_IDRestoredBetweenRetries(t *testing.T) { + // Verify the ID is restored after each failed attempt where the read clears it. + // resourceLiteLLMCredentialRead sets ID to "" on 404, and retryCredentialRead + // should restore it before the next attempt. + resp := CredentialResponse{ + CredentialName: "my-cred", + CredentialInfo: map[string]interface{}{}, + } + body, _ := json.Marshal(resp) + + var callCount int32 + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + n := atomic.AddInt32(&callCount, 1) + w.Header().Set("Content-Type", "application/json") + if n == 1 { + w.WriteHeader(http.StatusNotFound) + return + } + w.WriteHeader(http.StatusOK) + w.Write(body) + })) + defer srv.Close() + + client := NewClient(srv.URL, "test-key", true) + d := newTestResourceData(t, "my-cred") + + err := retryCredentialRead(d, client, 2) + if err != nil { + t.Fatalf("expected nil error, got: %v", err) + } + if d.Id() != "my-cred" { + t.Fatalf("expected ID 'my-cred', got %q", d.Id()) + } +} + +func TestRetryCredentialRead_MaxRetriesOne(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + })) + defer srv.Close() + + client := NewClient(srv.URL, "test-key", true) + d := newTestResourceData(t, "test-cred") + + err := retryCredentialRead(d, client, 1) + if err == nil { + t.Fatal("expected error with maxRetries=1 and always-404, got nil") + } + if err.Error() != "credential_not_found" { + t.Fatalf("expected 'credential_not_found', got: %v", err) + } +} + +func TestRetryCredentialRead_ConnectionError(t *testing.T) { + // Point to a server that's already closed to simulate connection failure + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) + srv.Close() + + client := NewClient(srv.URL, "test-key", true) + d := newTestResourceData(t, "test-cred") + + err := retryCredentialRead(d, client, 1) + if err == nil { + t.Fatal("expected error for connection failure, got nil") + } + // Connection error should not be retried (not a "credential_not_found") + fmt.Printf("connection error (expected): %v\n", err) +} diff --git a/terraform/provider/litellm/resource_key.go b/terraform/provider/litellm/resource_key.go new file mode 100644 index 00000000000..5c80198cf6a --- /dev/null +++ b/terraform/provider/litellm/resource_key.go @@ -0,0 +1,319 @@ +package litellm + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceKey() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceKeyCreate, + ReadContext: resourceKeyRead, + UpdateContext: resourceKeyUpdate, + DeleteContext: resourceKeyDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Optional: true, + WriteOnly: true, + Sensitive: true, + }, + "token_id": { + Type: schema.TypeString, + Computed: true, + }, + "models": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "max_budget": { + Type: schema.TypeFloat, + Optional: true, + Computed: true, + }, + "user_id": { + Type: schema.TypeString, + Optional: true, + }, + "team_id": { + Type: schema.TypeString, + Optional: true, + }, + "max_parallel_requests": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "metadata": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "tpm_limit": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "rpm_limit": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "budget_duration": { + Type: schema.TypeString, + Optional: true, + }, + "allowed_cache_controls": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "soft_budget": { + Type: schema.TypeFloat, + Optional: true, + Computed: true, + }, + "key_alias": { + Type: schema.TypeString, + Optional: true, + }, + "duration": { + Type: schema.TypeString, + Optional: true, + }, + "aliases": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "config": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "permissions": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "model_max_budget": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeFloat, Computed: true}, + }, + "model_rpm_limit": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeInt, Computed: true}, + }, + "model_tpm_limit": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeInt, Computed: true}, + }, + "guardrails": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "blocked": { + Type: schema.TypeBool, + Optional: true, + }, + "tags": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "spend": { + Type: schema.TypeFloat, + Computed: true, + }, + }, + } +} + +func resourceKeyCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(*Client) + + key := &Key{} + mapResourceDataToKey(d, key) + + createdKey, err := c.CreateKey(key) + if err != nil { + return diag.FromErr(fmt.Errorf("error creating key: %s", err)) + } + + d.SetId(createdKey.TokenID) + // Set the write-only key value so it's available during this apply + // but will not be persisted to state. + d.Set("key", createdKey.Key) + return resourceKeyRead(ctx, d, m) +} + +func resourceKeyRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(*Client) + + key, err := c.GetKey(d.Id()) + if err != nil { + return diag.FromErr(fmt.Errorf("error reading key: %s", err)) + } + + if key == nil { + d.SetId("") + return nil + } + + mapKeyToResourceData(d, key) + return nil +} + +func resourceKeyUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(*Client) + + key := &Key{Key: d.Id()} + mapResourceDataToKey(d, key) + + _, err := c.UpdateKey(key) + if err != nil { + return diag.FromErr(fmt.Errorf("error updating key: %s", err)) + } + + return resourceKeyRead(ctx, d, m) +} + +func resourceKeyDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(*Client) + + err := c.DeleteKey(d.Id()) + if err != nil { + return diag.FromErr(fmt.Errorf("error deleting key: %s", err)) + } + + d.SetId("") + return nil +} + +func mapResourceDataToKey(d *schema.ResourceData, key *Key) { + key.Models = expandStringList(d.Get("models").([]interface{})) + if v, ok := d.GetOk("max_budget"); ok { + val := v.(float64) + key.MaxBudget = &val + } + key.UserID = d.Get("user_id").(string) + key.TeamID = d.Get("team_id").(string) + if v, ok := d.GetOk("max_parallel_requests"); ok { + val := v.(int) + key.MaxParallelRequests = &val + } + key.Metadata = d.Get("metadata").(map[string]interface{}) + if v, ok := d.GetOk("tpm_limit"); ok { + val := v.(int) + key.TPMLimit = &val + } + if v, ok := d.GetOk("rpm_limit"); ok { + val := v.(int) + key.RPMLimit = &val + } + key.BudgetDuration = d.Get("budget_duration").(string) + key.AllowedCacheControls = expandStringList(d.Get("allowed_cache_controls").([]interface{})) + if v, ok := d.GetOk("soft_budget"); ok { + val := v.(float64) + key.SoftBudget = &val + } + key.KeyAlias = d.Get("key_alias").(string) + key.Duration = d.Get("duration").(string) + key.Aliases = d.Get("aliases").(map[string]interface{}) + key.Config = d.Get("config").(map[string]interface{}) + key.Permissions = d.Get("permissions").(map[string]interface{}) + key.ModelMaxBudget = d.Get("model_max_budget").(map[string]interface{}) + key.ModelRPMLimit = d.Get("model_rpm_limit").(map[string]interface{}) + key.ModelTPMLimit = d.Get("model_tpm_limit").(map[string]interface{}) + key.Guardrails = expandStringList(d.Get("guardrails").([]interface{})) + key.Blocked = d.Get("blocked").(bool) + key.Tags = expandStringList(d.Get("tags").([]interface{})) +} + +func mapKeyToResourceData(d *schema.ResourceData, key *Key) { + // token_id is the SHA-256 hash of the key, used as the resource ID. + // It is safe to store in state since it cannot be used to authenticate. + d.Set("token_id", d.Id()) + + // Note: "key" is write-only and must not be set here (Read operations). + // It is only set during Create so it is available during apply. + + if len(key.Models) > 0 { + d.Set("models", key.Models) + } + if key.MaxBudget != nil { + d.Set("max_budget", *key.MaxBudget) + } + if key.UserID != "" { + d.Set("user_id", key.UserID) + } + if key.TeamID != "" { + d.Set("team_id", key.TeamID) + } + if key.MaxParallelRequests != nil { + d.Set("max_parallel_requests", *key.MaxParallelRequests) + } + if key.Metadata != nil { + d.Set("metadata", key.Metadata) + } + if key.TPMLimit != nil { + d.Set("tpm_limit", *key.TPMLimit) + } + if key.RPMLimit != nil { + d.Set("rpm_limit", *key.RPMLimit) + } + if key.BudgetDuration != "" { + d.Set("budget_duration", key.BudgetDuration) + } + if len(key.AllowedCacheControls) > 0 { + d.Set("allowed_cache_controls", key.AllowedCacheControls) + } + if key.SoftBudget != nil { + d.Set("soft_budget", *key.SoftBudget) + } + if key.KeyAlias != "" { + d.Set("key_alias", key.KeyAlias) + } + if key.Duration != "" { + d.Set("duration", key.Duration) + } + if key.Aliases != nil { + d.Set("aliases", key.Aliases) + } + if key.Config != nil { + d.Set("config", key.Config) + } + if key.Permissions != nil { + d.Set("permissions", key.Permissions) + } + if key.ModelMaxBudget != nil { + d.Set("model_max_budget", key.ModelMaxBudget) + } + if key.ModelRPMLimit != nil { + d.Set("model_rpm_limit", key.ModelRPMLimit) + } + if key.ModelTPMLimit != nil { + d.Set("model_tpm_limit", key.ModelTPMLimit) + } + if len(key.Guardrails) > 0 { + d.Set("guardrails", key.Guardrails) + } + d.Set("blocked", key.Blocked) + if len(key.Tags) > 0 { + d.Set("tags", key.Tags) + } + if key.Spend != 0 { + d.Set("spend", key.Spend) + } +} diff --git a/terraform/provider/litellm/resource_key_utils.go b/terraform/provider/litellm/resource_key_utils.go new file mode 100644 index 00000000000..d426fec05b2 --- /dev/null +++ b/terraform/provider/litellm/resource_key_utils.go @@ -0,0 +1,230 @@ +package litellm + +import ( + "fmt" + "log" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func buildKeyData(d *schema.ResourceData) map[string]interface{} { + keyData := make(map[string]interface{}) + + if v, ok := d.GetOkExists("models"); ok { + models := expandStringList(v.([]interface{})) + if len(models) > 0 { + keyData["models"] = models + } + } + if v, ok := d.GetOkExists("max_budget"); ok { + keyData["max_budget"] = v.(float64) + } + if v, ok := d.GetOkExists("user_id"); ok { + keyData["user_id"] = v.(string) + } + if v, ok := d.GetOkExists("team_id"); ok { + keyData["team_id"] = v.(string) + } + if v, ok := d.GetOkExists("max_parallel_requests"); ok { + keyData["max_parallel_requests"] = v.(int) + } + if v, ok := d.GetOkExists("metadata"); ok { + keyData["metadata"] = v.(map[string]interface{}) + } + if v, ok := d.GetOkExists("tpm_limit"); ok { + keyData["tpm_limit"] = v.(int) + } + if v, ok := d.GetOkExists("rpm_limit"); ok { + keyData["rpm_limit"] = v.(int) + } + if v, ok := d.GetOkExists("budget_duration"); ok { + keyData["budget_duration"] = v.(string) + } + if v, ok := d.GetOkExists("allowed_cache_controls"); ok { + cacheControls := expandStringList(v.([]interface{})) + if len(cacheControls) > 0 { + keyData["allowed_cache_controls"] = cacheControls + } + } + if v, ok := d.GetOkExists("soft_budget"); ok { + keyData["soft_budget"] = v.(float64) + } + if v, ok := d.GetOkExists("key_alias"); ok { + keyData["key_alias"] = v.(string) + } + if v, ok := d.GetOkExists("duration"); ok { + keyData["duration"] = v.(string) + } + if v, ok := d.GetOkExists("aliases"); ok { + keyData["aliases"] = v.(map[string]interface{}) + } + if v, ok := d.GetOkExists("config"); ok { + keyData["config"] = v.(map[string]interface{}) + } + if v, ok := d.GetOkExists("permissions"); ok { + keyData["permissions"] = v.(map[string]interface{}) + } + if v, ok := d.GetOkExists("model_max_budget"); ok { + keyData["model_max_budget"] = v.(map[string]interface{}) + } + if v, ok := d.GetOkExists("model_rpm_limit"); ok { + keyData["model_rpm_limit"] = v.(map[string]interface{}) + } + if v, ok := d.GetOkExists("model_tpm_limit"); ok { + keyData["model_tpm_limit"] = v.(map[string]interface{}) + } + if v, ok := d.GetOkExists("guardrails"); ok { + guardrails := expandStringList(v.([]interface{})) + if len(guardrails) > 0 { + keyData["guardrails"] = guardrails + } + } + if v, ok := d.GetOkExists("blocked"); ok { + keyData["blocked"] = v.(bool) + } + if v, ok := d.GetOkExists("tags"); ok { + tags := expandStringList(v.([]interface{})) + if len(tags) > 0 { + keyData["tags"] = tags + } + } + + return keyData +} + +func setKeyResourceData(d *schema.ResourceData, key *Key) error { + fields := map[string]interface{}{ + "key": key.Key, + "models": key.Models, + "spend": key.Spend, + "user_id": key.UserID, + "team_id": key.TeamID, + "metadata": key.Metadata, + "budget_duration": key.BudgetDuration, + "allowed_cache_controls": key.AllowedCacheControls, + "key_alias": key.KeyAlias, + "duration": key.Duration, + "aliases": key.Aliases, + "config": key.Config, + "permissions": key.Permissions, + "model_max_budget": key.ModelMaxBudget, + "model_rpm_limit": key.ModelRPMLimit, + "model_tpm_limit": key.ModelTPMLimit, + "guardrails": key.Guardrails, + "blocked": key.Blocked, + "tags": key.Tags, + } + + for field, value := range fields { + if err := d.Set(field, value); err != nil { + log.Printf("[WARN] Error setting %s: %s", field, err) + return fmt.Errorf("error setting %s: %s", field, err) + } + } + + // Handle pointer fields separately - only set if not nil + if key.MaxBudget != nil { + if err := d.Set("max_budget", *key.MaxBudget); err != nil { + return fmt.Errorf("error setting max_budget: %s", err) + } + } + if key.SoftBudget != nil { + if err := d.Set("soft_budget", *key.SoftBudget); err != nil { + return fmt.Errorf("error setting soft_budget: %s", err) + } + } + if key.MaxParallelRequests != nil { + if err := d.Set("max_parallel_requests", *key.MaxParallelRequests); err != nil { + return fmt.Errorf("error setting max_parallel_requests: %s", err) + } + } + if key.TPMLimit != nil { + if err := d.Set("tpm_limit", *key.TPMLimit); err != nil { + return fmt.Errorf("error setting tpm_limit: %s", err) + } + } + if key.RPMLimit != nil { + if err := d.Set("rpm_limit", *key.RPMLimit); err != nil { + return fmt.Errorf("error setting rpm_limit: %s", err) + } + } + + return nil +} + +func expandStringList(list []interface{}) []string { + result := make([]string, len(list)) + for i, v := range list { + result[i] = v.(string) + } + return result +} + +func mapToKey(data map[string]interface{}) *Key { + key := &Key{} + for k, v := range data { + switch k { + case "key": + key.Key = v.(string) + case "models": + key.Models = v.([]string) + case "max_budget": + if v, ok := v.(float64); ok { + key.MaxBudget = &v + } + case "user_id": + key.UserID = v.(string) + case "team_id": + key.TeamID = v.(string) + case "max_parallel_requests": + if v, ok := v.(int); ok { + key.MaxParallelRequests = &v + } + case "metadata": + key.Metadata = v.(map[string]interface{}) + case "tpm_limit": + if v, ok := v.(int); ok { + key.TPMLimit = &v + } + case "rpm_limit": + if v, ok := v.(int); ok { + key.RPMLimit = &v + } + case "budget_duration": + key.BudgetDuration = v.(string) + case "allowed_cache_controls": + key.AllowedCacheControls = v.([]string) + case "soft_budget": + if v, ok := v.(float64); ok { + key.SoftBudget = &v + } + case "key_alias": + key.KeyAlias = v.(string) + case "duration": + key.Duration = v.(string) + case "aliases": + key.Aliases = v.(map[string]interface{}) + case "config": + key.Config = v.(map[string]interface{}) + case "permissions": + key.Permissions = v.(map[string]interface{}) + case "model_max_budget": + key.ModelMaxBudget = v.(map[string]interface{}) + case "model_rpm_limit": + key.ModelRPMLimit = v.(map[string]interface{}) + case "model_tpm_limit": + key.ModelTPMLimit = v.(map[string]interface{}) + case "guardrails": + key.Guardrails = v.([]string) + case "blocked": + key.Blocked = v.(bool) + case "tags": + key.Tags = v.([]string) + } + } + return key +} + +func buildKeyForCreation(data map[string]interface{}) *Key { + return mapToKey(data) +} diff --git a/terraform/provider/litellm/resource_mcp_server.go b/terraform/provider/litellm/resource_mcp_server.go new file mode 100644 index 00000000000..b3eaef4a468 --- /dev/null +++ b/terraform/provider/litellm/resource_mcp_server.go @@ -0,0 +1,176 @@ +package litellm + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func resourceLiteLLMMCPServer() *schema.Resource { + return &schema.Resource{ + Create: resourceLiteLLMMCPServerCreate, + Read: resourceLiteLLMMCPServerRead, + Update: resourceLiteLLMMCPServerUpdate, + Delete: resourceLiteLLMMCPServerDelete, + + Schema: map[string]*schema.Schema{ + "server_name": { + Type: schema.TypeString, + Required: true, + Description: "Name of the MCP server", + }, + "alias": { + Type: schema.TypeString, + Optional: true, + Description: "Alias for the MCP server", + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: "Description of the MCP server", + }, + "url": { + Type: schema.TypeString, + Required: true, + Description: "URL of the MCP server", + }, + "transport": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + "http", + "sse", + "stdio", + }, false), + Description: "Transport type for the MCP server (http, sse, stdio)", + }, + "spec_version": { + Type: schema.TypeString, + Optional: true, + Default: "2024-11-05", + Description: "MCP specification version", + }, + "auth_type": { + Type: schema.TypeString, + Optional: true, + Default: "none", + ValidateFunc: validation.StringInSlice([]string{ + "none", + "bearer", + "basic", + }, false), + Description: "Authentication type (none, bearer, basic)", + }, + "mcp_access_groups": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Description: "List of access groups for the MCP server", + }, + "command": { + Type: schema.TypeString, + Optional: true, + Description: "Command to run for stdio transport", + }, + "args": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Description: "Arguments for the command (stdio transport)", + }, + "env": { + Type: schema.TypeMap, + Optional: true, + Sensitive: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Description: "Environment variables for the command (stdio transport)", + }, + "mcp_info": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Description: "MCP server information and configuration", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "server_name": { + Type: schema.TypeString, + Optional: true, + Description: "Server name in MCP info", + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: "Description in MCP info", + }, + "logo_url": { + Type: schema.TypeString, + Optional: true, + Description: "Logo URL for the MCP server", + }, + "mcp_server_cost_info": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Description: "Cost information for MCP server tools", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "default_cost_per_query": { + Type: schema.TypeFloat, + Optional: true, + Description: "Default cost per query", + }, + "tool_name_to_cost_per_query": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeFloat}, + Description: "Map of tool names to their cost per query", + }, + }, + }, + }, + }, + }, + }, + // Read-only computed fields + "server_id": { + Type: schema.TypeString, + Computed: true, + Description: "Unique identifier for the MCP server", + }, + "created_at": { + Type: schema.TypeString, + Computed: true, + Description: "Timestamp when the server was created", + }, + "created_by": { + Type: schema.TypeString, + Computed: true, + Description: "User who created the server", + }, + "updated_at": { + Type: schema.TypeString, + Computed: true, + Description: "Timestamp when the server was last updated", + }, + "updated_by": { + Type: schema.TypeString, + Computed: true, + Description: "User who last updated the server", + }, + "status": { + Type: schema.TypeString, + Computed: true, + Description: "Current status of the MCP server", + }, + "last_health_check": { + Type: schema.TypeString, + Computed: true, + Description: "Timestamp of the last health check", + }, + "health_check_error": { + Type: schema.TypeString, + Computed: true, + Description: "Error message from the last health check, if any", + }, + }, + } +} diff --git a/terraform/provider/litellm/resource_mcp_server_crud.go b/terraform/provider/litellm/resource_mcp_server_crud.go new file mode 100644 index 00000000000..2a8980960f1 --- /dev/null +++ b/terraform/provider/litellm/resource_mcp_server_crud.go @@ -0,0 +1,317 @@ +package litellm + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +const ( + endpointMCPServerCreate = "/v1/mcp/server" + endpointMCPServerUpdate = "/v1/mcp/server" + endpointMCPServerRead = "/v1/mcp/server" + endpointMCPServerDelete = "/v1/mcp/server" +) + +// Helper function to convert schema data to MCPServerRequest +func buildMCPServerRequest(d *schema.ResourceData) *MCPServerRequest { + req := &MCPServerRequest{ + ServerName: d.Get("server_name").(string), + URL: d.Get("url").(string), + Transport: d.Get("transport").(string), + SpecVersion: d.Get("spec_version").(string), + AuthType: d.Get("auth_type").(string), + } + + // Set optional fields + if alias, ok := d.GetOk("alias"); ok { + req.Alias = alias.(string) + } + if description, ok := d.GetOk("description"); ok { + req.Description = description.(string) + } + if command, ok := d.GetOk("command"); ok { + req.Command = command.(string) + } + + // Handle access groups + if accessGroups, ok := d.GetOk("mcp_access_groups"); ok { + accessGroupsList := accessGroups.([]interface{}) + req.MCPAccessGroups = make([]string, len(accessGroupsList)) + for i, group := range accessGroupsList { + req.MCPAccessGroups[i] = group.(string) + } + } + + // Handle args + if args, ok := d.GetOk("args"); ok { + argsList := args.([]interface{}) + req.Args = make([]string, len(argsList)) + for i, arg := range argsList { + req.Args[i] = arg.(string) + } + } + + // Handle env + if env, ok := d.GetOk("env"); ok { + envMap := env.(map[string]interface{}) + req.Env = make(map[string]string) + for k, v := range envMap { + req.Env[k] = v.(string) + } + } + + // Handle mcp_info + if mcpInfoList, ok := d.GetOk("mcp_info"); ok { + mcpInfos := mcpInfoList.([]interface{}) + if len(mcpInfos) > 0 { + mcpInfoMap := mcpInfos[0].(map[string]interface{}) + req.MCPInfo = &MCPInfo{} + + if serverName, ok := mcpInfoMap["server_name"]; ok { + req.MCPInfo.ServerName = serverName.(string) + } + if description, ok := mcpInfoMap["description"]; ok { + req.MCPInfo.Description = description.(string) + } + if logoURL, ok := mcpInfoMap["logo_url"]; ok { + req.MCPInfo.LogoURL = logoURL.(string) + } + + // Handle cost info + if costInfoList, ok := mcpInfoMap["mcp_server_cost_info"]; ok { + costInfos := costInfoList.([]interface{}) + if len(costInfos) > 0 { + costInfoMap := costInfos[0].(map[string]interface{}) + req.MCPInfo.MCPServerCostInfo = &MCPServerCostInfo{} + + if defaultCost, ok := costInfoMap["default_cost_per_query"]; ok { + req.MCPInfo.MCPServerCostInfo.DefaultCostPerQuery = defaultCost.(float64) + } + if toolCosts, ok := costInfoMap["tool_name_to_cost_per_query"]; ok { + toolCostMap := toolCosts.(map[string]interface{}) + req.MCPInfo.MCPServerCostInfo.ToolNameToCostPerQuery = make(map[string]float64) + for k, v := range toolCostMap { + req.MCPInfo.MCPServerCostInfo.ToolNameToCostPerQuery[k] = v.(float64) + } + } + } + } + } + } + + return req +} + +// Helper function to update schema data from MCPServerResponse +func updateSchemaFromResponse(d *schema.ResourceData, resp *MCPServerResponse) error { + d.Set("server_id", resp.ServerID) + d.Set("server_name", resp.ServerName) + d.Set("alias", resp.Alias) + d.Set("description", resp.Description) + d.Set("url", resp.URL) + d.Set("transport", resp.Transport) + d.Set("spec_version", resp.SpecVersion) + d.Set("auth_type", resp.AuthType) + d.Set("created_at", resp.CreatedAt) + d.Set("created_by", resp.CreatedBy) + d.Set("updated_at", resp.UpdatedAt) + d.Set("updated_by", resp.UpdatedBy) + d.Set("status", resp.Status) + d.Set("last_health_check", resp.LastHealthCheck) + d.Set("health_check_error", resp.HealthCheckError) + d.Set("command", resp.Command) + + // Set access groups + if resp.MCPAccessGroups != nil { + d.Set("mcp_access_groups", resp.MCPAccessGroups) + } + + // Set args + if resp.Args != nil { + d.Set("args", resp.Args) + } + + // Set mcp_info + if resp.MCPInfo != nil { + mcpInfoList := make([]map[string]interface{}, 1) + mcpInfoMap := make(map[string]interface{}) + + mcpInfoMap["server_name"] = resp.MCPInfo.ServerName + mcpInfoMap["description"] = resp.MCPInfo.Description + mcpInfoMap["logo_url"] = resp.MCPInfo.LogoURL + + if resp.MCPInfo.MCPServerCostInfo != nil { + costInfoList := make([]map[string]interface{}, 1) + costInfoMap := make(map[string]interface{}) + + costInfoMap["default_cost_per_query"] = resp.MCPInfo.MCPServerCostInfo.DefaultCostPerQuery + if resp.MCPInfo.MCPServerCostInfo.ToolNameToCostPerQuery != nil { + costInfoMap["tool_name_to_cost_per_query"] = resp.MCPInfo.MCPServerCostInfo.ToolNameToCostPerQuery + } + + costInfoList[0] = costInfoMap + mcpInfoMap["mcp_server_cost_info"] = costInfoList + } + + mcpInfoList[0] = mcpInfoMap + d.Set("mcp_info", mcpInfoList) + } + + return nil +} + +func resourceLiteLLMMCPServerCreate(d *schema.ResourceData, m interface{}) error { + client, ok := m.(*Client) + if !ok { + return fmt.Errorf("invalid type assertion for client") + } + + req := buildMCPServerRequest(d) + + resp, err := MakeRequest(client, "POST", endpointMCPServerCreate, req) + if err != nil { + return fmt.Errorf("failed to create MCP server: %w", err) + } + defer resp.Body.Close() + + var mcpResp MCPServerResponse + if err := handleMCPAPIResponse(resp, &mcpResp, client); err != nil { + return fmt.Errorf("failed to create MCP server: %w", err) + } + + d.SetId(mcpResp.ServerID) + + // Update the state with the response data + if err := updateSchemaFromResponse(d, &mcpResp); err != nil { + return fmt.Errorf("failed to update state after create: %w", err) + } + + log.Printf("[INFO] MCP server created with ID %s", mcpResp.ServerID) + return nil +} + +func resourceLiteLLMMCPServerRead(d *schema.ResourceData, m interface{}) error { + client, ok := m.(*Client) + if !ok { + return fmt.Errorf("invalid type assertion for client") + } + + serverID := d.Id() + endpoint := fmt.Sprintf("%s/%s", endpointMCPServerRead, serverID) + + resp, err := MakeRequest(client, "GET", endpoint, nil) + if err != nil { + return fmt.Errorf("failed to read MCP server: %w", err) + } + defer resp.Body.Close() + + var mcpResp MCPServerResponse + if err := handleMCPAPIResponse(resp, &mcpResp, client); err != nil { + if err.Error() == "mcp_server_not_found" { + d.SetId("") + return nil + } + return fmt.Errorf("failed to read MCP server: %w", err) + } + + // Update the state with the response data + if err := updateSchemaFromResponse(d, &mcpResp); err != nil { + return fmt.Errorf("failed to update state after read: %w", err) + } + + return nil +} + +func resourceLiteLLMMCPServerUpdate(d *schema.ResourceData, m interface{}) error { + client, ok := m.(*Client) + if !ok { + return fmt.Errorf("invalid type assertion for client") + } + + req := buildMCPServerRequest(d) + req.ServerID = d.Id() // Ensure we include the server ID for updates + + resp, err := MakeRequest(client, "PUT", endpointMCPServerUpdate, req) + if err != nil { + return fmt.Errorf("failed to update MCP server: %w", err) + } + defer resp.Body.Close() + + var mcpResp MCPServerResponse + if err := handleMCPAPIResponse(resp, &mcpResp, client); err != nil { + return fmt.Errorf("failed to update MCP server: %w", err) + } + + // Update the state with the response data + if err := updateSchemaFromResponse(d, &mcpResp); err != nil { + return fmt.Errorf("failed to update state after update: %w", err) + } + + log.Printf("[INFO] MCP server updated with ID %s", mcpResp.ServerID) + return nil +} + +func resourceLiteLLMMCPServerDelete(d *schema.ResourceData, m interface{}) error { + client, ok := m.(*Client) + if !ok { + return fmt.Errorf("invalid type assertion for client") + } + + serverID := d.Id() + endpoint := fmt.Sprintf("%s/%s", endpointMCPServerDelete, serverID) + + resp, err := MakeRequest(client, "DELETE", endpoint, nil) + if err != nil { + return fmt.Errorf("failed to delete MCP server: %w", err) + } + defer resp.Body.Close() + + // For delete operations, we expect a simple string response + if resp.StatusCode != 200 { + return fmt.Errorf("failed to delete MCP server: unexpected status code %d", resp.StatusCode) + } + + d.SetId("") + log.Printf("[INFO] MCP server deleted with ID %s", serverID) + return nil +} + +// retryMCPServerRead attempts to read an MCP server with exponential backoff +func retryMCPServerRead(d *schema.ResourceData, m interface{}, maxRetries int) error { + var err error + delay := 1 * time.Second + maxDelay := 10 * time.Second + + for i := 0; i < maxRetries; i++ { + log.Printf("[INFO] Attempting to read MCP server (attempt %d/%d)", i+1, maxRetries) + + err = resourceLiteLLMMCPServerRead(d, m) + if err == nil { + log.Printf("[INFO] Successfully read MCP server after %d attempts", i+1) + return nil + } + + // Check if this is a "server not found" error + if err.Error() != "failed to read MCP server: mcp_server_not_found" { + // If it's a different error, don't retry + return err + } + + if i < maxRetries-1 { + log.Printf("[INFO] MCP server not found yet, retrying in %v...", delay) + time.Sleep(delay) + + // Exponential backoff with a maximum delay + delay *= 2 + if delay > maxDelay { + delay = maxDelay + } + } + } + + log.Printf("[WARN] Failed to read MCP server after %d attempts: %v", maxRetries, err) + return err +} diff --git a/terraform/provider/litellm/resource_mcp_server_crud_test.go b/terraform/provider/litellm/resource_mcp_server_crud_test.go new file mode 100644 index 00000000000..17300701954 --- /dev/null +++ b/terraform/provider/litellm/resource_mcp_server_crud_test.go @@ -0,0 +1,44 @@ +package litellm + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func TestMCPServerReadDoesNotPersistServerEnv(t *testing.T) { + d := schema.TestResourceDataRaw(t, resourceLiteLLMMCPServer().Schema, map[string]interface{}{ + "server_name": "gh", + "transport": "stdio", + "command": "npx", + "env": map[string]interface{}{ + "GITHUB_TOKEN": "from-config", + }, + }) + d.SetId("srv-1") + + resp := &MCPServerResponse{ + ServerID: "srv-1", + ServerName: "gh", + Transport: "stdio", + Command: "npx", + Env: map[string]string{ + "GITHUB_TOKEN": "raw-from-server", + "DB_PASSWORD": "leaked-secret", + }, + } + if err := updateSchemaFromResponse(d, resp); err != nil { + t.Fatalf("updateSchemaFromResponse failed: %v", err) + } + + got := d.Get("env").(map[string]interface{}) + if got["GITHUB_TOKEN"] != "from-config" { + t.Fatalf("config env overwritten by server response: %v", got) + } + if _, leaked := got["DB_PASSWORD"]; leaked { + t.Fatalf("server-returned env var persisted into state: %v", got) + } + if d.Get("server_name").(string) != "gh" { + t.Fatalf("read did not populate non-sensitive fields") + } +} diff --git a/terraform/provider/litellm/resource_model.go b/terraform/provider/litellm/resource_model.go new file mode 100644 index 00000000000..2858b6e763d --- /dev/null +++ b/terraform/provider/litellm/resource_model.go @@ -0,0 +1,177 @@ +package litellm + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func resourceLiteLLMModel() *schema.Resource { + return &schema.Resource{ + Create: resourceLiteLLMModelCreate, + Read: resourceLiteLLMModelRead, + Update: resourceLiteLLMModelUpdate, + Delete: resourceLiteLLMModelDelete, + + Schema: map[string]*schema.Schema{ + "model_name": { + Type: schema.TypeString, + Required: true, + }, + "custom_llm_provider": { + Type: schema.TypeString, + Required: true, + }, + "tpm": { + Type: schema.TypeInt, + Optional: true, + }, + "rpm": { + Type: schema.TypeInt, + Optional: true, + }, + "reasoning_effort": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "low", + "medium", + "high", + }, false), + }, + "thinking_enabled": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "thinking_budget_tokens": { + Type: schema.TypeInt, + Optional: true, + Default: 1024, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + // Only include thinking_budget_tokens in the diff if thinking_enabled is true + return !d.Get("thinking_enabled").(bool) + }, + }, + "merge_reasoning_content_in_choices": { + Type: schema.TypeBool, + Optional: true, + }, + "model_api_key": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + }, + "model_api_base": { + Type: schema.TypeString, + Optional: true, + }, + "api_version": { + Type: schema.TypeString, + Optional: true, + }, + "base_model": { + Type: schema.TypeString, + Required: true, + }, + "tier": { + Type: schema.TypeString, + Optional: true, + Default: "free", + }, + "team_id": { + Type: schema.TypeString, + Optional: true, + }, + "mode": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "completion", + "embedding", + "image_generation", + "chat", + "moderation", + "audio_transcription", + "audio_speech", + "rerank", + }, false), + }, + "input_cost_per_million_tokens": { + Type: schema.TypeFloat, + Optional: true, + }, + "output_cost_per_million_tokens": { + Type: schema.TypeFloat, + Optional: true, + }, + "input_cost_per_pixel": { + Type: schema.TypeFloat, + Optional: true, + }, + "output_cost_per_pixel": { + Type: schema.TypeFloat, + Optional: true, + }, + "input_cost_per_second": { + Type: schema.TypeFloat, + Optional: true, + }, + "output_cost_per_second": { + Type: schema.TypeFloat, + Optional: true, + }, + "aws_access_key_id": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + }, + "aws_secret_access_key": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + }, + "aws_region_name": { + Type: schema.TypeString, + Optional: true, + }, + "aws_session_name": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + }, + "aws_role_name": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + }, + "vertex_project": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + }, + "vertex_location": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + }, + "vertex_credentials": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + }, + "litellm_credential_name": { + Type: schema.TypeString, + Optional: true, + Description: "Name of the LiteLLM credential to use", + }, + "additional_litellm_params": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Description: "Additional parameters to pass to litellm_params beyond the standard ones", + }, + }, + } +} diff --git a/terraform/provider/litellm/resource_model_crud.go b/terraform/provider/litellm/resource_model_crud.go new file mode 100644 index 00000000000..40766c8e312 --- /dev/null +++ b/terraform/provider/litellm/resource_model_crud.go @@ -0,0 +1,407 @@ +package litellm + +import ( + "encoding/json" + "fmt" + "log" + "strconv" + "strings" + "time" + + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +// retryModelRead attempts to read a model with exponential backoff. +// It handles the case where resourceLiteLLMModelRead returns nil but clears the ID +// (eventual consistency: model created but not yet visible on read-back). +func retryModelRead(d *schema.ResourceData, m interface{}, maxRetries int) error { + delay := 1 * time.Second + maxDelay := 10 * time.Second + modelID := d.Id() + + for i := 0; i < maxRetries; i++ { + log.Printf("[INFO] Attempting to read model (attempt %d/%d)", i+1, maxRetries) + + err := resourceLiteLLMModelRead(d, m) + if err == nil { + if d.Id() != "" { + log.Printf("[INFO] Successfully read model after %d attempts", i+1) + return nil + } + // Read returned nil but cleared the ID — model not yet visible (eventual consistency). + // Restore the ID so we can retry. + d.SetId(modelID) + log.Printf("[INFO] Model not found yet (eventual consistency), retrying in %v...", delay) + } else { + log.Printf("[INFO] Read error, retrying in %v: %v", delay, err) + } + + if i < maxRetries-1 { + time.Sleep(delay) + delay *= 2 + if delay > maxDelay { + delay = maxDelay + } + } + } + + log.Printf("[WARN] Failed to read model after %d attempts", maxRetries) + return fmt.Errorf("model %s not found after %d read attempts post-create; the model may have been created successfully — re-running apply should resolve this", modelID, maxRetries) +} + +const ( + endpointModelNew = "/model/new" + endpointModelUpdate = "/model/update" + endpointModelInfo = "/model/info" + endpointModelDelete = "/model/delete" +) + +func createOrUpdateModel(d *schema.ResourceData, m interface{}, isUpdate bool) error { + client, ok := m.(*Client) + if !ok { + return fmt.Errorf("invalid type assertion for client") + } + + // Construct the model name in the format "custom_llm_provider/base_model" + customLLMProvider := d.Get("custom_llm_provider").(string) + baseModel := d.Get("base_model").(string) + modelName := fmt.Sprintf("%s/%s", customLLMProvider, baseModel) + + // Generate a UUID for new models + modelID := d.Id() + if !isUpdate { + modelID = uuid.New().String() + } + + // Create thinking configuration if enabled + var thinking map[string]interface{} + if d.Get("thinking_enabled").(bool) { + thinking = map[string]interface{}{ + "type": "enabled", + "budget_tokens": d.Get("thinking_budget_tokens").(int), + } + } + + // Build the base litellm_params as a map to allow for additional parameters + litellmParams := map[string]interface{}{ + "custom_llm_provider": customLLMProvider, + "model": modelName, + "merge_reasoning_content_in_choices": d.Get("merge_reasoning_content_in_choices").(bool), + } + + // Add optional parameters only if they have values + if tpm := d.Get("tpm").(int); tpm > 0 { + litellmParams["tpm"] = tpm + } + if rpm := d.Get("rpm").(int); rpm > 0 { + litellmParams["rpm"] = rpm + } + // Only include cost fields if explicitly set (non-zero) + if inputCostPerMillion := d.Get("input_cost_per_million_tokens").(float64); inputCostPerMillion > 0 { + litellmParams["input_cost_per_token"] = inputCostPerMillion / 1000000.0 + } + if outputCostPerMillion := d.Get("output_cost_per_million_tokens").(float64); outputCostPerMillion > 0 { + litellmParams["output_cost_per_token"] = outputCostPerMillion / 1000000.0 + } + if apiKey := d.Get("model_api_key").(string); apiKey != "" { + litellmParams["api_key"] = apiKey + } + if apiBase := d.Get("model_api_base").(string); apiBase != "" { + litellmParams["api_base"] = apiBase + } + if apiVersion := d.Get("api_version").(string); apiVersion != "" { + litellmParams["api_version"] = apiVersion + } + if inputCostPerPixel := d.Get("input_cost_per_pixel").(float64); inputCostPerPixel > 0 { + litellmParams["input_cost_per_pixel"] = inputCostPerPixel + } + if outputCostPerPixel := d.Get("output_cost_per_pixel").(float64); outputCostPerPixel > 0 { + litellmParams["output_cost_per_pixel"] = outputCostPerPixel + } + if inputCostPerSecond := d.Get("input_cost_per_second").(float64); inputCostPerSecond > 0 { + litellmParams["input_cost_per_second"] = inputCostPerSecond + } + if outputCostPerSecond := d.Get("output_cost_per_second").(float64); outputCostPerSecond > 0 { + litellmParams["output_cost_per_second"] = outputCostPerSecond + } + if awsAccessKeyID := d.Get("aws_access_key_id").(string); awsAccessKeyID != "" { + litellmParams["aws_access_key_id"] = awsAccessKeyID + } + if awsSecretAccessKey := d.Get("aws_secret_access_key").(string); awsSecretAccessKey != "" { + litellmParams["aws_secret_access_key"] = awsSecretAccessKey + } + if awsRegionName := d.Get("aws_region_name").(string); awsRegionName != "" { + litellmParams["aws_region_name"] = awsRegionName + } + if awsSessionName := d.Get("aws_session_name").(string); awsSessionName != "" { + litellmParams["aws_session_name"] = awsSessionName + } + if awsRoleName := d.Get("aws_role_name").(string); awsRoleName != "" { + litellmParams["aws_role_name"] = awsRoleName + } + if vertexProject := d.Get("vertex_project").(string); vertexProject != "" { + litellmParams["vertex_project"] = vertexProject + } + if vertexLocation := d.Get("vertex_location").(string); vertexLocation != "" { + litellmParams["vertex_location"] = vertexLocation + } + if vertexCredentials := d.Get("vertex_credentials").(string); vertexCredentials != "" { + litellmParams["vertex_credentials"] = vertexCredentials + } + if reasoningEffort := d.Get("reasoning_effort").(string); reasoningEffort != "" { + litellmParams["reasoning_effort"] = reasoningEffort + } + if thinking != nil { + litellmParams["thinking"] = thinking + } + + // Add additional parameters if provided + if additionalParams, ok := d.GetOk("additional_litellm_params"); ok { + var dropParams []string + + for key, value := range additionalParams.(map[string]interface{}) { + // Convert string values to appropriate types where possible + if strValue, ok := value.(string); ok { + // Check if it's JSON (starts with [ or {) + trimmedValue := strings.TrimSpace(strValue) + if strings.HasPrefix(trimmedValue, "[") || strings.HasPrefix(trimmedValue, "{") { + var parsedValue interface{} + if err := json.Unmarshal([]byte(strValue), &parsedValue); err == nil { + // Successfully parsed JSON + if key == "additional_drop_params" { + // Handle drop params specially + if dropList, ok := parsedValue.([]interface{}); ok { + for _, item := range dropList { + if paramStr, ok := item.(string); ok { + dropParams = append(dropParams, paramStr) + } + } + } + continue // Don't add to litellmParams + } else { + litellmParams[key] = parsedValue + } + } else { + // Not valid JSON, apply existing conversion logic + if strValue == "true" { + litellmParams[key] = true + } else if strValue == "false" { + litellmParams[key] = false + } else { + // Try to convert numeric strings + if intValue, err := strconv.Atoi(strValue); err == nil { + litellmParams[key] = intValue + } else if floatValue, err := strconv.ParseFloat(strValue, 64); err == nil { + litellmParams[key] = floatValue + } else { + // Keep as string + litellmParams[key] = strValue + } + } + } + } else { + // Apply existing conversion logic for non-JSON strings + if strValue == "true" { + litellmParams[key] = true + } else if strValue == "false" { + litellmParams[key] = false + } else { + // Try to convert numeric strings + if intValue, err := strconv.Atoi(strValue); err == nil { + litellmParams[key] = intValue + } else if floatValue, err := strconv.ParseFloat(strValue, 64); err == nil { + litellmParams[key] = floatValue + } else { + // Keep as string + litellmParams[key] = strValue + } + } + } + } else { + litellmParams[key] = value + } + } + + // Apply drop params at the end + for _, paramToDrop := range dropParams { + delete(litellmParams, paramToDrop) + } + } + + // Add litellm_credential_name to litellmParams if provided + if credentialName := d.Get("litellm_credential_name").(string); credentialName != "" { + litellmParams["litellm_credential_name"] = credentialName + } + + modelReq := ModelRequest{ + ModelName: d.Get("model_name").(string), + LiteLLMParams: litellmParams, + ModelInfo: ModelInfo{ + ID: modelID, + DBModel: true, + BaseModel: baseModel, + Tier: d.Get("tier").(string), + Mode: d.Get("mode").(string), + TeamID: d.Get("team_id").(string), + }, + Additional: make(map[string]interface{}), + } + + endpoint := endpointModelNew + if isUpdate { + endpoint = endpointModelUpdate + } + + resp, err := MakeRequest(client, "POST", endpoint, modelReq) + if err != nil { + return fmt.Errorf("failed to %s model: %w", map[bool]string{true: "update", false: "create"}[isUpdate], err) + } + defer resp.Body.Close() + + _, err = handleAPIResponse(resp, modelReq, client) + if err != nil { + if isUpdate && err.Error() == "model_not_found" { + return createOrUpdateModel(d, m, false) + } + return fmt.Errorf("failed to %s model: %w", map[bool]string{true: "update", false: "create"}[isUpdate], err) + } + + d.SetId(modelID) + + log.Printf("[INFO] Model created with ID %s. Starting retry mechanism to read the model...", modelID) + // Read back the resource with retries to ensure the state is consistent + return retryModelRead(d, m, 5) +} + +func resourceLiteLLMModelCreate(d *schema.ResourceData, m interface{}) error { + return createOrUpdateModel(d, m, false) +} + +func resourceLiteLLMModelRead(d *schema.ResourceData, m interface{}) error { + client, ok := m.(*Client) + if !ok { + return fmt.Errorf("invalid type assertion for client") + } + + resp, err := MakeRequest(client, "GET", fmt.Sprintf("%s?litellm_model_id=%s", endpointModelInfo, d.Id()), nil) + if err != nil { + return fmt.Errorf("failed to read model: %w", err) + } + defer resp.Body.Close() + + modelResp, err := handleAPIResponse(resp, nil, client) + if err != nil { + if err.Error() == "model_not_found" { + d.SetId("") + return nil + } + return fmt.Errorf("failed to read model: %w", err) + } + + // Update the state with values from the response or fall back to the data passed in during creation + d.Set("model_name", GetStringValue(modelResp.ModelName, d.Get("model_name").(string))) + d.Set("custom_llm_provider", GetStringValue(modelResp.LiteLLMParams.CustomLLMProvider, d.Get("custom_llm_provider").(string))) + d.Set("tpm", GetIntValue(modelResp.LiteLLMParams.TPM, d.Get("tpm").(int))) + d.Set("rpm", GetIntValue(modelResp.LiteLLMParams.RPM, d.Get("rpm").(int))) + d.Set("model_api_base", GetStringValue(modelResp.LiteLLMParams.APIBase, d.Get("model_api_base").(string))) + d.Set("api_version", GetStringValue(modelResp.LiteLLMParams.APIVersion, d.Get("api_version").(string))) + d.Set("base_model", GetStringValue(modelResp.ModelInfo.BaseModel, d.Get("base_model").(string))) + d.Set("tier", GetStringValue(modelResp.ModelInfo.Tier, d.Get("tier").(string))) + d.Set("mode", GetStringValue(modelResp.ModelInfo.Mode, d.Get("mode").(string))) + d.Set("team_id", GetStringValue(modelResp.ModelInfo.TeamID, d.Get("team_id").(string))) + + // Preserve credential name from state since it might not be returned by API + d.Set("litellm_credential_name", d.Get("litellm_credential_name").(string)) + + // Store sensitive information + d.Set("model_api_key", d.Get("model_api_key")) + d.Set("aws_access_key_id", d.Get("aws_access_key_id")) + d.Set("aws_secret_access_key", d.Get("aws_secret_access_key")) + d.Set("aws_region_name", GetStringValue(modelResp.LiteLLMParams.AWSRegionName, d.Get("aws_region_name").(string))) + d.Set("aws_session_name", d.Get("aws_session_name")) + d.Set("aws_role_name", d.Get("aws_role_name")) + + // Store cost information + d.Set("input_cost_per_million_tokens", d.Get("input_cost_per_million_tokens")) + d.Set("output_cost_per_million_tokens", d.Get("output_cost_per_million_tokens")) + + // Handle thinking configuration + if _, ok := d.GetOk("thinking_enabled"); ok { + // Keep the existing value from state + thinkingEnabled := d.Get("thinking_enabled").(bool) + d.Set("thinking_enabled", thinkingEnabled) + + // Only set thinking_budget_tokens if thinking is enabled and we have a value in state + if thinkingEnabled { + if _, ok := d.GetOk("thinking_budget_tokens"); ok { + d.Set("thinking_budget_tokens", d.Get("thinking_budget_tokens").(int)) + } + } + } else { + // Fall back to API response if no state value exists + if modelResp.LiteLLMParams.Thinking != nil { + if thinkingType, ok := modelResp.LiteLLMParams.Thinking["type"].(string); ok && thinkingType == "enabled" { + d.Set("thinking_enabled", true) + if budgetTokens, ok := modelResp.LiteLLMParams.Thinking["budget_tokens"].(float64); ok { + d.Set("thinking_budget_tokens", int(budgetTokens)) + } + } else { + d.Set("thinking_enabled", false) + } + } else { + d.Set("thinking_enabled", false) + } + } + + // Handle merge_reasoning_content_in_choices - preserve state value if not returned by API + if _, ok := d.GetOk("merge_reasoning_content_in_choices"); ok { + // Keep the existing value from state + d.Set("merge_reasoning_content_in_choices", d.Get("merge_reasoning_content_in_choices").(bool)) + } else { + // Only set from API response if we don't have a value in state + d.Set("merge_reasoning_content_in_choices", modelResp.LiteLLMParams.MergeReasoningContentInChoices) + } + + // Preserve additional_litellm_params from state since API might not return all custom parameters + if _, ok := d.GetOk("additional_litellm_params"); ok { + d.Set("additional_litellm_params", d.Get("additional_litellm_params")) + } + + return nil +} + +func resourceLiteLLMModelUpdate(d *schema.ResourceData, m interface{}) error { + return createOrUpdateModel(d, m, true) +} + +func resourceLiteLLMModelDelete(d *schema.ResourceData, m interface{}) error { + client, ok := m.(*Client) + if !ok { + return fmt.Errorf("invalid type assertion for client") + } + + deleteReq := struct { + ID string `json:"id"` + }{ + ID: d.Id(), + } + + resp, err := MakeRequest(client, "POST", endpointModelDelete, deleteReq) + if err != nil { + return fmt.Errorf("failed to delete model: %w", err) + } + defer resp.Body.Close() + + _, err = handleAPIResponse(resp, deleteReq, client) + if err != nil { + if err.Error() == "model_not_found" { + d.SetId("") + return nil + } + return fmt.Errorf("failed to delete model: %w", err) + } + + d.SetId("") + return nil +} diff --git a/terraform/provider/litellm/resource_organization.go b/terraform/provider/litellm/resource_organization.go new file mode 100644 index 00000000000..30e7feba1ec --- /dev/null +++ b/terraform/provider/litellm/resource_organization.go @@ -0,0 +1,210 @@ +package litellm + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +const ( + endpointOrganizationNew = "/organization/new" + endpointOrganizationInfo = "/organization/info" + endpointOrganizationUpdate = "/organization/update" + endpointOrganizationDelete = "/organization/delete" +) + +func resourceLiteLLMOrganization() *schema.Resource { + return &schema.Resource{ + Create: resourceLiteLLMOrganizationCreate, + Read: resourceLiteLLMOrganizationRead, + Update: resourceLiteLLMOrganizationUpdate, + Delete: resourceLiteLLMOrganizationDelete, + + Schema: map[string]*schema.Schema{ + "organization_alias": { + Type: schema.TypeString, + Required: true, + }, + "metadata": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "models": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "max_budget": { + Type: schema.TypeFloat, + Optional: true, + }, + "budget_duration": { + Type: schema.TypeString, + Optional: true, + }, + "tpm_limit": { + Type: schema.TypeInt, + Optional: true, + }, + "rpm_limit": { + Type: schema.TypeInt, + Optional: true, + }, + "blocked": { + Type: schema.TypeBool, + Optional: true, + }, + }, + } +} + +func resourceLiteLLMOrganizationCreate(d *schema.ResourceData, m interface{}) error { + client := m.(*Client) + + orgID := uuid.New().String() + orgData := buildOrganizationData(d, orgID) + + log.Printf("[DEBUG] Create organization request payload: %+v", orgData) + + resp, err := MakeRequest(client, "POST", endpointOrganizationNew, orgData) + if err != nil { + return fmt.Errorf("error creating organization: %w", err) + } + defer resp.Body.Close() + + if err := handleResponse(resp, "creating organization"); err != nil { + return err + } + + d.SetId(orgID) + log.Printf("[INFO] Organization created with ID: %s", orgID) + + return resourceLiteLLMOrganizationRead(d, m) +} + +func resourceLiteLLMOrganizationRead(d *schema.ResourceData, m interface{}) error { + client := m.(*Client) + + log.Printf("[INFO] Reading organization with ID: %s", d.Id()) + + resp, err := MakeRequest(client, "POST", endpointOrganizationInfo, map[string]interface{}{ + "organizations": []string{d.Id()}, + }) + if err != nil { + return fmt.Errorf("error reading organization: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusNotFound { + log.Printf("[WARN] Organization with ID %s not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + var orgResps []OrganizationResponse + if err := json.NewDecoder(resp.Body).Decode(&orgResps); err != nil { + return fmt.Errorf("error decoding organization info response: %w", err) + } + + if len(orgResps) == 0 { + log.Printf("[WARN] Organization with ID %s not found in response, removing from state", d.Id()) + d.SetId("") + return nil + } + + orgResp := orgResps[0] + + d.Set("organization_alias", GetStringValue(orgResp.OrganizationAlias, d.Get("organization_alias").(string))) + + if orgResp.Metadata != nil { + d.Set("metadata", orgResp.Metadata) + } else { + d.Set("metadata", d.Get("metadata")) + } + + if orgResp.Models != nil { + d.Set("models", orgResp.Models) + } else { + d.Set("models", d.Get("models")) + } + + if orgResp.MaxBudget != nil { + d.Set("max_budget", *orgResp.MaxBudget) + } + d.Set("budget_duration", GetStringValue(orgResp.BudgetDuration, d.Get("budget_duration").(string))) + if orgResp.TPMLimit != nil { + d.Set("tpm_limit", *orgResp.TPMLimit) + } + if orgResp.RPMLimit != nil { + d.Set("rpm_limit", *orgResp.RPMLimit) + } + d.Set("blocked", GetBoolValue(orgResp.Blocked, d.Get("blocked").(bool))) + + log.Printf("[INFO] Successfully read organization with ID: %s", d.Id()) + return nil +} + +func resourceLiteLLMOrganizationUpdate(d *schema.ResourceData, m interface{}) error { + client := m.(*Client) + + orgData := buildOrganizationData(d, d.Id()) + log.Printf("[DEBUG] Update organization request payload: %+v", orgData) + + resp, err := MakeRequest(client, "PATCH", endpointOrganizationUpdate, orgData) + if err != nil { + return fmt.Errorf("error updating organization: %w", err) + } + defer resp.Body.Close() + + if err := handleResponse(resp, "updating organization"); err != nil { + return err + } + + log.Printf("[INFO] Successfully updated organization with ID: %s", d.Id()) + return resourceLiteLLMOrganizationRead(d, m) +} + +func resourceLiteLLMOrganizationDelete(d *schema.ResourceData, m interface{}) error { + client := m.(*Client) + + log.Printf("[INFO] Deleting organization with ID: %s", d.Id()) + + deleteData := map[string]interface{}{ + "organization_ids": []string{d.Id()}, + } + + resp, err := MakeRequest(client, "DELETE", endpointOrganizationDelete, deleteData) + + if err != nil { + return fmt.Errorf("error deleting organization: %w", err) + } + defer resp.Body.Close() + + if err := handleResponse(resp, "deleting organization"); err != nil { + return err + } + + log.Printf("[INFO] Successfully deleted organization with ID: %s", d.Id()) + d.SetId("") + return nil +} + +func buildOrganizationData(d *schema.ResourceData, orgID string) map[string]interface{} { + orgData := map[string]interface{}{ + "organization_id": orgID, + "organization_alias": d.Get("organization_alias").(string), + } + + for _, key := range []string{"metadata", "models", "max_budget", "budget_duration", "tpm_limit", "rpm_limit", "blocked"} { + if v, ok := d.GetOk(key); ok { + orgData[key] = v + } + } + + return orgData +} diff --git a/terraform/provider/litellm/resource_organization_member.go b/terraform/provider/litellm/resource_organization_member.go new file mode 100644 index 00000000000..e9abd26b9ec --- /dev/null +++ b/terraform/provider/litellm/resource_organization_member.go @@ -0,0 +1,126 @@ +package litellm + +import ( + "fmt" + "log" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func resourceLiteLLMOrganizationMember() *schema.Resource { + return &schema.Resource{ + Create: resourceLiteLLMOrganizationMemberCreate, + Read: resourceLiteLLMOrganizationMemberRead, + Update: resourceLiteLLMOrganizationMemberUpdate, + Delete: resourceLiteLLMOrganizationMemberDelete, + + Schema: map[string]*schema.Schema{ + "organization_id": { + Type: schema.TypeString, + Required: true, + }, + "user_id": { + Type: schema.TypeString, + Required: true, + }, + "user_email": { + Type: schema.TypeString, + Optional: true, + }, + "role": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + "org_admin", + "internal_user", + "internal_user_viewer", + }, false), + }, + }, + } +} + +func resourceLiteLLMOrganizationMemberCreate(d *schema.ResourceData, m interface{}) error { + client := m.(*Client) + + memberData := map[string]interface{}{ + "member": []map[string]interface{}{ + { + "role": d.Get("role").(string), + "user_id": d.Get("user_id").(string), + "user_email": d.Get("user_email").(string), + }, + }, + "organization_id": d.Get("organization_id").(string), + } + + log.Printf("[DEBUG] Create organization member request payload: %+v", memberData) + + resp, err := client.AddOrganizationMember(memberData) + if err != nil { + return fmt.Errorf("error creating organization member: %v", err) + } + + log.Printf("[DEBUG] Create organization member response: %+v", resp) + + // Set a composite ID since there's no specific member ID returned + d.SetId(fmt.Sprintf("%s:%s", d.Get("organization_id").(string), d.Get("user_id").(string))) + + log.Printf("[INFO] Organization member created with ID: %s", d.Id()) + + return resourceLiteLLMOrganizationMemberRead(d, m) +} + +func resourceLiteLLMOrganizationMemberRead(d *schema.ResourceData, m interface{}) error { + // There's no specific endpoint to read a single organization member + // We'll just return the data we have in the state + log.Printf("[INFO] Reading organization member with ID: %s", d.Id()) + return nil +} + +func resourceLiteLLMOrganizationMemberUpdate(d *schema.ResourceData, m interface{}) error { + client := m.(*Client) + + updateData := map[string]interface{}{ + "user_id": d.Get("user_id").(string), + "user_email": d.Get("user_email").(string), + "organization_id": d.Get("organization_id").(string), + "role": d.Get("role").(string), + } + + log.Printf("[DEBUG] Update organization member request payload: %+v", updateData) + + resp, err := client.UpdateOrganizationMember(updateData) + if err != nil { + return fmt.Errorf("error updating organization member: %v", err) + } + + log.Printf("[DEBUG] Update organization member response: %+v", resp) + + log.Printf("[INFO] Successfully updated organization member with ID: %s", d.Id()) + + return resourceLiteLLMOrganizationMemberRead(d, m) +} + +func resourceLiteLLMOrganizationMemberDelete(d *schema.ResourceData, m interface{}) error { + client := m.(*Client) + + deleteData := map[string]interface{}{ + "user_id": d.Get("user_id").(string), + "user_email": d.Get("user_email").(string), + "organization_id": d.Get("organization_id").(string), + } + + log.Printf("[DEBUG] Delete organization member request payload: %+v", deleteData) + + _, err := client.DeleteOrganizationMember(deleteData) + if err != nil { + return fmt.Errorf("error deleting organization member: %v", err) + } + + log.Printf("[INFO] Successfully deleted organization member with ID: %s", d.Id()) + + d.SetId("") + return nil +} diff --git a/terraform/provider/litellm/resource_organization_member_add.go b/terraform/provider/litellm/resource_organization_member_add.go new file mode 100644 index 00000000000..9bb4de09861 --- /dev/null +++ b/terraform/provider/litellm/resource_organization_member_add.go @@ -0,0 +1,260 @@ +package litellm + +import ( + "fmt" + "log" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func resourceLiteLLMOrganizationMemberAdd() *schema.Resource { + return &schema.Resource{ + Create: resourceLiteLLMOrganizationMemberAddCreate, + Read: resourceLiteLLMOrganizationMemberAddRead, + Update: resourceLiteLLMOrganizationMemberAddUpdate, + Delete: resourceLiteLLMOrganizationMemberAddDelete, + + Schema: map[string]*schema.Schema{ + "organization_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "member": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "user_id": { + Type: schema.TypeString, + Optional: true, + }, + "user_email": { + Type: schema.TypeString, + Optional: true, + }, + "role": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + "org_admin", + "internal_user", + "internal_user_viewer", + }, false), + }, + }, + }, + }, + }, + } +} + +func resourceLiteLLMOrganizationMemberAddCreate(d *schema.ResourceData, m interface{}) error { + client := m.(*Client) + + orgID := d.Get("organization_id").(string) + members := d.Get("member").(*schema.Set) + + // Convert members to the expected format + membersList := make([]map[string]interface{}, 0, members.Len()) + for _, member := range members.List() { + m := member.(map[string]interface{}) + memberData := map[string]interface{}{ + "role": m["role"].(string), + } + if userID, ok := m["user_id"].(string); ok && userID != "" { + memberData["user_id"] = userID + } + if userEmail, ok := m["user_email"].(string); ok && userEmail != "" { + memberData["user_email"] = userEmail + } + membersList = append(membersList, memberData) + } + + memberData := map[string]interface{}{ + "member": membersList, + "organization_id": orgID, + } + + log.Printf("[DEBUG] Create organization members request payload: %+v", memberData) + + resp, err := client.AddOrganizationMember(memberData) + if err != nil { + return fmt.Errorf("error adding organization members: %v", err) + } + + log.Printf("[DEBUG] Create organization members response: %+v", resp) + + // Set ID as organization_id since this resource manages all members for an organization + d.SetId(orgID) + + return resourceLiteLLMOrganizationMemberAddRead(d, m) +} + +func resourceLiteLLMOrganizationMemberAddRead(d *schema.ResourceData, m interface{}) error { + // The API doesn't provide a way to read specific organization members easily + // We'll maintain the state as is + return nil +} + +func resourceLiteLLMOrganizationMemberAddUpdate(d *schema.ResourceData, m interface{}) error { + client := m.(*Client) + orgID := d.Get("organization_id").(string) + + o, n := d.GetChange("member") + oldMembers := o.(*schema.Set) + newMembers := n.(*schema.Set) + + // Create maps for easier lookup by user identifier + oldMemberMap := make(map[string]map[string]interface{}) + newMemberMap := make(map[string]map[string]interface{}) + + // Build old member map using user_id or user_email as key + for _, member := range oldMembers.List() { + m := member.(map[string]interface{}) + key := getOrgMemberKey(m) + if key != "" { + oldMemberMap[key] = m + } + } + + // Build new member map using user_id or user_email as key + for _, member := range newMembers.List() { + m := member.(map[string]interface{}) + key := getOrgMemberKey(m) + if key != "" { + newMemberMap[key] = m + } + } + + // Find members to delete (in old but not in new) + for key, oldMember := range oldMemberMap { + if _, exists := newMemberMap[key]; !exists { + deleteData := map[string]interface{}{ + "organization_id": orgID, + } + if userID, ok := oldMember["user_id"].(string); ok && userID != "" { + deleteData["user_id"] = userID + } + if userEmail, ok := oldMember["user_email"].(string); ok && userEmail != "" { + deleteData["user_email"] = userEmail + } + + log.Printf("[DEBUG] Delete organization member request payload: %+v", deleteData) + + _, err := client.DeleteOrganizationMember(deleteData) + if err != nil { + return fmt.Errorf("error deleting organization member: %v", err) + } + } + } + + // Find members to update (exist in both but with different attributes) + for key, newMember := range newMemberMap { + if oldMember, exists := oldMemberMap[key]; exists { + // Check if member attributes have changed + if orgMemberAttributesChanged(oldMember, newMember) { + updateData := map[string]interface{}{ + "organization_id": orgID, + "role": newMember["role"].(string), + } + if userID, ok := newMember["user_id"].(string); ok && userID != "" { + updateData["user_id"] = userID + } + if userEmail, ok := newMember["user_email"].(string); ok && userEmail != "" { + updateData["user_email"] = userEmail + } + + log.Printf("[DEBUG] Update organization member request payload: %+v", updateData) + + _, err := client.UpdateOrganizationMember(updateData) + if err != nil { + return fmt.Errorf("error updating organization member: %v", err) + } + } + } + } + + // Find members to add (in new but not in old) + var membersToAdd []map[string]interface{} + for key, newMember := range newMemberMap { + if _, exists := oldMemberMap[key]; !exists { + memberData := map[string]interface{}{ + "role": newMember["role"].(string), + } + if userID, ok := newMember["user_id"].(string); ok && userID != "" { + memberData["user_id"] = userID + } + if userEmail, ok := newMember["user_email"].(string); ok && userEmail != "" { + memberData["user_email"] = userEmail + } + membersToAdd = append(membersToAdd, memberData) + } + } + + if len(membersToAdd) > 0 { + memberData := map[string]interface{}{ + "member": membersToAdd, + "organization_id": orgID, + } + + log.Printf("[DEBUG] Adding new organization members request payload: %+v", memberData) + + resp, err := client.AddOrganizationMember(memberData) + if err != nil { + return fmt.Errorf("error adding organization members: %v", err) + } + + log.Printf("[DEBUG] Add organization members response: %+v", resp) + } + + return resourceLiteLLMOrganizationMemberAddRead(d, m) +} + +// getOrgMemberKey returns a unique key for a member based on user_id or user_email +func getOrgMemberKey(member map[string]interface{}) string { + if userID, ok := member["user_id"].(string); ok && userID != "" { + return "id:" + userID + } + if userEmail, ok := member["user_email"].(string); ok && userEmail != "" { + return "email:" + userEmail + } + return "" +} + +// orgMemberAttributesChanged checks if member attributes have changed between old and new +func orgMemberAttributesChanged(oldMember, newMember map[string]interface{}) bool { + // Compare role + oldRole, _ := oldMember["role"].(string) + newRole, _ := newMember["role"].(string) + return oldRole != newRole +} + +func resourceLiteLLMOrganizationMemberAddDelete(d *schema.ResourceData, m interface{}) error { + client := m.(*Client) + orgID := d.Get("organization_id").(string) + members := d.Get("member").(*schema.Set) + + // Delete each member + for _, member := range members.List() { + m := member.(map[string]interface{}) + deleteData := map[string]interface{}{ + "organization_id": orgID, + } + if userID, ok := m["user_id"].(string); ok && userID != "" { + deleteData["user_id"] = userID + } + if userEmail, ok := m["user_email"].(string); ok && userEmail != "" { + deleteData["user_email"] = userEmail + } + + _, err := client.DeleteOrganizationMember(deleteData) + if err != nil { + return fmt.Errorf("error deleting organization member: %v", err) + } + } + + d.SetId("") + return nil +} diff --git a/terraform/provider/litellm/resource_organization_member_add_test.go b/terraform/provider/litellm/resource_organization_member_add_test.go new file mode 100644 index 00000000000..a26c9ed5812 --- /dev/null +++ b/terraform/provider/litellm/resource_organization_member_add_test.go @@ -0,0 +1,74 @@ +package litellm + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccLiteLLMOrganizationMemberAdd_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccLiteLLMOrganizationMemberAddConfig("test-org-bulk", "bulk-user-1", "bulk-user-2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckLiteLLMOrganizationMemberAddExists("litellm_organization_member_add.test_members"), + resource.TestCheckResourceAttr("litellm_organization_member_add.test_members", "member.#", "2"), + ), + }, + }, + }) +} + +func testAccCheckLiteLLMOrganizationMemberAddExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + return nil + } +} + +func testAccLiteLLMOrganizationMemberAddConfig(orgAlias, user1, user2 string) string { + return fmt.Sprintf(` +resource "litellm_model" "test_model" { + model_name = "gpt-3.5-turbo" + custom_llm_provider = "openai" + base_model = "gpt-3.5-turbo" +} + +resource "litellm_organization" "test_org_bulk" { + organization_alias = "%s" + max_budget = 100.0 + budget_duration = "30d" + + depends_on = [litellm_model.test_model] +} + +resource "litellm_organization_member_add" "test_members" { + organization_id = litellm_organization.test_org_bulk.id + + member { + user_id = "%s" + user_email = "%s@example.com" + role = "org_admin" + } + + member { + user_id = "%s" + user_email = "%s@example.com" + role = "internal_user" + } +} +`, orgAlias, user1, user1, user2, user2) +} diff --git a/terraform/provider/litellm/resource_organization_member_test.go b/terraform/provider/litellm/resource_organization_member_test.go new file mode 100644 index 00000000000..8818ed81052 --- /dev/null +++ b/terraform/provider/litellm/resource_organization_member_test.go @@ -0,0 +1,66 @@ +package litellm + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccLiteLLMOrganizationMember_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccLiteLLMOrganizationMemberConfig("test-org-member", "test-user-1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckLiteLLMOrganizationMemberExists("litellm_organization_member.test_member"), + resource.TestCheckResourceAttr("litellm_organization_member.test_member", "role", "org_admin"), + resource.TestCheckResourceAttr("litellm_organization_member.test_member", "user_id", "test-user-1"), + ), + }, + }, + }) +} + +func testAccCheckLiteLLMOrganizationMemberExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + return nil + } +} + +func testAccLiteLLMOrganizationMemberConfig(orgAlias, userID string) string { + return fmt.Sprintf(` +resource "litellm_model" "test_model" { + model_name = "gpt-3.5-turbo" + custom_llm_provider = "openai" + base_model = "gpt-3.5-turbo" +} + +resource "litellm_organization" "test_org" { + organization_alias = "%s" + max_budget = 100.0 + budget_duration = "30d" + + depends_on = [litellm_model.test_model] +} + +resource "litellm_organization_member" "test_member" { + organization_id = litellm_organization.test_org.id + user_id = "%s" + user_email = "%s@example.com" + role = "org_admin" +} +`, orgAlias, userID, userID) +} diff --git a/terraform/provider/litellm/resource_organization_test.go b/terraform/provider/litellm/resource_organization_test.go new file mode 100644 index 00000000000..2a2c32438fe --- /dev/null +++ b/terraform/provider/litellm/resource_organization_test.go @@ -0,0 +1,59 @@ +package litellm + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccLiteLLMOrganization_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccLiteLLMOrganizationConfig("test-org", "test-org-alias"), + Check: resource.ComposeTestCheckFunc( + testAccCheckLiteLLMOrganizationExists("litellm_organization.test"), + resource.TestCheckResourceAttr("litellm_organization.test", "organization_alias", "test-org-alias"), + resource.TestCheckResourceAttr("litellm_organization.test", "max_budget", "100"), + ), + }, + }, + }) +} + +func testAccCheckLiteLLMOrganizationExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + return nil + } +} + +func testAccLiteLLMOrganizationConfig(name, alias string) string { + return fmt.Sprintf(` +resource "litellm_model" "test_model" { + model_name = "gpt-3.5-turbo" + custom_llm_provider = "openai" + base_model = "gpt-3.5-turbo" +} + +resource "litellm_organization" "test" { + organization_alias = "%s" + max_budget = 100.0 + budget_duration = "30d" + + depends_on = [litellm_model.test_model] +} +`, alias) +} diff --git a/terraform/provider/litellm/resource_team.go b/terraform/provider/litellm/resource_team.go new file mode 100644 index 00000000000..88e0dcd4811 --- /dev/null +++ b/terraform/provider/litellm/resource_team.go @@ -0,0 +1,311 @@ +package litellm + +import ( + "encoding/json" + "fmt" + "io" + "log" + "net/http" + + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +const ( + endpointTeamNew = "/team/new" + endpointTeamInfo = "/team/info" + endpointTeamUpdate = "/team/update" + endpointTeamDelete = "/team/delete" + endpointTeamPermissionsList = "/team/permissions_list" + endpointTeamPermissionsUpdate = "/team/permissions_update" +) + +func ResourceLiteLLMTeam() *schema.Resource { + return &schema.Resource{ + Create: resourceLiteLLMTeamCreate, + Read: resourceLiteLLMTeamRead, + Update: resourceLiteLLMTeamUpdate, + Delete: resourceLiteLLMTeamDelete, + + Schema: map[string]*schema.Schema{ + "team_alias": { + Type: schema.TypeString, + Required: true, + }, + "organization_id": { + Type: schema.TypeString, + Optional: true, + }, + "metadata": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "tpm_limit": { + Type: schema.TypeInt, + Optional: true, + }, + "rpm_limit": { + Type: schema.TypeInt, + Optional: true, + }, + "max_budget": { + Type: schema.TypeFloat, + Optional: true, + }, + "budget_duration": { + Type: schema.TypeString, + Optional: true, + }, + "models": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "blocked": { + Type: schema.TypeBool, + Optional: true, + }, + "team_member_permissions": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Description: "List of permissions granted to team members", + }, + }, + } +} + +func resourceLiteLLMTeamCreate(d *schema.ResourceData, m interface{}) error { + client := m.(*Client) + + teamID := uuid.New().String() + teamData := buildTeamData(d, teamID) + + log.Printf("[DEBUG] Create team request payload: %+v", teamData) + + resp, err := MakeRequest(client, "POST", endpointTeamNew, teamData) + if err != nil { + return fmt.Errorf("error creating team: %w", err) + } + defer resp.Body.Close() + + if err := handleResponse(resp, "creating team"); err != nil { + return err + } + + d.SetId(teamID) + log.Printf("[INFO] Team created with ID: %s", teamID) + + return resourceLiteLLMTeamRead(d, m) +} + +func resourceLiteLLMTeamRead(d *schema.ResourceData, m interface{}) error { + client := m.(*Client) + + log.Printf("[INFO] Reading team with ID: %s", d.Id()) + + resp, err := MakeRequest(client, "GET", fmt.Sprintf("%s?team_id=%s", endpointTeamInfo, d.Id()), nil) + if err != nil { + return fmt.Errorf("error reading team: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusNotFound { + log.Printf("[WARN] Team with ID %s not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + var teamResp TeamResponse + if err := json.NewDecoder(resp.Body).Decode(&teamResp); err != nil { + return fmt.Errorf("error decoding team info response: %w", err) + } + + // Update the state with values from the response or fall back to the data passed in during creation + d.Set("team_alias", GetStringValue(teamResp.TeamAlias, d.Get("team_alias").(string))) + d.Set("organization_id", GetStringValue(teamResp.OrganizationID, d.Get("organization_id").(string))) + + // Handle metadata separately as it's a map + if teamResp.Metadata != nil { + d.Set("metadata", teamResp.Metadata) + } else { + d.Set("metadata", d.Get("metadata")) + } + + if teamResp.TPMLimit != nil { + d.Set("tpm_limit", *teamResp.TPMLimit) + } + if teamResp.RPMLimit != nil { + d.Set("rpm_limit", *teamResp.RPMLimit) + } + if teamResp.MaxBudget != nil { + d.Set("max_budget", *teamResp.MaxBudget) + } + d.Set("budget_duration", GetStringValue(teamResp.BudgetDuration, d.Get("budget_duration").(string))) + + // Handle models separately as it's a list + if teamResp.Models != nil { + d.Set("models", teamResp.Models) + } else { + d.Set("models", d.Get("models")) + } + + d.Set("blocked", GetBoolValue(teamResp.Blocked, d.Get("blocked").(bool))) + + // Explicitly fetch the current permissions from the API + permResp, err := getTeamPermissions(client, d.Id()) + if err != nil { + log.Printf("[WARN] Error fetching team permissions: %s", err) + // Fall back to the permissions from the team info response + if teamResp.TeamMemberPermissions != nil { + d.Set("team_member_permissions", teamResp.TeamMemberPermissions) + } else { + d.Set("team_member_permissions", d.Get("team_member_permissions")) + } + } else { + // Use the permissions from the permissions_list endpoint + log.Printf("[DEBUG] Team permissions from API: %+v", permResp.TeamMemberPermissions) + d.Set("team_member_permissions", permResp.TeamMemberPermissions) + } + + log.Printf("[INFO] Successfully read team with ID: %s", d.Id()) + return nil +} + +func resourceLiteLLMTeamUpdate(d *schema.ResourceData, m interface{}) error { + client := m.(*Client) + + teamData := buildTeamData(d, d.Id()) + log.Printf("[DEBUG] Update team request payload: %+v", teamData) + + resp, err := MakeRequest(client, "POST", endpointTeamUpdate, teamData) + if err != nil { + return fmt.Errorf("error updating team: %w", err) + } + defer resp.Body.Close() + + if err := handleResponse(resp, "updating team"); err != nil { + return err + } + + // Check if team_member_permissions have changed and explicitly update them + if d.HasChange("team_member_permissions") { + _, newPerms := d.GetChange("team_member_permissions") + if newPerms != nil { + // Convert interface{} to []string + var permissions []string + for _, perm := range newPerms.([]interface{}) { + permissions = append(permissions, perm.(string)) + } + + log.Printf("[DEBUG] Explicitly updating team permissions: %+v", permissions) + if err := updateTeamPermissions(client, d.Id(), permissions); err != nil { + return fmt.Errorf("error updating team permissions: %w", err) + } + } + } + + log.Printf("[INFO] Successfully updated team with ID: %s", d.Id()) + return resourceLiteLLMTeamRead(d, m) +} + +func resourceLiteLLMTeamDelete(d *schema.ResourceData, m interface{}) error { + client := m.(*Client) + + log.Printf("[INFO] Deleting team with ID: %s", d.Id()) + + deleteData := map[string]interface{}{ + "team_ids": []string{d.Id()}, + } + + resp, err := MakeRequest(client, "POST", endpointTeamDelete, deleteData) + if err != nil { + return fmt.Errorf("error deleting team: %w", err) + } + defer resp.Body.Close() + + if err := handleResponse(resp, "deleting team"); err != nil { + return err + } + + log.Printf("[INFO] Successfully deleted team with ID: %s", d.Id()) + d.SetId("") + return nil +} + +func buildTeamData(d *schema.ResourceData, teamID string) map[string]interface{} { + teamData := map[string]interface{}{ + "team_id": teamID, + "team_alias": d.Get("team_alias").(string), + } + + for _, key := range []string{"organization_id", "metadata", "tpm_limit", "rpm_limit", "max_budget", "budget_duration", "models", "blocked", "team_member_permissions"} { + if v, ok := d.GetOk(key); ok { + teamData[key] = v + } + } + + return teamData +} + +func handleResponse(resp *http.Response, action string) error { + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + return fmt.Errorf("error %s: %s - %s", action, resp.Status, string(body)) + } + return nil +} + +// TeamPermissionsResponse represents a response from the API containing team permissions information. +type TeamPermissionsResponse struct { + TeamID string `json:"team_id"` + TeamMemberPermissions []string `json:"team_member_permissions"` + AllAvailablePermissions []string `json:"all_available_permissions"` +} + +// getTeamPermissions retrieves the current permissions and available permissions for a team. +func getTeamPermissions(client *Client, teamID string) (*TeamPermissionsResponse, error) { + log.Printf("[INFO] Getting permissions for team with ID: %s", teamID) + + resp, err := MakeRequest(client, "GET", fmt.Sprintf("%s?team_id=%s", endpointTeamPermissionsList, teamID), nil) + if err != nil { + return nil, fmt.Errorf("error getting team permissions: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("error getting team permissions: %s - %s", resp.Status, string(body)) + } + + var permResp TeamPermissionsResponse + if err := json.NewDecoder(resp.Body).Decode(&permResp); err != nil { + return nil, fmt.Errorf("error decoding team permissions response: %w", err) + } + + return &permResp, nil +} + +// updateTeamPermissions updates the permissions for a team. +func updateTeamPermissions(client *Client, teamID string, permissions []string) error { + log.Printf("[INFO] Updating permissions for team with ID: %s", teamID) + + permData := map[string]interface{}{ + "team_id": teamID, + "team_member_permissions": permissions, + } + + resp, err := MakeRequest(client, "POST", endpointTeamPermissionsUpdate, permData) + if err != nil { + return fmt.Errorf("error updating team permissions: %w", err) + } + defer resp.Body.Close() + + if err := handleResponse(resp, "updating team permissions"); err != nil { + return err + } + + log.Printf("[INFO] Successfully updated permissions for team with ID: %s", teamID) + return nil +} diff --git a/terraform/provider/litellm/resource_team_member.go b/terraform/provider/litellm/resource_team_member.go new file mode 100644 index 00000000000..84db07239fd --- /dev/null +++ b/terraform/provider/litellm/resource_team_member.go @@ -0,0 +1,146 @@ +package litellm + +import ( + "fmt" + "log" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func resourceLiteLLMTeamMember() *schema.Resource { + return &schema.Resource{ + Create: resourceLiteLLMTeamMemberCreate, + Read: resourceLiteLLMTeamMemberRead, + Update: resourceLiteLLMTeamMemberUpdate, + Delete: resourceLiteLLMTeamMemberDelete, + + Schema: map[string]*schema.Schema{ + "team_id": { + Type: schema.TypeString, + Required: true, + }, + "user_id": { + Type: schema.TypeString, + Required: true, + }, + "user_email": { + Type: schema.TypeString, + Required: true, + }, + "role": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + "org_admin", + "internal_user", + "internal_user_viewer", + "admin", + "user", + }, false), + }, + "max_budget_in_team": { + Type: schema.TypeFloat, + Optional: true, + }, + }, + } +} + +func resourceLiteLLMTeamMemberCreate(d *schema.ResourceData, m interface{}) error { + client := m.(*Client) + + memberData := map[string]interface{}{ + "member": []map[string]interface{}{ + { + "role": d.Get("role").(string), + "user_id": d.Get("user_id").(string), + "user_email": d.Get("user_email").(string), + }, + }, + "team_id": d.Get("team_id").(string), + "max_budget_in_team": d.Get("max_budget_in_team").(float64), + } + + log.Printf("[DEBUG] Create team member request payload: %+v", memberData) + + resp, err := MakeRequest(client, "POST", "/team/member_add", memberData) + if err != nil { + return fmt.Errorf("error creating team member: %v", err) + } + defer resp.Body.Close() + + if err := handleResponse(resp, "creating team member"); err != nil { + return err + } + + // Set a composite ID since there's no specific member ID returned + d.SetId(fmt.Sprintf("%s:%s", d.Get("team_id").(string), d.Get("user_id").(string))) + + log.Printf("[INFO] Team member created with ID: %s", d.Id()) + + return resourceLiteLLMTeamMemberRead(d, m) +} + +func resourceLiteLLMTeamMemberRead(d *schema.ResourceData, m interface{}) error { + // There's no specific endpoint to read a single team member + // We might need to read the entire team and find the member + // For now, we'll just return the data we have in the state + log.Printf("[INFO] Reading team member with ID: %s", d.Id()) + return nil +} + +func resourceLiteLLMTeamMemberUpdate(d *schema.ResourceData, m interface{}) error { + client := m.(*Client) + + updateData := map[string]interface{}{ + "user_id": d.Get("user_id").(string), + "user_email": d.Get("user_email").(string), + "team_id": d.Get("team_id").(string), + "role": d.Get("role").(string), + "max_budget_in_team": d.Get("max_budget_in_team").(float64), + } + + log.Printf("[DEBUG] Update team member request payload: %+v", updateData) + + resp, err := MakeRequest(client, "POST", "/team/member_update", updateData) + if err != nil { + return fmt.Errorf("error updating team member: %v", err) + } + defer resp.Body.Close() + + if err := handleResponse(resp, "updating team member"); err != nil { + return err + } + + log.Printf("[INFO] Successfully updated team member with ID: %s", d.Id()) + + return resourceLiteLLMTeamMemberRead(d, m) +} + +func resourceLiteLLMTeamMemberDelete(d *schema.ResourceData, m interface{}) error { + client := m.(*Client) + + deleteData := map[string]interface{}{ + "user_id": d.Get("user_id").(string), + "user_email": d.Get("user_email").(string), + "team_id": d.Get("team_id").(string), + } + + log.Printf("[DEBUG] Delete team member request payload: %+v", deleteData) + + resp, err := MakeRequest(client, "POST", "/team/member_delete", deleteData) + if err != nil { + return fmt.Errorf("error deleting team member: %v", err) + } + defer resp.Body.Close() + + if err := handleResponse(resp, "deleting team member"); err != nil { + return err + } + + log.Printf("[INFO] Successfully deleted team member with ID: %s", d.Id()) + + d.SetId("") + return nil +} diff --git a/terraform/provider/litellm/resource_team_member_add.go b/terraform/provider/litellm/resource_team_member_add.go new file mode 100644 index 00000000000..da5c7a6ebd7 --- /dev/null +++ b/terraform/provider/litellm/resource_team_member_add.go @@ -0,0 +1,342 @@ +package litellm + +import ( + "fmt" + "log" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func resourceLiteLLMTeamMemberAdd() *schema.Resource { + return &schema.Resource{ + Create: resourceLiteLLMTeamMemberAddCreate, + Read: resourceLiteLLMTeamMemberAddRead, + Update: resourceLiteLLMTeamMemberAddUpdate, + Delete: resourceLiteLLMTeamMemberAddDelete, + + Schema: map[string]*schema.Schema{ + "team_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "member": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "user_id": { + Type: schema.TypeString, + Optional: true, + }, + "user_email": { + Type: schema.TypeString, + Optional: true, + }, + "role": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + "admin", + "user", + }, false), + }, + }, + }, + }, + "max_budget_in_team": { + Type: schema.TypeFloat, + Optional: true, + }, + }, + } +} + +func resourceLiteLLMTeamMemberAddCreate(d *schema.ResourceData, m interface{}) error { + client := m.(*Client) + + teamID := d.Get("team_id").(string) + members := d.Get("member").(*schema.Set) + maxBudget := d.Get("max_budget_in_team").(float64) + + // Convert members to the expected format + membersList := make([]map[string]interface{}, 0, members.Len()) + for _, member := range members.List() { + m := member.(map[string]interface{}) + memberData := map[string]interface{}{ + "role": m["role"].(string), + } + if userID, ok := m["user_id"].(string); ok && userID != "" { + memberData["user_id"] = userID + } + if userEmail, ok := m["user_email"].(string); ok && userEmail != "" { + memberData["user_email"] = userEmail + } + membersList = append(membersList, memberData) + } + + memberData := map[string]interface{}{ + "member": membersList, + "team_id": teamID, + "max_budget_in_team": maxBudget, + } + + log.Printf("[DEBUG] Create team members request payload: %+v", memberData) + + resp, err := MakeRequest(client, "POST", "/team/member_add", memberData) + if err != nil { + return fmt.Errorf("error adding team members: %v", err) + } + defer resp.Body.Close() + + if err := handleResponse(resp, "adding team members"); err != nil { + return err + } + + // Set ID as team_id since this resource manages all members for a team + d.SetId(teamID) + + return resourceLiteLLMTeamMemberAddRead(d, m) +} + +func resourceLiteLLMTeamMemberAddRead(d *schema.ResourceData, m interface{}) error { + // The API doesn't provide a way to read specific team members + // We'll maintain the state as is + return nil +} + +func resourceLiteLLMTeamMemberAddUpdate(d *schema.ResourceData, m interface{}) error { + client := m.(*Client) + teamID := d.Get("team_id").(string) + maxBudget := d.Get("max_budget_in_team").(float64) + + o, n := d.GetChange("member") + oldMembers := o.(*schema.Set) + newMembers := n.(*schema.Set) + + // Create maps for easier lookup by user identifier + oldMemberMap := make(map[string]map[string]interface{}) + newMemberMap := make(map[string]map[string]interface{}) + + // Build old member map using user_id or user_email as key + for _, member := range oldMembers.List() { + m := member.(map[string]interface{}) + key := getMemberKey(m) + if key != "" { + oldMemberMap[key] = m + } + } + + // Build new member map using user_id or user_email as key + for _, member := range newMembers.List() { + m := member.(map[string]interface{}) + key := getMemberKey(m) + if key != "" { + newMemberMap[key] = m + } + } + + // Track which members have been updated to avoid duplicates + updatedMembers := make(map[string]bool) + + // Check if max_budget_in_team has changed + if d.HasChange("max_budget_in_team") { + log.Printf("[DEBUG] max_budget_in_team changed, updating all existing members with new budget: %f", maxBudget) + + // Update ALL existing members with the new budget + for key, newMember := range newMemberMap { + if _, exists := oldMemberMap[key]; exists { + updateData := map[string]interface{}{ + "team_id": teamID, + "role": newMember["role"].(string), + "max_budget_in_team": maxBudget, + } + if userID, ok := newMember["user_id"].(string); ok && userID != "" { + updateData["user_id"] = userID + } + if userEmail, ok := newMember["user_email"].(string); ok && userEmail != "" { + updateData["user_email"] = userEmail + } + + log.Printf("[DEBUG] Update team member budget request payload: %+v", updateData) + + resp, err := MakeRequest(client, "POST", "/team/member_update", updateData) + if err != nil { + return fmt.Errorf("error updating team member budget: %v", err) + } + defer resp.Body.Close() + + if err := handleResponse(resp, "updating team member budget"); err != nil { + return err + } + + // Mark this member as updated + updatedMembers[key] = true + } + } + } + + // Find members to delete (in old but not in new) + for key, oldMember := range oldMemberMap { + if _, exists := newMemberMap[key]; !exists { + deleteData := map[string]interface{}{ + "team_id": teamID, + } + if userID, ok := oldMember["user_id"].(string); ok && userID != "" { + deleteData["user_id"] = userID + } + if userEmail, ok := oldMember["user_email"].(string); ok && userEmail != "" { + deleteData["user_email"] = userEmail + } + + log.Printf("[DEBUG] Delete team member request payload: %+v", deleteData) + + resp, err := MakeRequest(client, "POST", "/team/member_delete", deleteData) + if err != nil { + return fmt.Errorf("error deleting team member: %v", err) + } + defer resp.Body.Close() + + if err := handleResponse(resp, "deleting team member"); err != nil { + return err + } + } + } + + // Find members to update (exist in both but with different attributes) + // Skip members that were already updated due to budget change + for key, newMember := range newMemberMap { + if oldMember, exists := oldMemberMap[key]; exists { + // Skip if already updated due to budget change + if updatedMembers[key] { + continue + } + + // Check if member attributes have changed + if memberAttributesChanged(oldMember, newMember) { + updateData := map[string]interface{}{ + "team_id": teamID, + "role": newMember["role"].(string), + "max_budget_in_team": maxBudget, + } + if userID, ok := newMember["user_id"].(string); ok && userID != "" { + updateData["user_id"] = userID + } + if userEmail, ok := newMember["user_email"].(string); ok && userEmail != "" { + updateData["user_email"] = userEmail + } + + log.Printf("[DEBUG] Update team member request payload: %+v", updateData) + + resp, err := MakeRequest(client, "POST", "/team/member_update", updateData) + if err != nil { + return fmt.Errorf("error updating team member: %v", err) + } + defer resp.Body.Close() + + if err := handleResponse(resp, "updating team member"); err != nil { + return err + } + } + } + } + + // Find members to add (in new but not in old) + var membersToAdd []map[string]interface{} + for key, newMember := range newMemberMap { + if _, exists := oldMemberMap[key]; !exists { + memberData := map[string]interface{}{ + "role": newMember["role"].(string), + } + if userID, ok := newMember["user_id"].(string); ok && userID != "" { + memberData["user_id"] = userID + } + if userEmail, ok := newMember["user_email"].(string); ok && userEmail != "" { + memberData["user_email"] = userEmail + } + membersToAdd = append(membersToAdd, memberData) + } + } + + if len(membersToAdd) > 0 { + memberData := map[string]interface{}{ + "member": membersToAdd, + "team_id": teamID, + "max_budget_in_team": maxBudget, + } + + log.Printf("[DEBUG] Adding new team members request payload: %+v", memberData) + + resp, err := MakeRequest(client, "POST", "/team/member_add", memberData) + if err != nil { + return fmt.Errorf("error adding team members: %v", err) + } + defer resp.Body.Close() + + if err := handleResponse(resp, "adding team members"); err != nil { + return err + } + } + + return resourceLiteLLMTeamMemberAddRead(d, m) +} + +// getMemberKey returns a unique key for a member based on user_id or user_email +func getMemberKey(member map[string]interface{}) string { + if userID, ok := member["user_id"].(string); ok && userID != "" { + return "id:" + userID + } + if userEmail, ok := member["user_email"].(string); ok && userEmail != "" { + return "email:" + userEmail + } + return "" +} + +// memberAttributesChanged checks if member attributes have changed between old and new +func memberAttributesChanged(oldMember, newMember map[string]interface{}) bool { + // Compare role + oldRole, _ := oldMember["role"].(string) + newRole, _ := newMember["role"].(string) + if oldRole != newRole { + return true + } + + // Note: max_budget_in_team is handled at the resource level, not per member + // so we don't need to compare it here + + return false +} + +func resourceLiteLLMTeamMemberAddDelete(d *schema.ResourceData, m interface{}) error { + client := m.(*Client) + teamID := d.Get("team_id").(string) + members := d.Get("member").(*schema.Set) + + // Delete each member + for _, member := range members.List() { + m := member.(map[string]interface{}) + deleteData := map[string]interface{}{ + "team_id": teamID, + } + if userID, ok := m["user_id"].(string); ok && userID != "" { + deleteData["user_id"] = userID + } + if userEmail, ok := m["user_email"].(string); ok && userEmail != "" { + deleteData["user_email"] = userEmail + } + + resp, err := MakeRequest(client, "POST", "/team/member_delete", deleteData) + if err != nil { + return fmt.Errorf("error deleting team member: %v", err) + } + defer resp.Body.Close() + + if err := handleResponse(resp, "deleting team member"); err != nil { + return err + } + } + + d.SetId("") + return nil +} diff --git a/terraform/provider/litellm/resource_team_member_test.go b/terraform/provider/litellm/resource_team_member_test.go new file mode 100644 index 00000000000..156c4b3abfa --- /dev/null +++ b/terraform/provider/litellm/resource_team_member_test.go @@ -0,0 +1,44 @@ +package litellm + +import ( + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func TestTeamMemberUpdateSendsRole(t *testing.T) { + var captured map[string]interface{} + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + body, _ := io.ReadAll(r.Body) + json.Unmarshal(body, &captured) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{}`)) + })) + defer srv.Close() + + client := NewClient(srv.URL, "test-key", true) + d := schema.TestResourceDataRaw(t, resourceLiteLLMTeamMember().Schema, map[string]interface{}{ + "team_id": "team-1", + "user_id": "user-1", + "user_email": "user@example.com", + "role": "user", + }) + d.SetId("team-1:user-1") + + if err := resourceLiteLLMTeamMemberUpdate(d, client); err != nil { + t.Fatalf("update failed: %v", err) + } + + role, ok := captured["role"] + if !ok { + t.Fatalf("update payload missing role field: %v", captured) + } + if role != "user" { + t.Fatalf("update payload sent role %v, want user", role) + } +} diff --git a/terraform/provider/litellm/resource_vector_store.go b/terraform/provider/litellm/resource_vector_store.go new file mode 100644 index 00000000000..f77ba18c6d4 --- /dev/null +++ b/terraform/provider/litellm/resource_vector_store.go @@ -0,0 +1,65 @@ +package litellm + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceLiteLLMVectorStore() *schema.Resource { + return &schema.Resource{ + Create: resourceLiteLLMVectorStoreCreate, + Read: resourceLiteLLMVectorStoreRead, + Update: resourceLiteLLMVectorStoreUpdate, + Delete: resourceLiteLLMVectorStoreDelete, + + Schema: map[string]*schema.Schema{ + "vector_store_id": { + Type: schema.TypeString, + Computed: true, + Description: "Unique identifier for the vector store", + }, + "vector_store_name": { + Type: schema.TypeString, + Required: true, + Description: "Name of the vector store", + }, + "custom_llm_provider": { + Type: schema.TypeString, + Required: true, + Description: "Custom LLM provider for the vector store", + }, + "vector_store_description": { + Type: schema.TypeString, + Optional: true, + Description: "Description of the vector store", + }, + "vector_store_metadata": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Description: "Metadata associated with the vector store", + }, + "litellm_credential_name": { + Type: schema.TypeString, + Optional: true, + Description: "Name of the LiteLLM credential to use", + }, + "litellm_params": { + Type: schema.TypeMap, + Optional: true, + Sensitive: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Description: "Additional LiteLLM parameters", + }, + "created_at": { + Type: schema.TypeString, + Computed: true, + Description: "Timestamp when the vector store was created", + }, + "updated_at": { + Type: schema.TypeString, + Computed: true, + Description: "Timestamp when the vector store was last updated", + }, + }, + } +} diff --git a/terraform/provider/litellm/resource_vector_store_crud.go b/terraform/provider/litellm/resource_vector_store_crud.go new file mode 100644 index 00000000000..b05017f7125 --- /dev/null +++ b/terraform/provider/litellm/resource_vector_store_crud.go @@ -0,0 +1,168 @@ +package litellm + +import ( + "fmt" + "net/http" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceLiteLLMVectorStoreCreate(d *schema.ResourceData, m interface{}) error { + client := m.(*Client) + + vectorStoreName := d.Get("vector_store_name").(string) + customLLMProvider := d.Get("custom_llm_provider").(string) + vectorStoreDescription := d.Get("vector_store_description").(string) + vectorStoreMetadata := d.Get("vector_store_metadata").(map[string]interface{}) + litellmCredentialName := d.Get("litellm_credential_name").(string) + litellmParams := d.Get("litellm_params").(map[string]interface{}) + + // Convert metadata to map[string]interface{} for JSON + metadataMap := make(map[string]interface{}) + for k, v := range vectorStoreMetadata { + metadataMap[k] = v + } + + // Convert litellm_params to map[string]interface{} for JSON + paramsMap := make(map[string]interface{}) + for k, v := range litellmParams { + paramsMap[k] = v + } + + vectorStoreRequest := VectorStoreRequest{ + CustomLLMProvider: customLLMProvider, + VectorStoreName: vectorStoreName, + VectorStoreDescription: vectorStoreDescription, + VectorStoreMetadata: metadataMap, + LiteLLMCredentialName: litellmCredentialName, + LiteLLMParams: paramsMap, + } + + resp, err := MakeRequest(client, "POST", "/vector_store/new", vectorStoreRequest) + if err != nil { + return fmt.Errorf("failed to create vector store: %w", err) + } + defer resp.Body.Close() + + err = handleVectorStoreAPIResponse(resp, nil, client) + if err != nil { + return fmt.Errorf("failed to create vector store: %w", err) + } + + // Set the resource ID to the vector store name for now + // We'll update this after reading the response to get the actual ID + d.SetId(vectorStoreName) + + return resourceLiteLLMVectorStoreRead(d, m) +} + +func resourceLiteLLMVectorStoreRead(d *schema.ResourceData, m interface{}) error { + client := m.(*Client) + vectorStoreID := d.Id() + + // Use the info endpoint to get vector store details + infoRequest := VectorStoreInfoRequest{ + VectorStoreID: vectorStoreID, + } + + resp, err := MakeRequest(client, "POST", "/vector_store/info", infoRequest) + if err != nil { + return fmt.Errorf("failed to read vector store: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusNotFound { + d.SetId("") + return nil + } + + var vectorStoreResp VectorStoreResponse + err = handleVectorStoreAPIResponse(resp, &vectorStoreResp, client) + if err != nil { + if err.Error() == "vector_store_not_found" { + d.SetId("") + return nil + } + return fmt.Errorf("failed to read vector store: %w", err) + } + + // Update the resource ID to the actual vector store ID from the response + if vectorStoreResp.VectorStoreID != "" { + d.SetId(vectorStoreResp.VectorStoreID) + } + + d.Set("vector_store_id", vectorStoreResp.VectorStoreID) + d.Set("vector_store_name", vectorStoreResp.VectorStoreName) + d.Set("custom_llm_provider", vectorStoreResp.CustomLLMProvider) + d.Set("vector_store_description", vectorStoreResp.VectorStoreDescription) + d.Set("vector_store_metadata", vectorStoreResp.VectorStoreMetadata) + d.Set("litellm_credential_name", vectorStoreResp.LiteLLMCredentialName) + d.Set("created_at", vectorStoreResp.CreatedAt) + d.Set("updated_at", vectorStoreResp.UpdatedAt) + + return nil +} + +func resourceLiteLLMVectorStoreUpdate(d *schema.ResourceData, m interface{}) error { + client := m.(*Client) + vectorStoreID := d.Id() + + vectorStoreName := d.Get("vector_store_name").(string) + customLLMProvider := d.Get("custom_llm_provider").(string) + vectorStoreDescription := d.Get("vector_store_description").(string) + vectorStoreMetadata := d.Get("vector_store_metadata").(map[string]interface{}) + + // Convert metadata to map[string]interface{} for JSON + metadataMap := make(map[string]interface{}) + for k, v := range vectorStoreMetadata { + metadataMap[k] = v + } + + vectorStoreRequest := VectorStoreRequest{ + VectorStoreID: vectorStoreID, + CustomLLMProvider: customLLMProvider, + VectorStoreName: vectorStoreName, + VectorStoreDescription: vectorStoreDescription, + VectorStoreMetadata: metadataMap, + } + + resp, err := MakeRequest(client, "POST", "/vector_store/update", vectorStoreRequest) + if err != nil { + return fmt.Errorf("failed to update vector store: %w", err) + } + defer resp.Body.Close() + + err = handleVectorStoreAPIResponse(resp, nil, client) + if err != nil { + return fmt.Errorf("failed to update vector store: %w", err) + } + + return resourceLiteLLMVectorStoreRead(d, m) +} + +func resourceLiteLLMVectorStoreDelete(d *schema.ResourceData, m interface{}) error { + client := m.(*Client) + vectorStoreID := d.Id() + + deleteRequest := VectorStoreDeleteRequest{ + VectorStoreID: vectorStoreID, + } + + resp, err := MakeRequest(client, "POST", "/vector_store/delete", deleteRequest) + if err != nil { + return fmt.Errorf("failed to delete vector store: %w", err) + } + defer resp.Body.Close() + + err = handleVectorStoreAPIResponse(resp, nil, client) + if err != nil { + if err.Error() == "vector_store_not_found" { + d.SetId("") + return nil + } + return fmt.Errorf("failed to delete vector store: %w", err) + } + + d.SetId("") + return nil +} diff --git a/terraform/provider/litellm/resource_vector_store_crud_test.go b/terraform/provider/litellm/resource_vector_store_crud_test.go new file mode 100644 index 00000000000..485ec54346d --- /dev/null +++ b/terraform/provider/litellm/resource_vector_store_crud_test.go @@ -0,0 +1,55 @@ +package litellm + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func TestVectorStoreReadDoesNotPersistServerLitellmParams(t *testing.T) { + resp := VectorStoreResponse{ + VectorStoreID: "vs-123", + VectorStoreName: "kb", + CustomLLMProvider: "openai", + LiteLLMParams: map[string]interface{}{ + "api_key": "sk-from-server", + "api_base": "https://upstream.example.com", + }, + } + body, _ := json.Marshal(resp) + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write(body) + })) + defer srv.Close() + + client := NewClient(srv.URL, "test-key", true) + d := schema.TestResourceDataRaw(t, resourceLiteLLMVectorStore().Schema, map[string]interface{}{ + "vector_store_name": "kb", + "custom_llm_provider": "openai", + "litellm_params": map[string]interface{}{ + "vector_store_id": "vs-123", + }, + }) + d.SetId("vs-123") + + if err := resourceLiteLLMVectorStoreRead(d, client); err != nil { + t.Fatalf("read failed: %v", err) + } + + got := d.Get("litellm_params").(map[string]interface{}) + if _, leaked := got["api_key"]; leaked { + t.Fatalf("server-returned api_key persisted into state: %v", got) + } + if got["vector_store_id"] != "vs-123" { + t.Fatalf("config litellm_params not preserved: %v", got) + } + if d.Get("vector_store_name").(string) != "kb" { + t.Fatalf("read did not populate non-sensitive fields") + } +} diff --git a/terraform/provider/litellm/types.go b/terraform/provider/litellm/types.go new file mode 100644 index 00000000000..069fe4b3e23 --- /dev/null +++ b/terraform/provider/litellm/types.go @@ -0,0 +1,248 @@ +package litellm + +// ProviderConfig holds the configuration for the LiteLLM provider. +type ProviderConfig struct { + APIBase string + APIKey string + InsecureSkipVerify bool +} + +// ErrorResponse represents an error response from the API. +type ErrorResponse struct { + Error struct { + Message interface{} `json:"message"` + } `json:"error"` + Detail struct { + Error string `json:"error"` + } `json:"detail"` +} + +// ModelResponse represents a response from the API containing model information. +type ModelResponse struct { + ModelName string `json:"model_name"` + LiteLLMParams LiteLLMParams `json:"litellm_params"` + ModelInfo ModelInfo `json:"model_info"` + Additional map[string]interface{} `json:"additional"` +} + +// ModelRequest represents a request to create or update a model. +type ModelRequest struct { + ModelName string `json:"model_name"` + LiteLLMParams map[string]interface{} `json:"litellm_params"` + ModelInfo ModelInfo `json:"model_info"` + Additional map[string]interface{} `json:"additional"` +} + +// TeamResponse represents a response from the API containing team information. +type TeamResponse struct { + TeamID string `json:"team_id,omitempty"` + TeamAlias string `json:"team_alias,omitempty"` + OrganizationID string `json:"organization_id,omitempty"` + Metadata map[string]interface{} `json:"metadata,omitempty"` + TPMLimit *int `json:"tpm_limit,omitempty"` + RPMLimit *int `json:"rpm_limit,omitempty"` + MaxBudget *float64 `json:"max_budget,omitempty"` + BudgetDuration string `json:"budget_duration,omitempty"` + Models []string `json:"models"` + Blocked bool `json:"blocked,omitempty"` + TeamMemberPermissions []string `json:"team_member_permissions,omitempty"` +} + +// OrganizationResponse represents a response from the API containing organization information. +type OrganizationResponse struct { + OrganizationID string `json:"organization_id,omitempty"` + OrganizationAlias string `json:"organization_alias,omitempty"` + Metadata map[string]interface{} `json:"metadata,omitempty"` + Models []string `json:"models,omitempty"` + MaxBudget *float64 `json:"max_budget,omitempty"` + BudgetDuration string `json:"budget_duration,omitempty"` + TPMLimit *int `json:"tpm_limit,omitempty"` + RPMLimit *int `json:"rpm_limit,omitempty"` + Blocked bool `json:"blocked,omitempty"` +} + +// LiteLLMParams represents the parameters for LiteLLM. +type LiteLLMParams struct { + CustomLLMProvider string `json:"custom_llm_provider"` + TPM int `json:"tpm,omitempty"` + RPM int `json:"rpm,omitempty"` + ReasoningEffort string `json:"reasoning_effort,omitempty"` + Thinking map[string]interface{} `json:"thinking,omitempty"` + MergeReasoningContentInChoices bool `json:"merge_reasoning_content_in_choices,omitempty"` + APIKey string `json:"api_key,omitempty"` + APIBase string `json:"api_base,omitempty"` + APIVersion string `json:"api_version,omitempty"` + Model string `json:"model"` + InputCostPerToken float64 `json:"input_cost_per_token,omitempty"` + OutputCostPerToken float64 `json:"output_cost_per_token,omitempty"` + InputCostPerPixel float64 `json:"input_cost_per_pixel,omitempty"` + OutputCostPerPixel float64 `json:"output_cost_per_pixel,omitempty"` + InputCostPerSecond float64 `json:"input_cost_per_second,omitempty"` + OutputCostPerSecond float64 `json:"output_cost_per_second,omitempty"` + AWSAccessKeyID string `json:"aws_access_key_id,omitempty"` + AWSSecretAccessKey string `json:"aws_secret_access_key,omitempty"` + AWSRegionName string `json:"aws_region_name,omitempty"` + AWSSessionName string `json:"aws_session_name,omitempty"` + AWSRoleName string `json:"aws_role_name,omitempty"` + VertexProject string `json:"vertex_project,omitempty"` + VertexLocation string `json:"vertex_location,omitempty"` + VertexCredentials string `json:"vertex_credentials,omitempty"` +} + +// ModelInfo represents information about a model. +type ModelInfo struct { + ID string `json:"id"` + DBModel bool `json:"db_model"` + BaseModel string `json:"base_model"` + Tier string `json:"tier"` + Mode string `json:"mode"` + TeamID string `json:"team_id,omitempty"` +} + +// Key represents a LiteLLM API key. +type Key struct { + Key string `json:"key,omitempty"` + TokenID string `json:"token_id,omitempty"` + Models []string `json:"models"` + Spend float64 `json:"spend,omitempty"` + MaxBudget *float64 `json:"max_budget,omitempty"` + UserID string `json:"user_id,omitempty"` + TeamID string `json:"team_id,omitempty"` + MaxParallelRequests *int `json:"max_parallel_requests,omitempty"` + Metadata map[string]interface{} `json:"metadata,omitempty"` + TPMLimit *int `json:"tpm_limit,omitempty"` + RPMLimit *int `json:"rpm_limit,omitempty"` + BudgetDuration string `json:"budget_duration,omitempty"` + AllowedCacheControls []string `json:"allowed_cache_controls,omitempty"` + SoftBudget *float64 `json:"soft_budget,omitempty"` + KeyAlias string `json:"key_alias,omitempty"` + Duration string `json:"duration,omitempty"` + Aliases map[string]interface{} `json:"aliases,omitempty"` + Config map[string]interface{} `json:"config,omitempty"` + Permissions map[string]interface{} `json:"permissions,omitempty"` + ModelMaxBudget map[string]interface{} `json:"model_max_budget,omitempty"` + ModelRPMLimit map[string]interface{} `json:"model_rpm_limit,omitempty"` + ModelTPMLimit map[string]interface{} `json:"model_tpm_limit,omitempty"` + Guardrails []string `json:"guardrails,omitempty"` + Blocked bool `json:"blocked"` + Tags []string `json:"tags,omitempty"` +} + +// KeyResponse represents a response from the API containing key information. +type KeyResponse struct { + Key string `json:"key"` +} + +// MCPServerCostInfo represents cost information for MCP server tools. +type MCPServerCostInfo struct { + DefaultCostPerQuery float64 `json:"default_cost_per_query,omitempty"` + ToolNameToCostPerQuery map[string]float64 `json:"tool_name_to_cost_per_query,omitempty"` +} + +// MCPInfo represents MCP server information and configuration. +type MCPInfo struct { + ServerName string `json:"server_name,omitempty"` + Description string `json:"description,omitempty"` + LogoURL string `json:"logo_url,omitempty"` + MCPServerCostInfo *MCPServerCostInfo `json:"mcp_server_cost_info,omitempty"` +} + +// MCPServerRequest represents a request to create or update an MCP server. +type MCPServerRequest struct { + ServerID string `json:"server_id,omitempty"` + ServerName string `json:"server_name"` + Alias string `json:"alias,omitempty"` + Description string `json:"description,omitempty"` + Transport string `json:"transport"` + SpecVersion string `json:"spec_version,omitempty"` + AuthType string `json:"auth_type,omitempty"` + URL string `json:"url"` + MCPInfo *MCPInfo `json:"mcp_info,omitempty"` + MCPAccessGroups []string `json:"mcp_access_groups,omitempty"` + Command string `json:"command,omitempty"` + Args []string `json:"args,omitempty"` + Env map[string]string `json:"env,omitempty"` +} + +// MCPServerResponse represents a response from the API containing MCP server information. +type MCPServerResponse struct { + ServerID string `json:"server_id"` + ServerName string `json:"server_name"` + Alias string `json:"alias,omitempty"` + Description string `json:"description,omitempty"` + URL string `json:"url"` + Transport string `json:"transport"` + SpecVersion string `json:"spec_version,omitempty"` + AuthType string `json:"auth_type,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + CreatedBy string `json:"created_by,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` + UpdatedBy string `json:"updated_by,omitempty"` + Teams []map[string]string `json:"teams,omitempty"` + MCPAccessGroups []string `json:"mcp_access_groups,omitempty"` + MCPInfo *MCPInfo `json:"mcp_info,omitempty"` + Status string `json:"status,omitempty"` + LastHealthCheck string `json:"last_health_check,omitempty"` + HealthCheckError string `json:"health_check_error,omitempty"` + Command string `json:"command,omitempty"` + Args []string `json:"args,omitempty"` + Env map[string]string `json:"env,omitempty"` +} + +// CredentialRequest represents a request to create or update a credential. +type CredentialRequest struct { + CredentialName string `json:"credential_name"` + CredentialInfo map[string]interface{} `json:"credential_info,omitempty"` + CredentialValues map[string]interface{} `json:"credential_values,omitempty"` + ModelID string `json:"model_id,omitempty"` +} + +// CredentialResponse represents a response from the API containing credential information. +type CredentialResponse struct { + CredentialName string `json:"credential_name"` + CredentialInfo map[string]interface{} `json:"credential_info,omitempty"` + CredentialValues map[string]interface{} `json:"credential_values,omitempty"` +} + +// VectorStoreRequest represents a request to create or update a vector store. +type VectorStoreRequest struct { + VectorStoreID string `json:"vector_store_id,omitempty"` + CustomLLMProvider string `json:"custom_llm_provider"` + VectorStoreName string `json:"vector_store_name"` + VectorStoreDescription string `json:"vector_store_description,omitempty"` + VectorStoreMetadata map[string]interface{} `json:"vector_store_metadata,omitempty"` + LiteLLMCredentialName string `json:"litellm_credential_name,omitempty"` + LiteLLMParams map[string]interface{} `json:"litellm_params,omitempty"` +} + +// VectorStoreResponse represents a response from the API containing vector store information. +type VectorStoreResponse struct { + VectorStoreID string `json:"vector_store_id"` + CustomLLMProvider string `json:"custom_llm_provider"` + VectorStoreName string `json:"vector_store_name"` + VectorStoreDescription string `json:"vector_store_description,omitempty"` + VectorStoreMetadata map[string]interface{} `json:"vector_store_metadata,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` + LiteLLMCredentialName string `json:"litellm_credential_name,omitempty"` + LiteLLMParams map[string]interface{} `json:"litellm_params,omitempty"` +} + +// VectorStoreListResponse represents a response from the API containing a list of vector stores. +type VectorStoreListResponse struct { + Object string `json:"object"` + Data []VectorStoreResponse `json:"data"` + TotalCount int `json:"total_count"` + CurrentPage int `json:"current_page"` + TotalPages int `json:"total_pages"` +} + +// VectorStoreDeleteRequest represents a request to delete a vector store. +type VectorStoreDeleteRequest struct { + VectorStoreID string `json:"vector_store_id"` +} + +// VectorStoreInfoRequest represents a request to get vector store information. +type VectorStoreInfoRequest struct { + VectorStoreID string `json:"vector_store_id"` +} diff --git a/terraform/provider/litellm/utils.go b/terraform/provider/litellm/utils.go new file mode 100644 index 00000000000..01d8045300c --- /dev/null +++ b/terraform/provider/litellm/utils.go @@ -0,0 +1,279 @@ +package litellm + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" +) + +func isModelNotFoundError(errResp ErrorResponse) bool { + if msg, ok := errResp.Error.Message.(string); ok { + if strings.Contains(msg, "model not found") { + return true + } + } + + if msgMap, ok := errResp.Error.Message.(map[string]interface{}); ok { + if errStr, ok := msgMap["error"].(string); ok { + if strings.Contains(errStr, "Model with id=") && strings.Contains(errStr, "not found in db") { + return true + } + } + } + + // Check Detail.Error field for LiteLLM proxy error format + if errResp.Detail.Error != "" { + if strings.Contains(errResp.Detail.Error, "not found on litellm proxy") { + return true + } + } + + return false +} + +func handleAPIResponse(resp *http.Response, reqBody interface{}, client *Client) (*ModelResponse, error) { + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %v", err) + } + + if resp.StatusCode != http.StatusOK { + var errResp ErrorResponse + if err := json.Unmarshal(bodyBytes, &errResp); err == nil { + if isModelNotFoundError(errResp) { + return nil, fmt.Errorf("model_not_found") + } + } + reqBodyBytes, _ := json.Marshal(reqBody) + return nil, fmt.Errorf("API request failed: Status: %s, Response: %s, Request: %s", + resp.Status, client.redactSensitiveData(string(bodyBytes)), client.redactSensitiveData(string(reqBodyBytes))) + } + + var modelResp ModelResponse + if err := json.Unmarshal(bodyBytes, &modelResp); err != nil { + return nil, fmt.Errorf("failed to parse response: %v", err) + } + + return &modelResp, nil +} + +// MakeRequest is a helper function to make HTTP requests +func MakeRequest(client *Client, method, endpoint string, body interface{}) (*http.Response, error) { + var req *http.Request + var err error + + if body != nil { + jsonData, err := json.Marshal(body) + if err != nil { + return nil, fmt.Errorf("failed to marshal request body: %w", err) + } + req, err = http.NewRequest(method, fmt.Sprintf("%s%s", client.APIBase, endpoint), bytes.NewBuffer(jsonData)) + } else { + req, err = http.NewRequest(method, fmt.Sprintf("%s%s", client.APIBase, endpoint), nil) + } + + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("x-api-key", client.APIKey) + + return client.httpClient.Do(req) +} + +// Helper functions to handle potential nil values from the API response +func GetStringValue(apiValue, defaultValue string) string { + if apiValue != "" { + return apiValue + } + return defaultValue +} + +func GetIntValue(apiValue, defaultValue int) int { + if apiValue != 0 { + return apiValue + } + return defaultValue +} + +func GetFloatValue(apiValue, defaultValue float64) float64 { + if apiValue != 0 { + return apiValue + } + return defaultValue +} + +func GetBoolValue(apiValue, defaultValue bool) bool { + return apiValue +} + +// handleMCPAPIResponse handles API responses specifically for MCP server operations +func handleMCPAPIResponse(resp *http.Response, result interface{}, client *Client) error { + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("failed to read response body: %v", err) + } + + if resp.StatusCode != http.StatusOK { + var errResp ErrorResponse + if err := json.Unmarshal(bodyBytes, &errResp); err == nil { + if isMCPServerNotFoundError(errResp) { + return fmt.Errorf("mcp_server_not_found") + } + } + return fmt.Errorf("API request failed: Status: %s, Response: %s", + resp.Status, client.redactSensitiveData(string(bodyBytes))) + } + + if err := json.Unmarshal(bodyBytes, result); err != nil { + return fmt.Errorf("failed to parse response: %v", err) + } + + return nil +} + +// isMCPServerNotFoundError checks if the error response indicates an MCP server not found +func isMCPServerNotFoundError(errResp ErrorResponse) bool { + if msg, ok := errResp.Error.Message.(string); ok { + if strings.Contains(msg, "mcp server not found") || strings.Contains(msg, "server not found") { + return true + } + } + + if msgMap, ok := errResp.Error.Message.(map[string]interface{}); ok { + if errStr, ok := msgMap["error"].(string); ok { + if strings.Contains(errStr, "MCP server with id=") && strings.Contains(errStr, "not found") { + return true + } + } + } + + // Check Detail.Error field for LiteLLM proxy error format + if errResp.Detail.Error != "" { + if strings.Contains(errResp.Detail.Error, "not found") { + return true + } + } + + return false +} + +// isCredentialNotFoundError checks if the error response indicates a credential not found +func isCredentialNotFoundError(errResp ErrorResponse) bool { + if msg, ok := errResp.Error.Message.(string); ok { + if strings.Contains(msg, "credential not found") { + return true + } + } + + if msgMap, ok := errResp.Error.Message.(map[string]interface{}); ok { + if errStr, ok := msgMap["error"].(string); ok { + if strings.Contains(errStr, "Credential with name=") && strings.Contains(errStr, "not found") { + return true + } + } + } + + // Check Detail.Error field for LiteLLM proxy error format + if errResp.Detail.Error != "" { + if strings.Contains(errResp.Detail.Error, "credential not found") { + return true + } + } + + return false +} + +// handleCredentialAPIResponse handles API responses specifically for credential operations +func handleCredentialAPIResponse(resp *http.Response, result interface{}, client *Client) error { + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("failed to read response body: %v", err) + } + + if resp.StatusCode == http.StatusNotFound { + return fmt.Errorf("credential_not_found") + } + + if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { + var errResp ErrorResponse + if err := json.Unmarshal(bodyBytes, &errResp); err == nil { + if isCredentialNotFoundError(errResp) { + return fmt.Errorf("credential_not_found") + } + } + return fmt.Errorf("API request failed: Status: %s, Response: %s", + resp.Status, client.redactSensitiveData(string(bodyBytes))) + } + + // For credential operations, we might get a simple string response or a credential object + if result != nil { + if err := json.Unmarshal(bodyBytes, result); err != nil { + // If parsing fails, it might be a simple string response which is fine for create/update/delete + return nil + } + } + + return nil +} + +// isVectorStoreNotFoundError checks if the error response indicates a vector store not found +func isVectorStoreNotFoundError(errResp ErrorResponse) bool { + if msg, ok := errResp.Error.Message.(string); ok { + if strings.Contains(msg, "vector store not found") { + return true + } + } + + if msgMap, ok := errResp.Error.Message.(map[string]interface{}); ok { + if errStr, ok := msgMap["error"].(string); ok { + if strings.Contains(errStr, "Vector store with id=") && strings.Contains(errStr, "not found") { + return true + } + } + } + + // Check Detail.Error field for LiteLLM proxy error format + if errResp.Detail.Error != "" { + if strings.Contains(errResp.Detail.Error, "vector store not found") { + return true + } + } + + return false +} + +// handleVectorStoreAPIResponse handles API responses specifically for vector store operations +func handleVectorStoreAPIResponse(resp *http.Response, result interface{}, client *Client) error { + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("failed to read response body: %v", err) + } + + if resp.StatusCode == http.StatusNotFound { + return fmt.Errorf("vector_store_not_found") + } + + if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { + var errResp ErrorResponse + if err := json.Unmarshal(bodyBytes, &errResp); err == nil { + if isVectorStoreNotFoundError(errResp) { + return fmt.Errorf("vector_store_not_found") + } + } + return fmt.Errorf("API request failed: Status: %s, Response: %s", + resp.Status, client.redactSensitiveData(string(bodyBytes))) + } + + if result != nil { + if err := json.Unmarshal(bodyBytes, result); err != nil { + return fmt.Errorf("failed to parse response: %v", err) + } + } + + return nil +} diff --git a/terraform/provider/main.go b/terraform/provider/main.go new file mode 100644 index 00000000000..abe83718899 --- /dev/null +++ b/terraform/provider/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "github.com/BerriAI/terraform-provider-litellm/litellm" + "github.com/hashicorp/terraform-plugin-sdk/v2/plugin" +) + +// main is the entry point for the plugin. It serves the provider +// using the Terraform plugin SDK. +func main() { + plugin.Serve(&plugin.ServeOpts{ + ProviderFunc: litellm.Provider, + }) +} diff --git a/terraform/provider/terraform-registry-manifest.json b/terraform/provider/terraform-registry-manifest.json new file mode 100644 index 00000000000..295001a07f7 --- /dev/null +++ b/terraform/provider/terraform-registry-manifest.json @@ -0,0 +1,6 @@ +{ + "version": 1, + "metadata": { + "protocol_versions": ["6.0"] + } +} diff --git a/terraform/provider/tools/dump_openapi.py b/terraform/provider/tools/dump_openapi.py new file mode 100644 index 00000000000..b4f2dceeb09 --- /dev/null +++ b/terraform/provider/tools/dump_openapi.py @@ -0,0 +1,23 @@ +"""Dump the LiteLLM proxy's OpenAPI schema to the path given as the only argument. + +Run from the litellm repo root with the proxy dependencies installed: + + python terraform/provider/tools/dump_openapi.py openapi.json +""" + +import json +import sys + +from litellm.proxy.proxy_server import app + + +def main(out_path: str) -> None: + with open(out_path, "w") as f: + json.dump(app.openapi(), f) + + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("usage: python terraform/provider/tools/dump_openapi.py ", file=sys.stderr) + sys.exit(2) + main(sys.argv[1]) diff --git a/terraform/provider/tools/endpointaudit/main.go b/terraform/provider/tools/endpointaudit/main.go new file mode 100644 index 00000000000..ebc011ee910 --- /dev/null +++ b/terraform/provider/tools/endpointaudit/main.go @@ -0,0 +1,345 @@ +package main + +import ( + "encoding/json" + "flag" + "fmt" + "go/ast" + "go/parser" + "go/token" + "os" + "regexp" + "sort" + "strconv" + "strings" +) + +type endpointCall struct { + Method string + Path string + Pos string +} + +type extraction struct { + Calls []endpointCall + Unresolved []string +} + +var formatVerbPattern = regexp.MustCompile(`%[sdv]`) + +func normalizePath(raw string) string { + withoutQuery := strings.SplitN(raw, "?", 2)[0] + return formatVerbPattern.ReplaceAllString(withoutQuery, "{param}") +} + +func stringLit(expr ast.Expr) (string, bool) { + lit, ok := expr.(*ast.BasicLit) + if !ok || lit.Kind != token.STRING { + return "", false + } + value, err := strconv.Unquote(lit.Value) + if err != nil { + return "", false + } + return value, true +} + +func packageConsts(files []*ast.File) map[string]string { + consts := make(map[string]string) + for _, file := range files { + for _, decl := range file.Decls { + genDecl, ok := decl.(*ast.GenDecl) + if !ok || (genDecl.Tok != token.CONST && genDecl.Tok != token.VAR) { + continue + } + for _, spec := range genDecl.Specs { + valueSpec, ok := spec.(*ast.ValueSpec) + if !ok { + continue + } + for i, name := range valueSpec.Names { + if i >= len(valueSpec.Values) { + continue + } + if value, ok := stringLit(valueSpec.Values[i]); ok { + consts[name.Name] = value + } + } + } + } + } + return consts +} + +func isSprintf(call *ast.CallExpr) bool { + sel, ok := call.Fun.(*ast.SelectorExpr) + if !ok || sel.Sel.Name != "Sprintf" { + return false + } + pkg, ok := sel.X.(*ast.Ident) + return ok && pkg.Name == "fmt" +} + +func resolveExpr(expr ast.Expr, fn *ast.FuncDecl, consts map[string]string) []string { + switch node := expr.(type) { + case *ast.BasicLit: + if value, ok := stringLit(node); ok { + return []string{value} + } + case *ast.Ident: + if value, ok := consts[node.Name]; ok { + return []string{value} + } + return resolveLocalIdent(node, fn, consts) + case *ast.CallExpr: + if isSprintf(node) && len(node.Args) > 0 { + return resolveSprintf(node, fn, consts) + } + } + return nil +} + +func resolveSprintf(call *ast.CallExpr, fn *ast.FuncDecl, consts map[string]string) []string { + formats := resolveExpr(call.Args[0], fn, consts) + results := formats + for _, arg := range call.Args[1:] { + argValues := resolveExpr(arg, fn, consts) + substituted := make([]string, 0, len(results)) + for _, format := range results { + verb := formatVerbPattern.FindStringIndex(format) + if verb == nil { + substituted = append(substituted, format) + continue + } + if len(argValues) == 0 { + substituted = append(substituted, format[:verb[0]]+"\x00param\x00"+format[verb[1]:]) + continue + } + for _, argValue := range argValues { + substituted = append(substituted, format[:verb[0]]+argValue+format[verb[1]:]) + } + } + results = substituted + } + restored := make([]string, 0, len(results)) + for _, result := range results { + restored = append(restored, strings.ReplaceAll(result, "\x00param\x00", "%s")) + } + return restored +} + +func resolveLocalIdent(ident *ast.Ident, fn *ast.FuncDecl, consts map[string]string) []string { + if fn == nil { + return nil + } + var values []string + ast.Inspect(fn.Body, func(node ast.Node) bool { + assign, ok := node.(*ast.AssignStmt) + if !ok { + return true + } + for i, lhs := range assign.Lhs { + lhsIdent, ok := lhs.(*ast.Ident) + if !ok || lhsIdent.Name != ident.Name || i >= len(assign.Rhs) { + continue + } + values = append(values, resolveExpr(assign.Rhs[i], fn, consts)...) + } + return true + }) + return values +} + +func requestCallMethodAndPath(call *ast.CallExpr) (methodArg ast.Expr, pathArg ast.Expr, matched bool) { + switch fun := call.Fun.(type) { + case *ast.SelectorExpr: + if fun.Sel.Name == "sendRequest" && len(call.Args) >= 2 { + return call.Args[0], call.Args[1], true + } + case *ast.Ident: + if fun.Name == "MakeRequest" && len(call.Args) >= 3 { + return call.Args[1], call.Args[2], true + } + } + return nil, nil, false +} + +func isRawHTTPRequest(call *ast.CallExpr) bool { + sel, ok := call.Fun.(*ast.SelectorExpr) + if !ok || (sel.Sel.Name != "NewRequest" && sel.Sel.Name != "NewRequestWithContext") { + return false + } + pkg, ok := sel.X.(*ast.Ident) + return ok && pkg.Name == "http" +} + +func extractFromFiles(fset *token.FileSet, files []*ast.File, helperFiles map[string]bool) extraction { + consts := packageConsts(files) + var result extraction + for _, file := range files { + fileName := fset.Position(file.Pos()).Filename + for _, decl := range file.Decls { + fn, ok := decl.(*ast.FuncDecl) + if !ok || fn.Body == nil { + continue + } + ast.Inspect(fn.Body, func(node ast.Node) bool { + call, ok := node.(*ast.CallExpr) + if !ok { + return true + } + pos := fset.Position(call.Pos()).String() + if isRawHTTPRequest(call) && !helperFiles[fileName] { + result.Unresolved = append(result.Unresolved, + fmt.Sprintf("%s: raw http.NewRequest outside the request helpers; route it through Client.sendRequest or MakeRequest", pos)) + return true + } + methodArg, pathArg, matched := requestCallMethodAndPath(call) + if !matched { + return true + } + methods := resolveExpr(methodArg, fn, consts) + paths := resolveExpr(pathArg, fn, consts) + if len(methods) == 0 || len(paths) == 0 { + result.Unresolved = append(result.Unresolved, + fmt.Sprintf("%s: cannot statically resolve method or path; use a string literal, package const, or fmt.Sprintf with a literal format", pos)) + return true + } + for _, method := range methods { + for _, path := range paths { + result.Calls = append(result.Calls, endpointCall{Method: method, Path: normalizePath(path), Pos: pos}) + } + } + return true + }) + } + } + return result +} + +func extractProviderCalls(providerDir string) (extraction, error) { + fset := token.NewFileSet() + pkgs, err := parser.ParseDir(fset, providerDir, func(info os.FileInfo) bool { + return !strings.HasSuffix(info.Name(), "_test.go") + }, 0) + if err != nil { + return extraction{}, err + } + var files []*ast.File + helperFiles := make(map[string]bool) + for _, pkg := range pkgs { + fileNames := make([]string, 0, len(pkg.Files)) + for name := range pkg.Files { + fileNames = append(fileNames, name) + } + sort.Strings(fileNames) + for _, name := range fileNames { + files = append(files, pkg.Files[name]) + base := name[strings.LastIndex(name, "/")+1:] + if base == "client.go" || base == "utils.go" { + helperFiles[name] = true + } + } + } + return extractFromFiles(fset, files, helperFiles), nil +} + +func loadSpecPaths(specPath string) (map[string]map[string]json.RawMessage, error) { + data, err := os.ReadFile(specPath) + if err != nil { + return nil, err + } + var spec struct { + Paths map[string]map[string]json.RawMessage `json:"paths"` + } + if err := json.Unmarshal(data, &spec); err != nil { + return nil, err + } + if len(spec.Paths) == 0 { + return nil, fmt.Errorf("spec %s contains no paths", specPath) + } + return spec.Paths, nil +} + +func segmentsMatch(providerSegment, specSegment string) bool { + if providerSegment == "{param}" { + return strings.HasPrefix(specSegment, "{") && strings.HasSuffix(specSegment, "}") + } + return providerSegment == specSegment +} + +func pathMatches(providerPath, specPath string) bool { + providerSegments := strings.Split(strings.Trim(providerPath, "/"), "/") + specSegments := strings.Split(strings.Trim(specPath, "/"), "/") + if len(providerSegments) != len(specSegments) { + return false + } + for i := range providerSegments { + if !segmentsMatch(providerSegments[i], specSegments[i]) { + return false + } + } + return true +} + +func auditCalls(calls []endpointCall, specPaths map[string]map[string]json.RawMessage) []string { + var violations []string + for _, call := range calls { + pathFound := false + methodFound := false + for specPath, operations := range specPaths { + if !pathMatches(call.Path, specPath) { + continue + } + pathFound = true + if _, ok := operations[strings.ToLower(call.Method)]; ok { + methodFound = true + break + } + } + if !pathFound { + violations = append(violations, fmt.Sprintf("%s: %s %s is not served by the proxy", call.Pos, call.Method, call.Path)) + } else if !methodFound { + violations = append(violations, fmt.Sprintf("%s: %s %s: path exists but method not allowed", call.Pos, call.Method, call.Path)) + } + } + return violations +} + +func run(providerDir, specPath string) error { + extracted, err := extractProviderCalls(providerDir) + if err != nil { + return err + } + if len(extracted.Unresolved) > 0 { + return fmt.Errorf("unresolved call sites:\n %s", strings.Join(extracted.Unresolved, "\n ")) + } + if len(extracted.Calls) == 0 { + return fmt.Errorf("extracted zero request call sites from %s; extractor or provider layout changed", providerDir) + } + specPaths, err := loadSpecPaths(specPath) + if err != nil { + return err + } + violations := auditCalls(extracted.Calls, specPaths) + if len(violations) > 0 { + sort.Strings(violations) + return fmt.Errorf("provider/proxy endpoint drift:\n %s", strings.Join(violations, "\n ")) + } + fmt.Printf("OK: %d request call sites verified against %d proxy OpenAPI paths\n", len(extracted.Calls), len(specPaths)) + return nil +} + +func main() { + providerDir := flag.String("provider-dir", "./litellm", "directory containing the provider Go source") + specPath := flag.String("spec", "", "path to the proxy OpenAPI schema JSON") + flag.Parse() + if *specPath == "" { + fmt.Fprintln(os.Stderr, "error: -spec is required") + os.Exit(2) + } + if err := run(*providerDir, *specPath); err != nil { + fmt.Fprintf(os.Stderr, "error: %v\n", err) + os.Exit(1) + } +} diff --git a/terraform/provider/tools/endpointaudit/main_test.go b/terraform/provider/tools/endpointaudit/main_test.go new file mode 100644 index 00000000000..d3d5e7dec9c --- /dev/null +++ b/terraform/provider/tools/endpointaudit/main_test.go @@ -0,0 +1,187 @@ +package main + +import ( + "encoding/json" + "os" + "path/filepath" + "sort" + "strings" + "testing" +) + +func writeFixture(t *testing.T, dir, name, body string) { + t.Helper() + if err := os.WriteFile(filepath.Join(dir, name), []byte(body), 0o644); err != nil { + t.Fatal(err) + } +} + +func extractFixture(t *testing.T, files map[string]string) extraction { + t.Helper() + dir := t.TempDir() + for name, body := range files { + writeFixture(t, dir, name, body) + } + result, err := extractProviderCalls(dir) + if err != nil { + t.Fatal(err) + } + return result +} + +func callSet(calls []endpointCall) []string { + set := make(map[string]bool) + for _, call := range calls { + set[call.Method+" "+call.Path] = true + } + keys := make([]string, 0, len(set)) + for key := range set { + keys = append(keys, key) + } + sort.Strings(keys) + return keys +} + +func TestExtractResolvesAllCallShapes(t *testing.T) { + result := extractFixture(t, map[string]string{ + "consts.go": `package p + +const ( + endpointModelNew = "/model/new" + endpointModelUpdate = "/model/update" + endpointMCPRead = "/v1/mcp/server" +) +`, + "calls.go": `package p + +import "fmt" + +func (c *Client) a() { + c.sendRequest("POST", "/team/new", nil) + c.sendRequest("GET", fmt.Sprintf("/team/info?team_id=%s", "x"), nil) +} + +func b(client *Client, isUpdate bool, serverID string) { + MakeRequest(client, "POST", "/credentials", nil) + endpoint := endpointModelNew + if isUpdate { + endpoint = endpointModelUpdate + } + MakeRequest(client, "POST", endpoint, nil) + readEndpoint := fmt.Sprintf("%s/%s", endpointMCPRead, serverID) + MakeRequest(client, "GET", readEndpoint, nil) +} +`, + }) + if len(result.Unresolved) != 0 { + t.Fatalf("unexpected unresolved: %v", result.Unresolved) + } + got := callSet(result.Calls) + want := []string{ + "GET /team/info", + "GET /v1/mcp/server/{param}", + "POST /credentials", + "POST /model/new", + "POST /model/update", + "POST /team/new", + } + if strings.Join(got, ",") != strings.Join(want, ",") { + t.Fatalf("got %v, want %v", got, want) + } +} + +func TestExtractFailsClosedOnDynamicPath(t *testing.T) { + result := extractFixture(t, map[string]string{ + "calls.go": `package p + +func a(c *Client, path string) { + c.sendRequest("GET", path, nil) +} +`, + }) + if len(result.Unresolved) != 1 { + t.Fatalf("want 1 unresolved call site, got %v", result.Unresolved) + } +} + +func TestExtractFlagsRawHTTPRequestOutsideHelpers(t *testing.T) { + result := extractFixture(t, map[string]string{ + "rogue.go": `package p + +import "net/http" + +func a() { + http.NewRequest("GET", "http://example.com/model/new", nil) +} +`, + }) + if len(result.Unresolved) != 1 || !strings.Contains(result.Unresolved[0], "raw http.NewRequest") { + t.Fatalf("want raw request violation, got %v", result.Unresolved) + } +} + +func TestExtractAllowsRawHTTPRequestInHelpers(t *testing.T) { + result := extractFixture(t, map[string]string{ + "utils.go": `package p + +import "net/http" + +func MakeRequest(client *Client, method, endpoint string, body interface{}) { + http.NewRequest(method, endpoint, nil) +} +`, + }) + if len(result.Unresolved) != 0 { + t.Fatalf("unexpected unresolved: %v", result.Unresolved) + } +} + +func specFixture(t *testing.T) map[string]map[string]json.RawMessage { + t.Helper() + raw := `{ + "paths": { + "/team/new": {"post": {}}, + "/organization/update": {"patch": {}}, + "/credentials/{credential_name}": {"get": {}, "delete": {}} + } + }` + dir := t.TempDir() + specPath := filepath.Join(dir, "spec.json") + if err := os.WriteFile(specPath, []byte(raw), 0o644); err != nil { + t.Fatal(err) + } + paths, err := loadSpecPaths(specPath) + if err != nil { + t.Fatal(err) + } + return paths +} + +func TestAuditDetectsMissingPathAndWrongMethod(t *testing.T) { + spec := specFixture(t) + violations := auditCalls([]endpointCall{ + {Method: "POST", Path: "/team/new", Pos: "a.go:1"}, + {Method: "GET", Path: "/credentials/{param}", Pos: "a.go:2"}, + {Method: "POST", Path: "/organization/update", Pos: "a.go:3"}, + {Method: "POST", Path: "/gone/away", Pos: "a.go:4"}, + }, spec) + if len(violations) != 2 { + t.Fatalf("want 2 violations, got %v", violations) + } + joined := strings.Join(violations, "\n") + if !strings.Contains(joined, "POST /organization/update: path exists but method not allowed") { + t.Fatalf("missing method violation: %v", violations) + } + if !strings.Contains(joined, "POST /gone/away is not served by the proxy") { + t.Fatalf("missing path violation: %v", violations) + } +} + +func TestNormalizePathStripsQueryAndVerbs(t *testing.T) { + if got := normalizePath("/key/info?key=%s"); got != "/key/info" { + t.Fatalf("got %q", got) + } + if got := normalizePath("/credentials/%s"); got != "/credentials/{param}" { + t.Fatalf("got %q", got) + } +} diff --git a/tests/e2e/CLAUDE.md b/tests/e2e/CLAUDE.md index b155a1a7024..20ab0212854 100644 --- a/tests/e2e/CLAUDE.md +++ b/tests/e2e/CLAUDE.md @@ -6,13 +6,14 @@ Code-style rules for writing tests under `tests/e2e/`. The harness already encod Each subdirectory under `tests/e2e/` is one suite, scoped to an endpoint family or behavior area. If you add a new folder, you must add a line here describing what kind of tests belong in it, so the layout stays self-describing. `gateway/` is the exception: it holds proxy configuration only and never tests -- `llm_translation/` - LLM endpoint and provider-translation behavior: passthrough, custom pricing, OCR +- `llm_translation/` - LLM endpoint and provider-translation behavior: passthrough, custom pricing, OCR, and the non-chat inference endpoints (`/v1/responses`, `/v1/messages`, `/embeddings`, `/v1/rerank`, `/v1/audio/speech`, `/v1/images/generations`), each against a deployment the test creates via `/model/new` and deletes on teardown +- `access_control/` - the gateway's authorization and error-shape contract: per-key model allow-lists, route-group permissions (`allowed_routes`), and unknown-model validation - `embeddings/` - the `/embeddings` endpoint across providers - `batches/` - the `/batches` endpoint (placeholder until the first test lands) - `realtime/` - realtime websocket sessions, including the pipecat audio path - `budgets/` - budget definition, enforcement, and reset windows (key, team, tag, soft, multi-window) - `spend_tracking/` - spend logging and cost attribution on `/spend/*` -- `models_mgmt/` - model-management routes (add/update, tpm persistence) +- `management/` - key/team/user/organization management routes: create/update/delete persistence via the info routes, team membership, and llm-only-key route denials - `logging/` - logging-integration delivery (datadog and friends) - `security/` - secret handling and log-leak protection - `router/` - routing and reliability behavior (rate limits, fallbacks, cooldowns) diff --git a/tests/e2e/access_control/access_control_client.py b/tests/e2e/access_control/access_control_client.py new file mode 100644 index 00000000000..d7bc9c280aa --- /dev/null +++ b/tests/e2e/access_control/access_control_client.py @@ -0,0 +1,56 @@ +"""Client for the access-control e2e suite.""" + +from __future__ import annotations + +from dataclasses import dataclass + +from e2e_gateway import Gateway, build_gateway +from e2e_http import StreamingResponse +from models import ( + ChatBody, + ChatMessage, + KeyGenerateBody, + LiteLLMParamsBody, + ModelInfoBody, + ModelNewBody, +) + +MODEL_ACCESS_DENIED_MARKER = "key_model_access_denied" +ROUTE_NOT_ALLOWED_MARKER = "not allowed to call this route" + + +@dataclass(frozen=True, slots=True) +class AccessControlClient: + gateway: Gateway + + def llm_only_key(self) -> str: + return self.gateway.generate_key( + KeyGenerateBody(models=[], allowed_routes=["llm_api_routes"]) + ) + + def delete_key(self, key: str) -> None: + self.gateway.delete_key(key) + + def chat_status(self, key: str, model: str, content: str) -> StreamingResponse: + return self.gateway.transport.send( + "/chat/completions", + headers=self.gateway.transport.bearer(key), + json=ChatBody( + model=model, messages=[ChatMessage(role="user", content=content)] + ), + ) + + def create_model_status(self, key: str, model_name: str) -> StreamingResponse: + return self.gateway.transport.send( + "/model/new", + headers=self.gateway.transport.bearer(key), + json=ModelNewBody( + model_name=model_name, + litellm_params=LiteLLMParamsBody(model="openai/gpt-4o-mini"), + model_info=ModelInfoBody(id=model_name), + ), + ) + + +def build_client() -> AccessControlClient: + return AccessControlClient(gateway=build_gateway()) diff --git a/tests/e2e/access_control/conftest.py b/tests/e2e/access_control/conftest.py new file mode 100644 index 00000000000..9f4a00fe06f --- /dev/null +++ b/tests/e2e/access_control/conftest.py @@ -0,0 +1,10 @@ +"""Access-control suite client fixture; lifecycle/skip/marker live in the parent conftest.""" + +import pytest + +from access_control_client import AccessControlClient, build_client + + +@pytest.fixture(scope="session") +def client() -> AccessControlClient: + return build_client() diff --git a/tests/e2e/access_control/test_access_control_e2e.py b/tests/e2e/access_control/test_access_control_e2e.py new file mode 100644 index 00000000000..ce649fa2400 --- /dev/null +++ b/tests/e2e/access_control/test_access_control_e2e.py @@ -0,0 +1,83 @@ +"""Live e2e: the gateway's authorization and error-shape contract. + +A virtual key may only call models in its allow-list and route groups in its +allowed_routes; both denials are a 403 raised before any provider is touched. A +syntactically valid request naming a non-existent model is a 400 with a JSON body, +never forwarded and never a 5xx. Migrated from +litellm-regression-tests/tests/test_access_control.py: the source asserted 401 for +the disallowed-model case against an older proxy, but the current contract +(auth_checks.py) is a 403 key_model_access_denied, and the unknown-route check is +replaced by a stronger route-permission check (an llm-only key rejected from a +management route). +""" + +from __future__ import annotations + +import json + +import pytest + +from access_control_client import ( + AccessControlClient, + MODEL_ACCESS_DENIED_MARKER, + ROUTE_NOT_ALLOWED_MARKER, +) +from e2e_config import unique_marker +from lifecycle import ResourceManager + +pytestmark = pytest.mark.e2e + +ALLOWED_MODEL = "gemini-2.5-flash" +DISALLOWED_MODEL = "gpt-5.5" + + +def _is_json(body: str) -> bool: + try: + json.loads(body) + return True + except ValueError: + return False + + +class TestAccessControl: + def test_disallowed_model_is_denied_403( + self, client: AccessControlClient, resources: ResourceManager + ) -> None: + key = resources.key(models=[ALLOWED_MODEL]) + result = client.chat_status( + key, DISALLOWED_MODEL, f"capital of France? {unique_marker()}" + ) + assert result.status_code == 403, ( + f"key limited to {ALLOWED_MODEL!r} calling {DISALLOWED_MODEL!r} must be " + f"denied 403, got {result.status_code}: {result.body[:300]}" + ) + assert MODEL_ACCESS_DENIED_MARKER in result.body, ( + f"403 body must be a model-access denial, got: {result.body[:300]}" + ) + + def test_llm_only_key_forbidden_from_management_route_403( + self, client: AccessControlClient, resources: ResourceManager + ) -> None: + key = client.llm_only_key() + resources.defer(lambda: client.delete_key(key)) + result = client.create_model_status(key, f"e2e-forbidden-{unique_marker()}") + assert result.status_code == 403, ( + f"llm-only key calling a management route must be denied 403, got " + f"{result.status_code}: {result.body[:300]}" + ) + assert ROUTE_NOT_ALLOWED_MARKER in result.body, ( + f"403 body must be a route-permission denial, got: {result.body[:300]}" + ) + + def test_unknown_model_returns_400( + self, client: AccessControlClient, resources: ResourceManager + ) -> None: + key = resources.key() + result = client.chat_status( + key, f"nonexistent-model-{unique_marker()}", "hi this is a test" + ) + assert result.status_code == 400, ( + f"unknown model must be rejected 400 before forwarding, got " + f"{result.status_code}: {result.body[:300]}" + ) + assert _is_json(result.body), f"400 body must be valid JSON: {result.body[:300]}" diff --git a/tests/e2e/batches/COVERAGE.md b/tests/e2e/batches/COVERAGE.md index 0026d4bb3d5..4ba9b1cea4b 100644 --- a/tests/e2e/batches/COVERAGE.md +++ b/tests/e2e/batches/COVERAGE.md @@ -9,20 +9,21 @@ cancels, and lists a batch; everything created is deleted on teardown. Only supported cells are tested. The capability table in `capabilities.py` holds one row per supported (provider, scenario) pair, so there are no skipped cells in the -parametrized run. +parametrized run. The batches suite never skips: missing provider creds or upstream +failures are hard test failures (see `tests/e2e/CLAUDE.md`). | Provider | create | retrieve | cancel | list | file backing | |-----------|--------|----------|--------|------|--------------| | OpenAI | yes | yes | yes | yes | OpenAI Files | | Azure | yes | yes | yes | yes | Azure Files | -| Vertex AI | yes | yes | yes | yes | GCS bucket | -| Bedrock | yes | yes | no (limited upstream) | no | S3 bucket | -| Anthropic | no | yes (env-gated) | no | no | Anthropic Files | +| Vertex AI | yes | yes | yes | yes | GCS bucket (`GCS_BUCKET_NAME` via files_settings) | +| Bedrock | yes | yes | no (limited upstream) | no | S3 bucket (`AWS_BATCH_S3_BUCKET` + `AWS_BATCH_ROLE_ARN` on model) | Bedrock cancel is unreliable upstream and list is unsupported, so both are gated off -(`can_cancel=False`, `can_list=False`). Anthropic cannot create/cancel/list through -litellm, so it has a standalone retrieve test that skips unless `ANTHROPIC_BATCH_ID` -points at a real Anthropic batch. +(`can_cancel=False`, `can_list=False`) when that provider is enabled in the matrix. +Bedrock file upload requires a model on the request (`encoded` / `unified` scenarios only); +`model_param` and `provider_fallback` are omitted because `POST /bedrock/v1/files` has no +model-less passthrough path. ## Routing scenarios (per `litellm/proxy/batches_endpoints/endpoints.py`) @@ -67,9 +68,10 @@ File delete asserts `object=="file"` and `deleted==True`. | File | Covers | |------|--------| -| `batch_client.py` | typed file upload/download + batch create/retrieve/cancel/list/delete over the shared Gateway; denial helpers | -| `capabilities.py` | the provider x scenario matrix + id-shape classifiers + per-provider raw-id assertion | -| `test_batches_e2e.py` | parametrized lifecycle with per-endpoint output assertions, file upload/delete outputs, key-model-access denial, anthropic retrieve | +| `batch_client.py` | typed file upload/download + batch create/retrieve/cancel/list/delete over the shared Gateway; runtime batch model registration via /model/new; denial helpers | +| `capabilities.py` | the provider x scenario matrix + per-provider /model/new params + id-shape classifiers + per-provider raw-id assertion | +| `conftest.py` | session-scoped batch deployment registration and teardown | +| `test_batches_e2e.py` | parametrized lifecycle with per-endpoint output assertions, file upload/delete outputs, key-model-access denial | ## Out of scope (intentionally) diff --git a/tests/e2e/batches/batch_client.py b/tests/e2e/batches/batch_client.py index 2a6376bf89e..fda4e87e478 100644 --- a/tests/e2e/batches/batch_client.py +++ b/tests/e2e/batches/batch_client.py @@ -1,11 +1,13 @@ """Client for the batches e2e suite: file upload/download and the batch operations (create / retrieve / cancel / list) over the shared Gateway. -`create_batch` returns the raw HTTP outcome (StreamingResponse) so a 403 model -access denial and a provider-native batch body both surface; the test parses -BatchObject from the body. A `provider` arg routes a call to /{provider}/v1/..., -which the provider-fallback scenario needs (its ids are raw, not model-encoded). -The request/response models are co-located here because only this suite uses them. +Batch deployments are registered at runtime via /model/new (see conftest.py), +not baked into the proxy config. `create_batch` returns the raw HTTP outcome +(StreamingResponse) so a 403 model access denial and a provider-native batch +body both surface; the test parses BatchObject from the body. A `provider` arg +routes a call to /{provider}/v1/..., which the provider-fallback scenario needs +(its ids are raw, not model-encoded). The request/response models are +co-located here because only this suite uses them. """ from __future__ import annotations @@ -22,6 +24,7 @@ from e2e_http import ( StreamingResponse, UnknownApiError, ) +from models import LiteLLMParamsBody class FileObject(BaseModel): @@ -84,6 +87,12 @@ def is_result_access_denied[R: BaseModel](result: Result[R]) -> bool: class BatchClient: gateway: Gateway + def create_model(self, model_name: str, litellm_params: LiteLLMParamsBody) -> str: + return self.gateway.create_model(model_name, litellm_params, mode="batch") + + def delete_model(self, model_id: str) -> None: + self.gateway.delete_model(model_id) + def upload_file( self, *, diff --git a/tests/e2e/batches/capabilities.py b/tests/e2e/batches/capabilities.py index 0416508a31a..522e3162e24 100644 --- a/tests/e2e/batches/capabilities.py +++ b/tests/e2e/batches/capabilities.py @@ -13,6 +13,8 @@ import base64 from dataclasses import dataclass from typing import Literal +from models import LiteLLMParamsBody + Scenario = Literal["encoded", "unified", "model_param", "provider_fallback"] IdShape = Literal["managed", "model_encoded", "raw"] @@ -33,6 +35,39 @@ class Provider: can_cancel: bool can_list: bool + def litellm_params(self) -> LiteLLMParamsBody: + match self.name: + case "openai": + return LiteLLMParamsBody( + model="openai/gpt-4o-mini", + api_key="os.environ/OPENAI_API_KEY", + ) + case "azure": + return LiteLLMParamsBody( + model="azure/gpt-4.1-mini-batch", + api_base="os.environ/AZURE_API_BASE", + api_key="os.environ/AZURE_API_KEY", + api_version="2024-07-01-preview", + ) + case "vertex_ai": + return LiteLLMParamsBody( + model="vertex_ai/gemini-2.5-flash", + vertex_project="os.environ/VERTEXAI_PROJECT", + vertex_location="us-central1", + vertex_credentials="os.environ/VERTEXAI_CREDENTIALS", + ) + case "bedrock": + return LiteLLMParamsBody( + model="bedrock/us.anthropic.claude-haiku-4-5-20251001-v1:0", + s3_access_key_id="os.environ/AWS_ACCESS_KEY_ID", + s3_secret_access_key="os.environ/AWS_SECRET_ACCESS_KEY", + s3_region_name="os.environ/AWS_REGION", + s3_bucket_name="os.environ/AWS_BATCH_S3_BUCKET", + aws_batch_role_arn="os.environ/AWS_BATCH_ROLE_ARN", + ) + case _: + raise ValueError(f"unknown batch provider: {self.name!r}") + @dataclass(frozen=True, slots=True) class Capability: @@ -66,19 +101,28 @@ PROVIDERS: tuple[Provider, ...] = ( Provider( "vertex_ai", "vertex-batch", "gemini-2.5-flash", can_cancel=True, can_list=True ), - # Provider( - # "bedrock", - # "bedrock-batch", - # "us.anthropic.claude-haiku-4-5-20251001-v1:0", - # can_cancel=False, - # can_list=False, - # ), + Provider( + "bedrock", + "bedrock-batch", + "bedrock/us.anthropic.claude-haiku-4-5-20251001-v1:0", + can_cancel=False, + can_list=False, + ), ) +BEDROCK_SCENARIOS: tuple[Scenario, ...] = ("encoded", "unified") + + +def scenarios_for_provider(provider: Provider) -> tuple[Scenario, ...]: + if provider.name == "bedrock": + return BEDROCK_SCENARIOS + return SCENARIOS + + CAPABILITIES: tuple[Capability, ...] = tuple( Capability(p.name, p.model, p.raw_model, scenario, p.can_cancel, p.can_list) for p in PROVIDERS - for scenario in SCENARIOS + for scenario in scenarios_for_provider(p) ) @@ -88,17 +132,13 @@ def raw_id_matches_provider(provider: str, batch_id: str) -> bool: if provider in ("openai", "azure"): return batch_id.startswith("batch") if provider == "vertex_ai": - # Vertex returns the batch prediction job id, which depending on the - # routing path arrives either as the full resource name - # (projects/.../batchPredictionJobs/) or as just the trailing - # numeric id, so accept either form. return ( batch_id.startswith("projects/") or "batchPredictionJobs" in batch_id or batch_id.isdigit() ) if provider == "bedrock": - return batch_id.startswith("arn:aws") + return batch_id.startswith("arn:aws:bedrock:") return True diff --git a/tests/e2e/batches/conftest.py b/tests/e2e/batches/conftest.py index 92905b33eee..2c6070c437a 100644 --- a/tests/e2e/batches/conftest.py +++ b/tests/e2e/batches/conftest.py @@ -4,13 +4,49 @@ The shared lifecycle (resources/scoped_key), proxy liveness skip, and e2e marker live in the parent tests/e2e/conftest.py. BatchClient holds the shared Gateway, so the `resources` fixture cleans up keys through it; tests register file deletes and batch cancels via `resources.defer(...)`. + +Batch deployments (openai-batch, azure-batch, vertex-batch, ...) are registered +once per session via /model/new and deleted on teardown so they need not live in +the proxy config. """ +from __future__ import annotations + +from typing import Iterator + import pytest from batch_client import BatchClient, build_client +from capabilities import PROVIDERS +from e2e_http import NoBody + + +def pytest_configure(config: pytest.Config) -> None: + config.addinivalue_line( + "markers", + "covers: registry cell a test covers, e.g. llm.batches.openai.basic.nonstream.works", + ) @pytest.fixture(scope="session") def client() -> BatchClient: return build_client() + + +@pytest.fixture(scope="session") +def batch_deployments(client: BatchClient) -> Iterator[None]: + probe = client.gateway.probe("/health/liveliness", params=NoBody()) + if not probe.healthy: + yield + return + + registered: list[str] = [] + try: + for provider in PROVIDERS: + registered.append( + client.create_model(provider.model, provider.litellm_params()) + ) + yield + finally: + for model_id in registered: + client.delete_model(model_id) diff --git a/tests/e2e/batches/test_batches_e2e.py b/tests/e2e/batches/test_batches_e2e.py index 1141e2f6a2f..a998f962c04 100644 --- a/tests/e2e/batches/test_batches_e2e.py +++ b/tests/e2e/batches/test_batches_e2e.py @@ -16,12 +16,13 @@ misroute to the wrong provider fails the create. from __future__ import annotations import json -import os import time from typing import Callable import pytest +from e2e_config import unique_marker + from batch_client import ( BatchClient, BatchCreateBody, @@ -42,16 +43,36 @@ from e2e_http import ( FileUploadForm, Result, StreamingResponse, + Success, + UnknownApiError, require_successful_call, unwrap, ) from lifecycle import ResourceManager +from models import KeyGenerateBody, SpendLogRow, SpendLogsParams pytestmark = pytest.mark.e2e CREATED_BATCH_STATUSES = {"validating", "in_progress", "finalizing"} BATCH_CANCEL_DELAY_SECONDS = 2 BATCH_TERMINAL_BEFORE_CANCEL = {"failed", "cancelled", "expired"} +BATCH_CANCEL_RETRIES = 3 + + +def cancel_batch( + client: BatchClient, batch_id: str, *, key: str, provider: str | None +) -> BatchObject: + last = client.cancel_batch(batch_id, key=key, provider=provider) + for _ in range(BATCH_CANCEL_RETRIES - 1): + match last: + case Success(data=data): + return data + case UnknownApiError(status_code=500): + time.sleep(1) + last = client.cancel_batch(batch_id, key=key, provider=provider) + case _: + break + return unwrap(last) def render_jsonl(model: str) -> bytes: @@ -146,7 +167,10 @@ def assert_batch_object(batch: BatchObject) -> None: @pytest.mark.parametrize("cap", CAPABILITIES, ids=[c.id for c in CAPABILITIES]) def test_batch_lifecycle( - cap: Capability, client: BatchClient, resources: ResourceManager + cap: Capability, + client: BatchClient, + resources: ResourceManager, + batch_deployments: None, ) -> None: key = resources.key() provider = op_provider(cap) @@ -199,11 +223,9 @@ def test_batch_lifecycle( ) if pre_cancel.status == "completed": return - cancelled = unwrap(client.cancel_batch(batch.id, key=key, provider=provider)) + cancelled = cancel_batch(client, batch.id, key=key, provider=provider) assert cancelled.id == batch.id assert cancelled.object == "batch" - # Vertex cancel is async: the job may still show its pre-cancel status - # briefly before transitioning to cancelling/cancelled. valid_post_cancel = {"cancelling", "cancelled"} if cap.provider == "vertex_ai": valid_post_cancel |= CREATED_BATCH_STATUSES @@ -213,7 +235,6 @@ def test_batch_lifecycle( if cap.can_list: listed = unwrap(client.list_batches(key=key, provider=provider)) - # OpenAI includes object="list"; Azure provider list often omits the envelope field. if listed.object is not None: assert listed.object == "list", f"list envelope object={listed.object!r}" match = next((b for b in listed.data if b.id == batch.id), None) @@ -222,7 +243,7 @@ def test_batch_lifecycle( def test_batch_key_model_access_denied( - client: BatchClient, resources: ResourceManager + client: BatchClient, resources: ResourceManager, batch_deployments: None ) -> None: key = resources.key(models=["openai-batch"]) @@ -257,7 +278,7 @@ def test_batch_key_model_access_denied( def test_file_upload_and_delete_outputs( - client: BatchClient, resources: ResourceManager + client: BatchClient, resources: ResourceManager, batch_deployments: None ) -> None: key = resources.key() file = unwrap( @@ -276,14 +297,69 @@ def test_file_upload_and_delete_outputs( assert deleted.deleted is True, "file was not reported deleted" -def test_anthropic_batch_retrieve(client: BatchClient, scoped_key: str) -> None: - batch_id = os.environ.get("ANTHROPIC_BATCH_ID") - if not batch_id: - pytest.skip( - "set ANTHROPIC_BATCH_ID to a real anthropic batch id to exercise retrieve" - ) - fetched = unwrap( - client.retrieve_batch(batch_id, key=scoped_key, provider="anthropic") +def unattributed_rows(rows: list[SpendLogRow]) -> list[SpendLogRow]: + """Spend rows that carry no caller identity (empty api_key). + + Every request the proxy bills is stamped with the calling key. A row with no + api_key is one the proxy could not attribute; LIT-3266 is exactly this: the + batch rate limiter's internal input-file read ran without the batch's auth + metadata, landing a spend row with empty api_key/user. The symptom is not + tied to a single call_type, so this catches any unattributed row rather than + only a named file-content one. + """ + return [row for row in rows if not row.api_key] + + +def test_rate_limited_batch_create_leaves_no_unattributed_spend_row( + client: BatchClient, resources: ResourceManager, batch_deployments: None +) -> None: + """LIT-3266: creating a batch on a rate-limited key runs the batch rate + limiter, which reads the input file to count tokens (the limiter only reads + the file when the key has applicable rpm/tpm limits, so an unlimited key + hides the path). That internal read must carry the batch's auth metadata; + the reported gap was that it did not, spawning a spend-log row with empty + api_key/user. Create returning 200 is not a reliable signal (the read error + is swallowed), so this asserts the hygiene contract instead: the operation + introduces no new unattributed spend row. + + The key sets generous rpm/tpm limits (not a restrictive model allowlist) so + the file-read path fires while the batch itself is not blocked. + ``resources.key()`` cannot set limits, so the key is minted on the gateway + directly and its delete deferred. + """ + user_id = f"e2e-batch-rl-{unique_marker()}" + key = client.gateway.generate_key( + KeyGenerateBody(models=[], tpm_limit=1_000_000, rpm_limit=1_000, user_id=user_id) + ) + resources.defer(lambda: client.gateway.delete_key(key)) + + before = frozenset( + row.request_id for row in unattributed_rows(client.gateway.spend_logs(SpendLogsParams())) + ) + + file = unwrap( + client.upload_file( + content=render_jsonl("gpt-4o-mini"), + form=FileUploadForm(purpose="batch"), + model="openai-batch", + key=key, + ) + ) + resources.defer(quietly(lambda: client.delete_file(file.id, key=key))) + + created = client.create_batch(body=BatchCreateBody(input_file_id=file.id), key=key) + require_successful_call(created) + batch = BatchObject.model_validate_json(created.body) + resources.defer(quietly(lambda: client.cancel_batch(batch.id, key=key))) + + _ = client.gateway.poll_logs_for_key(key, min_rows=1) + + new_orphans = [ + row + for row in unattributed_rows(client.gateway.spend_logs(SpendLogsParams())) + if row.request_id not in before + ] + assert not new_orphans, ( + "batch create on a rate-limited key left an unattributed spend row " + f"(LIT-3266); rows={[(r.request_id, r.call_type, r.model) for r in new_orphans]}" ) - assert fetched.id == batch_id - assert fetched.status diff --git a/tests/e2e/conftest.py b/tests/e2e/conftest.py index cc95c7538dd..9ca5840df24 100644 --- a/tests/e2e/conftest.py +++ b/tests/e2e/conftest.py @@ -33,6 +33,10 @@ def pytest_configure(config: pytest.Config) -> None: "markers", "e2e: live test that requires a running proxy and real provider keys", ) + config.addinivalue_line( + "markers", + "covers(cell_id, *, exercised_on=()): coverage-registry cell(s) this test covers", + ) def _liveness_reason(label: str, base_url: str) -> str | None: diff --git a/tests/e2e/coverage_registry/README.md b/tests/e2e/coverage_registry/README.md new file mode 100644 index 00000000000..a8aab3fcfbc --- /dev/null +++ b/tests/e2e/coverage_registry/README.md @@ -0,0 +1,55 @@ +# e2e coverage registry + +This directory is the **denominator** for e2e test coverage: the set of behaviors we +want covered, one row per behavior, checked into the repo so coverage is a number we +can track instead of a guess. It implements the plan in the "E2E Coverage Tracking" +note; the naming grammar lives in `tests/e2e/CLAUDE.md`. + +## The model + +A **cell** is one customer-noticeable behavior a single e2e test can assert pass/fail +on, for example `llm.chat_completions.bedrock_converse.tool_use.stream.works`. Cells are +grouped `module > feature > test`, six dashboard modules in all. Each cell carries a +tier (P0/P1/P2), a source, and a `fail_before_fix` flag. + +The rows live in per-prefix YAML files (`llm_*.yaml`, `mgmt.yaml`, `mcp.yaml`, +`reliability.yaml`, `logging.yaml`, `guardrail.yaml`, `other.yaml`) and validate against +the discriminated union in `schema.py`, so an LLM row cannot carry a guardrail field and +vice versa. `logging` and `guardrail` are two id-prefixes that roll up into the single +"Logging & Guardrails" dashboard module. + +A test declares what it covers with a marker: + +```python +@pytest.mark.covers("llm.chat_completions.openai.tool_use.stream.works") +def test_openai_streaming_tool_calls(self) -> None: + ... +``` + +## The number + +`collector.py` diffs the registry against those markers and reports coverage per module. +It is static: a collect-only pass reads the markers, so it runs no test and needs no live +proxy. Whether a covered cell currently passes or fails is a separate, live concern. + +``` +cd tests/e2e && PYTHONPATH=. python -m coverage_registry.collector +``` + +The headline is P0 coverage. The collector also lists markers that point at ids not in +the registry, so a typo or an unenumerated behavior surfaces instead of being silently +dropped. + +## Status: this is a draft for review + +The cells were enumerated from the codebase and the tiers are a first proposal. Known +things to settle before treating the set as final: + +- tiers are proposed, not signed off; 125 P0 is a lot to prove fail-before-fix, so P0 may + want tightening +- a few cells need a support check or a prune (for example `llm.embeddings.anthropic.*` + and `reliability.perf.throughput.under_slo`) +- auth is covered in two places (`other.auth.*` and the mgmt authz assertions); the + boundary needs a decision, and the auth cluster may deserve promotion to its own module +- the P2 "niche" cells each stand in for a large tail of integrations/providers by design, + so the denominator is deliberately P0-weighted rather than a full inventory diff --git a/tests/e2e/coverage_registry/__init__.py b/tests/e2e/coverage_registry/__init__.py new file mode 100644 index 00000000000..959b3327194 --- /dev/null +++ b/tests/e2e/coverage_registry/__init__.py @@ -0,0 +1,8 @@ +"""The e2e coverage registry: the denominator for e2e test coverage. + +`schema.py` defines one validated row per customer-noticeable behavior (a "cell"). +The `*.yaml` files hold the rows, one file per id-prefix. `registry.py` loads and +validates them; `collector.py` diffs the registry against the `@pytest.mark.covers` +markers on the live tests and reports coverage per module. See tests/e2e/CLAUDE.md +for the naming grammar. +""" diff --git a/tests/e2e/coverage_registry/collector.py b/tests/e2e/coverage_registry/collector.py new file mode 100644 index 00000000000..cc2ce16e3ea --- /dev/null +++ b/tests/e2e/coverage_registry/collector.py @@ -0,0 +1,156 @@ +"""Diff the registry (denominator) against the @pytest.mark.covers markers on the +live tests (numerator) and report coverage per module. + +Coverage here is static: it reads the markers via a collect-only pass, so it runs +no test and needs no live proxy. Whether a covered cell currently passes or fails +(covered_pass vs covered_fail) is a separate, live concern layered on top later. + + cd tests/e2e && PYTHONPATH=. python -m coverage_registry.collector +""" + +from __future__ import annotations + +import contextlib +import io +import sys +from dataclasses import dataclass +from pathlib import Path + +import pytest + +from .registry import load_registry +from .schema import MODULE_ORDER, ROLLUP, Cell, Tier + +E2E_DIR = Path(__file__).resolve().parent.parent + + +class _CoversSink: + """Pytest plugin: after collection, capture every cell id declared via + @pytest.mark.covers(...), plus any nodes that failed to import.""" + + def __init__(self) -> None: + self.covered_ids: frozenset[str] = frozenset() + self.collection_errors: tuple[str, ...] = () + + def pytest_collection_finish(self, session: pytest.Session) -> None: + self.covered_ids = frozenset( + arg + for item in session.items + for marker in item.iter_markers(name="covers") + for arg in marker.args + if isinstance(arg, str) + ) + + def pytest_collectreport(self, report: pytest.CollectReport) -> None: + if report.failed: + self.collection_errors = (*self.collection_errors, report.nodeid) + + +def collect_covered_ids(e2e_dir: Path = E2E_DIR) -> tuple[frozenset[str], tuple[str, ...]]: + """Return (covered cell ids, nodeids that failed to import).""" + sink = _CoversSink() + with contextlib.redirect_stdout(io.StringIO()): + pytest.main( + ["--collect-only", "-qq", "--continue-on-collection-errors", "-p", "no:cacheprovider", str(e2e_dir)], + plugins=[sink], + ) + return sink.covered_ids, sink.collection_errors + + +@dataclass(frozen=True, slots=True) +class ModuleCoverage: + module: str + total: int + covered: int + p0_total: int + p0_covered: int + + +@dataclass(frozen=True, slots=True) +class CoverageReport: + modules: tuple[ModuleCoverage, ...] + total: int + covered: int + p0_total: int + p0_covered: int + p0_gaps: tuple[str, ...] + orphan_markers: tuple[str, ...] + collection_errors: tuple[str, ...] + + +def _module_coverage(module: str, cells: tuple[Cell, ...], covered: frozenset[str]) -> ModuleCoverage: + in_module = tuple(c for c in cells if ROLLUP[c.module] == module) + p0 = tuple(c for c in in_module if c.tier is Tier.P0) + return ModuleCoverage( + module=module, + total=len(in_module), + covered=sum(1 for c in in_module if c.id in covered), + p0_total=len(p0), + p0_covered=sum(1 for c in p0 if c.id in covered), + ) + + +def compute_coverage( + cells: tuple[Cell, ...], + covered: frozenset[str], + collection_errors: tuple[str, ...] = (), +) -> CoverageReport: + p0_cells = tuple(c for c in cells if c.tier is Tier.P0) + registry_ids = frozenset(c.id for c in cells) + return CoverageReport( + modules=tuple(_module_coverage(m, cells, covered) for m in MODULE_ORDER), + total=len(cells), + covered=sum(1 for c in cells if c.id in covered), + p0_total=len(p0_cells), + p0_covered=sum(1 for c in p0_cells if c.id in covered), + p0_gaps=tuple(sorted(c.id for c in p0_cells if c.id not in covered)), + orphan_markers=tuple(sorted(covered - registry_ids)), + collection_errors=collection_errors, + ) + + +def _row(label: str, covered: int, total: int, p0_covered: int, p0_total: int) -> str: + frac = f"{covered}/{total}" + p0 = f"{p0_covered}/{p0_total}" + return f"{label:30}{frac:>12}{p0:>14}" + + +def render(report: CoverageReport) -> str: + rows = tuple(_row(m.module, m.covered, m.total, m.p0_covered, m.p0_total) for m in report.modules) + pct = (100.0 * report.p0_covered / report.p0_total) if report.p0_total else 0.0 + lines = ( + f"{'MODULE':30}{'COVERED':>12}{'P0 COVERED':>14}", + *rows, + "-" * 56, + _row("ALL", report.covered, report.total, report.p0_covered, report.p0_total), + "", + f"Headline (P0 coverage): {report.p0_covered}/{report.p0_total} ({pct:.1f}%)", + ) + orphans = ( + ( + f"\n{len(report.orphan_markers)} marker(s) point at ids not in the registry " + f"(reconcile: fix the marker or add the cell):\n " + "\n ".join(report.orphan_markers), + ) + if report.orphan_markers + else () + ) + warning = ( + ( + f"\nWARNING: {len(report.collection_errors)} node(s) failed to import during " + f"collection, so coverage may undercount:\n " + "\n ".join(report.collection_errors), + ) + if report.collection_errors + else () + ) + return "\n".join((*lines, *orphans, *warning)) + + +def main() -> int: + cells = load_registry() + covered, errors = collect_covered_ids() + print(render(compute_coverage(cells, covered, errors))) # noqa: T201 # CLI entrypoint output + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/e2e/coverage_registry/guardrail.yaml b/tests/e2e/coverage_registry/guardrail.yaml new file mode 100644 index 00000000000..792cbaaff7c --- /dev/null +++ b/tests/e2e/coverage_registry/guardrail.yaml @@ -0,0 +1,29 @@ +# Guardrail enforcement (behavior features). Grounded in litellm/proxy/guardrails/guardrail_hooks/. +# Rolls up into the "Logging & Guardrails" dashboard module together with logging.* +- {id: guardrail.presidio.pre_call.masks, module: guardrail, tier: P0, hook_point: pre_call, assertions: [masks], exercised_on: [chat_completions, messages], source: "guardrail_hooks/presidio.py", rationale: "PII masking pre-call; data-leak blast radius"} +- {id: guardrail.presidio.post_call.masks, module: guardrail, tier: P0, hook_point: post_call, assertions: [masks], exercised_on: [chat_completions, messages], source: "guardrail_hooks/presidio.py", rationale: "Mask PII in model output"} +- {id: guardrail.presidio.logging_only.masks, module: guardrail, tier: P0, hook_point: logging_only, assertions: [masks], exercised_on: [chat_completions, messages], source: "guardrail_hooks/presidio.py", rationale: "Redact in logs without blocking"} +- {id: guardrail.bedrock.pre_call.blocks, module: guardrail, tier: P0, hook_point: pre_call, assertions: [blocks], exercised_on: [chat_completions], source: "guardrail_hooks/bedrock_guardrails.py", rationale: "AWS content guardrail blocks harmful input"} +- {id: guardrail.bedrock.during.blocks, module: guardrail, tier: P0, hook_point: during, assertions: [blocks], exercised_on: [chat_completions], source: "guardrail_hooks/bedrock_guardrails.py", rationale: "During-call moderation for streaming"} +- {id: guardrail.bedrock.post_call.blocks, module: guardrail, tier: P0, hook_point: post_call, assertions: [blocks], exercised_on: [chat_completions], source: "guardrail_hooks/bedrock_guardrails.py", rationale: "Block harmful output"} +- {id: guardrail.lakera.pre_call.blocks, module: guardrail, tier: P0, hook_point: pre_call, assertions: [blocks], exercised_on: [chat_completions, messages], source: "guardrail_hooks/lakera_ai_v2.py", rationale: "Prompt-injection block pre-execution"} +- {id: guardrail.lakera.post_call.blocks, module: guardrail, tier: P0, hook_point: post_call, assertions: [blocks], exercised_on: [chat_completions], source: "guardrail_hooks/lakera_ai_v2.py", rationale: "Post-call injection on multi-turn chains"} +- {id: guardrail.openai_moderations.pre_call.blocks, module: guardrail, tier: P0, hook_point: pre_call, assertions: [blocks], exercised_on: [chat_completions, messages], source: "guardrail_hooks/openai/moderations.py", rationale: "Content policy for regulated industries"} +- {id: guardrail.aim.pre_call.blocks, module: guardrail, tier: P1, hook_point: pre_call, assertions: [blocks], exercised_on: [chat_completions, messages], source: "guardrail_hooks/aim/aim.py", rationale: "Security guardrail malicious-input"} +- {id: guardrail.aim.post_call.blocks, module: guardrail, tier: P1, hook_point: post_call, assertions: [blocks], exercised_on: [chat_completions], source: "guardrail_hooks/aim/aim.py", rationale: "Output security check"} +- {id: guardrail.ibm_guardrails.pre_call.blocks, module: guardrail, tier: P1, hook_point: pre_call, assertions: [blocks], exercised_on: [chat_completions], source: "guardrail_hooks/ibm_guardrails/ibm_detector.py", rationale: "Enterprise multi-policy"} +- {id: guardrail.ibm_guardrails.post_call.blocks, module: guardrail, tier: P1, hook_point: post_call, assertions: [blocks], exercised_on: [chat_completions], source: "guardrail_hooks/ibm_guardrails/ibm_detector.py", rationale: "Output policy validation"} +- {id: guardrail.semantic_guard.pre_call.blocks, module: guardrail, tier: P1, hook_point: pre_call, assertions: [blocks], exercised_on: [chat_completions], source: "guardrail_hooks/semantic_guard", rationale: "Semantic policy compliance"} +- {id: guardrail.block_code_execution.pre_call.blocks, module: guardrail, tier: P1, hook_point: pre_call, assertions: [blocks], exercised_on: [chat_completions], source: "guardrail_hooks/block_code_execution", rationale: "Code-injection prevention"} +- {id: guardrail.tool_permission.pre_call.allows, module: guardrail, tier: P1, hook_point: pre_call, assertions: [allows], exercised_on: [chat_completions], source: "guardrail_hooks/tool_permission.py", rationale: "Grant allowed tools"} +- {id: guardrail.tool_permission.pre_call.blocks, module: guardrail, tier: P1, hook_point: pre_call, assertions: [blocks], exercised_on: [chat_completions], source: "guardrail_hooks/tool_permission.py", rationale: "Block unauthorized tools"} +- {id: guardrail.microsoft_purview.pre_call.blocks, module: guardrail, tier: P1, hook_point: pre_call, assertions: [blocks], exercised_on: [chat_completions], source: "guardrail_hooks/microsoft_purview/purview_dlp.py", rationale: "DLP sensitive-data disclosure"} +- {id: guardrail.headroom.pre_call.blocks, module: guardrail, tier: P1, hook_point: pre_call, assertions: [blocks], exercised_on: [chat_completions], source: "guardrail_hooks/headroom/headroom.py", rationale: "Anomaly detection threshold"} +- {id: guardrail.generic_guardrail_api.pre_call.blocks, module: guardrail, tier: P1, hook_point: pre_call, assertions: [blocks], exercised_on: [chat_completions], source: "guardrail_hooks/generic_guardrail_api/generic_guardrail_api.py", rationale: "Vendor-agnostic custom API"} +- {id: guardrail.pangea.pre_call.blocks, module: guardrail, tier: P1, hook_point: pre_call, assertions: [blocks], exercised_on: [chat_completions], source: "guardrail_hooks/pangea/pangea.py", rationale: "API security + DLP"} +- {id: guardrail.niche_providers.pre_call.blocks, module: guardrail, tier: P2, hook_point: pre_call, assertions: [blocks], exercised_on: [chat_completions], source: grammar, rationale: "SMOKE cohort: lasso/hiddenlayer/model_armor/qualifire/guardrails_ai/cato/cisco/akto/prompt_security/promptguard/zscaler/vigil/etc"} +- {id: guardrail.niche_providers.post_call.blocks, module: guardrail, tier: P2, hook_point: post_call, assertions: [blocks], exercised_on: [chat_completions], source: grammar, rationale: "SMOKE niche output filtering"} +- {id: guardrail.niche_providers.pre_call.allows, module: guardrail, tier: P2, hook_point: pre_call, assertions: [allows], exercised_on: [chat_completions], source: grammar, rationale: "SMOKE niche allow-path passthrough"} +- {id: guardrail.tool_policy.pre_call.blocks, module: guardrail, tier: P2, hook_point: pre_call, assertions: [blocks], exercised_on: [chat_completions], source: "guardrail_hooks/tool_policy/tool_policy_guardrail.py", rationale: "Tool-use policy enforcement"} +- {id: guardrail.mcp_security.pre_call.blocks, module: guardrail, tier: P2, hook_point: pre_call, assertions: [blocks], exercised_on: [mcp_operations], source: "guardrail_hooks/mcp_security", rationale: "MCP protocol security"} +- {id: guardrail.llm_as_a_judge.pre_call.blocks, module: guardrail, tier: P2, hook_point: pre_call, assertions: [blocks], exercised_on: [chat_completions], source: "guardrail_hooks/llm_as_a_judge", rationale: "LLM-based judgment guardrail"} diff --git a/tests/e2e/coverage_registry/llm_conversational.yaml b/tests/e2e/coverage_registry/llm_conversational.yaml new file mode 100644 index 00000000000..365776da8bf --- /dev/null +++ b/tests/e2e/coverage_registry/llm_conversational.yaml @@ -0,0 +1,53 @@ +# LLM conversational endpoints (chat_completions, messages, responses). Grounded in proxy handlers + model_prices json. +- {id: llm.chat_completions.openai.basic.nonstream.works, module: llm, tier: P0, subject_endpoint: chat_completions, route: openai, capability: basic, streaming: nonstream, assertions: [works], source: "proxy_server.py:8455", rationale: "Core endpoint/route/capability"} +- {id: llm.chat_completions.openai.basic.stream.works, module: llm, tier: P0, subject_endpoint: chat_completions, route: openai, capability: basic, streaming: stream, assertions: [works], source: "proxy_server.py:8455", rationale: "Core streaming"} +- {id: llm.chat_completions.openai.basic.nonstream.cost_logged, module: llm, tier: P0, subject_endpoint: chat_completions, route: openai, capability: basic, streaming: nonstream, assertions: [works, cost_logged], source: "proxy_server.py:8455", rationale: "Cost logging regression catch"} +- {id: llm.chat_completions.openai.tool_use.nonstream.works, module: llm, tier: P0, subject_endpoint: chat_completions, route: openai, capability: tool_use, streaming: nonstream, assertions: [works], source: "model_prices json", rationale: "OpenAI function_calling; high usage"} +- {id: llm.chat_completions.openai.tool_use.stream.works, module: llm, tier: P0, subject_endpoint: chat_completions, route: openai, capability: tool_use, streaming: stream, assertions: [works], source: "model_prices json", rationale: "Tool calls over streaming"} +- {id: llm.chat_completions.openai.vision.nonstream.works, module: llm, tier: P0, subject_endpoint: chat_completions, route: openai, capability: vision, streaming: nonstream, assertions: [works], source: "model_prices json", rationale: "gpt-4o vision; high usage"} +- {id: llm.chat_completions.openai.prompt_cache_5m.nonstream.works, module: llm, tier: P0, subject_endpoint: chat_completions, route: openai, capability: prompt_cache_5m, streaming: nonstream, assertions: [works], source: "model_prices json", rationale: "Prompt caching cost optimization"} +- {id: llm.chat_completions.openai.thinking.nonstream.works, module: llm, tier: P1, subject_endpoint: chat_completions, route: openai, capability: thinking, streaming: nonstream, assertions: [works], source: "model_prices json", rationale: "o-series reasoning; emerging"} +- {id: llm.chat_completions.openai.structured_output.nonstream.works, module: llm, tier: P1, subject_endpoint: chat_completions, route: openai, capability: structured_output, streaming: nonstream, assertions: [works], source: "model_prices json", rationale: "response_schema extraction"} +- {id: llm.chat_completions.anthropic.basic.nonstream.works, module: llm, tier: P0, subject_endpoint: chat_completions, route: anthropic, capability: basic, streaming: nonstream, assertions: [works], source: "proxy_server.py:8455", rationale: "P0 route translated to Anthropic"} +- {id: llm.chat_completions.anthropic.basic.stream.works, module: llm, tier: P0, subject_endpoint: chat_completions, route: anthropic, capability: basic, streaming: stream, assertions: [works], source: "proxy_server.py:8455", rationale: "Streaming translation"} +- {id: llm.chat_completions.anthropic.tool_use.nonstream.works, module: llm, tier: P0, subject_endpoint: chat_completions, route: anthropic, capability: tool_use, streaming: nonstream, assertions: [works], source: "model_prices json", rationale: "Claude tool_use; high usage"} +- {id: llm.chat_completions.anthropic.tool_use.stream.works, module: llm, tier: P0, subject_endpoint: chat_completions, route: anthropic, capability: tool_use, streaming: stream, assertions: [works], source: "model_prices json", rationale: "Streaming tool calls"} +- {id: llm.chat_completions.anthropic.vision.nonstream.works, module: llm, tier: P0, subject_endpoint: chat_completions, route: anthropic, capability: vision, streaming: nonstream, assertions: [works], source: "model_prices json", rationale: "Claude vision; high usage"} +- {id: llm.chat_completions.anthropic.prompt_cache_5m.nonstream.works, module: llm, tier: P0, subject_endpoint: chat_completions, route: anthropic, capability: prompt_cache_5m, streaming: nonstream, assertions: [works], source: "model_prices json", rationale: "Claude prompt caching"} +- {id: llm.chat_completions.anthropic.thinking.nonstream.works, module: llm, tier: P1, subject_endpoint: chat_completions, route: anthropic, capability: thinking, streaming: nonstream, assertions: [works], source: "model_prices json", rationale: "Claude extended thinking"} +- {id: llm.chat_completions.anthropic.structured_output.nonstream.works, module: llm, tier: P1, subject_endpoint: chat_completions, route: anthropic, capability: structured_output, streaming: nonstream, assertions: [works], source: "model_prices json", rationale: "Claude response_schema"} +- {id: llm.chat_completions.bedrock_converse.basic.nonstream.works, module: llm, tier: P0, subject_endpoint: chat_completions, route: bedrock_converse, capability: basic, streaming: nonstream, assertions: [works], source: "proxy_server.py:8455", rationale: "P0 route; Bedrock Converse unified"} +- {id: llm.chat_completions.bedrock_converse.basic.stream.works, module: llm, tier: P0, subject_endpoint: chat_completions, route: bedrock_converse, capability: basic, streaming: stream, assertions: [works], source: "proxy_server.py:8455", rationale: "Streaming over Converse"} +- {id: llm.chat_completions.bedrock_converse.tool_use.nonstream.works, module: llm, tier: P0, subject_endpoint: chat_completions, route: bedrock_converse, capability: tool_use, streaming: nonstream, assertions: [works], source: "model_prices json", rationale: "Converse function_calling; AWS adoption"} +- {id: llm.chat_completions.bedrock_converse.vision.nonstream.works, module: llm, tier: P0, subject_endpoint: chat_completions, route: bedrock_converse, capability: vision, streaming: nonstream, assertions: [works], source: "model_prices json", rationale: "Bedrock vision (Anthropic/Nova)"} +- {id: llm.chat_completions.bedrock_converse.prompt_cache_5m.nonstream.works, module: llm, tier: P1, subject_endpoint: chat_completions, route: bedrock_converse, capability: prompt_cache_5m, streaming: nonstream, assertions: [works], source: "model_prices json", rationale: "Anthropic-on-Bedrock caching"} +- {id: llm.chat_completions.bedrock_converse.thinking.nonstream.works, module: llm, tier: P1, subject_endpoint: chat_completions, route: bedrock_converse, capability: thinking, streaming: nonstream, assertions: [works], source: "model_prices json", rationale: "Anthropic thinking on Bedrock"} +- {id: llm.chat_completions.vertex.basic.nonstream.works, module: llm, tier: P0, subject_endpoint: chat_completions, route: vertex, capability: basic, streaming: nonstream, assertions: [works], source: "proxy_server.py:8455", rationale: "P0 route; Vertex AI"} +- {id: llm.chat_completions.vertex.basic.stream.works, module: llm, tier: P0, subject_endpoint: chat_completions, route: vertex, capability: basic, streaming: stream, assertions: [works], source: "proxy_server.py:8455", rationale: "Streaming over Vertex"} +- {id: llm.chat_completions.vertex.tool_use.nonstream.works, module: llm, tier: P0, subject_endpoint: chat_completions, route: vertex, capability: tool_use, streaming: nonstream, assertions: [works], source: "model_prices json", rationale: "Vertex Gemini function_calling"} +- {id: llm.chat_completions.vertex.vision.nonstream.works, module: llm, tier: P0, subject_endpoint: chat_completions, route: vertex, capability: vision, streaming: nonstream, assertions: [works], source: "model_prices json", rationale: "Gemini vision"} +- {id: llm.chat_completions.vertex.prompt_cache_5m.nonstream.works, module: llm, tier: P1, subject_endpoint: chat_completions, route: vertex, capability: prompt_cache_5m, streaming: nonstream, assertions: [works], source: "model_prices json", rationale: "Vertex Gemini prompt caching"} +- {id: llm.chat_completions.azure_openai.basic.nonstream.works, module: llm, tier: P0, subject_endpoint: chat_completions, route: azure_openai, capability: basic, streaming: nonstream, assertions: [works], source: "proxy_server.py:8455", rationale: "P0 route; Azure OpenAI deployments"} +- {id: llm.chat_completions.azure_openai.tool_use.nonstream.works, module: llm, tier: P0, subject_endpoint: chat_completions, route: azure_openai, capability: tool_use, streaming: nonstream, assertions: [works], source: "model_prices json", rationale: "Azure OpenAI function_calling"} +- {id: llm.chat_completions.azure_foundry.basic.nonstream.works, module: llm, tier: P1, subject_endpoint: chat_completions, route: azure_foundry, capability: basic, streaming: nonstream, assertions: [works], source: "proxy_server.py:8455", rationale: "Azure Foundry (azure_ai); newer, smoke"} +- {id: llm.messages.anthropic.basic.nonstream.works, module: llm, tier: P0, subject_endpoint: messages, route: anthropic, capability: basic, streaming: nonstream, assertions: [works], source: "anthropic_endpoints/endpoints.py:64", rationale: "Core endpoint; Anthropic Messages native"} +- {id: llm.messages.anthropic.basic.stream.works, module: llm, tier: P0, subject_endpoint: messages, route: anthropic, capability: basic, streaming: stream, assertions: [works], source: "anthropic_endpoints/endpoints.py:64", rationale: "Streaming Messages API"} +- {id: llm.messages.anthropic.basic.nonstream.cost_logged, module: llm, tier: P0, subject_endpoint: messages, route: anthropic, capability: basic, streaming: nonstream, assertions: [works, cost_logged], source: "anthropic_endpoints/endpoints.py:64", rationale: "Cost logged on passthrough"} +- {id: llm.messages.anthropic.tool_use.nonstream.works, module: llm, tier: P0, subject_endpoint: messages, route: anthropic, capability: tool_use, streaming: nonstream, assertions: [works], source: "model_prices json", rationale: "Tool calls via Messages API"} +- {id: llm.messages.anthropic.tool_use.stream.works, module: llm, tier: P0, subject_endpoint: messages, route: anthropic, capability: tool_use, streaming: stream, assertions: [works], source: "model_prices json", rationale: "Streaming tool calls"} +- {id: llm.messages.anthropic.vision.nonstream.works, module: llm, tier: P0, subject_endpoint: messages, route: anthropic, capability: vision, streaming: nonstream, assertions: [works], source: "model_prices json", rationale: "Vision via Messages API"} +- {id: llm.messages.anthropic.prompt_cache_5m.nonstream.works, module: llm, tier: P0, subject_endpoint: messages, route: anthropic, capability: prompt_cache_5m, streaming: nonstream, assertions: [works], source: "model_prices json", rationale: "Prompt caching via Messages API"} +- {id: llm.messages.anthropic.thinking.nonstream.works, module: llm, tier: P1, subject_endpoint: messages, route: anthropic, capability: thinking, streaming: nonstream, assertions: [works], source: "model_prices json", rationale: "Extended thinking via Messages API"} +- {id: llm.responses.openai.basic.nonstream.works, module: llm, tier: P0, subject_endpoint: responses, route: openai, capability: basic, streaming: nonstream, assertions: [works], source: "response_api_endpoints/endpoints.py:26", rationale: "Core endpoint; OpenAI Responses native"} +- {id: llm.responses.openai.basic.stream.works, module: llm, tier: P0, subject_endpoint: responses, route: openai, capability: basic, streaming: stream, assertions: [works], source: "response_api_endpoints/endpoints.py:26", rationale: "Streaming via /v1/responses"} +- {id: llm.responses.openai.basic.nonstream.cost_logged, module: llm, tier: P0, subject_endpoint: responses, route: openai, capability: basic, streaming: nonstream, assertions: [works, cost_logged], source: "response_api_endpoints/endpoints.py:26", rationale: "Cost logged on responses"} +- {id: llm.responses.openai.tool_use.nonstream.works, module: llm, tier: P0, subject_endpoint: responses, route: openai, capability: tool_use, streaming: nonstream, assertions: [works], source: "model_prices json", rationale: "Tool calls via Responses API"} +- {id: llm.responses.openai.vision.nonstream.works, module: llm, tier: P0, subject_endpoint: responses, route: openai, capability: vision, streaming: nonstream, assertions: [works], source: "model_prices json", rationale: "Vision via Responses API"} +- {id: llm.responses.anthropic.basic.nonstream.works, module: llm, tier: P1, subject_endpoint: responses, route: anthropic, capability: basic, streaming: nonstream, assertions: [works], source: "response_api_endpoints/endpoints.py:26", rationale: "Responses w/ Anthropic translation (smoke)"} +- {id: llm.responses.anthropic.tool_use.nonstream.works, module: llm, tier: P1, subject_endpoint: responses, route: anthropic, capability: tool_use, streaming: nonstream, assertions: [works], source: "model_prices json", rationale: "Responses tool calls w/ Anthropic"} +- {id: llm.responses.bedrock_converse.basic.nonstream.works, module: llm, tier: P1, subject_endpoint: responses, route: bedrock_converse, capability: basic, streaming: nonstream, assertions: [works], source: "response_api_endpoints/endpoints.py:26", rationale: "Responses w/ Bedrock Converse (smoke)"} +- {id: llm.responses.bedrock_converse.tool_use.nonstream.works, module: llm, tier: P1, subject_endpoint: responses, route: bedrock_converse, capability: tool_use, streaming: nonstream, assertions: [works], source: "model_prices json", rationale: "Responses tool calls w/ Converse"} +- {id: llm.responses.vertex.basic.nonstream.works, module: llm, tier: P1, subject_endpoint: responses, route: vertex, capability: basic, streaming: nonstream, assertions: [works], source: "response_api_endpoints/endpoints.py:26", rationale: "Responses w/ Vertex (smoke)"} +- {id: llm.responses.vertex.tool_use.nonstream.works, module: llm, tier: P1, subject_endpoint: responses, route: vertex, capability: tool_use, streaming: nonstream, assertions: [works], source: "model_prices json", rationale: "Responses tool calls w/ Vertex"} +- {id: llm.responses.azure_openai.basic.nonstream.works, module: llm, tier: P1, subject_endpoint: responses, route: azure_openai, capability: basic, streaming: nonstream, assertions: [works], source: "response_api_endpoints/endpoints.py:26", rationale: "Responses w/ Azure OpenAI (smoke)"} +- {id: llm.responses.azure_openai.tool_use.nonstream.works, module: llm, tier: P1, subject_endpoint: responses, route: azure_openai, capability: tool_use, streaming: nonstream, assertions: [works], source: "model_prices json", rationale: "Responses tool calls w/ Azure OpenAI"} diff --git a/tests/e2e/coverage_registry/llm_nonconversational.yaml b/tests/e2e/coverage_registry/llm_nonconversational.yaml new file mode 100644 index 00000000000..b01b219476d --- /dev/null +++ b/tests/e2e/coverage_registry/llm_nonconversational.yaml @@ -0,0 +1,45 @@ +# LLM non-conversational endpoints. Grounded in litellm/proxy endpoints + llms/ handlers. +- {id: llm.embeddings.openai.basic.nonstream.works, module: llm, tier: P0, subject_endpoint: embeddings, route: openai, capability: basic, streaming: nonstream, assertions: [works], source: "test_embeddings_endpoint_e2e.py:23", rationale: "Core endpoint, live vector response"} +- {id: llm.embeddings.openai.basic.nonstream.cost_logged, module: llm, tier: P0, subject_endpoint: embeddings, route: openai, capability: basic, streaming: nonstream, assertions: [cost_logged], source: "SPEND_TRACKING_COVERAGE_MATRIX.md:34", rationale: "Cost tracking on embeddings"} +- {id: llm.embeddings.azure_openai.basic.nonstream.works, module: llm, tier: P0, subject_endpoint: embeddings, route: azure_openai, capability: basic, streaming: nonstream, assertions: [works], source: "llms/azure/azure.py", rationale: "Azure embeddings via translation"} +- {id: llm.embeddings.bedrock.basic.nonstream.works, module: llm, tier: P1, subject_endpoint: embeddings, route: bedrock_converse, capability: basic, streaming: nonstream, assertions: [works], source: "llms/bedrock/embed/embedding.py", rationale: "Bedrock Titan embeddings"} +- {id: llm.embeddings.vertex.basic.nonstream.works, module: llm, tier: P1, subject_endpoint: embeddings, route: vertex, capability: basic, streaming: nonstream, assertions: [works], source: "vertex_embeddings/embedding_handler.py", rationale: "Vertex embeddings"} +- {id: llm.embeddings.cohere.basic.nonstream.works, module: llm, tier: P1, subject_endpoint: embeddings, route: cohere, capability: basic, streaming: nonstream, assertions: [works], source: "llms/cohere/embed/handler.py", rationale: "Cohere embeddings"} +- {id: llm.embeddings.anthropic.basic.nonstream.works, module: llm, tier: P1, subject_endpoint: embeddings, route: anthropic, capability: basic, streaming: nonstream, assertions: [works], source: "llms/anthropic/chat/handler.py", rationale: "Anthropic vector API (verify support)"} +- {id: llm.batches.openai.create.nonstream.works, module: llm, tier: P0, subject_endpoint: batches, route: openai, capability: basic, streaming: nonstream, assertions: [works], source: "test_batches_e2e.py", rationale: "Core batch create"} +- {id: llm.batches.openai.retrieve.nonstream.works, module: llm, tier: P0, subject_endpoint: batches, route: openai, capability: basic, streaming: nonstream, assertions: [works], source: "test_batches_e2e.py", rationale: "Batch retrieve, id round-trip + status"} +- {id: llm.batches.openai.cancel.nonstream.works, module: llm, tier: P0, subject_endpoint: batches, route: openai, capability: basic, streaming: nonstream, assertions: [works], source: "test_batches_e2e.py", rationale: "Batch cancel"} +- {id: llm.batches.openai.list.nonstream.works, module: llm, tier: P0, subject_endpoint: batches, route: openai, capability: basic, streaming: nonstream, assertions: [works], source: "test_batches_e2e.py", rationale: "Batch list envelope"} +- {id: llm.batches.openai.file_lifecycle.nonstream.works, module: llm, tier: P0, subject_endpoint: batches, route: openai, capability: basic, streaming: nonstream, assertions: [works], source: "test_batches_e2e.py", rationale: "File upload/retrieve/delete for batch flow"} +- {id: llm.batches.openai_encoded.basic.nonstream.works, module: llm, tier: P0, subject_endpoint: batches, route: openai, capability: basic, streaming: nonstream, assertions: [works], source: "batches/capabilities.py", rationale: "Encoded scenario lifecycle"} +- {id: llm.batches.openai_unified.basic.nonstream.works, module: llm, tier: P0, subject_endpoint: batches, route: openai, capability: basic, streaming: nonstream, assertions: [works], source: "batches/capabilities.py", rationale: "Unified/managed-id scenario"} +- {id: llm.batches.openai_model_param.basic.nonstream.works, module: llm, tier: P0, subject_endpoint: batches, route: openai, capability: basic, streaming: nonstream, assertions: [works], source: "batches/capabilities.py", rationale: "Model-param scenario"} +- {id: llm.batches.openai_provider_fallback.basic.nonstream.works, module: llm, tier: P0, subject_endpoint: batches, route: openai, capability: basic, streaming: nonstream, assertions: [works], source: "batches/capabilities.py", rationale: "Provider-fallback raw-id scenario"} +- {id: llm.batches.azure_openai.basic.nonstream.works, module: llm, tier: P0, subject_endpoint: batches, route: azure_openai, capability: basic, streaming: nonstream, assertions: [works], source: "batches/capabilities.py:98", rationale: "Azure batches all scenarios"} +- {id: llm.batches.vertex.basic.nonstream.works, module: llm, tier: P0, subject_endpoint: batches, route: vertex, capability: basic, streaming: nonstream, assertions: [works], source: "batches/capabilities.py:98", rationale: "Vertex batches"} +- {id: llm.batches.bedrock.basic.nonstream.works, module: llm, tier: P0, subject_endpoint: batches, route: bedrock_converse, capability: basic, streaming: nonstream, assertions: [works], source: "batches/capabilities.py:98", rationale: "Bedrock batches (encoded/unified only)"} +- {id: llm.batches.openai.key_model_access_denied.nonstream.works, module: llm, tier: P0, subject_endpoint: batches, route: openai, capability: basic, streaming: nonstream, assertions: [works], source: "test_batches_e2e.py", rationale: "Key model restriction 403 on upload/create"} +- {id: llm.files.openai.upload.nonstream.works, module: llm, tier: P0, subject_endpoint: files, route: openai, capability: basic, streaming: nonstream, assertions: [works], source: "openai_files_endpoints/files_endpoints.py:46", rationale: "File upload returns OpenAIFileObject"} +- {id: llm.files.openai.retrieve.nonstream.works, module: llm, tier: P0, subject_endpoint: files, route: openai, capability: basic, streaming: nonstream, assertions: [works], source: "files_endpoints.py", rationale: "File retrieve by id"} +- {id: llm.files.openai.delete.nonstream.works, module: llm, tier: P0, subject_endpoint: files, route: openai, capability: basic, streaming: nonstream, assertions: [works], source: "files_endpoints.py", rationale: "File delete returns deleted=true"} +- {id: llm.files.openai.list.nonstream.works, module: llm, tier: P0, subject_endpoint: files, route: openai, capability: basic, streaming: nonstream, assertions: [works], source: "files_endpoints.py", rationale: "File list paginated"} +- {id: llm.files.azure_openai.upload.nonstream.works, module: llm, tier: P0, subject_endpoint: files, route: azure_openai, capability: basic, streaming: nonstream, assertions: [works], source: "batches/capabilities.py:45", rationale: "Azure file upload managed backend"} +- {id: llm.files.vertex.upload.nonstream.works, module: llm, tier: P0, subject_endpoint: files, route: vertex, capability: basic, streaming: nonstream, assertions: [works], source: "batches/capabilities.py:52", rationale: "Vertex file upload to GCS"} +- {id: llm.files.bedrock.upload.nonstream.works, module: llm, tier: P0, subject_endpoint: files, route: bedrock_converse, capability: basic, streaming: nonstream, assertions: [works], source: "batches/capabilities.py:59", rationale: "Bedrock file upload to S3"} +- {id: llm.rerank.cohere.basic.nonstream.works, module: llm, tier: P1, subject_endpoint: rerank, route: cohere, capability: basic, streaming: nonstream, assertions: [works], source: "test_rerank_e2e.py:29", rationale: "Cohere rerank, top_n + relevance_score"} +- {id: llm.rerank.bedrock.basic.nonstream.works, module: llm, tier: P1, subject_endpoint: rerank, route: bedrock_converse, capability: basic, streaming: nonstream, assertions: [works], source: "llms/bedrock/rerank/handler.py", rationale: "Bedrock rerank"} +- {id: llm.rerank.together_ai.basic.nonstream.works, module: llm, tier: P1, subject_endpoint: rerank, route: together_ai, capability: basic, streaming: nonstream, assertions: [works], source: "llms/together_ai/rerank/handler.py", rationale: "Together rerank"} +- {id: llm.images_generations.openai.basic.nonstream.works, module: llm, tier: P1, subject_endpoint: images_generations, route: openai, capability: basic, streaming: nonstream, assertions: [works], source: "test_image_generation_e2e.py:22", rationale: "OpenAI image gen, b64/url"} +- {id: llm.images_generations.azure_openai.basic.nonstream.works, module: llm, tier: P1, subject_endpoint: images_generations, route: azure_openai, capability: basic, streaming: nonstream, assertions: [works], source: "llms/azure/azure.py", rationale: "Azure DALL-E"} +- {id: llm.images_generations.vertex.basic.nonstream.works, module: llm, tier: P1, subject_endpoint: images_generations, route: vertex, capability: basic, streaming: nonstream, assertions: [works], source: "vertex_ai/image_generation/image_generation_handler.py", rationale: "Vertex Imagen"} +- {id: llm.images_generations.bedrock.basic.nonstream.works, module: llm, tier: P1, subject_endpoint: images_generations, route: bedrock_converse, capability: basic, streaming: nonstream, assertions: [works], source: "bedrock/image_generation/image_handler.py", rationale: "Bedrock Titan Image"} +- {id: llm.images_generations.black_forest_labs.basic.nonstream.works, module: llm, tier: P1, subject_endpoint: images_generations, route: openai, capability: basic, streaming: nonstream, assertions: [works], source: "black_forest_labs/image_generation/handler.py", rationale: "BFL Flux via OpenAI-compat"} +- {id: llm.audio_speech.openai.basic.nonstream.works, module: llm, tier: P1, subject_endpoint: audio_speech, route: openai, capability: basic, streaming: nonstream, assertions: [works], source: "test_audio_speech_e2e.py:22", rationale: "OpenAI TTS binary audio"} +- {id: llm.audio_speech.openai.basic.stream.works, module: llm, tier: P1, subject_endpoint: audio_speech, route: openai, capability: basic, streaming: stream, assertions: [works], source: "proxy_server.py:9043", rationale: "TTS streaming chunk generator"} +- {id: llm.audio_speech.azure_openai.basic.nonstream.works, module: llm, tier: P1, subject_endpoint: audio_speech, route: azure_openai, capability: basic, streaming: nonstream, assertions: [works], source: "llms/azure/azure.py", rationale: "Azure TTS"} +- {id: llm.audio_speech.vertex.basic.nonstream.works, module: llm, tier: P1, subject_endpoint: audio_speech, route: vertex, capability: basic, streaming: nonstream, assertions: [works], source: "vertex_ai/text_to_speech/text_to_speech_handler.py", rationale: "Vertex TTS"} +- {id: llm.audio_transcriptions.openai.basic.nonstream.works, module: llm, tier: P1, subject_endpoint: audio_transcriptions, route: openai, capability: basic, streaming: nonstream, assertions: [works], source: "openai/transcriptions/handler.py", rationale: "OpenAI Whisper"} +- {id: llm.audio_transcriptions.azure_openai.basic.nonstream.works, module: llm, tier: P1, subject_endpoint: audio_transcriptions, route: azure_openai, capability: basic, streaming: nonstream, assertions: [works], source: "azure/audio_transcriptions.py", rationale: "Azure STT"} +- {id: llm.audio_transcriptions.soniox.basic.nonstream.works, module: llm, tier: P2, subject_endpoint: audio_transcriptions, route: openai, capability: basic, streaming: nonstream, assertions: [works], source: "soniox/audio_transcription/handler.py", rationale: "Soniox via OpenAI-compat (smoke)"} +- {id: llm.audio_transcriptions.nvidia_riva.basic.nonstream.works, module: llm, tier: P2, subject_endpoint: audio_transcriptions, route: openai, capability: basic, streaming: nonstream, assertions: [works], source: "nvidia_riva/audio_transcription/handler.py", rationale: "NVIDIA Riva (smoke)"} +- {id: llm.moderations.openai.basic.nonstream.works, module: llm, tier: P1, subject_endpoint: moderations, route: openai, capability: basic, streaming: nonstream, assertions: [works], source: "proxy_server.py", rationale: "OpenAI moderations (only provider)"} diff --git a/tests/e2e/coverage_registry/logging.yaml b/tests/e2e/coverage_registry/logging.yaml new file mode 100644 index 00000000000..65ab8f0096f --- /dev/null +++ b/tests/e2e/coverage_registry/logging.yaml @@ -0,0 +1,25 @@ +# Logging integration delivery (behavior features). Grounded in litellm/integrations/. +- {id: logging.langfuse.success.logs_spend, module: logging, tier: P0, event: success, assertions: [logs_spend], exercised_on: [chat_completions, messages, embeddings], source: "integrations/langfuse/langfuse.py", rationale: "Primary tracing backend; cost accuracy"} +- {id: logging.langfuse.failure.logs_spend, module: logging, tier: P0, event: failure, assertions: [logs_spend], exercised_on: [chat_completions, messages], source: "integrations/langfuse/langfuse.py", rationale: "Failure path must still track spend"} +- {id: logging.langfuse.stream.logs_spend, module: logging, tier: P0, event: stream, assertions: [logs_spend], exercised_on: [chat_completions, messages], source: "integrations/langfuse/langfuse.py", rationale: "Streaming token counts aggregate"} +- {id: logging.s3.success.writes_object, module: logging, tier: P0, event: success, assertions: [writes_object], exercised_on: [chat_completions, messages, embeddings], source: "integrations/s3_v2.py", rationale: "Primary audit trail; batch flush no-drop"} +- {id: logging.s3.failure.writes_object, module: logging, tier: P0, event: failure, assertions: [writes_object], exercised_on: [chat_completions, messages], source: "integrations/s3_v2.py", rationale: "Failed calls persisted for compliance"} +- {id: logging.gcs_bucket.success.writes_object, module: logging, tier: P0, event: success, assertions: [writes_object], exercised_on: [chat_completions, messages, embeddings], source: "integrations/gcs_bucket/gcs_bucket.py", rationale: "GCS parallel to S3"} +- {id: logging.datadog.success.exports_metric, module: logging, tier: P0, event: success, assertions: [exports_metric], exercised_on: [chat_completions, messages, embeddings], source: "integrations/datadog/datadog.py", rationale: "Powers dashboards/alerts; cardinality regressions common"} +- {id: logging.datadog.failure.exports_metric, module: logging, tier: P0, event: failure, assertions: [exports_metric], exercised_on: [chat_completions], source: "integrations/datadog/datadog.py", rationale: "Failure metrics for alerting/SLO"} +- {id: logging.prometheus.success.exports_metric, module: logging, tier: P0, event: success, assertions: [exports_metric], exercised_on: [chat_completions, messages, embeddings], source: "integrations/prometheus.py", rationale: "Standard OSS metrics; per-key cardinality (existing e2e)"} +- {id: logging.otel.success.exports_metric, module: logging, tier: P0, event: success, assertions: [exports_metric], exercised_on: [chat_completions, messages, embeddings], source: "integrations/otel/logger.py", rationale: "OTEL spans on every call path"} +- {id: logging.otel.failure.exports_metric, module: logging, tier: P0, event: failure, assertions: [exports_metric], exercised_on: [chat_completions, messages], source: "integrations/otel/logger.py", rationale: "Error spans for observability continuity"} +- {id: logging.braintrust.success.logs_spend, module: logging, tier: P1, event: success, assertions: [logs_spend], exercised_on: [chat_completions, messages], source: "integrations/braintrust_logging.py", rationale: "Evals platform spend"} +- {id: logging.langsmith.success.logs_spend, module: logging, tier: P1, event: success, assertions: [logs_spend], exercised_on: [chat_completions, messages], source: "integrations/langsmith.py", rationale: "LangChain ecosystem"} +- {id: logging.arize.success.logs_spend, module: logging, tier: P1, event: success, assertions: [logs_spend], exercised_on: [chat_completions, embeddings], source: "integrations/arize/arize.py", rationale: "ML-ops observability"} +- {id: logging.mlflow.success.logs_spend, module: logging, tier: P1, event: success, assertions: [logs_spend], exercised_on: [chat_completions], source: "integrations/mlflow.py", rationale: "Experiment tracking cost/run"} +- {id: logging.opik.success.logs_spend, module: logging, tier: P1, event: success, assertions: [logs_spend], exercised_on: [chat_completions], source: "integrations/opik/opik.py", rationale: "Eval platform spend/case"} +- {id: logging.openmeter.success.exports_metric, module: logging, tier: P1, event: success, assertions: [exports_metric], exercised_on: [chat_completions, messages, embeddings], source: "integrations/openmeter.py", rationale: "Usage metering for billing"} +- {id: logging.literal_ai.success.logs_spend, module: logging, tier: P1, event: success, assertions: [logs_spend], exercised_on: [chat_completions, messages], source: "integrations/literal_ai.py", rationale: "Tracing platform spend"} +- {id: logging.posthog.success.exports_metric, module: logging, tier: P1, event: success, assertions: [exports_metric], exercised_on: [chat_completions, messages], source: "integrations/posthog.py", rationale: "Product analytics batching"} +- {id: logging.azure_storage.success.writes_object, module: logging, tier: P1, event: success, assertions: [writes_object], exercised_on: [chat_completions, messages], source: "integrations/azure_storage/azure_storage.py", rationale: "Azure blob for enterprise"} +- {id: logging.cloudzero.success.logs_spend, module: logging, tier: P1, event: success, assertions: [logs_spend], exercised_on: [chat_completions], source: "integrations/cloudzero/cloudzero.py", rationale: "Cost ops correlation"} +- {id: logging.focus.success.writes_object, module: logging, tier: P1, event: success, assertions: [writes_object], exercised_on: [chat_completions, messages], source: "integrations/focus/focus_logger.py", rationale: "Cost mgmt multi-destination export"} +- {id: logging.niche_integrations.success.logs_spend, module: logging, tier: P2, event: success, assertions: [logs_spend], exercised_on: [chat_completions], source: grammar, rationale: "SMOKE cohort: athina/galileo/deepeval/langtrace/weave/lunary/humanloop/traceloop/helicone/argilla/newrelic/sqs/supabase/dynamodb/agentops/lago/etc"} +- {id: logging.niche_integrations.failure.logs_spend, module: logging, tier: P2, event: failure, assertions: [logs_spend], exercised_on: [chat_completions], source: grammar, rationale: "SMOKE niche failure path"} diff --git a/tests/e2e/coverage_registry/mcp.yaml b/tests/e2e/coverage_registry/mcp.yaml new file mode 100644 index 00000000000..d477b257cb0 --- /dev/null +++ b/tests/e2e/coverage_registry/mcp.yaml @@ -0,0 +1,113 @@ +# MCP module. Grounded in litellm/proxy/_experimental/mcp_server/. See tests/e2e/CLAUDE.md for the grammar. +- id: mcp.list_tools.api_key.succeeds + module: mcp + tier: P0 + operation: list_tools + auth_family: api_key + assertions: [succeeds] + source: "server.py:637" + rationale: Core operation; most common auth path; high usage +- id: mcp.list_tools.api_key.denied_without_permission + module: mcp + tier: P0 + operation: list_tools + auth_family: api_key + assertions: [denied_without_permission] + source: "mcp_server_manager.py:1409" + rationale: Permission guard is high blast-radius; multi-tenant safety +- id: mcp.call_tool.api_key.succeeds + module: mcp + tier: P0 + operation: call_tool + auth_family: api_key + assertions: [succeeds] + source: "server.py:849" + rationale: Primary operation; customer-critical; high usage +- id: mcp.call_tool.api_key.denied_without_permission + module: mcp + tier: P0 + operation: call_tool + auth_family: api_key + assertions: [denied_without_permission] + source: "rest_endpoints.py:305-386" + rationale: Tool-level permission guard; multi-tenant safety +- id: mcp.list_tools.bearer.succeeds + module: mcp + tier: P1 + operation: list_tools + auth_family: bearer + assertions: [succeeds] + source: "server.py:662" + rationale: OAuth/bearer token flow; upstream delegation +- id: mcp.call_tool.bearer.succeeds + module: mcp + tier: P1 + operation: call_tool + auth_family: bearer + assertions: [succeeds] + source: "server.py:886" + rationale: Bearer token forwarding for tool invocation +- id: mcp.list_tools.oauth.succeeds + module: mcp + tier: P1 + operation: list_tools + auth_family: oauth + assertions: [succeeds] + source: "rest_endpoints.py:138-188" + rationale: Interactive OAuth2 flow; live token management +- id: mcp.call_tool.oauth.succeeds + module: mcp + tier: P1 + operation: call_tool + auth_family: oauth + assertions: [succeeds] + source: "db.py user_oauth_credential lookup" + rationale: OAuth2 token passthrough; per-user credential storage +- id: mcp.list_tools.none.succeeds + module: mcp + tier: P1 + operation: list_tools + auth_family: none + assertions: [succeeds] + source: "mcp_server_manager.py:1485-1492" + rationale: Public/anonymous servers; delegate_auth_to_upstream +- id: mcp.call_tool.none.succeeds + module: mcp + tier: P1 + operation: call_tool + auth_family: none + assertions: [succeeds] + source: "rest_endpoints.py:305-334" + rationale: No upstream auth required; demo servers +- id: mcp.get_prompt.api_key.succeeds + module: mcp + tier: P1 + operation: get_prompt + auth_family: api_key + assertions: [succeeds] + source: "server.py:1042" + rationale: Prompt op; same auth stack as tools +- id: mcp.read_resource.api_key.succeeds + module: mcp + tier: P1 + operation: read_resource + auth_family: api_key + assertions: [succeeds] + source: "server.py:1177" + rationale: Resource op; same permission model as tools +- id: mcp.list_prompts.api_key.succeeds + module: mcp + tier: P2 + operation: list_prompts + auth_family: api_key + assertions: [succeeds] + source: "server.py:993" + rationale: Smoke-level; same auth stack as list_tools +- id: mcp.list_resources.api_key.succeeds + module: mcp + tier: P2 + operation: list_resources + auth_family: api_key + assertions: [succeeds] + source: "server.py:1089" + rationale: Smoke; rarely used; same auth model as tools diff --git a/tests/e2e/coverage_registry/mgmt.yaml b/tests/e2e/coverage_registry/mgmt.yaml new file mode 100644 index 00000000000..8971a0cb42c --- /dev/null +++ b/tests/e2e/coverage_registry/mgmt.yaml @@ -0,0 +1,67 @@ +# Management/UI endpoint features. Grounded in litellm/proxy/management_endpoints/. +- {id: mgmt.key.generate.persists, module: mgmt, tier: P0, surface: api, assertions: [persists], source: "key_management_endpoints.py:1444", rationale: "API key survives DB roundtrip"} +- {id: mgmt.key.generate.admin_only, module: mgmt, tier: P0, surface: api, assertions: [admin_only], source: "key_management_endpoints.py:1444", rationale: "Only master/team-admin creates keys"} +- {id: mgmt.key.generate.happy_path, module: mgmt, tier: P0, surface: ui, assertions: [happy_path], source: "ui_sso.py:420", rationale: "SSO-driven key gen (UI path)"} +- {id: mgmt.key.update.persists, module: mgmt, tier: P0, surface: api, assertions: [persists], source: "key_management_endpoints.py:2462", rationale: "Budget/model changes persist"} +- {id: mgmt.key.update.admin_only, module: mgmt, tier: P0, surface: api, assertions: [admin_only], source: "key_management_endpoints.py:2462", rationale: "Non-admin cannot escalate perms"} +- {id: mgmt.key.delete.persists, module: mgmt, tier: P0, surface: api, assertions: [persists], source: "key_management_endpoints.py:3122", rationale: "Deletion revokes future calls"} +- {id: mgmt.key.delete.admin_only, module: mgmt, tier: P0, surface: api, assertions: [admin_only], source: "key_management_endpoints.py:3122", rationale: "Non-owner cannot delete"} +- {id: mgmt.key.info.persists, module: mgmt, tier: P0, surface: api, assertions: [persists], source: "key_management_endpoints.py:3380", rationale: "Info reflects all writes"} +- {id: mgmt.team.new.persists, module: mgmt, tier: P0, surface: api, assertions: [persists], source: "team_endpoints.py:897", rationale: "team_id/alias/budgets stored"} +- {id: mgmt.team.new.admin_only, module: mgmt, tier: P0, surface: api, assertions: [admin_only], source: "team_endpoints.py:897", rationale: "Only org-admin/master creates teams"} +- {id: mgmt.team.member_add.persists, module: mgmt, tier: P0, surface: api, assertions: [persists], source: "team_endpoints.py:2424", rationale: "Membership + per-member budget persist"} +- {id: mgmt.team.member_add.member_forbidden, module: mgmt, tier: P0, surface: api, assertions: [member_forbidden], source: "team_endpoints.py:2424", rationale: "Non-admin forbidden to add"} +- {id: mgmt.team.member_delete.persists, module: mgmt, tier: P0, surface: api, assertions: [persists], source: "team_endpoints.py:2800", rationale: "Removal revokes team key access"} +- {id: mgmt.team.member_delete.member_forbidden, module: mgmt, tier: P0, surface: api, assertions: [member_forbidden], source: "team_endpoints.py:2800", rationale: "Non-admin forbidden to remove"} +- {id: mgmt.budget.new.persists, module: mgmt, tier: P0, surface: api, assertions: [persists], source: "budget_management_endpoints.py:40", rationale: "max/soft/reset windows persist"} +- {id: mgmt.budget.new.admin_only, module: mgmt, tier: P0, surface: api, assertions: [admin_only], source: "budget_management_endpoints.py:40", rationale: "Requires master/admin"} +- {id: mgmt.model.add.persists, module: mgmt, tier: P0, surface: api, assertions: [persists], source: "model_management_endpoints.py:1201", rationale: "Registration persists for routing"} +- {id: mgmt.model.add.admin_only, module: mgmt, tier: P0, surface: api, assertions: [admin_only], source: "model_management_endpoints.py:1201", rationale: "Non-admin cannot inject model config"} +- {id: mgmt.user.new.happy_path, module: mgmt, tier: P0, surface: api, assertions: [happy_path], source: "internal_user_endpoints.py:360", rationale: "User creation full cycle"} +- {id: mgmt.key.list.happy_path, module: mgmt, tier: P1, surface: api, assertions: [happy_path], source: "key_management_endpoints.py:5119", rationale: "Key inventory pagination"} +- {id: mgmt.key.block.persists, module: mgmt, tier: P1, surface: api, assertions: [persists], source: "key_management_endpoints.py:5849", rationale: "Blocked stays blocked on restart"} +- {id: mgmt.key.unblock.persists, module: mgmt, tier: P1, surface: api, assertions: [persists], source: "key_management_endpoints.py:5960", rationale: "Unblock restores access"} +- {id: mgmt.key.regenerate.happy_path, module: mgmt, tier: P1, surface: api, assertions: [happy_path], source: "key_management_endpoints.py:6071", rationale: "Rotation: new works, old invalid"} +- {id: mgmt.key.health.happy_path, module: mgmt, tier: P1, surface: api, assertions: [happy_path], source: "key_management_endpoints.py:4292", rationale: "Key health endpoint"} +- {id: mgmt.key.bulk_update.happy_path, module: mgmt, tier: P1, surface: api, assertions: [happy_path], source: "key_management_endpoints.py:2677", rationale: "Batch key updates"} +- {id: mgmt.team.update.persists, module: mgmt, tier: P1, surface: api, assertions: [persists], source: "team_endpoints.py:1582", rationale: "Metadata/budget updates persist"} +- {id: mgmt.team.delete.persists, module: mgmt, tier: P1, surface: api, assertions: [persists], source: "team_endpoints.py:1750", rationale: "Deletion prevents key access"} +- {id: mgmt.team.block.persists, module: mgmt, tier: P1, surface: api, assertions: [persists], source: "team_endpoints.py", rationale: "Block suspends all members"} +- {id: mgmt.team.info.happy_path, module: mgmt, tier: P1, surface: api, assertions: [happy_path], source: "team_endpoints.py:2244", rationale: "Metadata+members+budgets"} +- {id: mgmt.team.list.happy_path, module: mgmt, tier: P1, surface: api, assertions: [happy_path], source: "team_endpoints.py:3645", rationale: "Pagination/filtering"} +- {id: mgmt.team.member_update.persists, module: mgmt, tier: P1, surface: api, assertions: [persists], source: "team_endpoints.py:2768", rationale: "Member budget/role updates persist"} +- {id: mgmt.user.update.persists, module: mgmt, tier: P1, surface: api, assertions: [persists], source: "internal_user_endpoints.py:555", rationale: "Metadata/perm updates persist"} +- {id: mgmt.user.delete.persists, module: mgmt, tier: P1, surface: api, assertions: [persists], source: "internal_user_endpoints.py:640", rationale: "Deletion revokes keys+teams"} +- {id: mgmt.user.list.happy_path, module: mgmt, tier: P1, surface: api, assertions: [happy_path], source: "internal_user_endpoints.py:475", rationale: "Admin view all users"} +- {id: mgmt.user.info.happy_path, module: mgmt, tier: P1, surface: api, assertions: [happy_path], source: "internal_user_endpoints.py:440", rationale: "Roles/perms/team membership"} +- {id: mgmt.organization.new.happy_path, module: mgmt, tier: P1, surface: api, assertions: [happy_path], source: "organization_endpoints.py:403", rationale: "Org for multi-tenant isolation"} +- {id: mgmt.organization.update.persists, module: mgmt, tier: P1, surface: api, assertions: [persists], source: "organization_endpoints.py:545", rationale: "Org metadata updates persist"} +- {id: mgmt.organization.delete.persists, module: mgmt, tier: P1, surface: api, assertions: [persists], source: "organization_endpoints.py:710", rationale: "Cascades to teams/keys"} +- {id: mgmt.organization.member_add.happy_path, module: mgmt, tier: P1, surface: api, assertions: [happy_path], source: "organization_endpoints.py:835", rationale: "Org member onboarding"} +- {id: mgmt.customer.new.happy_path, module: mgmt, tier: P1, surface: api, assertions: [happy_path], source: "customer_endpoints.py:372", rationale: "End-user for spend tracking"} +- {id: mgmt.customer.delete.persists, module: mgmt, tier: P1, surface: api, assertions: [persists], source: "customer_endpoints.py:480", rationale: "Removes from spend tracking"} +- {id: mgmt.end_user.new.happy_path, module: mgmt, tier: P1, surface: api, assertions: [happy_path], source: "customer_endpoints.py:730", rationale: "End-user create (synonym)"} +- {id: mgmt.tag.new.happy_path, module: mgmt, tier: P1, surface: api, assertions: [happy_path], source: "tag_management_endpoints.py:160", rationale: "Tag for spend categorization"} +- {id: mgmt.tag.list.happy_path, module: mgmt, tier: P1, surface: api, assertions: [happy_path], source: "tag_management_endpoints.py:315", rationale: "Tag enumeration"} +- {id: mgmt.tag.delete.persists, module: mgmt, tier: P1, surface: api, assertions: [persists], source: "tag_management_endpoints.py:390", rationale: "Stops future tagging"} +- {id: mgmt.model.update.persists, module: mgmt, tier: P1, surface: api, assertions: [persists], source: "model_management_endpoints.py:1358", rationale: "Pricing/concurrency persist"} +- {id: mgmt.model.delete.persists, module: mgmt, tier: P1, surface: api, assertions: [persists], source: "model_management_endpoints.py:1045", rationale: "Removes from registry"} +- {id: mgmt.model.block.persists, module: mgmt, tier: P1, surface: api, assertions: [persists], source: "model_management_endpoints.py", rationale: "Blocked model stays blocked"} +- {id: mgmt.access_group.new.happy_path, module: mgmt, tier: P1, surface: api, assertions: [happy_path], source: "model_access_group_management_endpoints.py:450", rationale: "Model permissioning group"} +- {id: mgmt.access_group.info.happy_path, module: mgmt, tier: P1, surface: api, assertions: [happy_path], source: "model_access_group_management_endpoints.py:600", rationale: "Access group membership query"} +- {id: mgmt.mcp_server.register.happy_path, module: mgmt, tier: P1, surface: api, assertions: [happy_path], source: "mcp_management_endpoints.py:880", rationale: "MCP server registration"} +- {id: mgmt.mcp_server.approve.persists, module: mgmt, tier: P1, surface: api, assertions: [persists], source: "mcp_management_endpoints.py:1200", rationale: "Admin approval persists"} +- {id: mgmt.budget.update.persists, module: mgmt, tier: P1, surface: api, assertions: [persists], source: "budget_management_endpoints.py:155", rationale: "Limit changes apply"} +- {id: mgmt.budget.delete.persists, module: mgmt, tier: P1, surface: api, assertions: [persists], source: "budget_management_endpoints.py:280", rationale: "Clears limits"} +- {id: mgmt.budget.list.happy_path, module: mgmt, tier: P1, surface: api, assertions: [happy_path], source: "budget_management_endpoints.py:215", rationale: "Budget enumeration"} +- {id: mgmt.callback.list.happy_path, module: mgmt, tier: P2, surface: api, assertions: [happy_path], source: "callback_management_endpoints.py", rationale: "Callback config (smoke)"} +- {id: mgmt.cache_settings.update.happy_path, module: mgmt, tier: P2, surface: api, assertions: [happy_path], source: "cache_settings_endpoints.py", rationale: "Cache config (smoke)"} +- {id: mgmt.cost_tracking.estimate.happy_path, module: mgmt, tier: P2, surface: api, assertions: [happy_path], source: "cost_tracking_settings.py", rationale: "Cost estimate (smoke)"} +- {id: mgmt.router_settings.update.happy_path, module: mgmt, tier: P2, surface: api, assertions: [happy_path], source: "router_settings_endpoints.py", rationale: "Router config (smoke)"} +- {id: mgmt.jwt_key_mapping.new.happy_path, module: mgmt, tier: P2, surface: api, assertions: [happy_path], source: "jwt_key_mapping_endpoints.py", rationale: "JWT->key mapping (smoke)"} +- {id: mgmt.compliance.gdpr.happy_path, module: mgmt, tier: P2, surface: api, assertions: [happy_path], source: "compliance_endpoints.py", rationale: "GDPR ops (smoke)"} +- {id: mgmt.tool_management.list.happy_path, module: mgmt, tier: P2, surface: api, assertions: [happy_path], source: "tool_management_endpoints.py", rationale: "Tool inventory (smoke)"} +- {id: mgmt.fallback_management.update.happy_path, module: mgmt, tier: P2, surface: api, assertions: [happy_path], source: "fallback_management_endpoints.py", rationale: "Fallback config (smoke)"} +- {id: mgmt.config_override.hashicorp_vault.happy_path, module: mgmt, tier: P2, surface: api, assertions: [happy_path], source: "config_override_endpoints.py", rationale: "Vault integration (smoke)"} +- {id: mgmt.workflow.list.happy_path, module: mgmt, tier: P2, surface: api, assertions: [happy_path], source: "workflow_management_endpoints.py", rationale: "Workflow tracking (smoke)"} +- {id: mgmt.credential_migration.check.happy_path, module: mgmt, tier: P2, surface: api, assertions: [happy_path], source: "key_management_endpoints.py:4252", rationale: "Encryption migration (smoke)"} diff --git a/tests/e2e/coverage_registry/other.yaml b/tests/e2e/coverage_registry/other.yaml new file mode 100644 index 00000000000..c2efecec677 --- /dev/null +++ b/tests/e2e/coverage_registry/other.yaml @@ -0,0 +1,28 @@ +# Other (holding pen). Grounded in litellm/proxy/auth/ + health_endpoints/ + proxy_server.py. +# PROMOTION NOTE: the auth cluster (~14 cells) is a candidate to promote to its own module once stable. +- {id: other.auth.master_key.valid_allows, module: other, tier: P0, area: auth, assertions: [valid_allows], source: "user_api_key_auth.py:1569-1588", rationale: "Master key authenticates; timing-safe compare"} +- {id: other.auth.master_key.invalid_denied, module: other, tier: P0, area: auth, assertions: [invalid_denied], source: "user_api_key_auth.py:1580", rationale: "Invalid master key rejected"} +- {id: other.auth.jwt.valid_token_allows, module: other, tier: P0, area: auth, assertions: [valid_token_allows], source: "handle_jwt.py:77-150", rationale: "Valid JWT with correct issuer + claims grants access"} +- {id: other.auth.jwt.expired_denied, module: other, tier: P0, area: auth, assertions: [expired_denied], source: "handle_jwt.py:125-135", rationale: "Expired JWT rejected even with valid signature"} +- {id: other.auth.jwt.invalid_signature_denied, module: other, tier: P0, area: auth, assertions: [invalid_signature_denied], source: "handle_jwt.py:145-150", rationale: "Bad/missing signature fails verification"} +- {id: other.auth.virtual_key.route_permission_enforced, module: other, tier: P0, area: auth, assertions: [route_permission_enforced], source: "route_checks.py:89-151", rationale: "allowed_routes whitelist denies disallowed routes"} +- {id: other.auth.virtual_key.route_group_allowed, module: other, tier: P1, area: auth, assertions: [route_group_allowed], source: "route_checks.py:106-128", rationale: "allowed_routes=[llm_api_routes] grants all LLM endpoints"} +- {id: other.auth.passthrough.model_allowlist_enforced, module: other, tier: P1, area: auth, assertions: [model_allowlist_enforced], source: "route_checks.py:135-151", rationale: "Passthrough enforces per-key model allow-lists"} +- {id: other.auth.oauth2.token_valid_allows, module: other, tier: P1, area: auth, assertions: [token_valid_allows], source: "oauth2_check.py:15-73", rationale: "OAuth2 introspection grants active token"} +- {id: other.auth.oauth2.token_invalid_denied, module: other, tier: P1, area: auth, assertions: [token_invalid_denied], source: "oauth2_check.py:37-73", rationale: "Expired/inactive OAuth2 token denied"} +- {id: other.auth.ip_allowlist.internal_ip_allows, module: other, tier: P1, area: auth, assertions: [internal_ip_allows], source: "ip_address_utils.py:54-76", rationale: "Internal CIDR bypasses public-API restriction"} +- {id: other.auth.ip_allowlist.external_ip_denied_to_private, module: other, tier: P1, area: auth, assertions: [external_ip_denied_to_private], source: "ip_address_utils.py:54-76", rationale: "External IP cannot reach internal-only resources"} +- {id: other.lifecycle.readiness.public_probe, module: other, tier: P0, area: lifecycle, assertions: [public_probe], source: "_health_endpoints.py:1551-1570", rationale: "Unauthenticated /health/readiness safe for LBs"} +- {id: other.lifecycle.readiness.reports_db_status, module: other, tier: P0, area: lifecycle, assertions: [reports_db_status], source: "_health_endpoints.py:1551-1570", rationale: "readiness distinguishes healthy vs DB-unreachable"} +- {id: other.lifecycle.readiness.shutting_down_returns_503, module: other, tier: P0, area: lifecycle, assertions: [shutting_down_returns_503], source: "_health_endpoints.py:1554-1556", rationale: "Graceful shutdown drains LB via 503"} +- {id: other.lifecycle.readiness_details.authenticated_diagnostics, module: other, tier: P1, area: lifecycle, assertions: [authenticated_diagnostics], source: "_health_endpoints.py:1574-1584", rationale: "Auth'd details expose cache/callback status"} +- {id: other.lifecycle.liveness.ping, module: other, tier: P1, area: lifecycle, assertions: [ping], source: "_health_endpoints.py:134-155", rationale: "Liveness confirms server responding"} +- {id: other.lifecycle.startup.config_loads, module: other, tier: P0, area: lifecycle, assertions: [config_loads], source: "proxy_server.py:4020-4100", rationale: "Startup loads YAML, resolves env, persists to DB"} +- {id: other.lifecycle.startup.env_vars_resolved, module: other, tier: P1, area: lifecycle, assertions: [env_vars_resolved], source: "proxy_server.py:3984-4010", rationale: "os.environ/ refs resolved at startup"} +- {id: other.lifecycle.background_health_check.interval_configurable, module: other, tier: P1, area: lifecycle, assertions: [interval_configurable], source: "proxy_server.py:3245-3310", rationale: "Background checks run at configurable interval"} +- {id: other.config.runtime_update.applies_at_runtime, module: other, tier: P0, area: config, assertions: [applies_at_runtime], source: "proxy_server.py:14014-14060", rationale: "/config/update persists to DB + invalidates cache"} +- {id: other.config.general_settings.alert_webhook_side_effect, module: other, tier: P1, area: config, assertions: [alert_webhook_side_effect], source: "proxy_server.py:14215", rationale: "alert_to_webhook_url auto-enables slack alerting"} +- {id: other.config.secret_resolution.kms_integration, module: other, tier: P1, area: config, assertions: [kms_integration], source: "proxy_server.py:3984-4010", rationale: "Resolves secrets from Vault/KMS at startup"} +- {id: other.config.overrides.audit_logged, module: other, tier: P1, area: config, assertions: [audit_logged], source: "config_override_endpoints.py:67-100", rationale: "Config override mutations audit-logged, values redacted"} +- {id: other.key_mgmt.regenerate.grace_period_honored, module: other, tier: P1, area: auth, assertions: [grace_period_honored], source: "key_management_endpoints.py:4503-4560", rationale: "Old key valid during grace_period then revoked"} +- {id: other.key_mgmt.spend_reset.resets_to_value, module: other, tier: P1, area: auth, assertions: [resets_to_value], source: "key_management_endpoints.py:4841", rationale: "reset_spend resets accumulated spend"} diff --git a/tests/e2e/coverage_registry/registry.py b/tests/e2e/coverage_registry/registry.py new file mode 100644 index 00000000000..7a472cfbf2f --- /dev/null +++ b/tests/e2e/coverage_registry/registry.py @@ -0,0 +1,26 @@ +"""Load and validate the registry: the denominator, built in one shot from the YAMLs.""" + +from __future__ import annotations + +from collections import Counter +from pathlib import Path + +import yaml + +from .schema import CELL_ADAPTER, Cell + +REGISTRY_DIR = Path(__file__).resolve().parent + + +def load_registry(registry_dir: Path = REGISTRY_DIR) -> tuple[Cell, ...]: + """Every cell across every `*.yaml`, validated. Raises on a schema violation or + a duplicate id, since either would corrupt the coverage denominator.""" + cells = tuple( + CELL_ADAPTER.validate_python(row) + for path in sorted(registry_dir.glob("*.yaml")) + for row in (yaml.safe_load(path.read_text()) or ()) + ) + duplicates = sorted(cid for cid, n in Counter(c.id for c in cells).items() if n > 1) + if duplicates: + raise ValueError(f"duplicate cell ids in registry: {duplicates}") + return cells diff --git a/tests/e2e/coverage_registry/reliability.yaml b/tests/e2e/coverage_registry/reliability.yaml new file mode 100644 index 00000000000..ad5630a32dd --- /dev/null +++ b/tests/e2e/coverage_registry/reliability.yaml @@ -0,0 +1,30 @@ +# Reliability & Performance (behavior features). Grounded in litellm/router.py + router_strategy/ + router_utils/. +- {id: reliability.fallback.5xx.routes_to_fallback, module: reliability, tier: P0, behavior: fallback, variant: "5xx", assertions: [routes_to_fallback], exercised_on: [chat_completions, messages], source: "litellm/router.py:2024", rationale: "Reroute on provider 5xx to alternate deployment"} +- {id: reliability.fallback.context_window.routes_to_fallback, module: reliability, tier: P0, behavior: fallback, variant: context_window, assertions: [routes_to_fallback], exercised_on: [chat_completions, messages], source: "litellm/router.py:6108", rationale: "Fallback when model exceeds context limit"} +- {id: reliability.fallback.content_policy.routes_to_fallback, module: reliability, tier: P0, behavior: fallback, variant: content_policy, assertions: [routes_to_fallback], exercised_on: [chat_completions, messages], source: "litellm/router.py:6023", rationale: "Reroute on content-policy violation"} +- {id: reliability.fallback.timeout.routes_to_fallback, module: reliability, tier: P0, behavior: fallback, variant: "timeout", assertions: [routes_to_fallback], exercised_on: [chat_completions, messages], source: "litellm/router.py:2766", rationale: "Fallback on request timeout"} +- {id: reliability.retry.5xx.succeeds_within_retries, module: reliability, tier: P0, behavior: retry, variant: "5xx", assertions: [succeeds_within_retries], exercised_on: [chat_completions, messages], source: "litellm/router.py:6414", rationale: "Transient 5xx often succeeds on retry"} +- {id: reliability.retry.timeout.succeeds_within_retries, module: reliability, tier: P0, behavior: retry, variant: timeout, assertions: [succeeds_within_retries], exercised_on: [chat_completions, messages], source: "get_retry_from_policy.py:44", rationale: "Timeout retried per policy"} +- {id: reliability.retry.429.succeeds_within_retries, module: reliability, tier: P0, behavior: retry, variant: "429", assertions: [succeeds_within_retries], exercised_on: [chat_completions, messages], source: "get_retry_from_policy.py:46", rationale: "429 retried per RateLimitErrorRetries policy"} +- {id: reliability.retry.auth.succeeds_within_retries, module: reliability, tier: P1, behavior: retry, variant: auth, assertions: [succeeds_within_retries], exercised_on: [chat_completions, messages], source: "get_retry_from_policy.py:42", rationale: "Transient auth glitch retry"} +- {id: reliability.retry.context_window.succeeds_within_retries, module: reliability, tier: P1, behavior: retry, variant: context_window, assertions: [succeeds_within_retries], exercised_on: [chat_completions, messages], source: "get_retry_from_policy.py:51", rationale: "Multi-attempt on context error"} +- {id: reliability.cooldown.5xx.trips_then_recovers, module: reliability, tier: P0, behavior: cooldown, variant: "5xx", assertions: [trips_then_recovers], exercised_on: [chat_completions, messages], source: "cooldown_handlers.py:40", rationale: "Deployment cools after repeated 5xx, recovers after cooldown_time"} +- {id: reliability.cooldown.429.trips_then_recovers, module: reliability, tier: P0, behavior: cooldown, variant: "429", assertions: [trips_then_recovers], exercised_on: [chat_completions, messages], source: "cooldown_handlers.py:69", rationale: "Cools on 429, avoids hammering exhausted provider"} +- {id: reliability.cooldown.auth.trips_then_recovers, module: reliability, tier: P1, behavior: cooldown, variant: auth, assertions: [trips_then_recovers], exercised_on: [chat_completions, messages], source: "cooldown_handlers.py:74", rationale: "Cools on 401 auth error"} +- {id: reliability.cooldown.timeout.trips_then_recovers, module: reliability, tier: P1, behavior: cooldown, variant: timeout, assertions: [trips_then_recovers], exercised_on: [chat_completions, messages], source: "cooldown_handlers.py:77", rationale: "Cools on 408 timeout"} +- {id: reliability.ratelimit.rpm.blocks_over_limit, module: reliability, tier: P0, behavior: ratelimit, variant: rpm, assertions: [blocks_over_limit], exercised_on: [chat_completions, messages], source: "dynamic_rate_limiter_v3.py", rationale: "v3 limiter enforces RPM per key/team/model; 429 on breach"} +- {id: reliability.ratelimit.tpm.blocks_over_limit, module: reliability, tier: P0, behavior: ratelimit, variant: tpm, assertions: [blocks_over_limit], exercised_on: [chat_completions, messages], source: "dynamic_rate_limiter_v3.py", rationale: "v3 limiter enforces TPM per key/team/model; 429 on breach"} +- {id: reliability.ratelimit.priority_generous.picks_under_tpm, module: reliability, tier: P1, behavior: ratelimit, variant: priority_generous, assertions: [picks_under_tpm], exercised_on: [chat_completions, messages], source: "dynamic_rate_limiter_v3.py:36-52", rationale: "Generous mode (<80% sat) allows priority borrowing"} +- {id: reliability.ratelimit.priority_strict.picks_under_tpm, module: reliability, tier: P1, behavior: ratelimit, variant: priority_strict, assertions: [picks_under_tpm], exercised_on: [chat_completions, messages], source: "dynamic_rate_limiter_v3.py:53-71", rationale: "Strict mode (>=80% sat) enforces priority fairness"} +- {id: reliability.routing.simple_shuffle.picks_healthy_deployment, module: reliability, tier: P1, behavior: routing, variant: simple_shuffle, assertions: [picks_healthy_deployment], exercised_on: [chat_completions, messages], source: "router_strategy/simple_shuffle.py", rationale: "Baseline weighted/uniform pick"} +- {id: reliability.routing.latency_based.picks_lowest_latency, module: reliability, tier: P1, behavior: routing, variant: latency_based, assertions: [picks_lowest_latency], exercised_on: [chat_completions, messages], source: "router_strategy/lowest_latency.py", rationale: "Routes to lowest-latency deployment"} +- {id: reliability.routing.cost_based.picks_lowest_cost, module: reliability, tier: P1, behavior: routing, variant: cost_based, assertions: [picks_lowest_cost], exercised_on: [chat_completions, messages], source: "router_strategy/lowest_cost.py", rationale: "Spend-aware routing"} +- {id: reliability.routing.usage_based.picks_under_tpm, module: reliability, tier: P0, behavior: routing, variant: usage_based, assertions: [picks_under_tpm], exercised_on: [chat_completions, messages], source: "router_strategy/lowest_tpm_rpm_v2.py", rationale: "Routes to lowest-TPM deployment; prevents over-allocation"} +- {id: reliability.routing.least_busy.picks_lowest_traffic, module: reliability, tier: P1, behavior: routing, variant: least_busy, assertions: [picks_lowest_traffic], exercised_on: [chat_completions, messages], source: "router_strategy/least_busy.py", rationale: "Fewest in-flight requests"} +- {id: reliability.cache.exact.returns_cached, module: reliability, tier: P1, behavior: cache, variant: exact, assertions: [returns_cached], exercised_on: [chat_completions, messages, embeddings], source: "litellm/caching/caching.py", rationale: "Response cache returns cached on exact match"} +- {id: reliability.cache.prompt_caching_model_select.returns_cached, module: reliability, tier: P1, behavior: cache, variant: prompt_caching_model_select, assertions: [returns_cached], exercised_on: [chat_completions], source: "router_utils/prompt_caching_cache.py", rationale: "Selects model supporting prompt caching for cacheable prefix"} +- {id: reliability.circuit_breaker.redis.trips_then_recovers, module: reliability, tier: P0, behavior: circuit_breaker, variant: redis, assertions: [trips_then_recovers], exercised_on: [chat_completions, messages, embeddings], source: "litellm/caching/redis_cache.py:99", rationale: "Redis breaker CLOSED->OPEN->HALF_OPEN; guards all cache/rate-limit ops"} +- {id: reliability.timeout.request_timeout.exceeds_deadline, module: reliability, tier: P1, behavior: timeout, variant: request_timeout, assertions: [exceeds_deadline], exercised_on: [chat_completions, messages], source: "litellm/router.py:545-551", rationale: "Per-request timeout raises Timeout"} +- {id: reliability.timeout.stream_timeout.exceeds_deadline, module: reliability, tier: P1, behavior: timeout, variant: stream_timeout, assertions: [exceeds_deadline], exercised_on: [chat_completions], source: "litellm/router.py:551", rationale: "Streaming chunk-delivery timeout"} +- {id: reliability.perf.latency.under_slo, module: reliability, tier: P1, behavior: perf, variant: latency, assertions: [under_slo], exercised_on: [chat_completions, messages], source: "router_strategy/lowest_latency.py", rationale: "Latency SLO (p50/p99) compliance"} +- {id: reliability.perf.throughput.under_slo, module: reliability, tier: P1, behavior: perf, variant: throughput, assertions: [under_slo], exercised_on: [chat_completions, messages], source: grammar, rationale: "Throughput SLO under load"} diff --git a/tests/e2e/coverage_registry/schema.py b/tests/e2e/coverage_registry/schema.py new file mode 100644 index 00000000000..756bde33e3d --- /dev/null +++ b/tests/e2e/coverage_registry/schema.py @@ -0,0 +1,107 @@ +"""Registry row schema: the contract every denominator cell validates against. + +A cell is one customer-noticeable behavior a single e2e test can assert pass/fail +on. `module` is the id's segment-1 prefix (seven of them); the six-way dashboard +rollup merges logging + guardrail via ROLLUP. The union is discriminated on +`module`, so an LLM row cannot carry a guardrail field and vice versa. +""" + +from __future__ import annotations + +from enum import Enum +from typing import Annotated, Literal + +from pydantic import BaseModel, ConfigDict, Field, TypeAdapter + + +class Tier(str, Enum): + P0 = "P0" + P1 = "P1" + P2 = "P2" + + +class FailBeforeFix(str, Enum): + proven = "proven" + unproven = "unproven" + + +class _Base(BaseModel): + model_config = ConfigDict(frozen=True, extra="forbid") + + id: str + tier: Tier + assertions: tuple[str, ...] + source: str + rationale: str = "" + fail_before_fix: FailBeforeFix = FailBeforeFix.unproven + supported: bool = True + + +class LlmCell(_Base): + module: Literal["llm"] + subject_endpoint: str + route: str + capability: str + streaming: Literal["stream", "nonstream", "na"] + + +class MgmtCell(_Base): + module: Literal["mgmt"] + surface: Literal["api", "ui"] + + +class McpCell(_Base): + module: Literal["mcp"] + operation: str + auth_family: Literal["none", "api_key", "bearer", "oauth"] + + +class ReliabilityCell(_Base): + module: Literal["reliability"] + behavior: str + variant: str + exercised_on: tuple[str, ...] + + +class LoggingCell(_Base): + module: Literal["logging"] + event: str + exercised_on: tuple[str, ...] + + +class GuardrailCell(_Base): + module: Literal["guardrail"] + hook_point: str + exercised_on: tuple[str, ...] + + +class OtherCell(_Base): + module: Literal["other"] + area: str + + +Cell = Annotated[ + LlmCell | MgmtCell | McpCell | ReliabilityCell | LoggingCell | GuardrailCell | OtherCell, + Field(discriminator="module"), +] + +CELL_ADAPTER: TypeAdapter[Cell] = TypeAdapter(Cell) + +ROLLUP: dict[str, str] = { + "llm": "LLMs", + "mcp": "MCPs", + "mgmt": "Management/UI", + "reliability": "Reliability & Performance", + "logging": "Logging & Guardrails", + "guardrail": "Logging & Guardrails", + "other": "Other", +} + +MODULE_ORDER: tuple[str, ...] = ( + "LLMs", + "MCPs", + "Management/UI", + "Reliability & Performance", + "Logging & Guardrails", + "Other", +) diff --git a/tests/e2e/coverage_registry/test_collector.py b/tests/e2e/coverage_registry/test_collector.py new file mode 100644 index 00000000000..065ebafdb3b --- /dev/null +++ b/tests/e2e/coverage_registry/test_collector.py @@ -0,0 +1,91 @@ +"""Tests for the coverage-registry tooling: pure logic plus a registry canary. + +No `e2e` marker, so these run without a proxy. They exercise the coverage math and +the registry loader, and guard the checked-in registry against schema drift and +duplicate ids. +""" + +from __future__ import annotations + +from pathlib import Path + +import pytest + +from coverage_registry.collector import compute_coverage +from coverage_registry.registry import load_registry +from coverage_registry.schema import GuardrailCell, LlmCell, LoggingCell, Tier + + +def _llm(cell_id: str, tier: Tier) -> LlmCell: + return LlmCell( + id=cell_id, + module="llm", + tier=tier, + assertions=("works",), + source="test", + subject_endpoint="chat_completions", + route="openai", + capability="basic", + streaming="nonstream", + ) + + +def test_compute_coverage_counts_covered_p0_and_gaps() -> None: + cells = (_llm("llm.a", Tier.P0), _llm("llm.b", Tier.P0), _llm("llm.c", Tier.P1)) + report = compute_coverage(cells, frozenset({"llm.a"})) + assert (report.total, report.covered) == (3, 1) + assert (report.p0_total, report.p0_covered) == (2, 1) + assert report.p0_gaps == ("llm.b",) + assert report.orphan_markers == () + + +def test_orphan_marker_is_reported_not_counted() -> None: + cells = (_llm("llm.a", Tier.P0),) + report = compute_coverage(cells, frozenset({"llm.a", "llm.ghost"})) + assert report.covered == 1 + assert report.orphan_markers == ("llm.ghost",) + + +def test_logging_and_guardrail_roll_up_into_one_module() -> None: + cells = ( + LoggingCell( + id="logging.x", + module="logging", + tier=Tier.P0, + assertions=("logs_spend",), + source="t", + event="success", + exercised_on=("chat_completions",), + ), + GuardrailCell( + id="guardrail.y", + module="guardrail", + tier=Tier.P1, + assertions=("blocks",), + source="t", + hook_point="pre_call", + exercised_on=("chat_completions",), + ), + ) + report = compute_coverage(cells, frozenset()) + logging_and_guardrails = next(m for m in report.modules if m.module == "Logging & Guardrails") + assert logging_and_guardrails.total == 2 + + +def test_real_registry_loads_and_ids_are_unique() -> None: + cells = load_registry() + ids = [c.id for c in cells] + assert len(cells) > 250 + assert len(ids) == len(set(ids)) + assert any(c.id == "logging.prometheus.success.exports_metric" for c in cells) + + +def test_load_registry_rejects_duplicate_ids(tmp_path: Path) -> None: + row = ( + "- {id: llm.dup, module: llm, tier: P0, assertions: [works], source: t, " + "subject_endpoint: chat_completions, route: openai, capability: basic, streaming: nonstream}\n" + ) + (tmp_path / "a.yaml").write_text(row) + (tmp_path / "b.yaml").write_text(row) + with pytest.raises(ValueError, match="duplicate cell ids"): + load_registry(tmp_path) diff --git a/tests/e2e/e2e_gateway.py b/tests/e2e/e2e_gateway.py index a67ea594a71..055d06d1c79 100644 --- a/tests/e2e/e2e_gateway.py +++ b/tests/e2e/e2e_gateway.py @@ -9,6 +9,7 @@ Gateway's key/customer methods for cleanup. Read-backs are eventually consistent from __future__ import annotations import time +import warnings from collections.abc import Callable from dataclasses import dataclass @@ -18,6 +19,7 @@ from e2e_http import ( Result, StreamingResponse, Success, + is_ok, unwrap, ) from models import ( @@ -32,8 +34,14 @@ from models import ( KeyInfo, KeyInfoParams, KeyInfoResponse, + LiteLLMParamsBody, + ModelDeleteBody, + ModelInfoBody, ModelInfoEntry, ModelInfoResponse, + ModelMode, + ModelNewBody, + ModelNewResponse, OcrBody, OcrResponse, SpendLogRow, @@ -111,6 +119,38 @@ class Gateway: ) ).data + def create_model( + self, + model_name: str, + litellm_params: LiteLLMParamsBody, + mode: ModelMode | None = None, + ) -> str: + """Register a deployment under `model_name` (id == model_name) and return the + model_id. add_deployment runs synchronously in /model/new, so the model is + callable as soon as this returns.""" + return unwrap( + self.transport.post( + "/model/new", + headers=self.transport.master, + json=ModelNewBody( + model_name=model_name, + litellm_params=litellm_params, + model_info=ModelInfoBody(id=model_name, mode=mode), + ), + response_type=ModelNewResponse, + ) + ).model_id + + def delete_model(self, model_id: str) -> None: + result = self.transport.post( + "/model/delete", + headers=self.transport.master, + json=ModelDeleteBody(id=model_id), + response_type=NoBody, + ) + if not is_ok(result): + warnings.warn(f"delete_model({model_id!r}) failed: {result}", stacklevel=2) + # ---- LLM calls ------------------------------------------------------ def chat(self, key: str, body: ChatBody) -> Result[ChatResponse]: diff --git a/tests/e2e/llm_translation/conftest.py b/tests/e2e/llm_translation/conftest.py index fbf008cf085..2a87ef7259d 100644 --- a/tests/e2e/llm_translation/conftest.py +++ b/tests/e2e/llm_translation/conftest.py @@ -7,9 +7,22 @@ Gateway, so the `resources` fixture cleans up keys this suite creates. import pytest +from endpoints_client import EndpointsClient, build_endpoints_client from passthrough_client import PassthroughClient, build_client +def pytest_configure(config: pytest.Config) -> None: + config.addinivalue_line( + "markers", + "covers: registry cell a test covers, e.g. llm.chat_completions.provider.basic.nonstream.works", + ) + + @pytest.fixture(scope="session") def client() -> PassthroughClient: return build_client() + + +@pytest.fixture(scope="session") +def endpoints_client() -> EndpointsClient: + return build_endpoints_client() diff --git a/tests/e2e/llm_translation/endpoints_client.py b/tests/e2e/llm_translation/endpoints_client.py new file mode 100644 index 00000000000..0ab87472748 --- /dev/null +++ b/tests/e2e/llm_translation/endpoints_client.py @@ -0,0 +1,190 @@ +"""Client for the non-chat inference endpoints (responses, messages, rerank, +embeddings, audio speech, image generation). + +Each test registers the deployment it needs through /model/new (deleted on +teardown), so nothing is hardcoded into the gateway config, then drives the +endpoint with `send` and parses the provider-native body with a suite-local model +so the assertion is on real content, not just a 200. +""" + +from __future__ import annotations + +from dataclasses import dataclass + +from pydantic import BaseModel + +from e2e_gateway import Gateway, build_gateway +from e2e_http import StreamingResponse +from models import ChatMessage, LiteLLMParamsBody + + +class ResponsesRequest(BaseModel): + model: str + input: str + instructions: str | None = None + + +class MessagesRequest(BaseModel): + model: str + max_tokens: int + messages: list[ChatMessage] + + +class EmbeddingsRequest(BaseModel): + model: str + input: str + + +class RerankRequest(BaseModel): + model: str + query: str + documents: list[str] + top_n: int + + +class SpeechRequest(BaseModel): + model: str + input: str + voice: str + + +class ImageRequest(BaseModel): + model: str + prompt: str + n: int = 1 + size: str = "1024x1024" + + +class ResponsesOutputContent(BaseModel): + type: str | None = None + text: str | None = None + + +class ResponsesOutputItem(BaseModel): + type: str | None = None + content: list[ResponsesOutputContent] = [] + + +class ResponsesResult(BaseModel): + id: str | None = None + status: str | None = None + model: str | None = None + output: list[ResponsesOutputItem] = [] + + @property + def text(self) -> str: + return "".join( + content.text or "" for item in self.output for content in item.content + ) + + +class AnthropicContentBlock(BaseModel): + type: str | None = None + text: str | None = None + + +class MessagesResult(BaseModel): + id: str | None = None + role: str | None = None + model: str | None = None + content: list[AnthropicContentBlock] = [] + + @property + def text(self) -> str: + return "".join(block.text or "" for block in self.content) + + +class EmbeddingItem(BaseModel): + embedding: list[float] = [] + + +class EmbeddingsResult(BaseModel): + data: list[EmbeddingItem] = [] + + @property + def first_vector(self) -> tuple[float, ...]: + return tuple(self.data[0].embedding) if self.data else () + + +class RerankItem(BaseModel): + index: int | None = None + relevance_score: float | None = None + + +class RerankResult(BaseModel): + results: list[RerankItem] = [] + + +class ImageItem(BaseModel): + url: str | None = None + b64_json: str | None = None + + +class ImagesResult(BaseModel): + data: list[ImageItem] = [] + + +@dataclass(frozen=True, slots=True) +class EndpointsClient: + gateway: Gateway + + def create_model(self, model_name: str, litellm_params: LiteLLMParamsBody) -> str: + return self.gateway.create_model(model_name, litellm_params) + + def delete_model(self, model_id: str) -> None: + self.gateway.delete_model(model_id) + + def _send(self, path: str, key: str, body: BaseModel) -> StreamingResponse: + return self.gateway.transport.send( + path, headers=self.gateway.transport.bearer(key), json=body + ) + + def responses(self, key: str, model: str, text: str) -> StreamingResponse: + return self._send( + "/v1/responses", + key, + ResponsesRequest( + model=model, input=text, instructions="You are a helpful assistant" + ), + ) + + def messages( + self, key: str, model: str, text: str, *, max_tokens: int = 64 + ) -> StreamingResponse: + return self._send( + "/v1/messages", + key, + MessagesRequest( + model=model, + max_tokens=max_tokens, + messages=[ChatMessage(role="user", content=text)], + ), + ) + + def embeddings(self, key: str, model: str, text: str) -> StreamingResponse: + return self._send("/embeddings", key, EmbeddingsRequest(model=model, input=text)) + + def rerank( + self, key: str, model: str, query: str, documents: list[str], top_n: int + ) -> StreamingResponse: + return self._send( + "/v1/rerank", + key, + RerankRequest(model=model, query=query, documents=documents, top_n=top_n), + ) + + def audio_speech( + self, key: str, model: str, text: str, *, voice: str = "alloy" + ) -> StreamingResponse: + return self._send( + "/v1/audio/speech", key, SpeechRequest(model=model, input=text, voice=voice) + ) + + def images(self, key: str, model: str, prompt: str) -> StreamingResponse: + return self._send( + "/v1/images/generations", key, ImageRequest(model=model, prompt=prompt) + ) + + +def build_endpoints_client() -> EndpointsClient: + return EndpointsClient(gateway=build_gateway()) diff --git a/tests/e2e/llm_translation/test_audio_speech_e2e.py b/tests/e2e/llm_translation/test_audio_speech_e2e.py new file mode 100644 index 00000000000..f7a04d94cb3 --- /dev/null +++ b/tests/e2e/llm_translation/test_audio_speech_e2e.py @@ -0,0 +1,40 @@ +"""Live e2e: POST /v1/audio/speech returns audio. + +Registers an OpenAI text-to-speech deployment at runtime and asserts the response +is an audio body (binary, not JSON). Migrated from +litellm-regression-tests/tests/test_inference_endpoints.py. +""" + +from __future__ import annotations + +import pytest + +from e2e_config import unique_marker +from e2e_http import require_successful_call +from endpoints_client import EndpointsClient +from lifecycle import ResourceManager +from models import LiteLLMParamsBody + +pytestmark = pytest.mark.e2e + + +class TestAudioSpeech: + def test_audio_speech_returns_audio( + self, endpoints_client: EndpointsClient, resources: ResourceManager + ) -> None: + model = f"e2e-speech-{unique_marker()}" + model_id = endpoints_client.create_model( + model, + LiteLLMParamsBody( + model="openai/gpt-4o-mini-tts", api_key="os.environ/OPENAI_API_KEY" + ), + ) + resources.defer(lambda: endpoints_client.delete_model(model_id)) + key = resources.key() + + result = endpoints_client.audio_speech(key, model, "Hello!") + require_successful_call(result) + assert "audio" in (result.content_type or ""), ( + f"/audio/speech content-type is not audio: {result.content_type!r}" + ) + assert result.body, "/audio/speech returned an empty body" diff --git a/tests/e2e/llm_translation/test_chat_completions_regression_e2e.py b/tests/e2e/llm_translation/test_chat_completions_regression_e2e.py new file mode 100644 index 00000000000..269cb5d6d22 --- /dev/null +++ b/tests/e2e/llm_translation/test_chat_completions_regression_e2e.py @@ -0,0 +1,58 @@ +"""Live regression net for /chat/completions across the configured providers. + +GH #28991 broke /chat/completions (and /responses) for most models on some +releases: a clean 200 came back but with no real completion. A status check +alone would not have caught it, so each case here asserts the product promise - +a non-empty assistant message and a real model name in the body - across the +three providers wired into the gateway config (OpenAI, Anthropic, Gemini). A +regression that empties the completion for any provider fails that provider's +row here. +""" + +from __future__ import annotations + +import pytest + +from e2e_config import unique_marker +from e2e_http import unwrap +from models import ChatBody, ChatMessage +from passthrough_client import PassthroughClient + +pytestmark = pytest.mark.e2e + +CHAT_MODELS: tuple[tuple[str, str], ...] = ( + ("gpt-5.5", "openai"), + ("claude-haiku-4-5", "anthropic"), + ("gemini-2.5-flash", "gemini"), +) + + +class TestChatCompletionsRegression: + @pytest.mark.parametrize( + ("model", "route"), + CHAT_MODELS, + ids=[f"{model}-{route}" for model, route in CHAT_MODELS], + ) + @pytest.mark.covers("llm.chat_completions.provider.basic.nonstream.works", exercised_on=[]) + def test_chat_returns_real_completion( + self, client: PassthroughClient, scoped_key: str, model: str, route: str + ) -> None: + response = unwrap( + client.gateway.chat( + scoped_key, + ChatBody( + model=model, + messages=[ + ChatMessage(role="user", content=f"reply with one word {unique_marker()}") + ], + max_tokens=512, + ), + ) + ) + + assert response.model, f"{model} ({route}): response carried no model name: {response}" + assert response.choices, f"{model} ({route}): response had no choices: {response}" + message = response.choices[0].message + assert message is not None and message.content and message.content.strip(), ( + f"{model} ({route}): 200 with an empty completion (#28991): {response}" + ) diff --git a/tests/e2e/llm_translation/test_custom_pricing_e2e.py b/tests/e2e/llm_translation/test_custom_pricing_e2e.py index 58faab61aac..7894b447be9 100644 --- a/tests/e2e/llm_translation/test_custom_pricing_e2e.py +++ b/tests/e2e/llm_translation/test_custom_pricing_e2e.py @@ -1,52 +1,48 @@ -"""Live e2e: a model's custom per-token pricing is loaded, billed, and isolated. +"""Live e2e: a deployment's custom per-token pricing is loaded, billed, and isolated. -The gateway config declares ``custom-priced-flash`` (gemini-2.5-flash underneath) -with input/output rates deliberately far above the canonical gemini price, read -back here from the same config file. Three behaviors are checked independently: +Each test registers the deployment(s) it needs through /model/new (deleted on +teardown) instead of relying on a statically configured model, so the check is +self-contained and never inherits pricing another suite or a stale config left on +the shared proxy. custom-priced-flash sets input/output rates deliberately far +above the canonical gemini price; the isolation sibling shares the same +gemini/gemini-2.5-flash backend but sets no override. Three behaviors are checked +independently: - billing: a real call's logged cost breakdown charges input and output tokens at the custom rates, each component checked separately (a base-rate bill lands ~100x lower; a swapped input/output rate passes a total-only check but not this) -- reporting: /model/info surfaces those rates for the model -- isolation: gemini-2.5-flash shares the same underlying gemini/gemini-2.5-flash - but sets no override, so it must keep its own price; an override that leaks into - the shared cost map misprices it. A regression that reintroduces that leak makes - the sibling's rate match the custom one and fails the isolation check. +- reporting: /model/info surfaces those rates for the deployment +- isolation: the sibling keeps its own price; an override that leaks into the + shared backend cost map (LIT-3897) misprices it, making the sibling's rate match + the custom one and failing the isolation check """ import time -from dataclasses import dataclass -from pathlib import Path import pytest -import yaml from pydantic import BaseModel, RootModel from e2e_config import unique_marker +from e2e_gateway import Gateway from e2e_http import Success, unwrap -from models import ChatBody, ChatMessage, CustomPricing, ModelInfoEntry, SpendLogsParams -from passthrough_client import PassthroughClient +from endpoints_client import EndpointsClient +from lifecycle import ResourceManager +from models import ( + ChatBody, + ChatMessage, + LiteLLMParamsBody, + ModelInfoEntry, + SpendLogsParams, +) pytestmark = pytest.mark.e2e -CUSTOM_MODEL = "custom-priced-flash" -BASE_MODEL = "gemini-2.5-flash" -CONFIG_PATH = Path(__file__).resolve().parents[1] / "gateway" / "litellm-config.yml" - - -@dataclass(frozen=True, slots=True) -class _Rates: - input_per_token: float - output_per_token: float - - -class _ConfiguredModel(BaseModel): - model_name: str - litellm_params: CustomPricing - - -class _GatewayConfig(BaseModel): - model_list: list[_ConfiguredModel] +BACKEND_MODEL = "gemini/gemini-2.5-flash" +GEMINI_API_KEY = "os.environ/GEMINI_API_KEY" +# Deliberately ~100x above canonical gemini-2.5-flash (input 3e-7 / output 2.5e-6) +# so an override that is ignored or under-applied bills at the base rate and fails. +CUSTOM_INPUT_RATE = 5e-05 +CUSTOM_OUTPUT_RATE = 1e-04 class _CostBreakdown(BaseModel): @@ -74,40 +70,60 @@ def _approx_equal(actual: float, expected: float) -> bool: return abs(actual - expected) <= max(1e-9, abs(expected) * 1e-2) -def _configured_pricing(model_name: str) -> _Rates: - """The custom rates declared for `model_name` in the gateway config the proxy - runs with - the source of truth the billed and reported prices are checked - against.""" - config = _GatewayConfig.model_validate(yaml.safe_load(CONFIG_PATH.read_text())) - for entry in config.model_list: - if entry.model_name == model_name: - pricing = entry.litellm_params - assert pricing.input_cost_per_token and pricing.output_cost_per_token, ( - f"{model_name} declares no custom per-token rates in {CONFIG_PATH.name}" - ) - return _Rates(pricing.input_cost_per_token, pricing.output_cost_per_token) - pytest.fail(f"{model_name} not found in {CONFIG_PATH.name}") +def _provision( + endpoints_client: EndpointsClient, + resources: ResourceManager, + prefix: str, + *, + input_cost_per_token: float | None, + output_cost_per_token: float | None, +) -> str: + """Register a fresh gemini/gemini-2.5-flash deployment (deleted on teardown) and + return its model name. With the cost fields set the deployment carries a custom + pricing override; with them None it is a plain sibling on the same backend. The + marker keeps the name unique so concurrent runs on the shared proxy never + collide.""" + model_name = f"{prefix}-{unique_marker()}" + model_id = endpoints_client.create_model( + model_name, + LiteLLMParamsBody( + model=BACKEND_MODEL, + api_key=GEMINI_API_KEY, + input_cost_per_token=input_cost_per_token, + output_cost_per_token=output_cost_per_token, + ), + ) + resources.defer(lambda: endpoints_client.delete_model(model_id)) + return model_name -def _model_info_entry( - entries: list[ModelInfoEntry], model_name: str -) -> ModelInfoEntry: +def _provision_custom_priced( + endpoints_client: EndpointsClient, resources: ResourceManager +) -> str: + return _provision( + endpoints_client, + resources, + "custom-priced-flash", + input_cost_per_token=CUSTOM_INPUT_RATE, + output_cost_per_token=CUSTOM_OUTPUT_RATE, + ) + + +def _model_info_entry(entries: list[ModelInfoEntry], model_name: str) -> ModelInfoEntry: for entry in entries: if entry.model_name == model_name: return entry pytest.fail(f"{model_name} absent from /model/info; the override did not load") -def _poll_breakdown_row( - client: PassthroughClient, key: str, response_id: str | None -) -> _SpendRow: +def _poll_breakdown_row(gateway: Gateway, key: str, response_id: str | None) -> _SpendRow: """Poll /spend/logs until the call's row lands with a cost breakdown (rows flush ~60s behind the call via proxy_batch_write_at).""" - deadline = time.monotonic() + client.gateway.poll_timeout + deadline = time.monotonic() + gateway.poll_timeout while time.monotonic() < deadline: - result = client.gateway.transport.get( + result = gateway.transport.get( "/spend/logs", - headers=client.gateway.transport.master, + headers=gateway.transport.master, params=SpendLogsParams(api_key=key), response_type=_SpendRows, ) @@ -128,85 +144,99 @@ def _poll_breakdown_row( return row if priced and response_id is None: return priced[0] - time.sleep(client.gateway.poll_interval) + time.sleep(gateway.poll_interval) pytest.fail("no spend row with a cost breakdown landed before the deadline") -def test_custom_pricing_is_billed_at_configured_rate( - client: PassthroughClient, scoped_key: str -) -> None: - rates = _configured_pricing(CUSTOM_MODEL) +class TestCustomPricing: + def test_custom_pricing_is_billed_at_configured_rate( + self, + endpoints_client: EndpointsClient, + resources: ResourceManager, + scoped_key: str, + ) -> None: + model = _provision_custom_priced(endpoints_client, resources) - chat = unwrap( - client.gateway.chat( - scoped_key, - ChatBody( - model=CUSTOM_MODEL, - messages=[ - ChatMessage( - role="user", content=f"reply with one word {unique_marker()}" - ) - ], - max_tokens=16, - ), + chat = unwrap( + endpoints_client.gateway.chat( + scoped_key, + ChatBody( + model=model, + messages=[ + ChatMessage( + role="user", content=f"reply with one word {unique_marker()}" + ) + ], + max_tokens=16, + ), + ) ) - ) - row = _poll_breakdown_row(client, scoped_key, chat.id) - assert row.metadata and row.metadata.cost_breakdown # guaranteed by the poll - breakdown = row.metadata.cost_breakdown + row = _poll_breakdown_row(endpoints_client.gateway, scoped_key, chat.id) + assert row.metadata and row.metadata.cost_breakdown # guaranteed by the poll + breakdown = row.metadata.cost_breakdown - prompt = row.prompt_tokens or 0 - completion = row.completion_tokens or 0 - assert prompt > 0 and completion > 0, f"call tokens not logged on the row: {row}" + prompt = row.prompt_tokens or 0 + completion = row.completion_tokens or 0 + assert prompt > 0 and completion > 0, f"call tokens not logged on the row: {row}" - input_cost = breakdown.input_cost - output_cost = breakdown.output_cost - assert input_cost is not None and output_cost is not None, ( - f"row cost breakdown missing input/output cost: {breakdown}" - ) - assert _approx_equal(input_cost, prompt * rates.input_per_token), ( - f"input_cost {input_cost} != {prompt} tokens * {rates.input_per_token} " - f"= {prompt * rates.input_per_token}" - ) - assert _approx_equal(output_cost, completion * rates.output_per_token), ( - f"output_cost {output_cost} != {completion} tokens * {rates.output_per_token} " - f"= {completion * rates.output_per_token}" - ) + input_cost = breakdown.input_cost + output_cost = breakdown.output_cost + assert input_cost is not None and output_cost is not None, ( + f"row cost breakdown missing input/output cost: {breakdown}" + ) + assert _approx_equal(input_cost, prompt * CUSTOM_INPUT_RATE), ( + f"input_cost {input_cost} != {prompt} tokens * {CUSTOM_INPUT_RATE} " + f"= {prompt * CUSTOM_INPUT_RATE}" + ) + assert _approx_equal(output_cost, completion * CUSTOM_OUTPUT_RATE), ( + f"output_cost {output_cost} != {completion} tokens * {CUSTOM_OUTPUT_RATE} " + f"= {completion * CUSTOM_OUTPUT_RATE}" + ) + def test_model_info_reports_custom_pricing( + self, endpoints_client: EndpointsClient, resources: ResourceManager + ) -> None: + model = _provision_custom_priced(endpoints_client, resources) + entry = _model_info_entry(endpoints_client.gateway.model_info(), model) -def test_model_info_reports_custom_pricing(client: PassthroughClient) -> None: - rates = _configured_pricing(CUSTOM_MODEL) - entry = _model_info_entry(client.gateway.model_info(), CUSTOM_MODEL) + assert entry.litellm_params.input_cost_per_token == CUSTOM_INPUT_RATE, ( + f"/model/info litellm_params input rate " + f"{entry.litellm_params.input_cost_per_token} != configured {CUSTOM_INPUT_RATE}" + ) + assert entry.litellm_params.output_cost_per_token == CUSTOM_OUTPUT_RATE, ( + f"/model/info litellm_params output rate " + f"{entry.litellm_params.output_cost_per_token} != configured {CUSTOM_OUTPUT_RATE}" + ) - assert entry.litellm_params.input_cost_per_token == rates.input_per_token, ( - f"/model/info litellm_params input rate " - f"{entry.litellm_params.input_cost_per_token} != configured " - f"{rates.input_per_token}" - ) - assert entry.litellm_params.output_cost_per_token == rates.output_per_token, ( - f"/model/info litellm_params output rate " - f"{entry.litellm_params.output_cost_per_token} != configured " - f"{rates.output_per_token}" - ) + def test_custom_pricing_is_isolated_from_sibling_deployment( + self, endpoints_client: EndpointsClient, resources: ResourceManager + ) -> None: + # Register the override first so its rate is in the backend cost map before + # the sibling resolves; a leak (LIT-3897) would then poison the sibling. + custom = _provision_custom_priced(endpoints_client, resources) + sibling = _provision( + endpoints_client, + resources, + "base-flash", + input_cost_per_token=None, + output_cost_per_token=None, + ) + entries = {entry.model_name: entry for entry in endpoints_client.gateway.model_info()} + custom_entry = entries.get(custom) + sibling_entry = entries.get(sibling) + assert custom_entry is not None, f"{custom} absent from /model/info" + assert sibling_entry is not None, f"{sibling} absent from /model/info" -def test_custom_pricing_is_isolated_from_sibling_deployment( - client: PassthroughClient, -) -> None: - entries = {entry.model_name: entry for entry in client.gateway.model_info()} - custom = entries.get(CUSTOM_MODEL) - base = entries.get(BASE_MODEL) - assert custom is not None, f"{CUSTOM_MODEL} absent from /model/info" - assert base is not None, f"{BASE_MODEL} absent from /model/info" - - # custom-priced-flash overrides pricing; gemini-2.5-flash shares the same - # underlying gemini/gemini-2.5-flash but sets no override, so it must keep its - # own price. Equal rates mean the override leaked into the shared cost map. - assert ( - base.model_info.input_cost_per_token != custom.model_info.input_cost_per_token - ), ( - f"{BASE_MODEL} input rate {base.model_info.input_cost_per_token} matches " - f"{CUSTOM_MODEL}'s override {custom.model_info.input_cost_per_token}; " - f"per-deployment custom pricing is not isolated" - ) + # custom-priced-flash overrides pricing; the sibling shares the same + # gemini/gemini-2.5-flash backend but sets no override, so it must keep its + # own price. Equal rates mean the override leaked into the shared cost map. + assert ( + sibling_entry.model_info.input_cost_per_token + != custom_entry.model_info.input_cost_per_token + ), ( + f"{sibling} input rate {sibling_entry.model_info.input_cost_per_token} matches " + f"{custom}'s override {custom_entry.model_info.input_cost_per_token}; " + f"per-deployment custom pricing is not isolated" + ) diff --git a/tests/e2e/llm_translation/test_deepseek_reasoning_e2e.py b/tests/e2e/llm_translation/test_deepseek_reasoning_e2e.py new file mode 100644 index 00000000000..f8f229aa2a7 --- /dev/null +++ b/tests/e2e/llm_translation/test_deepseek_reasoning_e2e.py @@ -0,0 +1,133 @@ +"""Live e2e: DeepSeek reasoner honors a request to turn reasoning OFF. + +DeepSeek's reasoner defaults thinking ON and surfaces the chain as +``message.reasoning_content``. Two documented ways to disable it are +``reasoning_effort="none"`` and ``thinking={"type": "disabled"}``. Today the +DeepSeek param mapper (``litellm/llms/deepseek/chat/transformation.py`` +``map_openai_params``) drops both without forwarding any disable signal, so the +outbound body carries no ``thinking`` key and DeepSeek keeps thinking on; the +response still comes back with ``reasoning_content``. That is the product gap +tracked by LIT-3686 / GH #27453. + +The control case proves the model and path work (reasoning is returned when +nothing asks to disable it), so the two disable assertions are meaningful. Those +two are marked xfail(strict) until the mapper forwards a real disable signal; an +xpass then alerts that the fix landed. + +Requires DEEPSEEK_API_KEY on the proxy (tests/e2e/.env). No skip gate: once the +proxy is up, a failure here is real, per the suite's hard-fail contract. +""" + +from __future__ import annotations + +import pytest + +from e2e_config import unique_marker +from e2e_http import unwrap +from lifecycle import ResourceManager +from models import ChatBody, ChatMessage, ChatResponse, LiteLLMParamsBody, ThinkingParam +from passthrough_client import PassthroughClient + +pytestmark = pytest.mark.e2e + +REASONER = "deepseek/deepseek-reasoner" +PROMPT = "What is 17 + 26? Answer with just the number." + + +def _register_reasoner(client: PassthroughClient, resources: ResourceManager) -> str: + model = f"e2e-deepseek-reasoner-{unique_marker()}" + model_id = client.gateway.create_model( + model, + LiteLLMParamsBody(model=REASONER, api_key="os.environ/DEEPSEEK_API_KEY"), + ) + resources.defer(lambda: client.gateway.delete_model(model_id)) + return model + + +def _reasoning_content(response: ChatResponse) -> str | None: + assert response.choices, f"reasoner returned no choices: {response}" + message = response.choices[0].message + assert message is not None, f"reasoner choice has no message: {response}" + return message.reasoning_content + + +class TestDeepSeekReasoningDisable: + def test_reasoner_returns_reasoning_by_default( + self, client: PassthroughClient, resources: ResourceManager + ) -> None: + model = _register_reasoner(client, resources) + key = resources.key() + + response = unwrap( + client.gateway.chat( + key, + ChatBody( + model=model, + messages=[ChatMessage(role="user", content=PROMPT)], + max_tokens=64, + ), + ) + ) + reasoning = _reasoning_content(response) + assert reasoning, ( + "control case: deepseek-reasoner returned no reasoning_content with no " + f"disable param, so the disable assertions below can't be trusted: {response}" + ) + + @pytest.mark.xfail( + strict=True, + reason=( + "LIT-3686 / GH #27453: DeepSeek reasoning_effort='none' and " + "thinking type='disabled' are silently dropped; reasoning not disabled" + ), + ) + def test_reasoning_effort_none_disables_reasoning( + self, client: PassthroughClient, resources: ResourceManager + ) -> None: + model = _register_reasoner(client, resources) + key = resources.key() + + response = unwrap( + client.gateway.chat( + key, + ChatBody( + model=model, + messages=[ChatMessage(role="user", content=PROMPT)], + max_tokens=64, + reasoning_effort="none", + ), + ) + ) + assert not _reasoning_content(response), ( + "reasoning_effort='none' must disable reasoning, but reasoning_content " + f"is still present: {response}" + ) + + @pytest.mark.xfail( + strict=True, + reason=( + "LIT-3686 / GH #27453: DeepSeek reasoning_effort='none' and " + "thinking type='disabled' are silently dropped; reasoning not disabled" + ), + ) + def test_thinking_disabled_disables_reasoning( + self, client: PassthroughClient, resources: ResourceManager + ) -> None: + model = _register_reasoner(client, resources) + key = resources.key() + + response = unwrap( + client.gateway.chat( + key, + ChatBody( + model=model, + messages=[ChatMessage(role="user", content=PROMPT)], + max_tokens=64, + thinking=ThinkingParam(type="disabled"), + ), + ) + ) + assert not _reasoning_content(response), ( + "thinking={'type': 'disabled'} must disable reasoning, but " + f"reasoning_content is still present: {response}" + ) diff --git a/tests/e2e/llm_translation/test_embeddings_endpoint_e2e.py b/tests/e2e/llm_translation/test_embeddings_endpoint_e2e.py new file mode 100644 index 00000000000..56f2de8bd4f --- /dev/null +++ b/tests/e2e/llm_translation/test_embeddings_endpoint_e2e.py @@ -0,0 +1,42 @@ +"""Live e2e: POST /embeddings returns a real vector. + +Registers an OpenAI embedding deployment at runtime and asserts a non-empty, +non-zero vector came back. Migrated from +litellm-regression-tests/tests/test_inference_endpoints.py; the LIT-3167 guard in +tests/e2e/embeddings/ covers the Gemini embedding path. +""" + +from __future__ import annotations + +import pytest + +from e2e_config import unique_marker +from e2e_http import require_successful_call +from endpoints_client import EmbeddingsResult, EndpointsClient +from lifecycle import ResourceManager +from models import LiteLLMParamsBody + +pytestmark = pytest.mark.e2e + + +class TestEmbeddingsEndpoint: + def test_embeddings_returns_vector( + self, endpoints_client: EndpointsClient, resources: ResourceManager + ) -> None: + model = f"e2e-embeddings-{unique_marker()}" + model_id = endpoints_client.create_model( + model, + LiteLLMParamsBody( + model="openai/text-embedding-3-small", api_key="os.environ/OPENAI_API_KEY" + ), + ) + resources.defer(lambda: endpoints_client.delete_model(model_id)) + key = resources.key() + + result = endpoints_client.embeddings(key, model, "Say this is a test!") + require_successful_call(result) + parsed = EmbeddingsResult.model_validate_json(result.body) + assert parsed.first_vector, f"/embeddings returned no vector: {result.body[:300]}" + assert any(component != 0.0 for component in parsed.first_vector), ( + f"embedding vector is all zeros: {result.body[:300]}" + ) diff --git a/tests/e2e/llm_translation/test_image_generation_e2e.py b/tests/e2e/llm_translation/test_image_generation_e2e.py new file mode 100644 index 00000000000..4d2211f3be4 --- /dev/null +++ b/tests/e2e/llm_translation/test_image_generation_e2e.py @@ -0,0 +1,42 @@ +"""Live e2e: POST /v1/images/generations returns an image. + +Registers an OpenAI image deployment at runtime and asserts the response carries a +generated image (url or base64). Migrated from +litellm-regression-tests/tests/test_inference_endpoints.py. +""" + +from __future__ import annotations + +import pytest + +from e2e_config import unique_marker +from e2e_http import require_successful_call +from endpoints_client import EndpointsClient, ImagesResult +from lifecycle import ResourceManager +from models import LiteLLMParamsBody + +pytestmark = pytest.mark.e2e + + +class TestImageGeneration: + def test_image_generation_returns_image( + self, endpoints_client: EndpointsClient, resources: ResourceManager + ) -> None: + model = f"e2e-image-{unique_marker()}" + model_id = endpoints_client.create_model( + model, + LiteLLMParamsBody( + model="openai/gpt-image-1-mini", api_key="os.environ/OPENAI_API_KEY" + ), + ) + resources.defer(lambda: endpoints_client.delete_model(model_id)) + key = resources.key() + + result = endpoints_client.images(key, model, "Draw a cute cat") + require_successful_call(result) + parsed = ImagesResult.model_validate_json(result.body) + assert parsed.data, f"/images/generations returned no data: {result.body[:300]}" + first = parsed.data[0] + assert first.b64_json or first.url, ( + f"generated image has neither b64_json nor url: {result.body[:300]}" + ) diff --git a/tests/e2e/llm_translation/test_messages_e2e.py b/tests/e2e/llm_translation/test_messages_e2e.py new file mode 100644 index 00000000000..b0a48f22118 --- /dev/null +++ b/tests/e2e/llm_translation/test_messages_e2e.py @@ -0,0 +1,39 @@ +"""Live e2e: POST /v1/messages (Anthropic Messages API) returns a real completion. + +Registers an Anthropic deployment at runtime, drives the Messages endpoint through +the gateway, and asserts an assistant message with text came back. Migrated from +litellm-regression-tests/tests/test_inference_endpoints.py. +""" + +from __future__ import annotations + +import pytest + +from e2e_config import unique_marker +from e2e_http import require_successful_call +from endpoints_client import EndpointsClient, MessagesResult +from lifecycle import ResourceManager +from models import LiteLLMParamsBody + +pytestmark = pytest.mark.e2e + + +class TestAnthropicMessages: + def test_messages_returns_completion( + self, endpoints_client: EndpointsClient, resources: ResourceManager + ) -> None: + model = f"e2e-messages-{unique_marker()}" + model_id = endpoints_client.create_model( + model, + LiteLLMParamsBody( + model="anthropic/claude-haiku-4-5", api_key="os.environ/ANTHROPIC_API_KEY" + ), + ) + resources.defer(lambda: endpoints_client.delete_model(model_id)) + key = resources.key() + + result = endpoints_client.messages(key, model, "reply with one word") + require_successful_call(result) + parsed = MessagesResult.model_validate_json(result.body) + assert parsed.role == "assistant", f"unexpected role: {result.body[:300]}" + assert parsed.text.strip(), f"/v1/messages returned no text: {result.body[:300]}" diff --git a/tests/e2e/llm_translation/test_ocr_rust_e2e.py b/tests/e2e/llm_translation/test_ocr_rust_e2e.py index 44908da2729..361bb5126a7 100644 --- a/tests/e2e/llm_translation/test_ocr_rust_e2e.py +++ b/tests/e2e/llm_translation/test_ocr_rust_e2e.py @@ -1,31 +1,32 @@ """Live e2e: Rust-backed OCR is reachable through the gateway across providers. -The gateway config declares one rust-ocr deployment per provider (mistral, -azure_ai, azure document intelligence, vertex mistral, vertex deepseek). Start the -proxy with the Rust OCR path enabled: +Each provider's OCR deployment is registered at runtime via /model/new and deleted +on teardown, so nothing is hardcoded into the gateway config. Every provider is its +own typed OcrProvider below: it owns the model id and the os.environ/* credential +references the proxy resolves at call time, so adding a provider is a new type +rather than another inline body. Start the proxy with the Rust OCR path enabled: - LITELLM_USE_RUST_OCR=1 litellm --config tests/e2e/gateway/litellm-config.yml - -Three behaviors are checked: the config declares every provider's deployment (a -pure config read, no proxy needed); the running proxy loaded them onto /model/info; -and each one returns a well-formed OCR document over /v1/ocr. Per the e2e -"skip on environment, fail on behavior" rule, the proxy-backed cases skip when no -proxy answers but fail (never skip) once a request reaches it, so a provider whose -credentials are missing surfaces as a hard failure rather than silent green. +Each case creates its deployment, drives a real /v1/ocr call, and asserts a +well-formed OCR document comes back. Per the e2e "skip on environment, fail on +behavior" rule, a case skips when no proxy answers but fails (never skips) once a +request reaches it: the proxy fetches each provider's referenced secrets, so a +missing credential surfaces as a live provider error rather than silent green. """ from __future__ import annotations from dataclasses import dataclass -from pathlib import Path +from typing import Protocol import pytest -import yaml -from pydantic import BaseModel +from e2e_config import unique_marker from e2e_http import unwrap -from models import OcrBody, OcrDocument, OcrResponse -from passthrough_client import PassthroughClient +from endpoints_client import EndpointsClient +from lifecycle import ResourceManager +from models import LiteLLMParamsBody, OcrBody, OcrDocument, OcrResponse + +pytestmark = pytest.mark.e2e # Tiny in-repo fixtures served via jsdelivr (sha-pinned, immutable) so the request # bodies stay stable across runs. @@ -40,53 +41,99 @@ TEST_IMAGE_URL = ( "/tests/image_gen_tests/test_image.png" ) -CONFIG_PATH = Path(__file__).resolve().parents[1] / "gateway" / "litellm-config.yml" + +class OcrProvider(Protocol): + """One OCR provider's deployment config: its model id plus the os.environ/* + credential references the proxy resolves at call time. Each provider owns which + env vars it reads, so a new provider is a new type, not another inline body.""" + + def litellm_params(self) -> LiteLLMParamsBody: ... + + +@dataclass(frozen=True, slots=True) +class MistralOcr: + model: str = "mistral/mistral-ocr-latest" + + def litellm_params(self) -> LiteLLMParamsBody: + return LiteLLMParamsBody(model=self.model, api_key="os.environ/MISTRAL_API_KEY") + + +@dataclass(frozen=True, slots=True) +class AzureAiOcr: + """azure_ai (mistral) OCR. The rust OCR path resolves credentials itself from + AZURE_AI_API_BASE / AZURE_AI_API_KEY when the deployment leaves them unset; it + does NOT unwrap an `os.environ/*` reference passed as api_base (it would be sent + to Azure verbatim), so we omit them and let litellm read the env vars by name.""" + + model: str + + def litellm_params(self) -> LiteLLMParamsBody: + return LiteLLMParamsBody(model=self.model) + + +@dataclass(frozen=True, slots=True) +class AzureDocIntelligenceOcr: + """azure_ai Document Intelligence OCR. A separate Azure resource from the + mistral one, so it has its own env vars: AZURE_DOCUMENT_INTELLIGENCE_ENDPOINT / + AZURE_DOCUMENT_INTELLIGENCE_API_KEY, which the OCR config resolves from the + doc-intelligence model name when api_base/api_key are left unset.""" + + model: str = "azure_ai/doc-intelligence/prebuilt-layout" + + def litellm_params(self) -> LiteLLMParamsBody: + return LiteLLMParamsBody(model=self.model) + + +@dataclass(frozen=True, slots=True) +class VertexOcr: + model: str + location: str + + def litellm_params(self) -> LiteLLMParamsBody: + return LiteLLMParamsBody( + model=self.model, + vertex_project="os.environ/VERTEXAI_PROJECT", + vertex_location=self.location, + vertex_credentials="os.environ/VERTEXAI_CREDENTIALS", + ) @dataclass(frozen=True, slots=True) class _OcrCase: - model: str + suffix: str + provider: OcrProvider document: OcrDocument RUST_OCR_CASES: tuple[_OcrCase, ...] = ( _OcrCase( - "rust-ocr-mistral", + "mistral", + MistralOcr(), OcrDocument(type="document_url", document_url=TEST_PDF_URL), ), _OcrCase( - "rust-ocr-azure-ai", + "azure-ai", + AzureAiOcr("azure_ai/mistral-document-ai-2505"), OcrDocument(type="document_url", document_url=TEST_PDF_URL), ), _OcrCase( - "rust-ocr-azure-document-intelligence", + "azure-document-intelligence", + AzureDocIntelligenceOcr(), OcrDocument(type="document_url", document_url=TEST_PDF_URL), ), _OcrCase( - "rust-ocr-vertex-mistral", + "vertex-mistral", + VertexOcr("vertex_ai/mistral-ocr-2505", "us-central1"), OcrDocument(type="document_url", document_url=TEST_PDF_URL), ), _OcrCase( - "rust-ocr-vertex-deepseek", + "vertex-deepseek", + VertexOcr("vertex_ai/deepseek-ocr-maas", "global"), OcrDocument(type="image_url", image_url=TEST_IMAGE_URL), ), ) -_EXPECTED_MODELS = frozenset(case.model for case in RUST_OCR_CASES) -_CASE_IDS = tuple(case.model.removeprefix("rust-ocr-") for case in RUST_OCR_CASES) - - -class _ConfiguredModel(BaseModel): - model_name: str - - -class _GatewayConfig(BaseModel): - model_list: list[_ConfiguredModel] - - -def _configured_model_names() -> frozenset[str]: - config = _GatewayConfig.model_validate(yaml.safe_load(CONFIG_PATH.read_text())) - return frozenset(entry.model_name for entry in config.model_list) +_CASE_IDS = tuple(case.suffix for case in RUST_OCR_CASES) def _assert_ocr_document(response: OcrResponse) -> None: @@ -96,22 +143,15 @@ def _assert_ocr_document(response: OcrResponse) -> None: assert response.pages[0].markdown is not None, "first page has no markdown" -def test_rust_ocr_models_declared_in_gateway_config() -> None: - """Pure config read (no proxy): every provider's rust-ocr deployment the suite - exercises is declared in the gateway config the proxy runs with. A case added - here without a matching deployment fails before any live call is attempted.""" - missing = _EXPECTED_MODELS - _configured_model_names() - assert not missing, f"rust-ocr models absent from {CONFIG_PATH.name}: {missing}" - - -@pytest.mark.e2e class TestRustOcrGateway: - def test_gateway_loaded_rust_ocr_models(self, client: PassthroughClient) -> None: - loaded = frozenset(entry.model_name for entry in client.gateway.model_info()) - missing = _EXPECTED_MODELS - loaded - assert not missing, f"proxy did not load rust-ocr models: {missing}" - @pytest.mark.parametrize("case", RUST_OCR_CASES, ids=_CASE_IDS) - def test_rust_ocr_response(self, client: PassthroughClient, scoped_key: str, case: _OcrCase) -> None: - response = unwrap(client.gateway.ocr(scoped_key, OcrBody(model=case.model, document=case.document))) + def test_rust_ocr_response( + self, endpoints_client: EndpointsClient, resources: ResourceManager, case: _OcrCase + ) -> None: + model = f"rust-ocr-{case.suffix}-{unique_marker()}" + model_id = endpoints_client.create_model(model, case.provider.litellm_params()) + resources.defer(lambda: endpoints_client.delete_model(model_id)) + key = resources.key() + + response = unwrap(endpoints_client.gateway.ocr(key, OcrBody(model=model, document=case.document))) _assert_ocr_document(response) diff --git a/tests/e2e/llm_translation/test_provider_features_e2e.py b/tests/e2e/llm_translation/test_provider_features_e2e.py new file mode 100644 index 00000000000..9c99c1be161 --- /dev/null +++ b/tests/e2e/llm_translation/test_provider_features_e2e.py @@ -0,0 +1,148 @@ +"""Live e2e for model-specific request features: service_tier and prompt caching. + +Each case asserts the feature took effect, not just a 200. + +service_tier is an OpenAI concept. The proxy forwards it and the provider echoes +the tier back on the response, so sending a non-default tier ("flex") and reading +it back off ``service_tier`` proves the param was honored end to end; litellm's own +default injection would report "default", so a "flex" echo can only come from the +request being forwarded. Bedrock and Vertex do not accept service_tier, so that +cell is OpenAI-only by design. + +Prompt caching is asserted through provider prompt-cache usage tokens. The +deterministic path is explicit ``cache_control`` on an Anthropic-family model +(here Bedrock's Claude): a large cacheable prefix is sent twice and the second +call must report ``cache_read_input_tokens > 0``. OpenAI and Gemini only offer +implicit automatic caching, which does not deterministically produce a cache read +within a test window (verified: repeated >3k-token prompts kept +``prompt_tokens_details.cached_tokens`` at 0), so those caching cells are out of +scope here and covered only by the explicit-cache-control Bedrock case. +""" + +from __future__ import annotations + +import pytest +from pydantic import BaseModel + +from e2e_config import unique_marker +from e2e_http import unwrap +from lifecycle import ResourceManager +from models import ChatBody, ChatMessage, ChatResponse, LiteLLMParamsBody +from passthrough_client import PassthroughClient + +pytestmark = pytest.mark.e2e + +SERVICE_TIER = "flex" +CACHE_MIN_READ_TOKENS = 1 + + +class CacheControl(BaseModel): + type: str = "ephemeral" + + +class CacheTextBlock(BaseModel): + type: str = "text" + text: str + cache_control: CacheControl | None = None + + +class RichMessage(BaseModel): + role: str + content: list[CacheTextBlock] + + +class CacheChatBody(BaseModel): + model: str + messages: list[RichMessage] + max_tokens: int + + +def cacheable_prefix() -> str: + return ( + "You are a policy compliance auditor. The following corpus is the immutable " + "reference the assistant must consult on every turn. " + ) + ("Clause: obey all safety, formatting, and citation rules exactly. " * 400) + + +def post_chat(client: PassthroughClient, key: str, body: BaseModel) -> ChatResponse: + return unwrap( + client.gateway.transport.post( + "/chat/completions", + headers=client.gateway.transport.bearer(key), + json=body, + response_type=ChatResponse, + ) + ) + + +class TestServiceTier: + @pytest.mark.covers("llm.chat_completions.openai.service_tier.works", exercised_on=[]) + def test_openai_service_tier_is_echoed( + self, client: PassthroughClient, resources: ResourceManager + ) -> None: + model = f"e2e-service-tier-{unique_marker()}" + model_id = client.gateway.create_model( + model, LiteLLMParamsBody(model="openai/gpt-5.5", api_key="os.environ/OPENAI_API_KEY") + ) + resources.defer(lambda: client.gateway.delete_model(model_id)) + key = resources.key() + + response = unwrap( + client.gateway.chat( + key, + ChatBody( + model=model, + messages=[ChatMessage(role="user", content="reply with one word")], + max_tokens=64, + service_tier=SERVICE_TIER, + ), + ) + ) + assert response.service_tier == SERVICE_TIER, ( + f"service_tier not honored: sent {SERVICE_TIER!r}, response reported " + f"{response.service_tier!r} ({response})" + ) + + +class TestPromptCaching: + @pytest.mark.covers( + "llm.chat_completions.bedrock_converse.prompt_cache_5m.nonstream.cache_hit", exercised_on=[] + ) + def test_bedrock_cache_control_produces_cache_read( + self, client: PassthroughClient, resources: ResourceManager + ) -> None: + model = f"e2e-bedrock-cache-{unique_marker()}" + model_id = client.gateway.create_model( + model, + LiteLLMParamsBody( + model="bedrock/us.anthropic.claude-haiku-4-5-20251001-v1:0", + aws_region_name="us-east-1", + ), + ) + resources.defer(lambda: client.gateway.delete_model(model_id)) + key = resources.key() + + body = CacheChatBody( + model=model, + max_tokens=32, + messages=[ + RichMessage( + role="user", + content=[ + CacheTextBlock(text=cacheable_prefix(), cache_control=CacheControl()), + CacheTextBlock(text="Answer in one word: acknowledged?"), + ], + ) + ], + ) + + first = post_chat(client, key, body) + assert first.usage is not None, f"first call reported no usage: {first}" + + second = post_chat(client, key, body) + assert second.usage is not None, f"second call reported no usage: {second}" + cache_read = second.usage.cache_read_input_tokens + assert cache_read is not None and cache_read >= CACHE_MIN_READ_TOKENS, ( + "second identical request did not read the prompt cache: " + f"cache_read_input_tokens={cache_read!r} (usage={second.usage})" + ) diff --git a/tests/e2e/llm_translation/test_rerank_e2e.py b/tests/e2e/llm_translation/test_rerank_e2e.py new file mode 100644 index 00000000000..4b30ac1ea5c --- /dev/null +++ b/tests/e2e/llm_translation/test_rerank_e2e.py @@ -0,0 +1,49 @@ +"""Live e2e: POST /v1/rerank ranks documents by relevance. + +Registers a Cohere rerank deployment at runtime and asserts the endpoint returns +scored results within the requested top_n. Migrated from +litellm-regression-tests/tests/test_inference_endpoints.py. +""" + +from __future__ import annotations + +import pytest + +from e2e_config import unique_marker +from e2e_http import require_successful_call +from endpoints_client import EndpointsClient, RerankResult +from lifecycle import ResourceManager +from models import LiteLLMParamsBody + +pytestmark = pytest.mark.e2e + +DOCUMENTS = [ + "Carson City is the capital city of the American state of Nevada.", + "The Commonwealth of the Northern Mariana Islands is a group of islands in the Pacific Ocean.", + "Washington, D.C. is the capital of the United States.", + "Capital punishment has existed in the United States since before it was a country.", +] + + +class TestRerank: + def test_rerank_scores_top_n( + self, endpoints_client: EndpointsClient, resources: ResourceManager + ) -> None: + model = f"e2e-rerank-{unique_marker()}" + model_id = endpoints_client.create_model( + model, + LiteLLMParamsBody(model="cohere/rerank-v3.5", api_key="os.environ/COHERE_API_KEY"), + ) + resources.defer(lambda: endpoints_client.delete_model(model_id)) + key = resources.key() + + result = endpoints_client.rerank( + key, model, "What is the capital of the United States?", DOCUMENTS, top_n=3 + ) + require_successful_call(result) + parsed = RerankResult.model_validate_json(result.body) + assert parsed.results, f"/rerank returned no results: {result.body[:300]}" + assert len(parsed.results) <= 3, f"top_n=3 not honored: {result.body[:300]}" + assert parsed.results[0].relevance_score is not None, ( + f"top rerank result has no relevance_score: {result.body[:300]}" + ) diff --git a/tests/e2e/llm_translation/test_responses_e2e.py b/tests/e2e/llm_translation/test_responses_e2e.py new file mode 100644 index 00000000000..743de79880f --- /dev/null +++ b/tests/e2e/llm_translation/test_responses_e2e.py @@ -0,0 +1,36 @@ +"""Live e2e: POST /v1/responses returns a real completion. + +Registers an OpenAI deployment at runtime, drives the Responses API through the +gateway, and asserts output text came back. Migrated from +litellm-regression-tests/tests/test_inference_endpoints.py. +""" + +from __future__ import annotations + +import pytest + +from e2e_config import unique_marker +from e2e_http import require_successful_call +from endpoints_client import EndpointsClient, ResponsesResult +from lifecycle import ResourceManager +from models import LiteLLMParamsBody + +pytestmark = pytest.mark.e2e + + +class TestResponses: + def test_responses_returns_completion( + self, endpoints_client: EndpointsClient, resources: ResourceManager + ) -> None: + model = f"e2e-responses-{unique_marker()}" + model_id = endpoints_client.create_model( + model, + LiteLLMParamsBody(model="openai/gpt-4o-mini", api_key="os.environ/OPENAI_API_KEY"), + ) + resources.defer(lambda: endpoints_client.delete_model(model_id)) + key = resources.key() + + result = endpoints_client.responses(key, model, "reply with one word") + require_successful_call(result) + parsed = ResponsesResult.model_validate_json(result.body) + assert parsed.text.strip(), f"/responses returned no output text: {result.body[:300]}" diff --git a/tests/e2e/logging/conftest.py b/tests/e2e/logging/conftest.py new file mode 100644 index 00000000000..40c19aefca7 --- /dev/null +++ b/tests/e2e/logging/conftest.py @@ -0,0 +1,37 @@ +"""Fixtures for the Datadog logging suite. + +These tests drive the Datadog batch-send path (#25663) directly against the real +Datadog logs intake with synthetic events - no LLM calls, no proxy, no log +read-back - so they need only the shipping credentials DD_API_KEY + DD_SITE +(DD_SERVICE is an optional tag). No Datadog Application key is required, and they +skip when the shipping credentials are absent from the environment. +""" + +import os + +import pytest + +from logging_client import LoggingClient, build_logging_client + + +def pytest_configure(config: pytest.Config) -> None: + config.addinivalue_line( + "markers", + "covers: registry cell a test covers, e.g. logging.datadog.success.writes_object", + ) + + +@pytest.fixture(scope="session") +def client() -> LoggingClient: + """The logging suite's client: holds the shared Gateway so `resources` / + `scoped_key` clean up keys, and adds `/metrics` scraping.""" + return build_logging_client() + + +@pytest.fixture +def datadog_creds() -> None: + """Gate the suite on the Datadog shipping credentials. The DataDogLogger is built + inside each async test, not here, because its __init__ schedules a periodic-flush + task via asyncio.create_task and so needs a running event loop.""" + if not (os.getenv("DD_API_KEY") and os.getenv("DD_SITE")): + pytest.skip("set DD_API_KEY and DD_SITE to run the Datadog logging suite") diff --git a/tests/e2e/logging/logging_client.py b/tests/e2e/logging/logging_client.py new file mode 100644 index 00000000000..a3213fbdb00 --- /dev/null +++ b/tests/e2e/logging/logging_client.py @@ -0,0 +1,48 @@ +"""Client for the logging e2e suite: drive traffic and scrape the proxy's +Prometheus ``/metrics`` endpoint. + +Holds the shared Gateway so the ``resources`` fixture cleans up keys it creates. +``/metrics`` is exposed as plaintext (not a typed JSON body), so scraping goes +through ``transport.probe`` and returns the raw exposition text for a Prometheus +parser to read. +""" + +from __future__ import annotations + +from dataclasses import dataclass + +from e2e_gateway import Gateway, build_gateway +from e2e_http import NoBody, unwrap +from models import ChatBody, ChatMessage, ChatResponse, KeyGenerateBody + + +@dataclass(frozen=True, slots=True) +class LoggingClient: + gateway: Gateway + + def key_with_alias(self, alias: str, *, models: list[str]) -> str: + return self.gateway.generate_key( + KeyGenerateBody(key_alias=alias, models=models, user_id=f"e2e-{alias}") + ) + + def delete_key(self, key: str) -> None: + self.gateway.delete_key(key) + + def chat(self, key: str, model: str, text: str) -> ChatResponse: + return unwrap( + self.gateway.chat( + key, + ChatBody( + model=model, + messages=[ChatMessage(role="user", content=text)], + max_tokens=64, + ), + ) + ) + + def scrape_metrics(self) -> str: + return self.gateway.probe("/metrics", params=NoBody()).body + + +def build_logging_client() -> LoggingClient: + return LoggingClient(gateway=build_gateway()) diff --git a/tests/e2e/logging/test_prometheus_cardinality_e2e.py b/tests/e2e/logging/test_prometheus_cardinality_e2e.py new file mode 100644 index 00000000000..163293a3009 --- /dev/null +++ b/tests/e2e/logging/test_prometheus_cardinality_e2e.py @@ -0,0 +1,70 @@ +"""Live e2e: Prometheus request metrics grow one series per virtual key. + +The proxy exposes ``/metrics`` (prometheus is in the callbacks and +``require_auth_for_metrics_endpoint`` is off in the e2e config). The counter +``litellm_requests_metric_total`` carries an ``api_key_alias`` label, so driving +traffic through keys with distinct aliases must produce a distinct labeled series +per alias. This is the per-key cardinality contract: a regression that stops +stamping ``api_key_alias`` (or collapses every key onto one series) would drop +the aliases and fail here. + +Scraping goes through ``transport.probe`` (raw text) and is parsed with +prometheus_client; the metric is eventually consistent (it increments on the +success-logging callback), so the scrape polls to a deadline. +""" + +from __future__ import annotations + +import time + +import pytest +from prometheus_client.parser import text_string_to_metric_families + +from e2e_config import unique_marker +from lifecycle import ResourceManager +from logging_client import LoggingClient + +pytestmark = pytest.mark.e2e + +DRIVER_MODEL = "gemini-2.5-flash" +REQUESTS_METRIC = "litellm_requests_metric_total" +ALIAS_LABEL = "api_key_alias" +DISTINCT_KEYS = 3 + + +def _aliases_in_metric(exposition: str, metric: str, label: str) -> frozenset[str]: + """The set of ``label`` values present on ``metric`` samples in a scrape.""" + return frozenset( + sample.labels[label] + for family in text_string_to_metric_families(exposition) + for sample in family.samples + if sample.name == metric and label in sample.labels + ) + + +class TestPrometheusPerKeyCardinality: + @pytest.mark.covers("logging.prometheus.success.exports_metric", exercised_on=[]) + def test_distinct_key_aliases_produce_distinct_series( + self, client: LoggingClient, resources: ResourceManager + ) -> None: + aliases = tuple(f"e2e-prom-{unique_marker()}" for _ in range(DISTINCT_KEYS)) + for alias in aliases: + key = client.key_with_alias(alias, models=[DRIVER_MODEL]) + resources.defer(lambda k=key: client.delete_key(k)) + response = client.chat(key, DRIVER_MODEL, f"reply with one word {alias}") + assert response.model, f"driver call for {alias} returned no model: {response}" + + wanted = frozenset(aliases) + deadline = time.monotonic() + client.gateway.poll_timeout + seen: frozenset[str] = frozenset() + while time.monotonic() < deadline: + seen = _aliases_in_metric(client.scrape_metrics(), REQUESTS_METRIC, ALIAS_LABEL) + if wanted <= seen: + break + time.sleep(client.gateway.poll_interval) + + missing = wanted - seen + assert not missing, ( + f"{REQUESTS_METRIC} is missing a per-key series for aliases {sorted(missing)}; " + f"each distinct {ALIAS_LABEL} must grow its own series" + ) diff --git a/tests/e2e/management/conftest.py b/tests/e2e/management/conftest.py new file mode 100644 index 00000000000..1a1a740cc0d --- /dev/null +++ b/tests/e2e/management/conftest.py @@ -0,0 +1,17 @@ +"""Management suite client fixture; lifecycle/skip/marker live in the parent conftest.""" + +import pytest + +from management_client import ManagementClient, build_client + + +def pytest_configure(config: pytest.Config) -> None: + config.addinivalue_line( + "markers", + "covers: registry cell a test covers, e.g. mgmt.key.generate.persists", + ) + + +@pytest.fixture(scope="session") +def client() -> ManagementClient: + return build_client() diff --git a/tests/e2e/management/management_client.py b/tests/e2e/management/management_client.py new file mode 100644 index 00000000000..5520b44993d --- /dev/null +++ b/tests/e2e/management/management_client.py @@ -0,0 +1,222 @@ +"""Client for the management-routes e2e suite: the shared Gateway plus the +key/team/user/organization writes, the info/list read-backs the tests assert, +and the raw-status calls judged by HTTP outcome (chat under a scoped key, an +llm-only key hitting a management route). +""" + +from __future__ import annotations + +from dataclasses import dataclass + +from e2e_gateway import Gateway, build_gateway +from e2e_http import NoBody, ProbeResult, StreamingResponse, unwrap +from models import ( + ChatBody, + ChatMessage, + KeyDeleteBody, + KeyGenerateBody, + KeyListParams, + KeyListResponse, + KeyUpdateBody, + OrgDeleteBody, + OrgInfoParams, + OrgInfoResponse, + OrgNewBody, + OrgNewResponse, + TeamData, + TeamDeleteBody, + TeamInfoParams, + TeamInfoResponse, + TeamMemberAddBody, + TeamMemberDeleteBody, + TeamMemberEntry, + TeamNewBody, + TeamNewResponse, + UserDeleteBody, + UserInfoParams, + UserInfoResponse, + UserListParams, + UserListResponse, + UserNewBody, + UserNewResponse, +) + +MODEL_ACCESS_DENIED_MARKER = "key_model_access_denied" +ROUTE_NOT_ALLOWED_MARKER = "not allowed to call this route" + + +@dataclass(frozen=True, slots=True) +class ManagementClient: + gateway: Gateway + + def llm_only_key(self) -> str: + return self.gateway.generate_key(KeyGenerateBody(models=[], allowed_routes=["llm_api_routes"])) + + def update_key_models(self, key: str, models: list[str]) -> None: + _ = unwrap( + self.gateway.transport.post( + "/key/update", + headers=self.gateway.transport.master, + json=KeyUpdateBody(key=key, models=models), + response_type=NoBody, + ) + ) + + def delete_key_strict(self, key: str) -> None: + """Strict delete for the act phase of a test: a failed delete is a hard + failure, unlike the warn-only Gateway.delete_key used at teardown.""" + _ = unwrap( + self.gateway.transport.post( + "/key/delete", + headers=self.gateway.transport.master, + json=KeyDeleteBody(keys=[key]), + response_type=NoBody, + ) + ) + + def key_alias_count(self, key_alias: str) -> int: + return unwrap( + self.gateway.transport.get( + "/key/list", + headers=self.gateway.transport.master, + params=KeyListParams(key_alias=key_alias), + response_type=KeyListResponse, + ) + ).total_count + + def create_team(self, body: TeamNewBody) -> str: + return unwrap( + self.gateway.transport.post( + "/team/new", + headers=self.gateway.transport.master, + json=body, + response_type=TeamNewResponse, + ) + ).team_id + + def delete_team(self, team_id: str) -> None: + _ = self.gateway.transport.post( + "/team/delete", + headers=self.gateway.transport.master, + json=TeamDeleteBody(team_ids=[team_id]), + response_type=NoBody, + ) + + def team_info(self, team_id: str) -> TeamData: + return unwrap( + self.gateway.transport.get( + "/team/info", + headers=self.gateway.transport.master, + params=TeamInfoParams(team_id=team_id), + response_type=TeamInfoResponse, + ) + ).team_info + + def team_info_status(self, team_id: str) -> ProbeResult: + return self.gateway.transport.probe("/team/info", params=TeamInfoParams(team_id=team_id)) + + def add_team_member(self, team_id: str, user_id: str) -> None: + _ = unwrap( + self.gateway.transport.post( + "/team/member_add", + headers=self.gateway.transport.master, + json=TeamMemberAddBody(team_id=team_id, member=TeamMemberEntry(role="user", user_id=user_id)), + response_type=NoBody, + ) + ) + + def delete_team_member(self, team_id: str, user_id: str) -> None: + _ = unwrap( + self.gateway.transport.post( + "/team/member_delete", + headers=self.gateway.transport.master, + json=TeamMemberDeleteBody(team_id=team_id, user_id=user_id), + response_type=NoBody, + ) + ) + + def create_user(self, body: UserNewBody) -> str: + return unwrap( + self.gateway.transport.post( + "/user/new", + headers=self.gateway.transport.master, + json=body, + response_type=UserNewResponse, + ) + ).user_id + + def delete_user(self, user_id: str) -> None: + _ = self.gateway.transport.post( + "/user/delete", + headers=self.gateway.transport.master, + json=UserDeleteBody(user_ids=[user_id]), + response_type=NoBody, + ) + + def user_info(self, user_id: str) -> UserInfoResponse: + return unwrap( + self.gateway.transport.get( + "/user/info", + headers=self.gateway.transport.master, + params=UserInfoParams(user_id=user_id), + response_type=UserInfoResponse, + ) + ) + + def user_count(self, user_id: str) -> int: + return unwrap( + self.gateway.transport.get( + "/user/list", + headers=self.gateway.transport.master, + params=UserListParams(user_ids=user_id), + response_type=UserListResponse, + ) + ).total + + def create_org(self, body: OrgNewBody) -> str: + return unwrap( + self.gateway.transport.post( + "/organization/new", + headers=self.gateway.transport.master, + json=body, + response_type=OrgNewResponse, + ) + ).organization_id + + def delete_org(self, organization_id: str) -> None: + _ = self.gateway.transport.delete( + "/organization/delete", + headers=self.gateway.transport.master, + json=OrgDeleteBody(organization_ids=[organization_id]), + response_type=NoBody, + ) + + def org_info(self, organization_id: str) -> OrgInfoResponse: + return unwrap( + self.gateway.transport.get( + "/organization/info", + headers=self.gateway.transport.master, + params=OrgInfoParams(organization_id=organization_id), + response_type=OrgInfoResponse, + ) + ) + + def chat_status(self, key: str, model: str, content: str) -> StreamingResponse: + return self.gateway.transport.send( + "/chat/completions", + headers=self.gateway.transport.bearer(key), + json=ChatBody(model=model, messages=[ChatMessage(role="user", content=content)], max_tokens=16), + ) + + def key_generate_status(self, key: str, body: KeyGenerateBody) -> StreamingResponse: + return self.gateway.transport.send("/key/generate", headers=self.gateway.transport.bearer(key), json=body) + + def team_new_status(self, key: str, body: TeamNewBody) -> StreamingResponse: + return self.gateway.transport.send("/team/new", headers=self.gateway.transport.bearer(key), json=body) + + def user_new_status(self, key: str, body: UserNewBody) -> StreamingResponse: + return self.gateway.transport.send("/user/new", headers=self.gateway.transport.bearer(key), json=body) + + +def build_client() -> ManagementClient: + return ManagementClient(gateway=build_gateway()) diff --git a/tests/e2e/management/test_management_e2e.py b/tests/e2e/management/test_management_e2e.py new file mode 100644 index 00000000000..3beb039b8bd --- /dev/null +++ b/tests/e2e/management/test_management_e2e.py @@ -0,0 +1,286 @@ +"""Live e2e: the key/team/user/organization management routes' lifecycle contract. + +Each test creates its resources under unique names (deleted on teardown) and +asserts both halves of the contract: the recorded state (the info route reflects +the write) and the enforced behavior (the data plane serves or refuses traffic +accordingly). Key writes reach the data plane when its auth cache entry expires, +so the traffic-facing read-backs poll to a deadline instead of asserting once. +""" + +from __future__ import annotations + +import time +from collections.abc import Callable + +import pytest + +from e2e_config import unique_marker +from e2e_http import StreamingResponse +from lifecycle import ResourceManager +from management_client import ( + MODEL_ACCESS_DENIED_MARKER, + ROUTE_NOT_ALLOWED_MARKER, + ManagementClient, +) +from models import KeyGenerateBody, OrgNewBody, TeamNewBody, UserNewBody + +pytestmark = pytest.mark.e2e + +def _poll[T](client: ManagementClient, attempt: Callable[[], T | None], failure: str) -> T: + deadline = time.monotonic() + client.gateway.poll_timeout + while time.monotonic() < deadline: + found = attempt() + if found is not None: + return found + time.sleep(client.gateway.poll_interval) + pytest.fail(failure) + + +def _generate_key(client: ManagementClient, resources: ResourceManager, body: KeyGenerateBody) -> str: + key = client.gateway.generate_key(body) + resources.defer(lambda: client.gateway.delete_key(key)) + return key + + +def _create_team(client: ManagementClient, resources: ResourceManager, alias: str, models: list[str]) -> str: + team_id = client.create_team(TeamNewBody(team_alias=alias, models=models)) + resources.defer(lambda: client.delete_team(team_id)) + return team_id + + +def _create_user(client: ManagementClient, resources: ResourceManager, body: UserNewBody) -> str: + user_id = client.create_user(body) + resources.defer(lambda: client.delete_user(user_id)) + return user_id + + +def _is_model_denial(outcome: StreamingResponse) -> bool: + return outcome.status_code == 403 and MODEL_ACCESS_DENIED_MARKER in outcome.body + + +def _assert_model_denied(outcome: StreamingResponse, model: str) -> None: + assert outcome.status_code == 403, ( + f"chat on {model!r} outside the key's model list must be denied 403, got " + f"{outcome.status_code}: {outcome.body[:300]}" + ) + assert MODEL_ACCESS_DENIED_MARKER in outcome.body, ( + f"403 body must be a model-access denial, got: {outcome.body[:300]}" + ) + + +def _poll_chat_ok(client: ManagementClient, key: str, model: str) -> None: + def attempt() -> bool | None: + outcome = client.chat_status(key, model, f"reply with one word {unique_marker()}") + return True if outcome.ok else None + + _ = _poll(client, attempt, f"chat on {model} never succeeded for the key before the deadline") + + +def _poll_chat_denied(client: ManagementClient, key: str, model: str) -> None: + def attempt() -> bool | None: + return True if _is_model_denial(client.chat_status(key, model, f"say hi {unique_marker()}")) else None + + _ = _poll( + client, + attempt, + f"chat on {model} was never denied with {MODEL_ACCESS_DENIED_MARKER} before the deadline", + ) + + +def _poll_model_access_granted(client: ManagementClient, key: str, model: str) -> None: + """The key's model-access check stopped denying `model`: any outcome other than + the key_model_access_denied 403 (a 200, or an upstream error) proves the flip. + Requiring a 200 would couple the assertion to `model` being a healthy routable + upstream, which is not the enforcement contract under test.""" + + def attempt() -> bool | None: + outcome = client.chat_status(key, model, f"say hi {unique_marker()}") + if _is_model_denial(outcome) or outcome.status_code == 401: + return None + return True + + _ = _poll(client, attempt, f"model-access denial on {model} never lifted before the deadline") + + +class TestKeyRoutes: + @pytest.mark.covers("mgmt.key.generate.persists") + def test_generate_persists_to_key_info_and_scopes_chat( + self, client: ManagementClient, resources: ResourceManager + ) -> None: + alias = f"e2e-mgmt-key-{unique_marker()}" + key = _generate_key( + client, + resources, + KeyGenerateBody(models=["gemini-2.5-flash"], key_alias=alias, tpm_limit=424242), + ) + + info = client.gateway.key_info(key) + assert info.key_alias == alias, f"/key/info reports key_alias {info.key_alias!r}, configured {alias!r}" + assert info.models == ["gemini-2.5-flash"], ( + f"/key/info reports models {info.models}, configured ['gemini-2.5-flash']" + ) + assert info.tpm_limit == 424242, ( + f"/key/info reports tpm_limit {info.tpm_limit}, configured 424242" + ) + + _poll_chat_ok(client, key, "gemini-2.5-flash") + _assert_model_denied( + client.chat_status(key, "gpt-5.5", f"say hi {unique_marker()}"), "gpt-5.5" + ) + + @pytest.mark.covers("mgmt.key.update.persists") + def test_update_models_persists_and_flips_enforcement( + self, client: ManagementClient, resources: ResourceManager + ) -> None: + key = _generate_key(client, resources, KeyGenerateBody(models=["gemini-2.5-flash"])) + _poll_chat_ok(client, key, "gemini-2.5-flash") + _assert_model_denied( + client.chat_status(key, "gpt-5.5", f"say hi {unique_marker()}"), "gpt-5.5" + ) + + client.update_key_models(key, ["gpt-5.5"]) + + info = client.gateway.key_info(key) + assert info.models == ["gpt-5.5"], ( + f"/key/info reports models {info.models} after /key/update to ['gpt-5.5']" + ) + + _poll_model_access_granted(client, key, "gpt-5.5") + _poll_chat_denied(client, key, "gemini-2.5-flash") + + @pytest.mark.covers("mgmt.key.delete.persists") + def test_delete_revokes_the_key_on_chat(self, client: ManagementClient, resources: ResourceManager) -> None: + """The teardown's deferred delete fires again on the already-deleted key by + design: the deferred cleanup must survive this test failing before the + in-body delete, and a repeat /key/delete is a cheap no-op the warn-only + teardown absorbs.""" + key = _generate_key(client, resources, KeyGenerateBody(models=["gemini-2.5-flash"])) + _poll_chat_ok(client, key, "gemini-2.5-flash") + + client.delete_key_strict(key) + + def rejected() -> bool | None: + outcome = client.chat_status(key, "gemini-2.5-flash", f"say hi {unique_marker()}") + return True if outcome.status_code == 401 else None + + _ = _poll(client, rejected, "deleted key was still accepted on chat (never rejected 401) at the deadline") + + +class TestTeamRoutes: + @pytest.mark.covers("management.team.new.persists") + def test_new_persists_to_team_info_and_binds_keys( + self, client: ManagementClient, resources: ResourceManager + ) -> None: + alias = f"e2e-mgmt-team-{unique_marker()}" + team_id = _create_team(client, resources, alias, ["gemini-2.5-flash"]) + + info = client.team_info(team_id) + assert info.team_alias == alias, f"/team/info reports team_alias {info.team_alias!r}, configured {alias!r}" + assert info.models == ["gemini-2.5-flash"], ( + f"/team/info reports models {info.models}, configured ['gemini-2.5-flash']" + ) + + key = _generate_key(client, resources, KeyGenerateBody(team_id=team_id)) + key_info = client.gateway.key_info(key) + assert key_info.team_id == team_id, ( + f"key generated under team {team_id} carries team_id {key_info.team_id!r} in /key/info" + ) + + @pytest.mark.covers("mgmt.team.member_add.persists") + def test_member_add_and_delete_persist_to_team_info( + self, client: ManagementClient, resources: ResourceManager + ) -> None: + user_id = _create_user( + client, + resources, + UserNewBody(user_email=f"e2e-mgmt-{unique_marker()}@example.com", user_role="internal_user"), + ) + team_id = _create_team(client, resources, f"e2e-mgmt-team-{unique_marker()}", ["gemini-2.5-flash"]) + + client.add_team_member(team_id, user_id) + member = next( + (entry for entry in client.team_info(team_id).members_with_roles if entry.user_id == user_id), None + ) + assert member is not None, f"/team/info does not list {user_id} after /team/member_add" + assert member.role == "user", f"member {user_id} added with role 'user' but /team/info reports {member.role!r}" + + client.delete_team_member(team_id, user_id) + remaining = client.team_info(team_id).members_with_roles + assert all(entry.user_id != user_id for entry in remaining), ( + f"/team/info still lists {user_id} after /team/member_delete" + ) + + +class TestUserRoutes: + @pytest.mark.covers("mgmt.user.new.persists") + def test_new_persists_to_user_info(self, client: ManagementClient, resources: ResourceManager) -> None: + email = f"e2e-mgmt-{unique_marker()}@example.com" + user_id = _create_user(client, resources, UserNewBody(user_email=email, user_role="internal_user")) + + info = client.user_info(user_id).user_info + assert info.user_email == email, f"/user/info reports user_email {info.user_email!r}, configured {email!r}" + assert info.user_role == "internal_user", ( + f"/user/info reports user_role {info.user_role!r}, configured 'internal_user'" + ) + + +class TestOrganizationRoutes: + @pytest.mark.covers("mgmt.organization.new.persists") + def test_new_persists_to_organization_info( + self, client: ManagementClient, resources: ResourceManager + ) -> None: + alias = f"e2e-mgmt-org-{unique_marker()}" + org_id = client.create_org(OrgNewBody(organization_alias=alias, models=["gemini-2.5-flash"])) + resources.defer(lambda: client.delete_org(org_id)) + + info = client.org_info(org_id) + assert info.organization_alias == alias, ( + f"/organization/info reports alias {info.organization_alias!r}, configured {alias!r}" + ) + assert info.models == ["gemini-2.5-flash"], ( + f"/organization/info reports models {info.models}, configured ['gemini-2.5-flash']" + ) + + +def _assert_route_forbidden(route: str, outcome: StreamingResponse) -> None: + assert outcome.status_code == 403, ( + f"llm-only key POSTing {route} must be denied exactly 403, got {outcome.status_code}: {outcome.body[:300]}" + ) + assert ROUTE_NOT_ALLOWED_MARKER in outcome.body, ( + f"{route} denial body must be a route-permission denial, got: {outcome.body[:300]}" + ) + + +class TestManagementRoutePermissions: + @pytest.mark.covers("mgmt.key.generate.member_forbidden") + def test_llm_only_key_forbidden_from_management_writes( + self, client: ManagementClient, resources: ResourceManager + ) -> None: + key = client.llm_only_key() + resources.defer(lambda: client.gateway.delete_key(key)) + marker = unique_marker() + alias = f"e2e-mgmt-forbidden-key-{marker}" + team_id = f"e2e-mgmt-forbidden-team-{marker}" + user_id = f"e2e-mgmt-forbidden-user-{marker}" + + _assert_route_forbidden( + "/key/generate", client.key_generate_status(key, KeyGenerateBody(models=[], key_alias=alias)) + ) + _assert_route_forbidden( + "/team/new", client.team_new_status(key, TeamNewBody(team_alias=team_id, team_id=team_id)) + ) + _assert_route_forbidden( + "/user/new", + client.user_new_status( + key, + UserNewBody(user_email=f"{user_id}@example.com", user_role="internal_user", user_id=user_id), + ), + ) + + assert client.key_alias_count(alias) == 0, f"key {alias} was created despite the 403 route denial" + team_probe = client.team_info_status(team_id) + assert team_probe.status_code == 404, ( + f"team {team_id} was created despite the 403 route denial: " + f"/team/info returned {team_probe.status_code}: {team_probe.body[:300]}" + ) + assert client.user_count(user_id) == 0, f"user {user_id} was created despite the 403 route denial" diff --git a/tests/e2e/models.py b/tests/e2e/models.py index 972f816905e..0490db286ea 100644 --- a/tests/e2e/models.py +++ b/tests/e2e/models.py @@ -6,6 +6,8 @@ response validates without mirroring every proxy field. No untyped dicts. from __future__ import annotations +from typing import Literal + from pydantic import BaseModel, ConfigDict, RootModel # ---------- keys ---------- @@ -30,11 +32,13 @@ class KeyGenerateBody(BaseModel): user_id: str | None = None team_id: str | None = None budget_id: str | None = None + key_alias: str | None = None model_max_budget: dict[str, ModelBudgetEntry] | None = None budget_fallbacks: dict[str, list[str]] | None = None budget_limits: list[BudgetWindow] | None = None tpm_limit: int | None = None rpm_limit: int | None = None + allowed_routes: list[str] | None = None class KeyGenerateResponse(BaseModel): @@ -57,6 +61,10 @@ class LiteLLMBudgetTable(BaseModel): class KeyInfo(BaseModel): + key_alias: str | None = None + models: list[str] = [] + tpm_limit: int | None = None + team_id: str | None = None spend: float | None = None max_budget: float | None = None budget_reset_at: str | None = None @@ -87,6 +95,16 @@ class ChatMessage(BaseModel): content: str +class ThinkingParam(BaseModel): + """Extended-thinking control shared by Anthropic and DeepSeek reasoner models. + DeepSeek accepts only ``type`` (enabled/disabled) and ignores budget_tokens; + Anthropic also honors budget_tokens. Sending ``type="disabled"`` is the + product-facing way a caller turns reasoning off (LIT-3686 / GH #27453).""" + + type: Literal["enabled", "disabled"] + budget_tokens: int | None = None + + class ChatBody(BaseModel): model: str messages: list[ChatMessage] @@ -94,6 +112,9 @@ class ChatBody(BaseModel): max_tokens: int | None = None user: str | None = None metadata: ChatMetadata | None = None + reasoning_effort: str | None = None + thinking: ThinkingParam | None = None + service_tier: str | None = None class AnthropicMessagesBody(BaseModel): @@ -104,16 +125,24 @@ class AnthropicMessagesBody(BaseModel): class OutMessage(BaseModel): content: str | None = None + reasoning_content: str | None = None class ChatChoice(BaseModel): message: OutMessage | None = None +class PromptTokensDetails(BaseModel): + cached_tokens: int | None = None + + class Usage(BaseModel): prompt_tokens: int | None = None completion_tokens: int | None = None total_tokens: int | None = None + cache_read_input_tokens: int | None = None + cache_creation_input_tokens: int | None = None + prompt_tokens_details: PromptTokensDetails | None = None class ChatResponse(BaseModel): @@ -121,6 +150,7 @@ class ChatResponse(BaseModel): model: str | None = None choices: list[ChatChoice] = [] usage: Usage | None = None + service_tier: str | None = None class EmbedBody(BaseModel): @@ -165,6 +195,7 @@ class OcrResponse(BaseModel): class SpendLogRow(BaseModel): request_id: str | None = None + api_key: str | None = None model: str | None = None spend: float | None = None status: str | None = None @@ -189,6 +220,25 @@ class SpendLogsParams(BaseModel): api_key: str | None = None +class SpendLogsPageParams(BaseModel): + """Query for /spend/logs/v2, which requires an explicit date window and + serves pages of at most 100 rows.""" + + start_date: str + end_date: str + page: int + page_size: int + api_key: str | None = None + + +class SpendLogsPage(BaseModel): + data: list[SpendLogRow] = [] + total: int + page: int + page_size: int + total_pages: int + + # ---------- spend calculate ---------- @@ -284,3 +334,204 @@ class ModelInfoEntry(BaseModel): class ModelInfoResponse(BaseModel): data: list[ModelInfoEntry] = [] + + +class FileEntry(BaseModel): + id: str + + +class FileListResponse(BaseModel): + """GET /files answer. `data` is required on purpose: a 200 whose body lacks + the OpenAI-format file list must fail validation, not pass vacuously.""" + + data: list[FileEntry] + + +class FineTuningJobsParams(BaseModel): + custom_llm_provider: Literal["openai", "azure"] + + +class FineTuningJobEntry(BaseModel): + id: str + + +class FineTuningJobsResponse(BaseModel): + """GET /fine_tuning/jobs answer; `data` required for the same reason as + FileListResponse.""" + + data: list[FineTuningJobEntry] + + +# ---------- model management ---------- + + +class LiteLLMParamsBody(BaseModel): + """POST /model/new litellm_params: `model` is the only required field; `api_key` + et al may be an `os.environ/FOO` reference the proxy resolves at call time. + `input_cost_per_token`/`output_cost_per_token` register a per-deployment custom + pricing override; left None (and dropped from the body) the deployment keeps the + backend's canonical rate.""" + + model: str + api_key: str | None = None + api_base: str | None = None + api_version: str | None = None + aws_region_name: str | None = None + vertex_project: str | None = None + vertex_location: str | None = None + vertex_credentials: str | None = None + bucket_name: str | None = None + s3_bucket_name: str | None = None + s3_region_name: str | None = None + s3_access_key_id: str | None = None + s3_secret_access_key: str | None = None + aws_batch_role_arn: str | None = None + input_cost_per_token: float | None = None + output_cost_per_token: float | None = None + + +ModelMode = Literal["batch", "realtime", "image_generation"] + + +class ModelInfoBody(BaseModel): + id: str + mode: ModelMode | None = None + + +class ModelNewBody(BaseModel): + model_config = ConfigDict(protected_namespaces=()) + model_name: str + litellm_params: LiteLLMParamsBody + model_info: ModelInfoBody + + +class ModelNewResponse(BaseModel): + model_config = ConfigDict(protected_namespaces=()) + model_id: str + + +class ModelDeleteBody(BaseModel): + id: str + + +# ---------- key / team / user / organization management ---------- + + +class KeyUpdateBody(BaseModel): + key: str + models: list[str] + + +class KeyListParams(BaseModel): + key_alias: str + + +class KeyListResponse(BaseModel): + total_count: int + + +class TeamMemberEntry(BaseModel): + role: Literal["admin", "user"] + user_id: str + + +class TeamNewBody(BaseModel): + team_alias: str + models: list[str] = [] + team_id: str | None = None + + +class TeamNewResponse(BaseModel): + team_id: str + + +class TeamInfoParams(BaseModel): + team_id: str + + +class TeamData(BaseModel): + team_alias: str | None = None + models: list[str] = [] + members_with_roles: list[TeamMemberEntry] = [] + + +class TeamInfoResponse(BaseModel): + team_id: str + team_info: TeamData + + +class TeamMemberAddBody(BaseModel): + team_id: str + member: TeamMemberEntry + + +class TeamMemberDeleteBody(BaseModel): + team_id: str + user_id: str + + +class TeamDeleteBody(BaseModel): + team_ids: list[str] + + +UserRole = Literal["proxy_admin", "proxy_admin_viewer", "internal_user", "internal_user_viewer"] + + +class UserNewBody(BaseModel): + user_email: str + user_role: UserRole + user_id: str | None = None + + +class UserNewResponse(BaseModel): + user_id: str + + +class UserInfoParams(BaseModel): + user_id: str + + +class UserData(BaseModel): + user_id: str | None = None + user_email: str | None = None + user_role: str | None = None + + +class UserInfoResponse(BaseModel): + user_id: str + user_info: UserData + + +class UserDeleteBody(BaseModel): + user_ids: list[str] + + +class UserListParams(BaseModel): + user_ids: str + + +class UserListResponse(BaseModel): + total: int + + +class OrgNewBody(BaseModel): + organization_alias: str + models: list[str] = [] + + +class OrgNewResponse(BaseModel): + organization_id: str + + +class OrgInfoParams(BaseModel): + organization_id: str + + +class OrgInfoResponse(BaseModel): + organization_id: str + organization_alias: str | None = None + models: list[str] = [] + + +class OrgDeleteBody(BaseModel): + organization_ids: list[str] diff --git a/tests/e2e/spend_tracking/SPEND_TRACKING_COVERAGE_MATRIX.md b/tests/e2e/spend_tracking/SPEND_TRACKING_COVERAGE_MATRIX.md index 77c0fe06bdb..062ef8d73da 100644 --- a/tests/e2e/spend_tracking/SPEND_TRACKING_COVERAGE_MATRIX.md +++ b/tests/e2e/spend_tracking/SPEND_TRACKING_COVERAGE_MATRIX.md @@ -43,6 +43,7 @@ proxy + SpendLogs rows. Status: `covered` / `partial` / `gap`. | Tag | `test_update_daily_tag_spend.py` | partial | yes (`test_tag_spend_matches_sum_of_tagged_logs`) | | End-user | `test_proxy_update_spend.py` | covered | yes | | Spend == sum(logs) consistency | none | gap | yes (key + tag aggregate == sum of rows) | +| Concurrent increments (one key, parallel writers) | `tests/spend_tracking_tests/test_spend_accuracy_tests.py` (burst) | partial | yes (`test_burst_of_concurrent_calls_loses_no_spend`) | ## Spend read endpoints (verification surface) @@ -51,6 +52,7 @@ proxy + SpendLogs rows. Status: `covered` / `partial` / `gap`. | `/spend/logs` (request_id / api_key) | `test_spend_management_endpoints.py` | covered | yes (primary read path; `test_spend_logs_endpoint_returns_spend` asserts 200 + spend, never 5xx) | | `/spend/calculate` | `local_testing/test_spend_calculate_endpoint.py` | covered | yes (`test_spend_calculate_returns_nonzero_cost`) | | `/spend/tags` | `test_spend_management_endpoints.py` | partial | yes (tag accuracy test) | +| `/spend/logs/v2` pagination (total/total_pages/out-of-range) | `test_spend_query_optimization.py` | covered | yes (`test_spend_logs_v2_pagination_caps_pages_and_keeps_total`; filter takes the hashed token, not the raw key) | | whole spend GET surface (22 routes) | unit per-handler | partial | yes (`test_spend_routes.py` probes each for 404/5xx) | ## What this suite pins @@ -69,6 +71,8 @@ proxy + SpendLogs rows. Status: `covered` / `partial` / `gap`. | `test_failure_call_writes_failure_status_row` | failed call -> `status=failure`, `spend=0` | | `test_spend_calculate_returns_nonzero_cost` | cost-map smoke (no batch wait) | | `test_spend_logs_endpoint_returns_spend` | `/spend/logs` returns 200 + the key's spend, never a 5xx (intermittent-500 regression) | +| `test_burst_of_concurrent_calls_loses_no_spend` | N parallel calls on one key: N distinct costed rows, key aggregate == sum (no lost increments) | +| `test_spend_logs_v2_pagination_caps_pages_and_keeps_total` | `/spend/logs/v2` page cap, stable total on out-of-range page, zero total on no-match filter | | `test_spend_routes.py` (23) | no spend route 404s or 5xxs | ## Design + timing diff --git a/tests/e2e/spend_tracking/conftest.py b/tests/e2e/spend_tracking/conftest.py index 1d01ab3d17a..0e80764236b 100644 --- a/tests/e2e/spend_tracking/conftest.py +++ b/tests/e2e/spend_tracking/conftest.py @@ -1,16 +1,55 @@ -"""Spend-tracking suite's `client` fixture. +"""Spend-tracking suite's `client` fixture and driver-model registration. The shared lifecycle (resources/scoped_key), proxy liveness skip, and e2e marker live in the parent tests/e2e/conftest.py. SpendClient exposes the shared Gateway (GatewayProvider), so the `resources` fixture cleans up keys and customers this suite creates. + +The suite drives real calls through three deployments. On the stage gateway they +are baked into the proxy config; on a local dev proxy they usually are not, so +`driver_models` registers whichever are missing via /model/new and deletes only +the ones it created, never a config-baked deployment. Each registration carries +the provider key from the test runner's env when set (so a local proxy whose +container env lacks the key still works); otherwise it falls back to an +os.environ reference resolved from the proxy's own env, the stage convention. """ +import os +from typing import Iterator + import pytest +from models import LiteLLMParamsBody from spend_e2e_client import SpendClient, build_client +def _driver_params(provider_model: str, env_var: str) -> LiteLLMParamsBody: + return LiteLLMParamsBody( + model=provider_model, + api_key=os.environ.get(env_var) or f"os.environ/{env_var}", + ) + + +DRIVER_MODELS: tuple[tuple[str, str, str], ...] = ( + ("gemini-2.5-flash", "gemini/gemini-2.5-flash", "GEMINI_API_KEY"), + ("claude-haiku-4-5", "anthropic/claude-haiku-4-5", "ANTHROPIC_API_KEY"), + ("openai-text-embedding-3-small", "openai/text-embedding-3-small", "OPENAI_API_KEY"), +) + + @pytest.fixture(scope="session") def client() -> SpendClient: return build_client() + + +@pytest.fixture(scope="session", autouse=True) +def driver_models(client: SpendClient) -> Iterator[None]: + existing = frozenset(entry.model_name for entry in client.gateway.model_info()) + created = tuple( + client.gateway.create_model(name, _driver_params(provider_model, env_var)) + for name, provider_model, env_var in DRIVER_MODELS + if name not in existing + ) + yield + for model_id in created: + client.gateway.delete_model(model_id) diff --git a/tests/e2e/spend_tracking/spend_e2e_client.py b/tests/e2e/spend_tracking/spend_e2e_client.py index 4f3bc9a461e..c4991199187 100644 --- a/tests/e2e/spend_tracking/spend_e2e_client.py +++ b/tests/e2e/spend_tracking/spend_e2e_client.py @@ -15,6 +15,7 @@ import os import time from collections.abc import Callable from dataclasses import dataclass +from datetime import datetime, timedelta, timezone from e2e_config import unique_marker from e2e_http import ( @@ -39,6 +40,8 @@ from models import ( SpendCalculateBody, SpendCalculateResponse, SpendLogRow, + SpendLogsPage, + SpendLogsPageParams, SpendTagsResponse, TagSpend, ) @@ -180,6 +183,28 @@ class SpendClient: time.sleep(self.gateway.poll_interval) return spend + def spend_logs_page( + self, *, api_key: str | None, page: int, page_size: int + ) -> SpendLogsPage: + """One page of /spend/logs/v2 over a window wide enough to contain every + row this test run wrote (the endpoint requires explicit dates).""" + now = datetime.now(timezone.utc) + fmt = "%Y-%m-%d %H:%M:%S" + return unwrap( + self.gateway.transport.get( + "/spend/logs/v2", + headers=self.gateway.transport.master, + params=SpendLogsPageParams( + start_date=(now - timedelta(days=1)).strftime(fmt), + end_date=(now + timedelta(days=1)).strftime(fmt), + page=page, + page_size=page_size, + api_key=api_key, + ), + response_type=SpendLogsPage, + ) + ) + def probe(self, path: str, *, params: DateRangeParams) -> ProbeResult: return self.gateway.transport.probe(path, params=params) diff --git a/tests/e2e/spend_tracking/test_spend_routes.py b/tests/e2e/spend_tracking/test_spend_routes.py index e3c96a4d578..9b4eaefae34 100644 --- a/tests/e2e/spend_tracking/test_spend_routes.py +++ b/tests/e2e/spend_tracking/test_spend_routes.py @@ -27,13 +27,21 @@ pytestmark = pytest.mark.e2e # Verified present and responsive on a live proxy. One per row of the spend # surface: key / user / team / org / customer aggregation, model-cost, tags, -# activity. +# activity. Most are include_in_schema=False, so keep this list exhaustive by +# hand; the schema test below only auto-catches the visible minority. Excluded +# by design: path-param routes (/spend/logs/ui/{request_id}), POST readers +# (/spend/calculate has its own test, /global/spend/end_users), mutating +# POSTs (/global/spend/reset, /global/spend/refresh), and /provider/budgets, +# which 500s whenever router_settings.provider_budget_config is absent, so it +# is only probeable on a proxy configured with provider budget routing. SPEND_ROUTES = ( "/spend/keys", "/spend/users", "/spend/tags", "/spend/logs", "/spend/logs/ui", + "/spend/logs/v2", + "/spend/logs/session/ui", "/global/spend", "/global/spend/keys", "/global/spend/teams", @@ -43,9 +51,18 @@ SPEND_ROUTES = ( "/global/spend/tags", "/global/spend/logs", "/global/spend/all_tag_names", + "/global/all_end_users", "/global/activity", "/global/activity/model", "/global/activity/exceptions", + "/global/activity/exceptions/deployment", + "/user/daily/activity", + "/user/daily/activity/aggregated", + "/team/daily/activity", + "/organization/daily/activity", + "/customer/daily/activity", + "/end_user/daily/activity", + "/tag/daily/activity", "/key/list", "/user/list", "/team/list", diff --git a/tests/e2e/spend_tracking/test_spend_tracking_e2e.py b/tests/e2e/spend_tracking/test_spend_tracking_e2e.py index f8ece3c48c1..3495011eab6 100644 --- a/tests/e2e/spend_tracking/test_spend_tracking_e2e.py +++ b/tests/e2e/spend_tracking/test_spend_tracking_e2e.py @@ -17,12 +17,13 @@ fails the test; a pricing or token-count drift does not. import time from collections.abc import Callable +from concurrent.futures import ThreadPoolExecutor import pytest -from e2e_http import Success +from e2e_http import Result, Success from lifecycle import ResourceManager -from models import SpendLogs, SpendLogsParams +from models import ChatResponse, SpendLogs, SpendLogsParams from spend_e2e_client import SpendClient, SpendLogRow, is_ok, unique_marker, unwrap pytestmark = pytest.mark.e2e @@ -202,6 +203,104 @@ def test_key_spend_equals_sum_of_logs(client: SpendClient, scoped_key: str) -> N ), f"key aggregate {key_spend} != sum of logs {logs_total}; rows: {_summarize(rows)}" +def test_burst_of_concurrent_calls_loses_no_spend( + client: SpendClient, scoped_key: str +) -> None: + """Six concurrent calls on one key: every call lands its own spend row under a + distinct request_id and the key aggregate equals the sum of the rows. + Sequential accuracy is covered by test_key_spend_equals_sum_of_logs; this pins + the concurrent increment path (parallel writers racing on one key's counter), + where a lost update can never be reproduced by sequential calls.""" + burst = 6 + + def call(idx: int) -> Result[ChatResponse]: + return client.chat( + scoped_key, + "gemini-2.5-flash", + f"burst call {idx} {unique_marker()}", + max_tokens=16, + ) + + with ThreadPoolExecutor(max_workers=burst) as pool: + results = tuple(pool.map(call, range(burst))) + failed = [r for r in results if not is_ok(r)] + assert not failed, f"{len(failed)}/{burst} burst calls failed; first: {failed[0]}" + + rows = client.poll_logs_for_key( + scoped_key, + min_rows=burst, + predicate=lambda rs: len([r for r in rs if (r.spend or 0) > 0]) >= burst, + ) + costed = [r for r in rows if (r.spend or 0) > 0] + assert len(costed) >= burst, ( + f"only {len(costed)}/{burst} burst calls produced a costed row - " + f"rows lost under concurrency: {_summarize(rows)}" + ) + request_ids = [r.request_id for r in costed] + assert len(set(request_ids)) == len(request_ids), ( + f"concurrent rows collapsed onto shared request_ids: {_summarize(rows)}" + ) + + logs_total = sum((r.spend or 0) for r in rows) + key_spend = client.poll_key_spend(scoped_key, minimum=logs_total * 0.999) + assert _approx_equal(key_spend, logs_total), ( + f"key aggregate {key_spend} != sum of {len(rows)} rows {logs_total} - " + f"spend increments lost under concurrency: {_summarize(rows)}" + ) + + +def test_spend_logs_v2_pagination_caps_pages_and_keeps_total( + client: SpendClient, scoped_key: str +) -> None: + """/spend/logs/v2 pagination contract for the key filter: page_size caps the + rows returned, total counts every row for the filter (so with page_size=1, + total_pages == total), a page past the end returns no rows while reporting + the same total (an out-of-range page must not reset the count the UI + paginates by), and a filter matching nothing reports zero without erroring. + + Unlike /spend/logs, the v2 filter matches the hashed token exactly as stored + on the row (the form the UI passes), not the raw sk- key, so the filter value + is read off the rows the poll returned.""" + for _ in range(2): + _ = unwrap( + client.chat( + scoped_key, + "gemini-2.5-flash", + f"page fodder {unique_marker()}", + max_tokens=16, + ) + ) + rows = client.poll_logs_for_key( + scoped_key, min_rows=2, predicate=lambda rs: sum((r.spend or 0) for r in rs) > 0 + ) + hashed_key = rows[0].api_key + assert hashed_key, f"polled rows carry no api_key: {_summarize(rows)}" + + first = client.spend_logs_page(api_key=hashed_key, page=1, page_size=1) + assert first.total >= 2, f"expected >=2 rows for the key, got total={first.total}" + assert len(first.data) == 1, f"page_size=1 returned {len(first.data)} rows" + assert first.total_pages == first.total, ( + f"page_size=1 must give one page per row: " + f"total={first.total} total_pages={first.total_pages}" + ) + + beyond = client.spend_logs_page( + api_key=hashed_key, page=first.total_pages + 7, page_size=1 + ) + assert beyond.data == [], f"out-of-range page returned rows: {beyond.data}" + assert beyond.total == first.total, ( + f"out-of-range page changed the total: {beyond.total} != {first.total}" + ) + + nomatch = client.spend_logs_page( + api_key=f"sk-no-such-key-{unique_marker()}", page=1, page_size=1 + ) + assert nomatch.total == 0 and nomatch.data == [], ( + f"filter matching nothing must report zero: " + f"total={nomatch.total} rows={len(nomatch.data)}" + ) + + def test_request_tags_round_trip(client: SpendClient, scoped_key: str) -> None: tag = f"e2e-spend-{unique_marker()}" _ = unwrap( diff --git a/tests/e2e/test_e2e_gateway.py b/tests/e2e/test_e2e_gateway.py new file mode 100644 index 00000000000..a6dcc6112d6 --- /dev/null +++ b/tests/e2e/test_e2e_gateway.py @@ -0,0 +1,148 @@ +"""Unit coverage for the Gateway model-management surface (create_model / +delete_model). + +The batches conftest and several llm_translation tests register deployments at +runtime through gateway.create_model; when that method went missing, every batch +test errored at fixture setup (AttributeError) before a single request reached +the proxy. This pins the surface with a typed fake Transport so a rename or +signature drift fails here instead of in a live stage run. +""" + +from dataclasses import dataclass, field + +from pydantic import BaseModel + +from batches.batch_client import BatchClient +from e2e_gateway import Gateway +from e2e_http import ( + AuthHeaders, + FileUploadForm, + ProbeResult, + Result, + StreamingResponse, + Success, +) +from models import ( + LiteLLMParamsBody, + ModelDeleteBody, + ModelNewBody, + ModelNewResponse, +) + + +@dataclass +class _RecordingTransport: + """Typed fake fulfilling the Transport protocol; records every post and + answers with a canned success so the test asserts on what was sent.""" + + posts: list[tuple[str, BaseModel]] = field(default_factory=list) + + def post[R: BaseModel]( + self, path: str, *, headers: BaseModel, json: BaseModel, response_type: type[R] + ) -> Result[R]: + self.posts.append((path, json)) + payload = ( + {"model_id": "registered-id"} if response_type is ModelNewResponse else {} + ) + return Success(data=response_type.model_validate(payload)) + + def stream( + self, path: str, *, headers: BaseModel, json: BaseModel + ) -> StreamingResponse: + raise AssertionError("stream is not part of model management") + + def send( + self, + path: str, + *, + headers: BaseModel, + json: BaseModel, + params: BaseModel | None = None, + stream: bool = False, + ) -> StreamingResponse: + raise AssertionError("send is not part of model management") + + def get[R: BaseModel]( + self, + path: str, + *, + headers: BaseModel, + params: BaseModel, + response_type: type[R], + ) -> Result[R]: + raise AssertionError("get is not part of model management") + + def delete[R: BaseModel]( + self, path: str, *, headers: BaseModel, json: BaseModel, response_type: type[R] + ) -> Result[R]: + raise AssertionError("delete is not part of model management") + + def probe(self, path: str, *, params: BaseModel) -> ProbeResult: + raise AssertionError("probe is not part of model management") + + def upload[R: BaseModel]( + self, + path: str, + *, + headers: BaseModel, + form: FileUploadForm, + filename: str, + content: bytes, + params: BaseModel | None = None, + response_type: type[R], + ) -> Result[R]: + raise AssertionError("upload is not part of model management") + + def download(self, path: str, *, headers: BaseModel) -> StreamingResponse: + raise AssertionError("download is not part of model management") + + def bearer(self, key: str) -> AuthHeaders: + return AuthHeaders(authorization=f"Bearer {key}") + + @property + def master(self) -> AuthHeaders: + return self.bearer("sk-test-master") + + +def test_gateway_create_model_registers_deployment_and_returns_model_id() -> None: + transport = _RecordingTransport() + gateway = Gateway(transport=transport) + + model_id = gateway.create_model( + "e2e-test-model", LiteLLMParamsBody(model="openai/gpt-4o-mini") + ) + + assert model_id == "registered-id" + path, body = transport.posts[0] + assert path == "/model/new" + assert isinstance(body, ModelNewBody) + assert body.model_name == "e2e-test-model" + assert body.model_info.id == "e2e-test-model" + assert body.model_info.mode is None + + +def test_batch_client_create_model_registers_a_batch_mode_deployment() -> None: + transport = _RecordingTransport() + client = BatchClient(gateway=Gateway(transport=transport)) + + model_id = client.create_model( + "e2e-batch-model", LiteLLMParamsBody(model="openai/gpt-4o-mini") + ) + + assert model_id == "registered-id" + path, body = transport.posts[0] + assert path == "/model/new" + assert isinstance(body, ModelNewBody) + assert body.model_info.mode == "batch" + + +def test_gateway_delete_model_posts_the_model_id() -> None: + transport = _RecordingTransport() + gateway = Gateway(transport=transport) + + gateway.delete_model("registered-id") + + path, body = transport.posts[0] + assert path == "/model/delete" + assert isinstance(body, ModelDeleteBody) + assert body.id == "registered-id" diff --git a/tests/e2e/test_transport.py b/tests/e2e/test_transport.py new file mode 100644 index 00000000000..c7ce61b90c1 --- /dev/null +++ b/tests/e2e/test_transport.py @@ -0,0 +1,52 @@ +"""Unit coverage for SplitTransport path routing (is_control_plane_path). + +Model-management calls (/model/new, /model/delete, /model/info) must go to the +control plane: the data-plane gateway does not serve management routes, so a +misrouted /model/new 404s and takes down every suite that registers deployments +at runtime (llm_translation, batches, access_control). /models must stay on the +data plane; it is the OpenAI-compatible list-models route, not a management +route. +""" + +import pytest + +from transport import is_control_plane_path + + +@pytest.mark.parametrize( + "path", + [ + "/model/new", + "/model/delete", + "/model/update", + "/model/info", + "/key/generate", + "/budget/new", + "/spend/logs", + "/end_user/daily/activity", + "/user/daily/activity", + "/team/daily/activity", + "/tag/daily/activity", + ], +) +def test_management_routes_go_to_the_control_plane(path: str) -> None: + assert is_control_plane_path(path), ( + f"{path} is a management route; sending it to the data plane 404s" + ) + + +@pytest.mark.parametrize( + "path", + [ + "/models", + "/v1/models", + "/chat/completions", + "/v1/messages", + "/embeddings", + "/anthropic/v1/messages", + ], +) +def test_llm_routes_stay_on_the_data_plane(path: str) -> None: + assert not is_control_plane_path(path), ( + f"{path} is an LLM route; it must go to the data plane" + ) diff --git a/tests/e2e/transport.py b/tests/e2e/transport.py index 2e109bdf5d9..10e090f07a9 100644 --- a/tests/e2e/transport.py +++ b/tests/e2e/transport.py @@ -202,9 +202,10 @@ CONTROL_PLANE_PREFIXES: tuple[str, ...] = ( "/team", "/organization", "/customer", + "/end_user", "/tag", "/budget", - "/model/info", + "/model/", "/spend", "/global", "/openapi.json", diff --git a/tests/llm_responses_api_testing/test_base_responses_api_streaming_iterator.py b/tests/llm_responses_api_testing/test_base_responses_api_streaming_iterator.py index 37fcc602d37..5388c5aef83 100644 --- a/tests/llm_responses_api_testing/test_base_responses_api_streaming_iterator.py +++ b/tests/llm_responses_api_testing/test_base_responses_api_streaming_iterator.py @@ -67,6 +67,7 @@ class TestBaseResponsesAPIStreamingIterator: mock_logging_obj = Mock(spec=LiteLLMLoggingObj) mock_logging_obj.model_call_details = {"litellm_params": {}} + mock_logging_obj.completion_start_time = None mock_config = Mock(spec=BaseResponsesAPIConfig) mock_responses_api_response = Mock(spec=ResponsesAPIResponse) @@ -107,6 +108,7 @@ class TestBaseResponsesAPIStreamingIterator: mock_response.headers = {} mock_logging_obj = Mock(spec=LiteLLMLoggingObj) mock_logging_obj.model_call_details = {"litellm_params": {}} + mock_logging_obj.completion_start_time = None mock_config = Mock(spec=BaseResponsesAPIConfig) # Create a mock ResponsesAPIResponse for the completed event @@ -179,6 +181,7 @@ class TestBaseResponsesAPIStreamingIterator: mock_response.headers = {} mock_logging_obj = Mock(spec=LiteLLMLoggingObj) mock_logging_obj.model_call_details = {"litellm_params": {}} + mock_logging_obj.completion_start_time = None mock_config = Mock(spec=BaseResponsesAPIConfig) # Create a mock OutputTextDeltaEvent (not a completed event) @@ -239,6 +242,7 @@ class TestBaseResponsesAPIStreamingIterator: mock_response.headers = {} mock_logging_obj = Mock(spec=LiteLLMLoggingObj) mock_logging_obj.model_call_details = {"litellm_params": {}} + mock_logging_obj.completion_start_time = None mock_config = Mock(spec=BaseResponsesAPIConfig) # Create the iterator instance @@ -265,6 +269,7 @@ class TestBaseResponsesAPIStreamingIterator: mock_response.headers = {} mock_logging_obj = Mock(spec=LiteLLMLoggingObj) mock_logging_obj.model_call_details = {"litellm_params": {}} + mock_logging_obj.completion_start_time = None mock_config = Mock(spec=BaseResponsesAPIConfig) # Create the iterator instance @@ -291,6 +296,7 @@ class TestBaseResponsesAPIStreamingIterator: mock_response.headers = {} mock_logging_obj = Mock(spec=LiteLLMLoggingObj) mock_logging_obj.model_call_details = {"litellm_params": {}} + mock_logging_obj.completion_start_time = None mock_config = Mock(spec=BaseResponsesAPIConfig) # Create the iterator instance @@ -329,6 +335,7 @@ class TestBaseResponsesAPIStreamingIterator: mock_response.aiter_bytes = Mock() mock_logging_obj = Mock(spec=LiteLLMLoggingObj) mock_logging_obj.model_call_details = {"litellm_params": {}} + mock_logging_obj.completion_start_time = None mock_logging_obj.async_success_handler = Mock() mock_logging_obj.success_handler = Mock() mock_config = Mock(spec=BaseResponsesAPIConfig) @@ -397,6 +404,7 @@ class TestBaseResponsesAPIStreamingIterator: mock_logging_obj = Mock(spec=LiteLLMLoggingObj) mock_logging_obj.model_call_details = {"litellm_params": {}} + mock_logging_obj.completion_start_time = None mock_logging_obj.async_failure_handler = Mock() mock_logging_obj.failure_handler = Mock() @@ -457,6 +465,7 @@ class TestBaseResponsesAPIStreamingIterator: mock_logging_obj = Mock(spec=LiteLLMLoggingObj) mock_logging_obj.model_call_details = {"litellm_params": {}} + mock_logging_obj.completion_start_time = None mock_logging_obj.async_failure_handler = Mock() mock_logging_obj.failure_handler = Mock() @@ -505,6 +514,7 @@ class TestBaseResponsesAPIStreamingIterator: mock_response.aiter_bytes = Mock() mock_logging_obj = Mock(spec=LiteLLMLoggingObj) mock_logging_obj.model_call_details = {"litellm_params": {}} + mock_logging_obj.completion_start_time = None mock_logging_obj.async_failure_handler = Mock() mock_logging_obj.failure_handler = Mock() mock_logging_obj.async_success_handler = Mock() @@ -587,6 +597,7 @@ class TestBaseResponsesAPIStreamingIterator: mock_response.aiter_bytes = Mock() mock_logging_obj = Mock(spec=LiteLLMLoggingObj) mock_logging_obj.model_call_details = {"litellm_params": {}} + mock_logging_obj.completion_start_time = None mock_logging_obj.async_failure_handler = Mock() mock_logging_obj.failure_handler = Mock() mock_logging_obj.async_success_handler = Mock() @@ -636,9 +647,10 @@ class TestBaseResponsesAPIStreamingIterator: assert result.type == ResponsesAPIStreamEvents.RESPONSE_INCOMPLETE assert iterator.completed_response == result - # Success handler should have been called (via _handle_logging_completed_response) + # Success handlers are dispatched as one async task (via _handle_logging_completed_response); + # the sync handler must never be submitted to the executor concurrently (LIT-4210) mock_create_task.assert_called_once() - mock_executor.submit.assert_called_once() + mock_executor.submit.assert_not_called() # Failure handlers should NOT have been called mock_logging_obj.async_failure_handler.assert_not_called() diff --git a/tests/llm_responses_api_testing/test_responses_hooks.py b/tests/llm_responses_api_testing/test_responses_hooks.py index 3799a0b9121..2344a62de4d 100644 --- a/tests/llm_responses_api_testing/test_responses_hooks.py +++ b/tests/llm_responses_api_testing/test_responses_hooks.py @@ -34,9 +34,15 @@ class _FakeLoggingObj: self.last_success_kwargs = None self.last_async_success_kwargs = None self.start_time = datetime.now() + self.completion_start_time = None self.model_call_details = {"litellm_params": {}} # Signature alignment with Logging handlers + async def dispatch_success_handlers(self, *args, **kwargs): + kwargs.pop("prefer_async_handlers", None) + await self.async_success_handler(*args, **kwargs) + self.success_handler(*args, **kwargs) + def success_handler(self, *args, **kwargs): self.success_calls += 1 self.last_success_kwargs = kwargs @@ -51,6 +57,10 @@ class _FakeLoggingObj: async def async_failure_handler(self, *args, **kwargs): self.async_failure_calls += 1 + def _update_completion_start_time(self, completion_start_time): + self.completion_start_time = completion_start_time + self.model_call_details["completion_start_time"] = completion_start_time + def _make_completed_response(response_id: str = "resp_test") -> ResponseCompletedEvent: return ResponseCompletedEvent( diff --git a/tests/llm_translation/realtime/test_realtime_guardrails_openai.py b/tests/llm_translation/realtime/test_realtime_guardrails_openai.py index 413f5d1ff8b..cf596aa597e 100644 --- a/tests/llm_translation/realtime/test_realtime_guardrails_openai.py +++ b/tests/llm_translation/realtime/test_realtime_guardrails_openai.py @@ -4,7 +4,8 @@ Integration tests for RealTimeStreaming guardrails against a live OpenAI backend These tests require OPENAI_API_KEY and are skipped if not set. They verify end-to-end that: - 1. A text message blocked by a guardrail -> error event sent to client, NO AI response. + 1. A text message blocked by a guardrail -> error event sent to client, the blocked + message never reaches OpenAI, and the client's response.create is not forwarded. 2. A voice transcript blocked by a guardrail -> error event sent, response.create NOT sent. 3. A clean text message passes through and triggers a real OpenAI response. @@ -55,9 +56,25 @@ def _make_guardrail(event_hook=GuardrailEventHooks.pre_call): ) -async def _wait_for_event( - client_events: List[dict], event_type: str, timeout: float = 15.0 -) -> dict: +class RecordingBackendWebSocket: + """Wraps a real backend WebSocket and records every frame sent to it.""" + + def __init__(self, backend_ws): + self._backend_ws = backend_ws + self.sent_messages: List[str] = [] + + async def send(self, message): + self.sent_messages.append(message) + await self._backend_ws.send(message) + + async def recv(self, *args, **kwargs): + return await self._backend_ws.recv(*args, **kwargs) + + async def close(self): + await self._backend_ws.close() + + +async def _wait_for_event(client_events: List[dict], event_type: str, timeout: float = 15.0) -> dict: """Poll client_events list until an event with matching type appears.""" deadline = asyncio.get_event_loop().time() + timeout while asyncio.get_event_loop().time() < deadline: @@ -65,9 +82,7 @@ async def _wait_for_event( if matching: return matching[0] await asyncio.sleep(0.05) - raise TimeoutError( - f"Timed out waiting for '{event_type}'. Got so far: {[e.get('type') for e in client_events]}" - ) + raise TimeoutError(f"Timed out waiting for '{event_type}'. Got so far: {[e.get('type') for e in client_events]}") async def _build_streaming(client_events: List[dict], backend_ws, request_data=None): @@ -99,12 +114,21 @@ async def _build_streaming(client_events: List[dict], backend_ws, request_data=N @pytest.mark.asyncio async def test_text_message_blocked_by_guardrail_no_ai_response(): """ - Send a text message containing the blocked phrase. + Send a text message containing the blocked phrase, immediately followed by + response.create (the reflexive client pattern). Guardrail must: - Send error event (guardrail_violation) to client. - Send response.output_audio_transcript.delta (or beta-protocol - response.audio_transcript.delta) with the block message to client. - - NOT forward response.create to OpenAI (no AI response). + response.audio_transcript.delta) to client. + - NEVER forward the blocked message to OpenAI. + - Drop the client's response.create; the only response.create OpenAI sees + is the guardrail's own (which voices the block message), so the model + can never answer the blocked content. + + Assertions are on the recorded backend wire traffic, not on the model's + reply wording: gpt-realtime phrases its voicing/refusal of the guardrail + prompt nondeterministically, which made wording-based assertions flaky + (see PRs #28191, #28200, #29477). """ import websockets @@ -119,21 +143,16 @@ async def test_text_message_blocked_by_guardrail_no_ai_response(): additional_headers={ "Authorization": f"Bearer {OPENAI_API_KEY}", }, - ) as backend_ws: + ) as raw_backend_ws: + backend_ws = RecordingBackendWebSocket(raw_backend_ws) streaming, input_queue = await _build_streaming(client_events, backend_ws) - # Start backend -> client forwarding - backend_task = asyncio.create_task( - streaming.backend_to_client_send_messages() - ) - # Start client -> backend forwarding (reads from input_queue) + backend_task = asyncio.create_task(streaming.backend_to_client_send_messages()) client_task = asyncio.create_task(streaming.client_ack_messages()) try: - # Wait until session is ready await _wait_for_event(client_events, "session.created", timeout=15) - # Send the blocked message + response.create blocked_item = json.dumps( { "type": "conversation.item.create", @@ -149,34 +168,23 @@ async def test_text_message_blocked_by_guardrail_no_ai_response(): } ) await input_queue.put(blocked_item) - # Give guardrail time to process before the follow-up response.create - await asyncio.sleep(0.3) await input_queue.put(json.dumps({"type": "response.create"})) - # Allow time for guardrail round-trip - await asyncio.sleep(3.0) + await _wait_for_event(client_events, "response.done", timeout=30) finally: backend_task.cancel() client_task.cancel() await asyncio.gather(backend_task, client_task, return_exceptions=True) - # --- Assertions --- event_types = [e.get("type") for e in client_events] - # 1. Must have received guardrail error (may not be the first error event - # if the OpenAI session emits other errors, e.g. missing parameters) error_events = [e for e in client_events if e.get("type") == "error"] - guardrail_errors = [ - e - for e in error_events - if e.get("error", {}).get("type") == "guardrail_violation" - ] - assert ( - len(guardrail_errors) >= 1 - ), f"Expected at least one guardrail_violation error but got: {[e.get('error', {}).get('type') for e in error_events]}" + guardrail_errors = [e for e in error_events if e.get("error", {}).get("type") == "guardrail_violation"] + assert len(guardrail_errors) >= 1, ( + f"Expected at least one guardrail_violation error but got: {[e.get('error', {}).get('type') for e in error_events]}" + ) - # 2. Must have the guardrail message surfaced as an AI transcript delta transcript_deltas = [ e for e in client_events @@ -186,64 +194,30 @@ async def test_text_message_blocked_by_guardrail_no_ai_response(): "response.audio_transcript.delta", ) ] - assert ( - len(transcript_deltas) >= 1 - ), f"Expected guardrail message in transcript delta, got: {event_types}" + assert len(transcript_deltas) >= 1, f"Expected guardrail message in transcript delta, got: {event_types}" - # 3. No *real* AI response to the blocked content should have been - # generated. The original user message is blocked BEFORE it is - # forwarded to OpenAI, so the only thing the model ever sees is the - # guardrail's "say exactly: " prompt - # (see realtime_streaming.py). Two safe outcomes are possible: - # - the model voices the block message verbatim (older realtime - # snapshots did this -> text contains "blocked"), or - # - the model declines to repeat it (gpt-realtime tends to refuse - # verbatim-repeat instructions, e.g. "I'm sorry, but I can't - # repeat that message."). - # Both mean the blocked prompt itself was never answered, so we - # accept either. The hard invariant is that the blocked phrase must - # never leak into AI output, and the model must not have produced a - # normal answer to the user (which would have neither a block nor a - # refusal marker). - safe_markers = ( - "block", - "guardrail", - "content filter", - "policy", - "can't repeat", - "cannot repeat", - "can't say", - "cannot say", - "won't repeat", - "can't assist", - "can't help", - "unable to", - "i'm sorry", - "i am sorry", + sent_frames = backend_ws.sent_messages + assert all(BLOCKED_PHRASE not in frame for frame in sent_frames), ( + f"Blocked message was forwarded to OpenAI: {sent_frames}" ) + + sent_types = [json.loads(frame).get("type") for frame in sent_frames] + assert sent_types.count("response.create") == 1, ( + f"Expected only the guardrail's response.create to reach OpenAI, got backend frames: {sent_types}" + ) + assert sent_types.count("conversation.item.create") == 1, ( + f"Expected only the guardrail's conversation.item.create to reach OpenAI, got backend frames: {sent_types}" + ) + done_events = [e for e in client_events if e.get("type") == "response.done"] + assert len(done_events) >= 1, f"Expected response.done, got: {event_types}" for done in done_events: output = done.get("response", {}).get("output", []) ai_texts = [ - c.get("text", "") or c.get("transcript", "") - for item in output - for c in item.get("content", []) + c.get("text", "") or c.get("transcript", "") for item in output for c in item.get("content", []) ] real_ai_text = " ".join(ai_texts).strip() - if real_ai_text: - assert ( - BLOCKED_PHRASE not in real_ai_text - ), f"Blocked phrase leaked into AI response: {real_ai_text!r}" - normalized_ai_text = ( - real_ai_text.lower() - .replace("\u2019", "'") - .replace("\u2018", "'") - .replace("\u201c", '"') - .replace("\u201d", '"') - ) - assert any( - marker in normalized_ai_text for marker in safe_markers - ), f"AI responded with non-guardrail content even though message was blocked: {real_ai_text!r}" + assert BLOCKED_PHRASE not in real_ai_text, f"Blocked phrase leaked into AI response: {real_ai_text!r}" finally: litellm.callbacks = [] @@ -289,9 +263,7 @@ async def test_voice_transcript_blocked_by_guardrail(): # 1. Error event must be sent to client error_events = [e for e in client_events if e.get("type") == "error"] - assert ( - len(error_events) >= 1 - ), f"Expected guardrail error event, got: {event_types}" + assert len(error_events) >= 1, f"Expected guardrail error event, got: {event_types}" assert error_events[0]["error"]["type"] == "guardrail_violation" # 2. Check what was sent to backend. @@ -299,16 +271,12 @@ async def test_voice_transcript_blocked_by_guardrail(): # + response.create (to speak the block message). That's acceptable. # What we assert is that a response.cancel was sent (blocking the original). sent_to_backend = [ - json.loads(c.args[0]) - for c in backend_ws.send.call_args_list - if c.args and isinstance(c.args[0], str) + json.loads(c.args[0]) for c in backend_ws.send.call_args_list if c.args and isinstance(c.args[0], str) ] - response_cancels = [ - e for e in sent_to_backend if e.get("type") == "response.cancel" - ] - assert ( - len(response_cancels) >= 1 or len(sent_to_backend) == 0 - ), f"Guardrail should have sent response.cancel or nothing, got: {sent_to_backend}" + response_cancels = [e for e in sent_to_backend if e.get("type") == "response.cancel"] + assert len(response_cancels) >= 1 or len(sent_to_backend) == 0, ( + f"Guardrail should have sent response.cancel or nothing, got: {sent_to_backend}" + ) # Note: The guardrail may or may not send transcript deltas; the error event # (assertion #1) is the primary signal that the blocked content was handled. @@ -339,9 +307,7 @@ async def test_clean_text_message_passes_through_to_openai(): ) as backend_ws: streaming, input_queue = await _build_streaming(client_events, backend_ws) - backend_task = asyncio.create_task( - streaming.backend_to_client_send_messages() - ) + backend_task = asyncio.create_task(streaming.backend_to_client_send_messages()) client_task = asyncio.create_task(streaming.client_ack_messages()) try: @@ -353,9 +319,7 @@ async def test_clean_text_message_passes_through_to_openai(): "type": "conversation.item.create", "item": { "role": "user", - "content": [ - {"type": "input_text", "text": "Reply with just: OK"} - ], + "content": [{"type": "input_text", "text": "Reply with just: OK"}], }, } ) @@ -373,20 +337,14 @@ async def test_clean_text_message_passes_through_to_openai(): # No guardrail error should have been sent error_events = [e for e in client_events if e.get("type") == "error"] - guardrail_errors = [ - e - for e in error_events - if e.get("error", {}).get("type") == "guardrail_violation" - ] - assert ( - len(guardrail_errors) == 0 - ), f"Clean message should not trigger guardrail, got: {guardrail_errors}" + guardrail_errors = [e for e in error_events if e.get("error", {}).get("type") == "guardrail_violation"] + assert len(guardrail_errors) == 0, f"Clean message should not trigger guardrail, got: {guardrail_errors}" # AI response must be present done_events = [e for e in client_events if e.get("type") == "response.done"] - assert ( - len(done_events) >= 1 - ), f"Expected response.done from OpenAI, got: {[e.get('type') for e in client_events]}" + assert len(done_events) >= 1, ( + f"Expected response.done from OpenAI, got: {[e.get('type') for e in client_events]}" + ) finally: litellm.callbacks = [] diff --git a/tests/local_testing/test_cost_calc.py b/tests/local_testing/test_cost_calc.py index ab4d44d2240..3623af59848 100644 --- a/tests/local_testing/test_cost_calc.py +++ b/tests/local_testing/test_cost_calc.py @@ -101,7 +101,16 @@ def test_run(model: str): pytest.skip( "LLM API returning inconsistent usage" ) # handles transient openai errors - streaming_cost_calc = completion_cost(response) * 100 + streaming_cost_calc = ( + completion_cost( + response, + custom_cost_per_token={ + "input_cost_per_token": kwargs["input_cost_per_token"], + "output_cost_per_token": kwargs["output_cost_per_token"], + }, + ) + * 100 + ) print(f"Stream output : {output}") print(f"Stream usage : {response.usage}") # type: ignore diff --git a/tests/local_testing/test_dynamic_rate_limit_handler.py b/tests/local_testing/test_dynamic_rate_limit_handler.py index ff540e22e7a..d288d622cfa 100644 --- a/tests/local_testing/test_dynamic_rate_limit_handler.py +++ b/tests/local_testing/test_dynamic_rate_limit_handler.py @@ -7,7 +7,7 @@ import sys import time import traceback from litellm._uuid import uuid -from datetime import datetime +from datetime import datetime, timezone from typing import Optional, Tuple from dotenv import load_dotenv @@ -38,7 +38,8 @@ Basic test cases: @pytest.fixture def dynamic_rate_limit_handler() -> DynamicRateLimitHandler: internal_cache = DualCache() - return DynamicRateLimitHandler(internal_usage_cache=internal_cache) + frozen_now = datetime(2024, 1, 1, 10, 30, 0, tzinfo=timezone.utc) + return DynamicRateLimitHandler(internal_usage_cache=internal_cache, time_fn=lambda: frozen_now) @pytest.fixture diff --git a/tests/local_testing/test_router_fallbacks.py b/tests/local_testing/test_router_fallbacks.py index 6547a3eb663..7c09c978029 100644 --- a/tests/local_testing/test_router_fallbacks.py +++ b/tests/local_testing/test_router_fallbacks.py @@ -1330,6 +1330,8 @@ def test_router_fallbacks_with_custom_model_costs(): Goal: make sure custom model doesn't override default model costs. """ + default_model_info = litellm.get_model_info(model="claude-sonnet-4-5-20250929") + model_list = [ { "model_name": "claude-sonnet-4-5-20250929", @@ -1383,8 +1385,8 @@ def test_router_fallbacks_with_custom_model_costs(): print(f"key: {model_info['key']}") - assert model_info["input_cost_per_token"] == 30 - assert model_info["output_cost_per_token"] == 60 + assert model_info["input_cost_per_token"] == default_model_info["input_cost_per_token"] + assert model_info["output_cost_per_token"] == default_model_info["output_cost_per_token"] @pytest.mark.parametrize("sync_mode", [True, False]) diff --git a/tests/mcp_tests/test_mcp_server.py b/tests/mcp_tests/test_mcp_server.py index 321a17fbb03..515bf1233aa 100644 --- a/tests/mcp_tests/test_mcp_server.py +++ b/tests/mcp_tests/test_mcp_server.py @@ -1556,6 +1556,10 @@ async def test_add_update_server_with_alias(): mock_mcp_server.registration_url = None mock_mcp_server.token_url = None mock_mcp_server.oauth2_flow = None + mock_mcp_server.token_exchange_endpoint = None + mock_mcp_server.audience = None + mock_mcp_server.subject_token_type = None + mock_mcp_server.token_exchange_profile = None # Additional fields used by build_mcp_server_from_table mock_mcp_server.extra_headers = None mock_mcp_server.allow_all_keys = False @@ -1615,6 +1619,10 @@ async def test_add_update_server_without_alias(): mock_mcp_server.registration_url = None mock_mcp_server.token_url = None mock_mcp_server.oauth2_flow = None + mock_mcp_server.token_exchange_endpoint = None + mock_mcp_server.audience = None + mock_mcp_server.subject_token_type = None + mock_mcp_server.token_exchange_profile = None # Additional fields used by build_mcp_server_from_table mock_mcp_server.extra_headers = None mock_mcp_server.allow_all_keys = False @@ -1674,6 +1682,10 @@ async def test_add_update_server_fallback_to_server_id(): mock_mcp_server.registration_url = None mock_mcp_server.token_url = None mock_mcp_server.oauth2_flow = None + mock_mcp_server.token_exchange_endpoint = None + mock_mcp_server.audience = None + mock_mcp_server.subject_token_type = None + mock_mcp_server.token_exchange_profile = None # Additional fields used by build_mcp_server_from_table - set explicitly # to avoid MagicMock objects being passed to Pydantic MCPServer constructor mock_mcp_server.extra_headers = None diff --git a/tests/pass_through_unit_tests/test_vertex_ai_anthropic_streaming_cost_injection.py b/tests/pass_through_unit_tests/test_vertex_ai_anthropic_streaming_cost_injection.py index b42f1fe6f0a..ac754aefaea 100644 --- a/tests/pass_through_unit_tests/test_vertex_ai_anthropic_streaming_cost_injection.py +++ b/tests/pass_through_unit_tests/test_vertex_ai_anthropic_streaming_cost_injection.py @@ -57,6 +57,7 @@ async def test_vertex_ai_anthropic_streaming_cost_injection_enabled(): # Setup logging object with model info litellm_logging_obj = MagicMock(spec=LiteLLMLoggingObj) litellm_logging_obj.model_call_details = {"model": "claude-sonnet-4@20250514"} + litellm_logging_obj.completion_start_time = None litellm_logging_obj.async_success_handler = AsyncMock() request_body = {"model": "claude-sonnet-4@20250514"} @@ -135,6 +136,7 @@ async def test_vertex_ai_anthropic_streaming_cost_injection_disabled(): litellm_logging_obj = MagicMock(spec=LiteLLMLoggingObj) litellm_logging_obj.model_call_details = {"model": "claude-sonnet-4@20250514"} + litellm_logging_obj.completion_start_time = None litellm_logging_obj.async_success_handler = AsyncMock() request_body = {"model": "claude-sonnet-4@20250514"} @@ -196,6 +198,7 @@ async def test_vertex_ai_anthropic_streaming_cost_injection_no_usage_chunk(): litellm_logging_obj = MagicMock(spec=LiteLLMLoggingObj) litellm_logging_obj.model_call_details = {"model": "claude-sonnet-4@20250514"} + litellm_logging_obj.completion_start_time = None litellm_logging_obj.async_success_handler = AsyncMock() request_body = {"model": "claude-sonnet-4@20250514"} @@ -250,6 +253,7 @@ async def test_vertex_ai_anthropic_streaming_model_extraction(): litellm_logging_obj = MagicMock(spec=LiteLLMLoggingObj) litellm_logging_obj.model_call_details = {} + litellm_logging_obj.completion_start_time = None litellm_logging_obj.async_success_handler = AsyncMock() # Test model extraction from request body diff --git a/tests/proxy_unit_tests/test_check_batch_cost.py b/tests/proxy_unit_tests/test_check_batch_cost.py index 63bbe147801..f8f15f2e008 100644 --- a/tests/proxy_unit_tests/test_check_batch_cost.py +++ b/tests/proxy_unit_tests/test_check_batch_cost.py @@ -397,6 +397,72 @@ class TestCheckBatchCost: ), "update() must include batch_processed=True when column is present" assert update_data["status"] == "complete" + @pytest.mark.asyncio + async def test_cost_tracking_failure_leaves_job_unprocessed( + self, check_batch_cost_instance, mock_prisma_client, mock_llm_router + ): + """LIT-4008 regression: when fetching a completed batch's results fails + (e.g. Anthropic rejecting a msgbatch_ id on the Files API), the job must + NOT be marked complete/batch_processed. Pre-fix the $0 spend row was + written and batch_processed=True made it permanent; the failure must + instead leave the row untouched so the next poll retries, without + aborting the poll cycle. + """ + from unittest.mock import patch + + mock_prisma_client.db.litellm_managedobjecttable.update_many = AsyncMock( + return_value=0 + ) + mock_prisma_client.db.litellm_managedobjecttable.update = AsyncMock() + mock_prisma_client.db.litellm_usertable.find_unique = AsyncMock( + return_value=None + ) + + mock_job = MagicMock() + mock_job.id = "job-anthropic-1" + mock_job.unified_object_id = "dW5pZmllZF9iYXRjaF9pZA==" + mock_job.created_by = "user-1" + + mock_prisma_client.db.litellm_managedobjecttable.find_many = AsyncMock( + return_value=[mock_job] + ) + + mock_response = MagicMock() + mock_response.status = "completed" + mock_response.output_file_id = "msgbatch_01WA5hdsa2Xx8w4zyPjV1frs" + + mock_llm_router.aretrieve_batch = AsyncMock(return_value=mock_response) + mock_llm_router.get_deployment_credentials_with_provider = MagicMock( + return_value={"api_key": "sk-test", "custom_llm_provider": "anthropic"} + ) + + decoded_id = "llm_model_id,model-123;llm_batch_id,msgbatch_01WA5hdsa2Xx8w4zyPjV1frs;" + + with ( + patch( + "litellm.proxy.openai_files_endpoints.common_utils._is_base64_encoded_unified_file_id", + side_effect=[decoded_id, None], + ), + patch( + "litellm.proxy.openai_files_endpoints.common_utils.get_model_id_from_unified_batch_id", + return_value="model-123", + ), + patch( + "litellm.proxy.openai_files_endpoints.common_utils.get_batch_id_from_unified_batch_id", + return_value="msgbatch_01WA5hdsa2Xx8w4zyPjV1frs", + ), + patch( + "litellm.files.main.afile_content", + new_callable=AsyncMock, + side_effect=Exception("File id must have `file_` prefix."), + ), + ): + await check_batch_cost_instance.check_batch_cost() + + assert ( + mock_prisma_client.db.litellm_managedobjecttable.update.call_count == 0 + ), "a failed cost tracking attempt must not mark the job processed" + @pytest.mark.asyncio @pytest.mark.parametrize("terminal_status", ["failed", "expired", "cancelled"]) async def test_terminal_status_marks_job_processed( diff --git a/tests/router_unit_tests/test_router_helper_utils.py b/tests/router_unit_tests/test_router_helper_utils.py index 83d6d56df4f..848a6c28a57 100644 --- a/tests/router_unit_tests/test_router_helper_utils.py +++ b/tests/router_unit_tests/test_router_helper_utils.py @@ -523,6 +523,55 @@ async def test_deployment_callback_on_success(sync_mode): assert tpm_key is not None +@pytest.mark.asyncio +async def test_deployment_callback_on_success_tracks_tpm_for_io_deployment(): + """ + An IO-limited deployment (itpm/otpm, no tpm/rpm) must still record TPM usage + in the router's routing counter so TPM-aware routing strategies see its real + load in mixed model groups; its itpm/otpm enforcement runs separately. + """ + import time + + model_list = [ + { + "model_name": "opus", + "litellm_params": { + "model": "openai/gpt-4o-mini", + "api_key": "sk-fake", + "itpm": 1000, + }, + "model_info": {"id": "io-100"}, + } + ] + router = Router(model_list=model_list) + + standard_logging_payload = create_standard_logging_payload() + standard_logging_payload["total_tokens"] = 100 + standard_logging_payload["model_id"] = "io-100" + kwargs = { + "litellm_params": { + "metadata": { + "deployment": "openai/gpt-4o-mini", + "model_group": "opus", + }, + "model_info": {"id": "io-100"}, + }, + "standard_logging_object": standard_logging_payload, + } + response = litellm.ModelResponse(model="openai/gpt-4o-mini", usage={"total_tokens": 100}) + + tpm_key = await router.deployment_callback_on_success( + kwargs=kwargs, + completion_response=response, + start_time=time.time(), + end_time=time.time(), + ) + + # The IO deployment is no longer skipped: its TPM routing counter is tracked. + assert tpm_key is not None + assert await router.cache.async_get_cache(key=tpm_key) == 100 + + @pytest.mark.asyncio async def test_deployment_callback_on_failure(model_list): """Test if the '_deployment_callback_on_failure' function is working correctly""" @@ -923,6 +972,227 @@ async def test_set_response_headers_subtracts_in_flight_delta(model_list): assert headers["x-ratelimit-limit-requests"] == 100 +@pytest.mark.asyncio +async def test_set_response_headers_in_flight_delta_only_adjusts_tpm_rpm(model_list): + """ + The in-flight replay applies only to the post-incremented TPM/RPM counters + (`x-ratelimit-remaining-tokens` / `-requests`). The ITPM/OTPM counters are + incremented at reservation time (pre-call), so the input/output token + headers already reflect this request and must pass through untouched. + """ + from pydantic import BaseModel + + class _Usage(BaseModel): + total_tokens: int = 30 + prompt_tokens: int = 20 + completion_tokens: int = 10 + + class _Resp(BaseModel): + usage: _Usage = _Usage() + _hidden_params: dict = {} + + router = Router(model_list=model_list) + router.get_remaining_model_group_usage = AsyncMock( + return_value={ + "x-ratelimit-remaining-tokens": 1000, + "x-ratelimit-remaining-requests": 100, + "x-ratelimit-remaining-input-tokens": 1000, + "x-ratelimit-remaining-output-tokens": 500, + } + ) + + resp = _Resp() + resp._hidden_params = {} + await router.set_response_headers(response=resp, model_group="gpt-3.5-turbo") + + headers = resp._hidden_params["additional_headers"] + # TPM/RPM headers replay the in-flight increment... + assert headers["x-ratelimit-remaining-tokens"] == 970 + assert headers["x-ratelimit-remaining-requests"] == 99 + # ...but the reservation-based input/output headers pass through unchanged. + assert headers["x-ratelimit-remaining-input-tokens"] == 1000 + assert headers["x-ratelimit-remaining-output-tokens"] == 500 + + +@pytest.mark.asyncio +async def test_get_model_group_io_token_usage_sums_across_deployments(): + """ + get_model_group_io_token_usage must sum ITPM/OTPM across every deployment + in the model group (not just the first), reading the same per-deployment + cache keys the pre-call reservation writes to. + """ + from litellm.types.router import RouterCacheEnum + from litellm.utils import get_utc_datetime + + router = Router( + model_list=[ + { + "model_name": "opus", + "litellm_params": { + "model": "openai/gpt-4o-mini", + "itpm": 1000, + "otpm": 500, + }, + "model_info": {"id": "io-usage-dep-1"}, + }, + { + "model_name": "opus", + "litellm_params": { + "model": "openai/gpt-4o", + "itpm": 1000, + "otpm": 500, + }, + "model_info": {"id": "io-usage-dep-2"}, + }, + ] + ) + + minute = get_utc_datetime().strftime("%H-%M") + keys_and_values = [ + ( + RouterCacheEnum.ITPM.value.format( + id="io-usage-dep-1", model="openai/gpt-4o-mini", current_minute=minute + ), + 30, + ), + ( + RouterCacheEnum.OTPM.value.format( + id="io-usage-dep-1", model="openai/gpt-4o-mini", current_minute=minute + ), + 10, + ), + ( + RouterCacheEnum.ITPM.value.format( + id="io-usage-dep-2", model="openai/gpt-4o", current_minute=minute + ), + 70, + ), + ( + RouterCacheEnum.OTPM.value.format( + id="io-usage-dep-2", model="openai/gpt-4o", current_minute=minute + ), + 20, + ), + ] + for key, value in keys_and_values: + await router.cache.async_increment_cache(key=key, value=value, ttl=60) + + current_itpm, current_otpm = await router.get_model_group_io_token_usage("opus") + + assert current_itpm == 100 + assert current_otpm == 30 + + +@pytest.mark.asyncio +async def test_get_model_group_io_token_usage_no_deployments_returns_none(): + router = Router(model_list=[]) + current_itpm, current_otpm = await router.get_model_group_io_token_usage( + "nonexistent-group" + ) + assert current_itpm is None + assert current_otpm is None + + +@pytest.mark.asyncio +async def test_get_remaining_model_group_usage_merges_io_and_tpm_headers(model_list): + """ + A model group with both itpm/otpm and tpm/rpm limits must expose the + standard remaining-tokens/requests headers alongside the input/output token + headers, so clients and prometheus gauges relying on either still get data. + """ + from unittest.mock import Mock + + from litellm.types.router import ModelGroupInfo + + router = Router(model_list=model_list) + router._cached_get_model_group_info = Mock( + return_value=ModelGroupInfo( + model_group="gpt-3.5-turbo", + providers=["openai"], + itpm=2000, + otpm=1000, + tpm=5000, + rpm=50, + ) + ) + router.get_model_group_io_token_usage = AsyncMock(return_value=(100, 40)) + router.get_model_group_usage = AsyncMock(return_value=(500, 5)) + + headers = await router.get_remaining_model_group_usage("gpt-3.5-turbo") + + assert headers["x-ratelimit-remaining-input-tokens"] == 1900 + assert headers["x-ratelimit-remaining-output-tokens"] == 960 + assert headers["x-ratelimit-remaining-tokens"] == 4500 + assert headers["x-ratelimit-remaining-requests"] == 45 + + +@pytest.mark.asyncio +async def test_set_response_headers_native_input_token_header_does_not_suppress_router_headers(model_list): + """ + A provider that natively returns `x-ratelimit-remaining-input-tokens` must + not suppress the router's own remaining-tokens/requests headers for a + non-IO model group. + """ + from pydantic import BaseModel + + class _Usage(BaseModel): + total_tokens: int = 42 + + class _Resp(BaseModel): + usage: _Usage = _Usage() + _hidden_params: dict = {} + + router = Router(model_list=model_list) + router.get_remaining_model_group_usage = AsyncMock( + return_value={ + "x-ratelimit-remaining-tokens": 1000, + "x-ratelimit-remaining-requests": 100, + } + ) + + resp = _Resp() + resp._hidden_params = {"additional_headers": {"x-ratelimit-remaining-input-tokens": 5}} + await router.set_response_headers(response=resp, model_group="gpt-3.5-turbo") + + headers = resp._hidden_params["additional_headers"] + assert headers["x-ratelimit-remaining-tokens"] == 958 + assert headers["x-ratelimit-remaining-requests"] == 99 + # the provider's native header is left untouched + assert headers["x-ratelimit-remaining-input-tokens"] == 5 + + +@pytest.mark.asyncio +async def test_set_response_headers_native_token_header_does_not_suppress_io_headers(model_list): + from pydantic import BaseModel + + class _Usage(BaseModel): + total_tokens: int = 42 + + class _Resp(BaseModel): + usage: _Usage = _Usage() + _hidden_params: dict = {} + + router = Router(model_list=model_list) + router.get_remaining_model_group_usage = AsyncMock( + return_value={ + "x-ratelimit-remaining-tokens": 1000, + "x-ratelimit-remaining-requests": 100, + "x-ratelimit-remaining-input-tokens": 900, + "x-ratelimit-remaining-output-tokens": 450, + } + ) + + resp = _Resp() + resp._hidden_params = {"additional_headers": {"x-ratelimit-remaining-tokens": 5}} + await router.set_response_headers(response=resp, model_group="gpt-3.5-turbo") + + headers = resp._hidden_params["additional_headers"] + assert headers["x-ratelimit-remaining-tokens"] == 5 + assert headers["x-ratelimit-remaining-requests"] == 99 + assert headers["x-ratelimit-remaining-input-tokens"] == 900 + assert headers["x-ratelimit-remaining-output-tokens"] == 450 + + @pytest.mark.asyncio async def test_set_response_headers_handles_missing_usage(model_list): """ @@ -952,6 +1222,72 @@ async def test_set_response_headers_handles_missing_usage(model_list): assert headers["x-ratelimit-remaining-requests"] == 99 +@pytest.mark.asyncio +async def test_set_response_headers_dict_anthropic_messages_response(model_list): + """Anthropic /v1/messages returns a dict; IO rate-limit headers must attach.""" + router = Router(model_list=model_list) + router.get_remaining_model_group_usage = AsyncMock( + return_value={ + "x-ratelimit-limit-input-tokens": 25, + "x-ratelimit-remaining-input-tokens": 20, + "x-ratelimit-limit-output-tokens": 100, + "x-ratelimit-remaining-output-tokens": 95, + } + ) + + resp = { + "id": "msg_123", + "type": "message", + "role": "assistant", + "content": [{"type": "text", "text": "hi"}], + "usage": {"input_tokens": 5, "output_tokens": 1}, + } + await router.set_response_headers(response=resp, model_group="io-itpm-strict") + + assert "_hidden_params" in resp + headers = resp["_hidden_params"]["additional_headers"] + assert headers["x-litellm-model-group"] == "io-itpm-strict" + assert headers["x-ratelimit-limit-input-tokens"] == 25 + assert headers["x-ratelimit-remaining-input-tokens"] == 20 + assert headers["x-ratelimit-remaining-output-tokens"] == 95 + + +@pytest.mark.asyncio +async def test_set_response_headers_wraps_bare_async_generator(model_list): + """ + Streaming responses that never go through Router.make_call's usual + object-based wrappers (e.g. the Anthropic /v1/messages -> Responses API + bridge, which yields a raw async generator with no `_hidden_params` slot) + must still get IO rate-limit headers attached via a thin wrapper. + """ + + async def _raw_generator(): + yield {"type": "message_start"} + yield {"type": "message_stop"} + + router = Router(model_list=model_list) + router.get_remaining_model_group_usage = AsyncMock( + return_value={ + "x-ratelimit-limit-input-tokens": 25, + "x-ratelimit-remaining-input-tokens": 20, + } + ) + + wrapped = await router.set_response_headers(response=_raw_generator(), model_group="io-itpm-strict") + + assert hasattr(wrapped, "_hidden_params") + headers = wrapped._hidden_params["additional_headers"] + assert headers["x-litellm-model-group"] == "io-itpm-strict" + assert headers["x-ratelimit-limit-input-tokens"] == 25 + assert headers["x-ratelimit-remaining-input-tokens"] == 20 + + from collections.abc import AsyncIterator + + assert isinstance(wrapped, AsyncIterator) + chunks = [chunk async for chunk in wrapped] + assert chunks == [{"type": "message_start"}, {"type": "message_stop"}] + + def test_get_all_deployments(model_list): """Test if the 'get_all_deployments' function is working correctly""" router = Router(model_list=model_list) diff --git a/tests/store_model_in_db_tests/test_callbacks_in_db.py b/tests/store_model_in_db_tests/test_callbacks_in_db.py index 4a851251a3e..e92aeb6ebc4 100644 --- a/tests/store_model_in_db_tests/test_callbacks_in_db.py +++ b/tests/store_model_in_db_tests/test_callbacks_in_db.py @@ -15,17 +15,30 @@ import os import dotenv from dotenv import load_dotenv import pytest -from openai import AsyncOpenAI +from openai import AsyncOpenAI, APIConnectionError from openai.types.chat import ChatCompletion load_dotenv() # used for testing LANGFUSE_BASE_URL = "https://exampleopenaiendpoint-production-c715.up.railway.app" +PROXY_BASE_URL = "http://127.0.0.1:4000" + + +async def wait_for_proxy_ready(session, timeout: int = 60): + for _ in range(timeout): + try: + async with session.get(f"{PROXY_BASE_URL}/health/liveliness") as response: + if response.status == 200: + return + except aiohttp.ClientError: + pass + await asyncio.sleep(1) + raise RuntimeError(f"Proxy at {PROXY_BASE_URL} not ready after {timeout}s") async def config_update(session, routing_strategy=None): - url = "http://0.0.0.0:4000/config/update" + url = f"{PROXY_BASE_URL}/config/update" headers = {"Authorization": "Bearer sk-1234", "Content-Type": "application/json"} print("routing_strategy: ", routing_strategy) data = { @@ -62,32 +75,41 @@ async def check_langfuse_request(response_id: str): async def make_chat_completions_request() -> ChatCompletion: - client = AsyncOpenAI(api_key="sk-1234", base_url="http://0.0.0.0:4000") - response = await client.chat.completions.create( - model="fake-openai-endpoint", - messages=[{"role": "user", "content": "Hello, world!"}], + client = AsyncOpenAI(api_key="sk-1234", base_url=PROXY_BASE_URL) + last_error = None + for _ in range(10): + try: + response = await client.chat.completions.create( + model="fake-openai-endpoint", + messages=[{"role": "user", "content": "Hello, world!"}], + ) + print(response) + return response + except APIConnectionError as e: + last_error = e + await asyncio.sleep(2) + raise AssertionError( + f"Proxy at {PROXY_BASE_URL} unreachable after retries: {last_error!r}" ) - print(response) - return response @pytest.mark.asyncio async def test_e2e_langfuse_callbacks_in_db(): - session = aiohttp.ClientSession() + async with aiohttp.ClientSession() as session: + # add langfuse callback to DB + await config_update(session) - # add langfuse callback to DB - await config_update(session) + # wait 20 seconds for the callback to be loaded into the instance + await asyncio.sleep(20) + await wait_for_proxy_ready(session) + + # make a /chat/completions request to the proxy + response = await make_chat_completions_request() + print(response) + response_id = response.id + print("response_id: ", response_id) - # wait 20 seconds for the callback to be loaded into the instance await asyncio.sleep(20) - - # make a /chat/completions request to the proxy - response = await make_chat_completions_request() - print(response) - response_id = response.id - print("response_id: ", response_id) - - await asyncio.sleep(11) # check if the request is logged in Langfuse await check_langfuse_request(response_id) diff --git a/tests/test_litellm/a2a_protocol/test_a2a_streaming_iterator.py b/tests/test_litellm/a2a_protocol/test_a2a_streaming_iterator.py new file mode 100644 index 00000000000..d86cbb94a91 --- /dev/null +++ b/tests/test_litellm/a2a_protocol/test_a2a_streaming_iterator.py @@ -0,0 +1,102 @@ +""" +Regression test for LIT-4210: completing an A2A stream must not run the sync +success_handler on the thread-pool executor concurrently with +async_success_handler (cross-thread pydantic mutation segfaults pydantic-core). +""" + +import asyncio +import time +from types import SimpleNamespace + +import pytest + +import litellm +from litellm.a2a_protocol import streaming_iterator as a2a_streaming_iterator_module +from litellm.a2a_protocol.streaming_iterator import A2AStreamingIterator +from litellm.integrations.custom_logger import CustomLogger +from litellm.litellm_core_utils import thread_pool_executor as thread_pool_executor_module +from litellm.litellm_core_utils.litellm_logging import Logging as LitellmLogging + + +class RecordingCustomLogger(CustomLogger): + def __init__(self): + super().__init__() + self.async_hook_fired = False + + async def async_log_success_event(self, kwargs, response_obj, start_time, end_time): + self.async_hook_fired = True + + async def async_log_stream_event(self, kwargs, response_obj, start_time, end_time): + self.async_hook_fired = True + + +class RecordingExecutor: + def __init__(self, inner): + self._inner = inner + self.submits: list = [] + + def submit(self, fn, *args, **kwargs): + self.submits.append(fn) + return self._inner.submit(fn, *args, **kwargs) + + def submitted_for(self, logging_obj) -> list: + return [fn for fn in self.submits if getattr(fn, "__self__", None) is logging_obj] + + +@pytest.fixture(autouse=True) +def _isolate_callbacks(): + saved = ( + litellm.callbacks, + litellm.success_callback, + litellm._async_success_callback, + litellm.failure_callback, + litellm._async_failure_callback, + ) + yield + ( + litellm.callbacks, + litellm.success_callback, + litellm._async_success_callback, + litellm.failure_callback, + litellm._async_failure_callback, + ) = saved + + +@pytest.mark.asyncio +async def test_custom_logger_only_never_submits_sync_success_handler(monkeypatch): + recording_executor = RecordingExecutor(thread_pool_executor_module.executor) + monkeypatch.setattr(thread_pool_executor_module, "executor", recording_executor) + monkeypatch.setattr(a2a_streaming_iterator_module, "executor", recording_executor, raising=False) + + recorder = RecordingCustomLogger() + litellm.success_callback = [recorder] + litellm._async_success_callback = [recorder] + + logging_obj = LitellmLogging( + model="a2a/test-agent", + messages=[{"role": "user", "content": "hi"}], + stream=True, + call_type="a2a_send_message_streaming", + start_time=time.time(), + litellm_call_id="lit-4210-test", + function_id="lit-4210-test", + ) + + async def _empty_stream(): + return + yield + + iterator = A2AStreamingIterator( + stream=_empty_stream(), + request=SimpleNamespace( + params=SimpleNamespace(message={"role": "user", "parts": [{"kind": "text", "text": "hi"}]}) + ), + logging_obj=logging_obj, + agent_name="test-agent", + ) + + await iterator._handle_stream_complete() + await asyncio.sleep(0.5) + + assert recorder.async_hook_fired is True + assert recording_executor.submitted_for(logging_obj) == [] diff --git a/tests/test_litellm/batches/test_batch_utils.py b/tests/test_litellm/batches/test_batch_utils.py index 3aebfcb911e..9de5cd69b1e 100644 --- a/tests/test_litellm/batches/test_batch_utils.py +++ b/tests/test_litellm/batches/test_batch_utils.py @@ -733,3 +733,192 @@ def test_total_usage_vertex_disable_transform_path(monkeypatch): usage = bu._get_batch_job_total_usage_from_file_content([], custom_llm_provider="vertex_ai", model_name="gemini-x") assert usage.total_tokens == 3 + + +def _anthropic_usage(input_tokens, output_tokens, cache_creation=0, cache_read=0): + return { + "input_tokens": input_tokens, + "output_tokens": output_tokens, + "cache_creation_input_tokens": cache_creation, + "cache_read_input_tokens": cache_read, + } + + +def _anthropic_succeeded_row(model="claude-sonnet-4-5-20250929", usage=None): + return { + "custom_id": "req-1", + "result": { + "type": "succeeded", + "message": { + "id": "msg_1", + "type": "message", + "role": "assistant", + "model": model, + "content": [{"type": "text", "text": "ok"}], + "stop_reason": "end_turn", + "usage": usage or _anthropic_usage(10, 5), + }, + }, + } + + +def _anthropic_errored_row(): + return { + "custom_id": "req-2", + "result": { + "type": "errored", + "error": {"type": "invalid_request_error", "message": "bad request"}, + }, + } + + +_ANTHROPIC_MODEL_INFO = { + "input_cost_per_token": 3e-6, + "output_cost_per_token": 15e-6, + "cache_read_input_token_cost": 3e-7, + "cache_creation_input_token_cost": 3.75e-6, +} + + +@pytest.mark.parametrize( + "row,expected", + [ + (_anthropic_succeeded_row(), True), + (_anthropic_errored_row(), False), + ({"custom_id": "x", "result": {"type": "canceled"}}, False), + ({"custom_id": "x", "result": {"type": "expired"}}, False), + ({"custom_id": "x"}, False), + ({"custom_id": "x", "result": None}, False), + ], +) +def test_anthropic_result_line_success_check(row, expected): + """ + LIT-4008 regression: anthropic batch results JSONL lines are not + OpenAI-shaped; success is result.type == "succeeded", not + response.status_code == 200. Pre-fix every anthropic line parsed as + unsuccessful, so completed batches were billed $0 forever. + """ + assert bu._batch_response_was_successful(row, custom_llm_provider="anthropic") is expected + + +def test_anthropic_response_body_is_result_message(): + row = _anthropic_succeeded_row(model="claude-sonnet-4-5-20250929") + body = bu._get_response_from_batch_job_output_file(row, custom_llm_provider="anthropic") + assert body["model"] == "claude-sonnet-4-5-20250929" + assert body["usage"] == _anthropic_usage(10, 5) + + +def test_anthropic_usage_conversion_includes_cache_tokens(): + body = {"model": "claude-sonnet-4-5-20250929", "usage": _anthropic_usage(1000, 200, cache_creation=2000, cache_read=8000)} + usage = bu._get_batch_job_usage_from_response_body(body, custom_llm_provider="anthropic") + assert usage.prompt_tokens == 11000 + assert usage.completion_tokens == 200 + assert usage.total_tokens == 11200 + assert usage.prompt_tokens_details.cached_tokens == 8000 + assert usage.prompt_tokens_details.cache_creation_tokens == 2000 + + +def test_anthropic_total_usage_sums_succeeded_only(): + rows = [ + _anthropic_succeeded_row(usage=_anthropic_usage(10, 5)), + _anthropic_errored_row(), + _anthropic_succeeded_row(usage=_anthropic_usage(20, 10, cache_read=100)), + ] + usage = bu._get_batch_job_total_usage_from_file_content(rows, custom_llm_provider="anthropic") + assert (usage.prompt_tokens, usage.completion_tokens, usage.total_tokens) == (130, 15, 145) + + +def test_anthropic_total_usage_aggregates_cache_token_details(): + rows = [ + _anthropic_succeeded_row(usage=_anthropic_usage(1000, 200, cache_creation=2000, cache_read=8000)), + _anthropic_errored_row(), + _anthropic_succeeded_row(usage=_anthropic_usage(50, 20, cache_creation=300, cache_read=700)), + ] + usage = bu._get_batch_job_total_usage_from_file_content(rows, custom_llm_provider="anthropic") + assert usage.prompt_tokens_details.cached_tokens == 8700 + assert usage.prompt_tokens_details.cache_creation_tokens == 2300 + assert usage.cache_read_input_tokens == 8700 + assert usage.cache_creation_input_tokens == 2300 + + +def test_total_usage_without_cache_tokens_has_no_prompt_details(): + rows = [ + { + "custom_id": "req-1", + "response": {"status_code": 200, "body": {"model": "gpt-5.2", "usage": {"prompt_tokens": 10, "completion_tokens": 5, "total_tokens": 15}}}, + } + ] + usage = bu._get_batch_job_total_usage_from_file_content(rows, custom_llm_provider="openai") + assert (usage.prompt_tokens, usage.completion_tokens, usage.total_tokens) == (10, 5, 15) + assert usage.prompt_tokens_details is None + + +def test_anthropic_cost_applies_batch_discount_and_cache_pricing(): + """Anthropic batches bill at 50% of the regular rate for base input, + cache reads, cache writes, and output tokens alike.""" + rows = [ + _anthropic_succeeded_row(usage=_anthropic_usage(1000, 200, cache_creation=2000, cache_read=8000)), + _anthropic_errored_row(), + ] + + total = bu._get_batch_job_cost_from_file_content( + rows, + custom_llm_provider="anthropic", + model_info=_ANTHROPIC_MODEL_INFO, # type: ignore[arg-type] + ) + + expected_half_price = (1000 * 3e-6 + 8000 * 3e-7 + 2000 * 3.75e-6 + 200 * 15e-6) / 2 + assert total == pytest.approx(expected_half_price) + + +def test_anthropic_cost_without_model_info_uses_batch_cost_calculator(monkeypatch): + import litellm.cost_calculator as cc + + seen = [] + + def _fake_batch_cost_calculator(**kw): + seen.append(kw) + return (0.1, 0.2) + + monkeypatch.setattr(cc, "batch_cost_calculator", _fake_batch_cost_calculator) + monkeypatch.setattr( + litellm, + "completion_cost", + lambda **kw: pytest.fail("anthropic rows must not go through completion_cost"), + ) + + total = bu._get_batch_job_cost_from_file_content( + [_anthropic_succeeded_row()], custom_llm_provider="anthropic" + ) + + assert total == pytest.approx(0.3) + assert seen[0]["model"] == "claude-sonnet-4-5-20250929" + assert seen[0]["custom_llm_provider"] == "anthropic" + assert seen[0]["usage"].prompt_tokens == 10 + + +def test_anthropic_batch_models_collected_from_succeeded_rows(): + rows = [ + _anthropic_succeeded_row(model="claude-sonnet-4-5-20250929"), + _anthropic_errored_row(), + ] + assert bu._get_batch_models_from_file_content(rows, None, "anthropic") == ["claude-sonnet-4-5-20250929"] + + +@pytest.mark.asyncio +async def test_calculate_batch_cost_and_usage_anthropic_end_to_end(): + rows = [ + _anthropic_succeeded_row(usage=_anthropic_usage(1000, 200, cache_creation=2000, cache_read=8000)), + _anthropic_errored_row(), + ] + + cost, usage, models = await bu.calculate_batch_cost_and_usage( + file_content_dictionary=rows, + custom_llm_provider="anthropic", + model_name="claude-sonnet-4-5", + model_info=_ANTHROPIC_MODEL_INFO, # type: ignore[arg-type] + ) + + assert cost == pytest.approx(1000 * 3e-6 / 2 + 8000 * 3e-7 / 2 + 2000 * 3.75e-6 / 2 + 200 * 15e-6 / 2) + assert (usage.prompt_tokens, usage.completion_tokens, usage.total_tokens) == (11000, 200, 11200) + assert models == ["claude-sonnet-4-5"] diff --git a/tests/test_litellm/caching/test_disk_cache.py b/tests/test_litellm/caching/test_disk_cache.py new file mode 100644 index 00000000000..b8d3b7b8d36 --- /dev/null +++ b/tests/test_litellm/caching/test_disk_cache.py @@ -0,0 +1,41 @@ +import pytest + +pytest.importorskip("diskcache") + +from litellm.caching.disk_cache import DiskCache + + +@pytest.fixture +def cache(tmp_path): + return DiskCache(disk_cache_dir=str(tmp_path)) + + +def test_increment_cache_starts_from_zero_when_key_missing(cache): + assert cache.increment_cache("counter", 3) == 3 + assert cache.get_cache("counter") == 3 + + +def test_increment_cache_adds_to_existing_int(cache): + cache.set_cache("counter", 7) + assert cache.increment_cache("counter", 5) == 12 + assert cache.get_cache("counter") == 12 + + +def test_increment_cache_treats_non_int_cached_value_as_zero(cache): + cache.set_cache("counter", "not-a-number") + assert cache.increment_cache("counter", 4) == 4 + assert cache.get_cache("counter") == 4 + + +async def test_async_increment_starts_from_zero_when_key_missing(cache): + assert await cache.async_increment("counter", 2) == 2 + + +async def test_async_increment_adds_to_existing_int(cache): + await cache.async_set_cache("counter", 10) + assert await cache.async_increment("counter", 5) == 15 + + +async def test_async_increment_treats_non_int_cached_value_as_zero(cache): + await cache.async_set_cache("counter", "corrupt") + assert await cache.async_increment("counter", 9) == 9 diff --git a/tests/test_litellm/caching/test_valkey_semantic_cache.py b/tests/test_litellm/caching/test_valkey_semantic_cache.py index 44b9f061998..d2df0a98e12 100644 --- a/tests/test_litellm/caching/test_valkey_semantic_cache.py +++ b/tests/test_litellm/caching/test_valkey_semantic_cache.py @@ -300,6 +300,59 @@ async def test_async_set_and_get_roundtrip(): assert metadata["semantic-similarity"] == pytest.approx(0.95) +@pytest.mark.asyncio +async def test_async_set_cache_passes_only_metadata_to_get_async_embedding(): + async_client = AsyncMock() + async_client.ft = _async_ft(0.05) + cache = _make_cache(async_client=async_client) + captured: dict[str, object] = {} + + async def spy_embedding(prompt: str, metadata: dict | None = None) -> list[float]: + captured["prompt"] = prompt + captured["metadata"] = metadata + return [0.1, 0.2, 0.3] + + cache._get_async_embedding = spy_embedding + + await cache.async_set_cache( + key="cache-key", + value={"content": "Paris"}, + messages=[{"role": "user", "content": "What is the capital of France?"}], + metadata={"user_api_key": "sk-test"}, + cache_key="abc123", + custom_llm_provider="openai", + ) + + assert captured["metadata"] == {"user_api_key": "sk-test"} + async_client.hset.assert_awaited_once() + + +@pytest.mark.asyncio +async def test_async_get_cache_passes_only_metadata_to_get_async_embedding(): + async_client = AsyncMock() + async_client.ft = _async_ft(0.05) + cache = _make_cache(async_client=async_client) + captured: dict[str, object] = {} + + async def spy_embedding(prompt: str, metadata: dict | None = None) -> list[float]: + captured["prompt"] = prompt + captured["metadata"] = dict(metadata) if metadata is not None else None + return [0.1, 0.2, 0.3] + + cache._get_async_embedding = spy_embedding + + result = await cache.async_get_cache( + key="cache-key", + messages=[{"role": "user", "content": "What is the capital of France?"}], + metadata={"user_api_key": "sk-test"}, + cache_key="abc123", + custom_llm_provider="openai", + ) + + assert result == {"content": "Paris"} + assert captured["metadata"] == {"user_api_key": "sk-test"} + + @pytest.mark.asyncio async def test_async_get_cache_misses_below_threshold(): async_client = AsyncMock() diff --git a/tests/test_litellm/integrations/otel/test_otel_v2_logger.py b/tests/test_litellm/integrations/otel/test_otel_v2_logger.py index 674b2bec829..697b9293eea 100644 --- a/tests/test_litellm/integrations/otel/test_otel_v2_logger.py +++ b/tests/test_litellm/integrations/otel/test_otel_v2_logger.py @@ -168,6 +168,43 @@ def test_async_log_success_event_emits_llm_call_span(): assert span.status.status_code is StatusCode.UNSET +def test_streaming_span_carries_time_to_first_chunk(): + logger, exporter = _logger() + kwargs = { + **_kwargs(payload=_payload(stream=True)), + "optional_params": {"stream": True}, + "api_call_start_time": datetime(2026, 5, 26, 12, 0, 0, tzinfo=timezone.utc), + "completion_start_time": datetime(2026, 5, 26, 12, 0, 0, 750000, tzinfo=timezone.utc), + } + _emit_llm(logger, kwargs) + (span,) = exporter.get_finished_spans() + assert span.attributes[GenAI.RESPONSE_TIME_TO_FIRST_CHUNK] == pytest.approx(0.75) + + +def test_non_streaming_span_has_no_time_to_first_chunk(): + logger, exporter = _logger() + kwargs = { + **_kwargs(), + "optional_params": {}, + "api_call_start_time": datetime(2026, 5, 26, 12, 0, 0, tzinfo=timezone.utc), + "completion_start_time": datetime(2026, 5, 26, 12, 0, 5, tzinfo=timezone.utc), + } + _emit_llm(logger, kwargs) + (span,) = exporter.get_finished_spans() + assert GenAI.RESPONSE_TIME_TO_FIRST_CHUNK not in span.attributes + + +def test_streaming_span_without_timing_omits_time_to_first_chunk(): + logger, exporter = _logger() + kwargs = { + **_kwargs(payload=_payload(stream=True)), + "optional_params": {"stream": True}, + } + _emit_llm(logger, kwargs) + (span,) = exporter.get_finished_spans() + assert GenAI.RESPONSE_TIME_TO_FIRST_CHUNK not in span.attributes + + def test_async_log_failure_event_marks_error_status(): logger, exporter = _logger() payload = _payload( diff --git a/tests/test_litellm/integrations/websearch_interception/test_websearch_interception_handler.py b/tests/test_litellm/integrations/websearch_interception/test_websearch_interception_handler.py index ea117380edf..b6ff3b70a4d 100644 --- a/tests/test_litellm/integrations/websearch_interception/test_websearch_interception_handler.py +++ b/tests/test_litellm/integrations/websearch_interception/test_websearch_interception_handler.py @@ -12,6 +12,8 @@ from litellm.constants import LITELLM_WEB_SEARCH_TOOL_NAME from litellm.integrations.websearch_interception.handler import ( WebSearchInterceptionLogger, ) +from litellm.llms.base_llm.search.transformation import SearchResponse +from litellm.proxy._types import LiteLLM_ObjectPermissionTable, LiteLLM_TeamTable, ProxyException, UserAPIKeyAuth from litellm.types.utils import LlmProviders @@ -56,9 +58,7 @@ def test_initialize_from_proxy_config_honors_dict_callback_specific_params(): """A valid dict under callback_settings.websearch_interception is applied.""" logger = WebSearchInterceptionLogger.initialize_from_proxy_config( litellm_settings={}, - callback_specific_params={ - "websearch_interception": {"search_tool_name": "ws-tool"} - }, + callback_specific_params={"websearch_interception": {"search_tool_name": "ws-tool"}}, ) assert logger.search_tool_name == "ws-tool" @@ -119,9 +119,7 @@ async def test_async_build_agentic_loop_plan_returns_request_patch(): "response_format": "anthropic", } logging_obj = MagicMock() - logging_obj.model_call_details = { - "agentic_loop_params": {"model": "bedrock/invoke/claude-3-5-sonnet"} - } + logging_obj.model_call_details = {"agentic_loop_params": {"model": "bedrock/invoke/claude-3-5-sonnet"}} kwargs = { "temperature": 0.2, "_websearch_interception_converted_stream": True, @@ -162,8 +160,6 @@ async def test_internal_flags_filtered_from_followup_kwargs(): to the follow-up LLM request, causing "Extra inputs are not permitted" errors from providers like Bedrock that use strict parameter validation. """ - logger = WebSearchInterceptionLogger(enabled_providers=["bedrock"]) - # Simulate kwargs that would be passed during agentic loop execution kwargs_with_internal_flags = { "_websearch_interception_converted_stream": True, @@ -174,9 +170,7 @@ async def test_internal_flags_filtered_from_followup_kwargs(): # Apply the same filtering logic used in _execute_agentic_loop kwargs_for_followup = { - k: v - for k, v in kwargs_with_internal_flags.items() - if not k.startswith("_websearch_interception") + k: v for k, v in kwargs_with_internal_flags.items() if not k.startswith("_websearch_interception") } # Verify internal flags are filtered out @@ -188,6 +182,138 @@ async def test_internal_flags_filtered_from_followup_kwargs(): assert kwargs_for_followup["max_tokens"] == 1024 +@pytest.mark.asyncio +async def test_execute_search_passes_selected_search_tool_litellm_params(monkeypatch): + import litellm + from litellm.proxy import proxy_server + + logger = WebSearchInterceptionLogger( + enabled_providers=["bedrock"], + search_tool_name="ui-tavily", + ) + router = MagicMock() + router.search_tools = [ + { + "search_tool_name": "ui-tavily", + "litellm_params": { + "search_provider": "tavily", + "api_key": "fake-ui-key", + "api_base": "https://api.tavily.com", + "timeout": 10.0, + "max_retries": 2, + "country": None, + }, + } + ] + mock_asearch = AsyncMock(return_value=SearchResponse(object="search", results=[])) + user_api_key_auth = UserAPIKeyAuth( + object_permission=LiteLLM_ObjectPermissionTable( + object_permission_id="op-allowed-search", + search_tools=["ui-tavily"], + ) + ) + + monkeypatch.setattr(proxy_server, "llm_router", router) + monkeypatch.setattr(litellm, "asearch", mock_asearch) + + await logger._execute_search( + "what is litellm", + kwargs={"litellm_params": {"metadata": {"user_api_key_auth": user_api_key_auth}}}, + ) + + mock_asearch.assert_awaited_once_with( + query="what is litellm", + search_provider="tavily", + api_key="fake-ui-key", + api_base="https://api.tavily.com", + timeout=10.0, + max_retries=2, + ) + + +@pytest.mark.asyncio +async def test_execute_search_enforces_key_search_tool_permission(monkeypatch): + import litellm + from litellm.proxy import proxy_server + + logger = WebSearchInterceptionLogger( + enabled_providers=["bedrock"], + search_tool_name="blocked-search", + ) + router = MagicMock() + router.search_tools = [ + { + "search_tool_name": "blocked-search", + "litellm_params": { + "search_provider": "tavily", + "api_key": "fake-ui-key", + }, + } + ] + mock_asearch = AsyncMock(return_value=SearchResponse(object="search", results=[])) + user_api_key_auth = UserAPIKeyAuth( + object_permission=LiteLLM_ObjectPermissionTable( + object_permission_id="op-key-search", + search_tools=["allowed-search"], + ) + ) + + monkeypatch.setattr(proxy_server, "llm_router", router) + monkeypatch.setattr(litellm, "asearch", mock_asearch) + + with pytest.raises(ProxyException): + await logger._execute_search( + "what is litellm", + kwargs={"metadata": {"user_api_key_auth": user_api_key_auth}}, + ) + + mock_asearch.assert_not_awaited() + + +@pytest.mark.asyncio +async def test_execute_search_enforces_team_search_tool_permission(monkeypatch): + import litellm + from litellm.proxy import proxy_server + + logger = WebSearchInterceptionLogger( + enabled_providers=["bedrock"], + search_tool_name="blocked-search", + ) + router = MagicMock() + router.search_tools = [ + { + "search_tool_name": "blocked-search", + "litellm_params": { + "search_provider": "tavily", + "api_key": "fake-ui-key", + }, + } + ] + mock_asearch = AsyncMock(return_value=SearchResponse(object="search", results=[])) + team_key_auth = UserAPIKeyAuth(team_id="team-1") + team_object = LiteLLM_TeamTable( + team_id="team-1", + object_permission=LiteLLM_ObjectPermissionTable( + object_permission_id="op-team-search", + search_tools=["allowed-search"], + ), + ) + mock_get_team_object = AsyncMock(return_value=team_object) + + monkeypatch.setattr(proxy_server, "llm_router", router) + monkeypatch.setattr(litellm, "asearch", mock_asearch) + monkeypatch.setattr("litellm.proxy.auth.auth_checks.get_team_object", mock_get_team_object) + + with pytest.raises(ProxyException): + await logger._execute_search( + "what is litellm", + kwargs={"metadata": {"user_api_key_auth": team_key_auth}}, + ) + + mock_get_team_object.assert_awaited_once() + mock_asearch.assert_not_awaited() + + @pytest.mark.asyncio async def test_async_pre_call_deployment_hook_provider_from_top_level_kwargs(): """Test that async_pre_call_deployment_hook finds custom_llm_provider at top-level kwargs. @@ -216,15 +342,12 @@ async def test_async_pre_call_deployment_hook_provider_from_top_level_kwargs(): assert result is not None # The web_search tool should be converted to litellm_web_search (OpenAI format) assert any( - t.get("type") == "function" - and t.get("function", {}).get("name") == "litellm_web_search" + t.get("type") == "function" and t.get("function", {}).get("name") == "litellm_web_search" for t in result["tools"] ) # The non-web-search tool should be preserved assert any( - t.get("type") == "function" - and t.get("function", {}).get("name") == "other_tool" - for t in result["tools"] + t.get("type") == "function" and t.get("function", {}).get("name") == "other_tool" for t in result["tools"] ) @@ -261,8 +384,7 @@ async def test_async_pre_call_deployment_hook_returns_full_kwargs(): assert result["custom_llm_provider"] == "openai" # Tools should be converted assert any( - t.get("type") == "function" - and t.get("function", {}).get("name") == "litellm_web_search" + t.get("type") == "function" and t.get("function", {}).get("name") == "litellm_web_search" for t in result["tools"] ) @@ -323,8 +445,7 @@ async def test_async_pre_call_deployment_hook_nested_litellm_params_fallback(): assert result is not None assert any( - t.get("type") == "function" - and t.get("function", {}).get("name") == "litellm_web_search" + t.get("type") == "function" and t.get("function", {}).get("name") == "litellm_web_search" for t in result["tools"] ) # Full kwargs preserved @@ -357,8 +478,7 @@ async def test_async_pre_call_deployment_hook_provider_derived_from_model_name() # Should NOT be None — the hook should derive "openai" from "openai/gpt-4o-mini" assert result is not None assert any( - t.get("type") == "function" - and t.get("function", {}).get("name") == "litellm_web_search" + t.get("type") == "function" and t.get("function", {}).get("name") == "litellm_web_search" for t in result["tools"] ) # Full kwargs preserved @@ -478,9 +598,7 @@ def test_sync_forced_tool_choice_leaves_non_forced_untouched(tool_choice): and None pass through unchanged.""" converted_tools = [{"name": LITELLM_WEB_SEARCH_TOOL_NAME}] - result = WebSearchInterceptionLogger._sync_forced_tool_choice( - tool_choice, converted_tools - ) + result = WebSearchInterceptionLogger._sync_forced_tool_choice(tool_choice, converted_tools) assert result == tool_choice diff --git a/tests/test_litellm/interactions/test_interactions_streaming_iterator.py b/tests/test_litellm/interactions/test_interactions_streaming_iterator.py new file mode 100644 index 00000000000..9f88b2c9611 --- /dev/null +++ b/tests/test_litellm/interactions/test_interactions_streaming_iterator.py @@ -0,0 +1,97 @@ +""" +Regression test for LIT-4210: completing an async Interactions API stream must +not run the sync success_handler on the thread-pool executor concurrently with +async_success_handler (cross-thread pydantic mutation segfaults pydantic-core). +""" + +import asyncio +import time + +import httpx +import pytest + +import litellm +from litellm.integrations.custom_logger import CustomLogger +from litellm.interactions import streaming_iterator as interactions_streaming_iterator_module +from litellm.interactions.streaming_iterator import InteractionsAPIStreamingIterator +from litellm.litellm_core_utils import thread_pool_executor as thread_pool_executor_module +from litellm.litellm_core_utils.litellm_logging import Logging as LitellmLogging +from litellm.types.interactions import InteractionsAPIStreamingResponse + + +class RecordingCustomLogger(CustomLogger): + def __init__(self): + super().__init__() + self.async_hook_fired = False + + async def async_log_success_event(self, kwargs, response_obj, start_time, end_time): + self.async_hook_fired = True + + async def async_log_stream_event(self, kwargs, response_obj, start_time, end_time): + self.async_hook_fired = True + + +class RecordingExecutor: + def __init__(self, inner): + self._inner = inner + self.submits: list = [] + + def submit(self, fn, *args, **kwargs): + self.submits.append(fn) + return self._inner.submit(fn, *args, **kwargs) + + def submitted_for(self, logging_obj) -> list: + return [fn for fn in self.submits if getattr(fn, "__self__", None) is logging_obj] + + +@pytest.fixture(autouse=True) +def _isolate_callbacks(): + saved = ( + litellm.callbacks, + litellm.success_callback, + litellm._async_success_callback, + litellm.failure_callback, + litellm._async_failure_callback, + ) + yield + ( + litellm.callbacks, + litellm.success_callback, + litellm._async_success_callback, + litellm.failure_callback, + litellm._async_failure_callback, + ) = saved + + +@pytest.mark.asyncio +async def test_custom_logger_only_never_submits_sync_success_handler(monkeypatch): + recording_executor = RecordingExecutor(thread_pool_executor_module.executor) + monkeypatch.setattr(thread_pool_executor_module, "executor", recording_executor) + monkeypatch.setattr(interactions_streaming_iterator_module, "executor", recording_executor) + + recorder = RecordingCustomLogger() + litellm.success_callback = [recorder] + litellm._async_success_callback = [recorder] + + logging_obj = LitellmLogging( + model="gemini/gemini-3-pro-preview", + messages=[{"role": "user", "content": "hi"}], + stream=True, + call_type="ainteraction", + start_time=time.time(), + litellm_call_id="lit-4210-test", + function_id="lit-4210-test", + ) + iterator = InteractionsAPIStreamingIterator( + response=httpx.Response(200), + model="gemini/gemini-3-pro-preview", + interactions_api_config=None, + logging_obj=logging_obj, + ) + iterator.completed_response = InteractionsAPIStreamingResponse() + + iterator._handle_logging_completed_response() + await asyncio.sleep(0.5) + + assert recorder.async_hook_fired is True + assert recording_executor.submitted_for(logging_obj) == [] diff --git a/tests/test_litellm/litellm_core_utils/test_audio_utils.py b/tests/test_litellm/litellm_core_utils/test_audio_utils.py index b2645c8f2ce..0e8176fffce 100644 --- a/tests/test_litellm/litellm_core_utils/test_audio_utils.py +++ b/tests/test_litellm/litellm_core_utils/test_audio_utils.py @@ -326,3 +326,24 @@ class TestGetAudioFileContentHash: assert isinstance(hash_result, str) assert len(hash_result) == 64, "Should return valid hash even on fallback" + + +class TestNormalizeTranscriptionLanguageToBcp47: + @pytest.mark.parametrize( + "language,expected", + [ + ("en", "en-US"), + ("EN", "en-US"), + ("ja", "ja-JP"), + ("en-US", "en-US"), + ("en-GB", "en-GB"), + ("auto", "auto"), + ("xx", "xx"), + ], + ) + def test_normalization(self, language, expected): + from litellm.litellm_core_utils.audio_utils.utils import ( + normalize_transcription_language_to_bcp47, + ) + + assert normalize_transcription_language_to_bcp47(language) == expected diff --git a/tests/test_litellm/litellm_core_utils/test_exception_mapping_utils.py b/tests/test_litellm/litellm_core_utils/test_exception_mapping_utils.py index 53960847fdc..f441270be7c 100644 --- a/tests/test_litellm/litellm_core_utils/test_exception_mapping_utils.py +++ b/tests/test_litellm/litellm_core_utils/test_exception_mapping_utils.py @@ -626,3 +626,45 @@ def test_replicate_422_maps_to_unprocessable_entity(): ) assert excinfo.value.llm_provider == "replicate" + + +def test_upstream_4xx_without_model_maps_to_bad_request(): + """Responses API follow-ups (cancel/get/delete) call ``exception_type`` with + ``model=None``; the provider mapping used to be gated on ``if model:``, so an + upstream 400 like Azure's "Cannot cancel a synchronous response." fell through to + the generic 500 APIConnectionError instead of surfacing as a 400.""" + from litellm.llms.base_llm.chat.transformation import BaseLLMException + + original_exception = BaseLLMException( + status_code=400, + message='{"error": {"message": "Cannot cancel a synchronous response.", "type": "invalid_request_error"}}', + ) + + with pytest.raises(litellm.BadRequestError) as excinfo: + exception_type( + model=None, + original_exception=original_exception, + custom_llm_provider="azure", + ) + + assert excinfo.value.status_code == 400 + assert "Cannot cancel a synchronous response." in excinfo.value.message + + +def test_azure_404_with_invalid_request_error_type_maps_to_not_found(): + from litellm.llms.base_llm.chat.transformation import BaseLLMException + + original_exception = BaseLLMException( + status_code=404, + message='{"error": {"message": "Response with id \'resp_abc\' not found.", "type": "invalid_request_error"}}', + ) + + with pytest.raises(litellm.NotFoundError) as excinfo: + exception_type( + model=None, + original_exception=original_exception, + custom_llm_provider="azure", + ) + + assert excinfo.value.status_code == 404 + assert "Response with id 'resp_abc' not found." in excinfo.value.message diff --git a/tests/test_litellm/litellm_core_utils/test_litellm_logging.py b/tests/test_litellm/litellm_core_utils/test_litellm_logging.py index 41560c18d15..0523ed7ecb1 100644 --- a/tests/test_litellm/litellm_core_utils/test_litellm_logging.py +++ b/tests/test_litellm/litellm_core_utils/test_litellm_logging.py @@ -704,7 +704,9 @@ async def test_logging_non_streaming_request(): litellm.callbacks = original_callbacks -@pytest.mark.parametrize("async_flag", ["acompletion", "aresponses"]) +@pytest.mark.parametrize( + "async_flag", ["acompletion", "aresponses", "allm_passthrough_route"] +) def test_success_handler_skips_sync_callbacks_for_async_requests( logging_obj, async_flag ): @@ -792,6 +794,21 @@ def test_success_handler_runs_sync_callbacks_for_sync_requests(logging_obj, call def test_is_sync_litellm_request(): assert LitellmLogging._is_sync_litellm_request({}) is True assert LitellmLogging._is_sync_litellm_request({"acompletion": True}) is False + assert ( + LitellmLogging._is_sync_litellm_request({"allm_passthrough_route": True}) + is False + ) + + +def test_get_litellm_params_propagates_allm_passthrough_route(): + """`allm_passthrough_route=True` set on kwargs by the async passthrough entrypoint + must land in `litellm_params` so `_is_sync_litellm_request` sees it and the + request is classified as async. Regression guard for LIT-4192.""" + from litellm.litellm_core_utils.get_litellm_params import get_litellm_params + + params = get_litellm_params(allm_passthrough_route=True) + assert params.get("allm_passthrough_route") is True + assert LitellmLogging._is_sync_litellm_request(params) is False @pytest.mark.asyncio diff --git a/tests/test_litellm/litellm_core_utils/test_realtime_streaming.py b/tests/test_litellm/litellm_core_utils/test_realtime_streaming.py index 766befd1a99..dff54515098 100644 --- a/tests/test_litellm/litellm_core_utils/test_realtime_streaming.py +++ b/tests/test_litellm/litellm_core_utils/test_realtime_streaming.py @@ -2961,10 +2961,13 @@ async def test_log_messages_routes_async_logging_through_bounded_worker(): with ( patch("litellm.litellm_core_utils.realtime_streaming.GLOBAL_LOGGING_WORKER") as mock_worker, patch("litellm.litellm_core_utils.realtime_streaming.asyncio.create_task") as mock_create_task, - patch("litellm.litellm_core_utils.realtime_streaming.executor.submit"), ): await streaming.log_messages() mock_worker.ensure_initialized_and_enqueue.assert_called_once() + enqueued = mock_worker.ensure_initialized_and_enqueue.call_args + assert (enqueued.args or tuple(enqueued.kwargs.values()))[0] is logging_obj.dispatch_success_handlers.return_value + logging_obj.dispatch_success_handlers.assert_called_once_with(streaming.messages, prefer_async_handlers=True) + logging_obj.success_handler.assert_not_called() # the bare create_task path must no longer be used for success logging mock_create_task.assert_not_called() diff --git a/tests/test_litellm/litellm_core_utils/test_streaming_handler.py b/tests/test_litellm/litellm_core_utils/test_streaming_handler.py index 81af0ad3e6f..e430ce3b084 100644 --- a/tests/test_litellm/litellm_core_utils/test_streaming_handler.py +++ b/tests/test_litellm/litellm_core_utils/test_streaming_handler.py @@ -986,6 +986,86 @@ async def test_bedrock_validation_error_raises_directly(logging_obj: Logging): assert getattr(excinfo.value, "status_code", None) == 400 +def _hosted_vllm_stream_wrapper(logging_obj: Logging, error_payload: dict) -> CustomStreamWrapper: + """A CustomStreamWrapper over the real OpenAI-compatible line iterator, + fed an HTTP 200 SSE body that carries an in-body error payload the way + vLLM/sglang emit it.""" + from litellm.llms.openai.chat.gpt_transformation import ( + OpenAIChatCompletionStreamingHandler, + ) + + async def _stream(): + yield f"data: {json.dumps(error_payload)}" + yield "data: [DONE]" + + completion_stream = OpenAIChatCompletionStreamingHandler( + streaming_response=_stream(), sync_stream=False + ) + return CustomStreamWrapper( + completion_stream=completion_stream, + model="qwen-vl", + logging_obj=logging_obj, + custom_llm_provider="hosted_vllm", + ) + + +@pytest.mark.asyncio +async def test_in_body_stream_error_400_raises_bad_request(logging_obj: Logging): + """Regression for https://github.com/BerriAI/litellm/issues/25492: a 400 + error returned inside a 200 SSE body must surface as BadRequestError with + the provider's message, not be parsed as an empty chunk that silently + ends the stream (and never as an internal MidStreamFallbackError).""" + from litellm.exceptions import MidStreamFallbackError + + response = _hosted_vllm_stream_wrapper( + logging_obj, + { + "error": { + "object": "error", + "message": "The model is not multimodal. Please remove image inputs.", + "type": "BadRequestError", + "param": None, + "code": 400, + } + }, + ) + + with pytest.raises(litellm.BadRequestError) as excinfo: + await response.__anext__() + + assert not isinstance(excinfo.value, MidStreamFallbackError) + assert excinfo.value.status_code == 400 + assert "not multimodal" in str(excinfo.value) + + +@pytest.mark.asyncio +async def test_in_body_stream_error_500_wraps_for_midstream_fallback( + logging_obj: Logging, +): + """An in-body 5xx error wraps into MidStreamFallbackError so the Router's + FallbackStreamWrapper can switch to a configured fallback deployment.""" + from litellm.exceptions import MidStreamFallbackError + + response = _hosted_vllm_stream_wrapper( + logging_obj, + { + "error": { + "object": "error", + "message": "internal engine crash", + "type": "InternalServerError", + "param": None, + "code": 500, + } + }, + ) + + with pytest.raises(MidStreamFallbackError) as excinfo: + await response.__anext__() + + assert excinfo.value.is_pre_first_chunk is True + assert "internal engine crash" in str(excinfo.value) + + @pytest.mark.asyncio async def test_async_streaming_read_timeout_triggers_midstream_fallback( logging_obj: Logging, diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_streaming_iterator.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_streaming_iterator.py new file mode 100644 index 00000000000..6ea9098c228 --- /dev/null +++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_streaming_iterator.py @@ -0,0 +1,245 @@ +import json +import os +import sys +from datetime import datetime + +import pytest + +sys.path.insert(0, os.path.abspath("../../../../..")) + +from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj +from litellm.llms.anthropic.experimental_pass_through.messages.streaming_iterator import ( + INCOMPLETE_STREAM_ERROR_MESSAGE, + BaseAnthropicMessagesStreamingIterator, + _incomplete_stream_error_sse_event, + _is_message_stop_chunk, +) + + +class _RecordingLoggingIterator(BaseAnthropicMessagesStreamingIterator): + def __init__(self, litellm_logging_obj: LiteLLMLoggingObj, request_body: dict): + super().__init__(litellm_logging_obj=litellm_logging_obj, request_body=request_body) + self.logged_chunks: list = [] + + async def _handle_streaming_logging(self, collected_chunks): + self.logged_chunks = list(collected_chunks) + + +def _make_logging_obj(test_name: str) -> LiteLLMLoggingObj: + return LiteLLMLoggingObj( + model="bedrock/invoke/anthropic.claude-3-sonnet-20240229-v1:0", + messages=[{"role": "user", "content": "hi"}], + stream=True, + call_type="chat", + start_time=datetime.now(), + litellm_call_id=test_name, + function_id=test_name, + ) + + +def _make_iterator(test_name: str) -> BaseAnthropicMessagesStreamingIterator: + return BaseAnthropicMessagesStreamingIterator( + litellm_logging_obj=_make_logging_obj(test_name), + request_body={}, + ) + + +async def _collect(iterator, stream): + return [chunk async for chunk in iterator.async_sse_wrapper(stream)] + + +TRUNCATED_TOOL_USE_EVENTS = ( + {"type": "message_start", "message": {"id": "msg_1", "usage": {"input_tokens": 10, "output_tokens": 1}}}, + { + "type": "content_block_start", + "index": 0, + "content_block": {"type": "tool_use", "id": "tooluse_1", "name": "write", "input": {}}, + }, + { + "type": "content_block_delta", + "index": 0, + "delta": {"type": "input_json_delta", "partial_json": '{"path": "/builder/docs/QUAL'}, + }, +) + + +@pytest.mark.asyncio +async def test_async_sse_wrapper_emits_error_event_when_stream_ends_without_message_stop(): + """ + Regression test for LIT-3724: a Bedrock stream that goes silent + mid tool_use must not be passed through as a successful, complete + SSE stream. An `error` SSE event must be appended so strict clients + (Anthropic SDK, Claude Code) surface the truncation instead of + crashing on unterminated tool-call JSON. + """ + + async def _truncated_stream(): + for event in TRUNCATED_TOOL_USE_EVENTS: + yield event + + iterator = _make_iterator("test_truncated_stream_emits_error") + chunks = await _collect(iterator, _truncated_stream()) + + assert len(chunks) == len(TRUNCATED_TOOL_USE_EVENTS) + 1 + error_chunk = chunks[-1].decode() + assert error_chunk.startswith("event: error\n") + assert error_chunk.endswith("\n\n") + + error_payload = json.loads(error_chunk.split("data: ", 1)[1]) + assert error_payload["type"] == "error" + assert error_payload["error"]["type"] == "api_error" + assert error_payload["error"]["message"] == INCOMPLETE_STREAM_ERROR_MESSAGE + + +@pytest.mark.asyncio +async def test_async_sse_wrapper_no_error_event_on_complete_stream(): + async def _complete_stream(): + for event in TRUNCATED_TOOL_USE_EVENTS: + yield event + yield {"type": "content_block_stop", "index": 0} + yield {"type": "message_delta", "delta": {"stop_reason": "tool_use"}, "usage": {"output_tokens": 5}} + yield {"type": "message_stop"} + + iterator = _make_iterator("test_complete_stream_no_error") + chunks = await _collect(iterator, _complete_stream()) + + assert len(chunks) == len(TRUNCATED_TOOL_USE_EVENTS) + 3 + decoded = [chunk.decode() for chunk in chunks] + assert decoded[-1].startswith("event: message_stop\n") + assert not any(chunk.startswith("event: error\n") for chunk in decoded) + + +@pytest.mark.asyncio +async def test_async_sse_wrapper_emits_error_event_on_empty_stream(): + async def _empty_stream(): + return + yield + + iterator = _make_iterator("test_empty_stream_emits_error") + chunks = await _collect(iterator, _empty_stream()) + + assert len(chunks) == 1 + assert chunks[0].decode().startswith("event: error\n") + + +@pytest.mark.asyncio +async def test_async_sse_wrapper_treats_message_stop_bytes_as_complete(): + async def _byte_stream(): + yield b'event: message_start\ndata: {"type": "message_start"}\n\n' + yield b'event: message_stop\ndata: {"type": "message_stop"}\n\n' + + iterator = _make_iterator("test_byte_stream_message_stop") + chunks = await _collect(iterator, _byte_stream()) + + assert len(chunks) == 2 + assert not any(chunk.startswith(b"event: error\n") for chunk in chunks) + + +def test_is_message_stop_chunk(): + assert _is_message_stop_chunk({"type": "message_stop"}) is True + assert _is_message_stop_chunk({"type": "message_delta"}) is False + assert _is_message_stop_chunk(b'event: message_stop\ndata: {}\n\n') is True + assert _is_message_stop_chunk(b"raw-bytes") is False + assert _is_message_stop_chunk("message_stop") is False + + +def test_is_message_stop_chunk_ignores_substring_in_payload(): + """ + Regression: a `content_block_delta` frame whose payload happens to contain + the literal string `message_stop` (e.g. inside a tool's partial_json) must + not be treated as a terminal stop event. + """ + delta_frame_with_substring = ( + b'event: content_block_delta\n' + b'data: {"type": "content_block_delta", "delta": ' + b'{"type": "input_json_delta", "partial_json": "\\"message_stop\\""}}\n\n' + ) + assert _is_message_stop_chunk(delta_frame_with_substring) is False + + +@pytest.mark.asyncio +async def test_async_sse_wrapper_emits_error_when_bytes_stream_only_mentions_message_stop_in_payload(): + """ + Regression for the bytes-branch substring false positive: a stream whose + payload text contains `message_stop` (but never emits the actual + `event: message_stop` frame) must still be flagged as incomplete. + """ + async def _byte_stream(): + yield b'event: message_start\ndata: {"type": "message_start"}\n\n' + yield ( + b'event: content_block_delta\n' + b'data: {"type": "content_block_delta", "delta": ' + b'{"type": "input_json_delta", "partial_json": "\\"message_stop\\""}}\n\n' + ) + + iterator = _make_iterator("test_bytes_substring_does_not_mark_complete") + chunks = await _collect(iterator, _byte_stream()) + + assert len(chunks) == 3 + assert chunks[-1].decode().startswith("event: error\n") + + +@pytest.mark.asyncio +async def test_async_sse_wrapper_does_not_double_error_on_provider_error_dict(): + """ + Regression: when the provider itself terminates the stream with an + `error` event (without a `message_stop`), the wrapper must forward that + error and not append a second synthetic incomplete-stream error. + """ + provider_error = {"type": "error", "error": {"type": "overloaded_error", "message": "boom"}} + + async def _error_terminated_stream(): + yield {"type": "message_start", "message": {"id": "msg_1"}} + yield provider_error + + iterator = _make_iterator("test_provider_error_terminal_dict") + chunks = await _collect(iterator, _error_terminated_stream()) + + assert len(chunks) == 2 + error_frames = [c for c in chunks if c.startswith(b"event: error\n")] + assert len(error_frames) == 1 + payload = json.loads(error_frames[0].decode().split("data: ", 1)[1]) + assert payload == provider_error + + +@pytest.mark.asyncio +async def test_async_sse_wrapper_does_not_double_error_on_provider_error_bytes(): + async def _byte_stream(): + yield b'event: message_start\ndata: {"type": "message_start"}\n\n' + yield b'event: error\ndata: {"type": "error", "error": {"type": "overloaded_error"}}\n\n' + + iterator = _make_iterator("test_provider_error_terminal_bytes") + chunks = await _collect(iterator, _byte_stream()) + + assert len(chunks) == 2 + error_frames = [c for c in chunks if c.startswith(b"event: error\n")] + assert len(error_frames) == 1 + + +@pytest.mark.asyncio +async def test_async_sse_wrapper_excludes_synthetic_error_event_from_logged_chunks(): + async def _truncated_stream(): + for event in TRUNCATED_TOOL_USE_EVENTS: + yield event + + iterator = _RecordingLoggingIterator( + litellm_logging_obj=_make_logging_obj("test_synthetic_error_not_logged"), + request_body={}, + ) + chunks = await _collect(iterator, _truncated_stream()) + + assert chunks[-1].startswith(b"event: error\n") + assert iterator.logged_chunks == chunks[:-1] + assert not any(chunk.startswith(b"event: error\n") for chunk in iterator.logged_chunks) + + +def test_incomplete_stream_error_sse_event_is_valid_anthropic_error(): + event = _incomplete_stream_error_sse_event().decode() + lines = event.split("\n") + assert lines[0] == "event: error" + payload = json.loads(lines[1].removeprefix("data: ")) + assert payload == { + "type": "error", + "error": {"type": "api_error", "message": INCOMPLETE_STREAM_ERROR_MESSAGE}, + } + assert event.endswith("\n\n") diff --git a/tests/test_litellm/llms/anthropic/files/test_anthropic_files_transformation.py b/tests/test_litellm/llms/anthropic/files/test_anthropic_files_transformation.py index 9fc4981510f..d9763f173a7 100644 --- a/tests/test_litellm/llms/anthropic/files/test_anthropic_files_transformation.py +++ b/tests/test_litellm/llms/anthropic/files/test_anthropic_files_transformation.py @@ -309,6 +309,31 @@ class TestAnthropicFilesConfig: assert url == f"{ANTHROPIC_FILES_API_BASE}/v1/files/file-abc123/content" assert params == {} + def test_transform_file_content_request_routes_message_batch_id_to_batch_results(self): + """ + Regression test for anthropic passthrough batch cost tracking (LIT-4008). + + Anthropic batch results are exposed via output_file_id=. + The Files API rejects those ids ("File id must have `file_` prefix"), + so file content for a msgbatch_ id must be fetched from the message + batches results endpoint instead. + """ + url, params = self.config.transform_file_content_request( + file_content_request={"file_id": "msgbatch_01WA5hdsa2Xx8w4zyPjV1frs"}, + optional_params={}, + litellm_params={}, + ) + assert url == f"{ANTHROPIC_FILES_API_BASE}/v1/messages/batches/msgbatch_01WA5hdsa2Xx8w4zyPjV1frs/results" + assert params == {} + + def test_transform_file_content_request_message_batch_id_custom_api_base(self): + url, _ = self.config.transform_file_content_request( + file_content_request={"file_id": "msgbatch_abc"}, + optional_params={}, + litellm_params={"api_base": "https://custom.example.com/"}, + ) + assert url == "https://custom.example.com/v1/messages/batches/msgbatch_abc/results" + def test_transform_file_content_request_rejects_dot_segment(self): with pytest.raises(ValueError, match="file_id cannot be a dot path segment"): self.config.transform_file_content_request( diff --git a/tests/test_litellm/llms/azure/response/test_azure_transformation.py b/tests/test_litellm/llms/azure/response/test_azure_transformation.py index a4bd14d69ff..24ae563fb76 100644 --- a/tests/test_litellm/llms/azure/response/test_azure_transformation.py +++ b/tests/test_litellm/llms/azure/response/test_azure_transformation.py @@ -337,6 +337,24 @@ class TestAzureResponsesAPIConfig: assert url == expected_url assert data == {} + def test_azure_list_input_items_request_url_path_before_query(self): + from litellm.types.router import GenericLiteLLMParams + + api_base = "https://test.openai.azure.com/openai/responses?api-version=2025-03-01-preview" + + url, params = self.config.transform_list_input_items_request( + response_id="resp_test123", + api_base=api_base, + litellm_params=GenericLiteLLMParams(api_version="2025-03-01-preview"), + headers={}, + ) + + assert ( + url + == "https://test.openai.azure.com/openai/responses/resp_test123/input_items?api-version=2025-03-01-preview" + ) + assert params == {"limit": 20, "order": "desc"} + def test_azure_cancel_response_api_response(self): """Test Azure cancel response API response transformation""" from unittest.mock import Mock diff --git a/tests/test_litellm/llms/bedrock/messages/invoke_transformations/test_anthropic_claude3_transformation.py b/tests/test_litellm/llms/bedrock/messages/invoke_transformations/test_anthropic_claude3_transformation.py index 03d0d87a58c..d7a62aae38b 100644 --- a/tests/test_litellm/llms/bedrock/messages/invoke_transformations/test_anthropic_claude3_transformation.py +++ b/tests/test_litellm/llms/bedrock/messages/invoke_transformations/test_anthropic_claude3_transformation.py @@ -90,6 +90,87 @@ async def test_bedrock_sse_wrapper_encodes_dict_chunks(): assert collected[1] == b"raw-bytes" +@pytest.mark.asyncio +async def test_bedrock_sse_wrapper_appends_error_event_when_stream_truncates_mid_tool_use(): + """ + Regression test for LIT-3724: Bedrock invoke streams that go silent + mid tool_use (no content_block_stop / message_delta / message_stop) + used to be closed as a successful SSE stream, handing clients + unterminated tool-call JSON with HTTP 200. The stream must now end + with an Anthropic-protocol `error` SSE event. + """ + cfg = AmazonAnthropicClaudeMessagesConfig() + + async def _truncated_stream(): + yield {"type": "message_start", "message": {"id": "msg_1", "usage": {"input_tokens": 3, "output_tokens": 1}}} + yield { + "type": "content_block_start", + "index": 0, + "content_block": {"type": "tool_use", "id": "tooluse_1", "name": "write", "input": {}}, + } + yield { + "type": "content_block_delta", + "index": 0, + "delta": {"type": "input_json_delta", "partial_json": '{"path": "/builder/docs/QUAL'}, + } + + collected: list[bytes] = [] + async for chunk in cfg.bedrock_sse_wrapper( + _truncated_stream(), + litellm_logging_obj=LiteLLMLoggingObj( + model="bedrock/invoke/anthropic.claude-3-sonnet-20240229-v1:0", + messages=[{"role": "user", "content": "write the file"}], + stream=True, + call_type="chat", + start_time=datetime.now(), + litellm_call_id="test_bedrock_sse_wrapper_truncated_tool_use", + function_id="test_bedrock_sse_wrapper_truncated_tool_use", + ), + request_body={}, + ): + collected.append(chunk) + + assert len(collected) == 4 + error_event = collected[-1].decode() + assert error_event.startswith("event: error\n") + error_payload = json.loads(error_event.split("data: ", 1)[1]) + assert error_payload["type"] == "error" + assert error_payload["error"]["type"] == "api_error" + + +@pytest.mark.asyncio +async def test_bedrock_sse_wrapper_no_error_event_when_stream_ends_with_message_stop(): + cfg = AmazonAnthropicClaudeMessagesConfig() + + async def _complete_stream(): + yield {"type": "message_start", "message": {"id": "msg_1", "usage": {"input_tokens": 3, "output_tokens": 1}}} + yield {"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}} + yield {"type": "content_block_delta", "index": 0, "delta": {"type": "text_delta", "text": "hi"}} + yield {"type": "content_block_stop", "index": 0} + yield {"type": "message_delta", "delta": {"stop_reason": "end_turn"}, "usage": {"output_tokens": 2}} + yield {"type": "message_stop"} + + collected: list[bytes] = [] + async for chunk in cfg.bedrock_sse_wrapper( + _complete_stream(), + litellm_logging_obj=LiteLLMLoggingObj( + model="bedrock/invoke/anthropic.claude-3-sonnet-20240229-v1:0", + messages=[{"role": "user", "content": "hi"}], + stream=True, + call_type="chat", + start_time=datetime.now(), + litellm_call_id="test_bedrock_sse_wrapper_complete_stream", + function_id="test_bedrock_sse_wrapper_complete_stream", + ), + request_body={}, + ): + collected.append(chunk) + + assert len(collected) == 6 + assert collected[-1].startswith(b"event: message_stop\n") + assert not any(chunk.startswith(b"event: error\n") for chunk in collected) + + @pytest.mark.asyncio async def test_bedrock_sse_wrapper_keeps_usage_in_message_start_and_message_delta(): """Regression test: usage should be available on both message_start and message_delta SSE events.""" diff --git a/tests/test_litellm/llms/bedrock/realtime/test_bedrock_realtime_handler.py b/tests/test_litellm/llms/bedrock/realtime/test_bedrock_realtime_handler.py index 18e48169bc1..ddc2e026e83 100644 --- a/tests/test_litellm/llms/bedrock/realtime/test_bedrock_realtime_handler.py +++ b/tests/test_litellm/llms/bedrock/realtime/test_bedrock_realtime_handler.py @@ -2,12 +2,14 @@ import json import os import sys import types +from types import SimpleNamespace from unittest.mock import MagicMock import pytest sys.path.insert(0, os.path.abspath("../../../../..")) # Adds the parent directory to the system path +from litellm.llms.bedrock.common_utils import BedrockError from litellm.llms.bedrock.realtime.handler import BedrockRealtime from litellm.llms.bedrock.realtime.transformation import BedrockRealtimeConfig @@ -80,6 +82,111 @@ class EndedBedrockStream: return (None, EndedBedrockReceiver()) +class RealtimeClientWS: + def __init__(self): + self.closed = False + + async def receive_text(self): + raise RuntimeError("client disconnected") + + async def close(self, code=None, reason=None): + self.closed = True + + +class ImmediatelyEndingBedrockStream: + def __init__(self): + self.input_stream = FakeInputStream() + + async def await_output(self): + return (None, EndedBedrockReceiver()) + + +class FakeStaticCredentialsResolver: + pass + + +class NoCredentialsBedrockRealtime(BedrockRealtime): + def get_credentials(self, **kwargs): + return None + + +class StubCredentialsBedrockRealtime(BedrockRealtime): + def __init__(self, frozen_credentials): + super().__init__() + self.frozen_credentials = frozen_credentials + self.get_credentials_kwargs = None + + def get_credentials(self, **kwargs): + self.get_credentials_kwargs = kwargs + return SimpleNamespace(get_frozen_credentials=lambda: self.frozen_credentials) + + +@pytest.fixture +def stub_aws_sdk_client(monkeypatch): + captured = {} + + class CapturingConfig: + def __init__(self, **kwargs): + captured["config_kwargs"] = kwargs + self.kwargs = kwargs + + class FakeOperationInput: + def __init__(self, model_id): + self.model_id = model_id + + class FakeBedrockRuntimeClient: + def __init__(self, config): + captured["client_config"] = config + + async def invoke_model_with_bidirectional_stream(self, operation_input): + captured["operation_input"] = operation_input + return ImmediatelyEndingBedrockStream() + + package = types.ModuleType("aws_sdk_bedrock_runtime") + client_module = types.ModuleType("aws_sdk_bedrock_runtime.client") + client_module.BedrockRuntimeClient = FakeBedrockRuntimeClient + client_module.InvokeModelWithBidirectionalStreamOperationInput = FakeOperationInput + config_module = types.ModuleType("aws_sdk_bedrock_runtime.config") + config_module.Config = CapturingConfig + models_module = types.ModuleType("aws_sdk_bedrock_runtime.models") + models_module.BidirectionalInputPayloadPart = FakePayloadPart + models_module.InvokeModelWithBidirectionalStreamInputChunk = FakeInputChunk + package.client = client_module + package.config = config_module + package.models = models_module + smithy_package = types.ModuleType("smithy_aws_core") + identity_module = types.ModuleType("smithy_aws_core.identity") + identity_module.StaticCredentialsResolver = FakeStaticCredentialsResolver + smithy_package.identity = identity_module + + stubbed_modules = { + "aws_sdk_bedrock_runtime": package, + "aws_sdk_bedrock_runtime.client": client_module, + "aws_sdk_bedrock_runtime.config": config_module, + "aws_sdk_bedrock_runtime.models": models_module, + "smithy_aws_core": smithy_package, + "smithy_aws_core.identity": identity_module, + } + for module_name, module in stubbed_modules.items(): + monkeypatch.setitem(sys.modules, module_name, module) + + for env_var in ( + "AWS_ACCESS_KEY_ID", + "AWS_SECRET_ACCESS_KEY", + "AWS_SESSION_TOKEN", + "AWS_REGION_NAME", + "AWS_SESSION_NAME", + "AWS_PROFILE_NAME", + "AWS_ROLE_NAME", + "AWS_WEB_IDENTITY_TOKEN", + "AWS_STS_ENDPOINT", + "AWS_EXTERNAL_ID", + ): + monkeypatch.delenv(env_var, raising=False) + + return captured + + @pytest.fixture def stub_aws_models(monkeypatch): package = types.ModuleType("aws_sdk_bedrock_runtime") @@ -170,5 +277,86 @@ class TestBedrockRealtimeHandler: assert client_ws.closed +class TestBedrockRealtimeAwsAuth: + """AWS auth params passed via litellm_params must reach the Smithy client config (LIT-3923 regression)""" + + @pytest.mark.asyncio + async def test_static_credentials_from_litellm_params_reach_smithy_config(self, stub_aws_sdk_client): + handler = BedrockRealtime() + websocket = RealtimeClientWS() + + await handler.async_realtime( + model="amazon.nova-sonic-v1:0", + websocket=websocket, + logging_obj=MagicMock(), + aws_region_name="us-east-1", + aws_access_key_id="litellm-params-access-key", + aws_secret_access_key="litellm-params-secret-key", + aws_session_token="litellm-params-session-token", + ) + + config_kwargs = stub_aws_sdk_client["config_kwargs"] + assert config_kwargs["aws_access_key_id"] == "litellm-params-access-key" + assert config_kwargs["aws_secret_access_key"] == "litellm-params-secret-key" + assert config_kwargs["aws_session_token"] == "litellm-params-session-token" + assert isinstance(config_kwargs["aws_credentials_identity_resolver"], FakeStaticCredentialsResolver) + assert config_kwargs["region"] == "us-east-1" + assert stub_aws_sdk_client["client_config"].kwargs is config_kwargs + assert stub_aws_sdk_client["operation_input"].model_id == "amazon.nova-sonic-v1:0" + assert websocket.closed + + @pytest.mark.asyncio + async def test_role_assumption_params_forwarded_to_get_credentials(self, stub_aws_sdk_client): + handler = StubCredentialsBedrockRealtime( + SimpleNamespace( + access_key="assumed-access-key", + secret_key="assumed-secret-key", + token="assumed-session-token", + ) + ) + + await handler.async_realtime( + model="amazon.nova-sonic-v1:0", + websocket=RealtimeClientWS(), + logging_obj=MagicMock(), + aws_region_name="eu-west-1", + aws_role_name="arn:aws:iam::123456789012:role/nova-sonic", + aws_session_name="realtime-session", + aws_external_id="realtime-external-id", + ) + + assert handler.get_credentials_kwargs == { + "aws_access_key_id": None, + "aws_secret_access_key": None, + "aws_session_token": None, + "aws_region_name": "eu-west-1", + "aws_session_name": "realtime-session", + "aws_profile_name": None, + "aws_role_name": "arn:aws:iam::123456789012:role/nova-sonic", + "aws_web_identity_token": None, + "aws_sts_endpoint": None, + "aws_external_id": "realtime-external-id", + } + config_kwargs = stub_aws_sdk_client["config_kwargs"] + assert config_kwargs["aws_access_key_id"] == "assumed-access-key" + assert config_kwargs["aws_secret_access_key"] == "assumed-secret-key" + assert config_kwargs["aws_session_token"] == "assumed-session-token" + assert isinstance(config_kwargs["aws_credentials_identity_resolver"], FakeStaticCredentialsResolver) + + @pytest.mark.asyncio + async def test_unresolvable_credentials_raise_clear_auth_error(self, stub_aws_sdk_client): + handler = NoCredentialsBedrockRealtime() + + with pytest.raises(BedrockError, match="No AWS credentials found for Bedrock realtime"): + await handler.async_realtime( + model="amazon.nova-sonic-v1:0", + websocket=RealtimeClientWS(), + logging_obj=MagicMock(), + aws_region_name="us-east-1", + ) + + assert "config_kwargs" not in stub_aws_sdk_client + + if __name__ == "__main__": pytest.main([__file__, "-v"]) diff --git a/tests/test_litellm/llms/bedrock/test_base_aws_llm.py b/tests/test_litellm/llms/bedrock/test_base_aws_llm.py index 2d5242d510f..470448251c9 100644 --- a/tests/test_litellm/llms/bedrock/test_base_aws_llm.py +++ b/tests/test_litellm/llms/bedrock/test_base_aws_llm.py @@ -11,7 +11,7 @@ sys.path.insert( from datetime import datetime, timedelta, timezone -from typing import Any, Dict +from typing import Any, Dict, Optional from unittest.mock import MagicMock, patch from botocore.awsrequest import AWSPreparedRequest, AWSRequest @@ -2653,3 +2653,184 @@ class TestGetBedrockModelIdArnHandling: """invoke/ prefix stripping still works after the fix.""" model_id = self._call("invoke/anthropic.claude-3-sonnet-20240229-v1:0") assert model_id == "anthropic.claude-3-sonnet-20240229-v1:0" + + +def _recomputed_sigv4_signature(url: str, secret_key: str, authorization: str, headers: Dict[str, Any], body) -> str: + import hashlib + import hmac + from urllib.parse import urlparse + + parsed = urlparse(url) + credential_scope = authorization.split("Credential=")[1].split(",")[0].split("/", 1)[1] + signed_header_names = authorization.split("SignedHeaders=")[1].split(",")[0].split(";") + header_lookup = {name.lower(): str(value) for name, value in headers.items()} + header_lookup["host"] = parsed.netloc + body_bytes = body if isinstance(body, bytes) else str(body).encode() + canonical_request = "\n".join( + [ + "POST", + parsed.path or "/", + "", + "".join(f"{name}:{header_lookup[name]}\n" for name in signed_header_names), + ";".join(signed_header_names), + hashlib.sha256(body_bytes).hexdigest(), + ] + ) + string_to_sign = "\n".join( + [ + "AWS4-HMAC-SHA256", + header_lookup["x-amz-date"], + credential_scope, + hashlib.sha256(canonical_request.encode()).hexdigest(), + ] + ) + key = f"AWS4{secret_key}".encode() + for scope_part in credential_scope.split("/"): + key = hmac.new(key, scope_part.encode(), hashlib.sha256).digest() + return hmac.new(key, string_to_sign.encode(), hashlib.sha256).hexdigest() + + +class TestSignRequestResign: + """Regression: retrying a Bedrock request with headers from a previous SigV4 sign + (e.g. the /v1/messages strip-thinking-and-retry path) must produce a fresh + Authorization / X-Amz-Date for the new body, not inherit the stale ones and 403.""" + + URL = "https://bedrock-runtime.us-east-1.amazonaws.com/model/test-model/invoke" + ACCESS_KEY = "AKIAIOSFODNN7EXAMPLE" + SECRET_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" + + @pytest.fixture(autouse=True) + def _clean_aws_env(self, monkeypatch): + for env_var in ("AWS_BEARER_TOKEN_BEDROCK", "AWS_SESSION_TOKEN", "AWS_PROFILE"): + monkeypatch.delenv(env_var, raising=False) + + def _optional_params(self) -> Dict[str, Any]: + return { + "aws_access_key_id": self.ACCESS_KEY, + "aws_secret_access_key": self.SECRET_KEY, + "aws_region_name": "us-east-1", + } + + def _sign(self, headers: Dict[str, Any], request_data: Dict[str, Any]): + return BaseAWSLLM()._sign_request( + service_name="bedrock", + headers=headers, + optional_params=self._optional_params(), + request_data=request_data, + api_base=self.URL, + ) + + def test_resign_with_previously_signed_headers_replaces_stale_sigv4_headers(self): + original_body = { + "messages": [ + { + "role": "assistant", + "content": [{"type": "thinking", "thinking": "x", "signature": ""}], + } + ] + } + first_headers, _ = self._sign(headers={"Content-Type": "application/json"}, request_data=original_body) + assert first_headers["Authorization"].startswith("AWS4-HMAC-SHA256") + + stale_headers = {**first_headers, "X-Amz-Date": "20200101T000000Z"} + stripped_body = {"messages": [{"role": "user", "content": "hi"}]} + second_headers, second_signed_body = self._sign(headers=stale_headers, request_data=stripped_body) + + assert second_headers["X-Amz-Date"] != "20200101T000000Z" + assert second_headers["Authorization"] != stale_headers["Authorization"] + assert second_headers["Authorization"].split("Signature=")[1] == _recomputed_sigv4_signature( + url=self.URL, + secret_key=self.SECRET_KEY, + authorization=second_headers["Authorization"], + headers=second_headers, + body=second_signed_body, + ) + + def test_forwarded_headers_still_added_back_after_signing(self): + signed_headers, _ = self._sign( + headers={"Content-Type": "application/json", "anthropic-version": "bedrock-2023-05-31"}, + request_data={"messages": []}, + ) + assert signed_headers["anthropic-version"] == "bedrock-2023-05-31" + assert signed_headers["Content-Type"] == "application/json" + + def test_caller_supplied_bearer_authorization_survives_signing(self): + signed_headers, _ = self._sign( + headers={"Content-Type": "application/json", "Authorization": "Bearer caller-token"}, + request_data={"messages": []}, + ) + assert signed_headers["Authorization"] == "Bearer caller-token" + + +class TestGetRequestHeadersResign: + """Regression: get_request_headers (invoke/converse/embed/image paths) must not let + stale SigV4 values present in the input headers clobber the freshly computed signature.""" + + URL = "https://bedrock-runtime.us-east-1.amazonaws.com/model/test-model/converse" + ACCESS_KEY = "AKIAIOSFODNN7EXAMPLE" + SECRET_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" + SESSION_TOKEN = "fresh-session-token" + + @pytest.fixture(autouse=True) + def _clean_aws_env(self, monkeypatch): + for env_var in ("AWS_BEARER_TOKEN_BEDROCK", "AWS_SESSION_TOKEN", "AWS_PROFILE"): + monkeypatch.delenv(env_var, raising=False) + + def _prepare(self, headers: Dict[str, Any], data: str, extra_headers: Optional[Dict[str, str]] = None): + return BaseAWSLLM().get_request_headers( + credentials=Credentials(self.ACCESS_KEY, self.SECRET_KEY, self.SESSION_TOKEN), + aws_region_name="us-east-1", + extra_headers=extra_headers, + endpoint_url=self.URL, + data=data, + headers=headers, + ) + + def test_stale_sigv4_headers_in_input_replaced_by_fresh_signature(self): + first_prepped = self._prepare( + headers={"Content-Type": "application/json"}, + data=json.dumps({"messages": [{"role": "user", "content": "original"}]}), + ) + stale_authorization = first_prepped.headers["Authorization"] + assert stale_authorization.startswith("AWS4-HMAC-SHA256") + + stale_headers = { + "Content-Type": "application/json", + "Authorization": stale_authorization, + "X-Amz-Date": "20200101T000000Z", + "X-Amz-Security-Token": "stale-session-token", + } + retry_data = json.dumps({"messages": [{"role": "user", "content": "retry"}]}) + second_prepped = self._prepare(headers=stale_headers, data=retry_data) + + assert second_prepped.headers["X-Amz-Date"] != "20200101T000000Z" + assert second_prepped.headers["X-Amz-Security-Token"] == self.SESSION_TOKEN + assert second_prepped.headers["Authorization"] != stale_authorization + assert second_prepped.headers["Authorization"].split("Signature=")[1] == _recomputed_sigv4_signature( + url=self.URL, + secret_key=self.SECRET_KEY, + authorization=second_prepped.headers["Authorization"], + headers=dict(second_prepped.headers), + body=retry_data, + ) + + def test_forwarded_headers_still_added_back_after_signing(self): + prepped = self._prepare( + headers={ + "Content-Type": "application/json", + "anthropic-version": "bedrock-2023-05-31", + "user-agent": "litellm-test-client", + }, + data=json.dumps({"messages": []}), + ) + assert prepped.headers["anthropic-version"] == "bedrock-2023-05-31" + assert prepped.headers["user-agent"] == "litellm-test-client" + assert prepped.headers["Content-Type"] == "application/json" + + def test_extra_headers_bearer_authorization_still_overrides_signature(self): + prepped = self._prepare( + headers={"Content-Type": "application/json"}, + data=json.dumps({"messages": []}), + extra_headers={"Authorization": "Bearer foo"}, + ) + assert prepped.headers["Authorization"] == "Bearer foo" diff --git a/tests/test_litellm/llms/bedrock/test_mantle.py b/tests/test_litellm/llms/bedrock/test_mantle.py index f7f8f582abc..c5ce0d5aba7 100644 --- a/tests/test_litellm/llms/bedrock/test_mantle.py +++ b/tests/test_litellm/llms/bedrock/test_mantle.py @@ -204,6 +204,88 @@ def test_mantle_transform_request_strips_prefix_and_adds_model(): ) assert request["model"] == "anthropic.claude-mythos-preview" assert "mantle/" not in request["model"] + assert "stream" not in request + + +def test_mantle_transform_request_keeps_stream_in_body(): + config = AmazonMantleConfig() + request = config.transform_request( + model="mantle/anthropic.claude-mythos-preview", + messages=[{"role": "user", "content": "Hello"}], + optional_params={"max_tokens": 100, "stream": True}, + litellm_params={}, + headers={}, + ) + assert request["stream"] is True + assert request["model"] == "anthropic.claude-mythos-preview" + + +@pytest.mark.asyncio +async def test_mantle_async_transform_request_keeps_stream_in_body(): + config = AmazonMantleConfig() + request = await config.async_transform_request( + model="mantle/anthropic.claude-mythos-preview", + messages=[{"role": "user", "content": "Hello"}], + optional_params={"max_tokens": 100, "stream": True}, + litellm_params={}, + headers={}, + ) + assert request["stream"] is True + assert request["model"] == "anthropic.claude-mythos-preview" + + +@pytest.mark.asyncio +async def test_mantle_async_transform_request_omits_stream_when_not_streaming(): + config = AmazonMantleConfig() + request = await config.async_transform_request( + model="mantle/anthropic.claude-mythos-preview", + messages=[{"role": "user", "content": "Hello"}], + optional_params={"max_tokens": 100}, + litellm_params={}, + headers={}, + ) + assert "stream" not in request + + +def test_mantle_messages_transform_request_keeps_stream_in_body(): + from litellm.types.router import GenericLiteLLMParams + + config = AmazonMantleMessagesConfig() + request = config.transform_anthropic_messages_request( + model="mantle/anthropic.claude-mythos-preview", + messages=[{"role": "user", "content": "Hello"}], + anthropic_messages_optional_request_params={"max_tokens": 100, "stream": True}, + litellm_params=GenericLiteLLMParams(), + headers={}, + ) + assert request["stream"] is True + assert request["model"] == "anthropic.claude-mythos-preview" + + +def test_mantle_messages_transform_request_omits_stream_when_not_streaming(): + from litellm.types.router import GenericLiteLLMParams + + config = AmazonMantleMessagesConfig() + request = config.transform_anthropic_messages_request( + model="mantle/anthropic.claude-mythos-preview", + messages=[{"role": "user", "content": "Hello"}], + anthropic_messages_optional_request_params={"max_tokens": 100}, + litellm_params=GenericLiteLLMParams(), + headers={}, + ) + assert "stream" not in request + + +def test_mantle_chat_streaming_uses_anthropic_sse_iterator(): + from litellm.llms.anthropic.chat.handler import ModelResponseIterator + + config = AmazonMantleConfig() + assert config.has_custom_stream_wrapper is False + iterator = config.get_model_response_iterator( + streaming_response=iter([]), + sync_stream=True, + ) + assert isinstance(iterator, ModelResponseIterator) def test_mantle_validate_environment_sets_workspace_header(): @@ -347,3 +429,164 @@ async def test_mantle_anthropic_messages_routes_to_vpc_api_base(): assert len(urls) == 1 assert urls[0] == f"{_VPC_ENDPOINT}/anthropic/v1/messages" assert "api.aws" not in urls[0] + + +_ANTHROPIC_SSE_EVENTS = ( + ( + "message_start", + { + "type": "message_start", + "message": { + "id": "msg_stream_test", + "type": "message", + "role": "assistant", + "model": "anthropic.claude-mythos-preview", + "content": [], + "stop_reason": None, + "stop_sequence": None, + "usage": {"input_tokens": 10, "output_tokens": 1}, + }, + }, + ), + ( + "content_block_start", + { + "type": "content_block_start", + "index": 0, + "content_block": {"type": "text", "text": ""}, + }, + ), + ( + "content_block_delta", + { + "type": "content_block_delta", + "index": 0, + "delta": {"type": "text_delta", "text": "pong"}, + }, + ), + ("content_block_stop", {"type": "content_block_stop", "index": 0}), + ( + "message_delta", + { + "type": "message_delta", + "delta": {"stop_reason": "end_turn", "stop_sequence": None}, + "usage": {"output_tokens": 2}, + }, + ), + ("message_stop", {"type": "message_stop"}), +) + + +def _anthropic_sse_bytes() -> bytes: + return "".join( + f"event: {event}\ndata: {json.dumps(payload)}\n\n" + for event, payload in _ANTHROPIC_SSE_EVENTS + ).encode() + + +def _anthropic_sse_response(url: str) -> httpx.Response: + return httpx.Response( + status_code=200, + content=_anthropic_sse_bytes(), + headers={"content-type": "text/event-stream"}, + request=httpx.Request("POST", url), + ) + + +def test_mantle_completion_streaming_sends_stream_and_decodes_sse(): + import litellm + + requests = [] + + def mock_post(self, url, data=None, headers=None, **kwargs): + requests.append(_capture_request(url=url, headers=headers or {}, data=data)) + return _anthropic_sse_response(url) + + with patch("litellm.llms.custom_httpx.http_handler.HTTPHandler.post", mock_post): + response = litellm.completion( + model="bedrock/mantle/anthropic.claude-mythos-preview", + messages=[{"role": "user", "content": "ping"}], + max_tokens=10, + stream=True, + aws_access_key_id="fake-key", + aws_secret_access_key="fake-secret", + aws_region_name="us-east-1", + ) + chunks = list(response) + + assert len(requests) == 1 + assert requests[0]["body"]["stream"] is True + content = "".join(chunk.choices[0].delta.content or "" for chunk in chunks) + assert content == "pong" + assert chunks[-1].choices[0].finish_reason == "stop" + + +@pytest.mark.asyncio +async def test_mantle_acompletion_streaming_sends_stream_and_decodes_sse(): + import litellm + + requests = [] + + async def mock_post(self, url, data=None, headers=None, **kwargs): + requests.append(_capture_request(url=url, headers=headers or {}, data=data)) + return _anthropic_sse_response(url) + + try: + with patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", + new=mock_post, + ): + response = await litellm.acompletion( + model="bedrock/mantle/anthropic.claude-mythos-preview", + messages=[{"role": "user", "content": "ping"}], + max_tokens=10, + stream=True, + aws_access_key_id="fake-key", + aws_secret_access_key="fake-secret", + aws_region_name="us-east-1", + ) + chunks = [chunk async for chunk in response] + finally: + await litellm.close_litellm_async_clients() + + assert len(requests) == 1 + assert requests[0]["body"]["stream"] is True + content = "".join(chunk.choices[0].delta.content or "" for chunk in chunks) + assert content == "pong" + assert chunks[-1].choices[0].finish_reason == "stop" + + +@pytest.mark.asyncio +async def test_mantle_anthropic_messages_streaming_sends_stream_and_passes_through_sse(): + import litellm + + requests = [] + + async def mock_post(self, url, data=None, headers=None, **kwargs): + requests.append(_capture_request(url=url, headers=headers or {}, data=data)) + return _anthropic_sse_response(str(url)) + + try: + with patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", + new=mock_post, + ): + response = await litellm.anthropic_messages( + model="bedrock/mantle/anthropic.claude-mythos-preview", + messages=[{"role": "user", "content": "ping"}], + max_tokens=10, + stream=True, + aws_access_key_id="fake-key", + aws_secret_access_key="fake-secret", + aws_region_name="us-east-1", + ) + raw = b"".join([chunk async for chunk in response]) + finally: + await litellm.close_litellm_async_clients() + + assert len(requests) == 1 + assert requests[0]["body"]["stream"] is True + text = raw.decode() + assert "event: message_start" in text + assert '"text": "pong"' in text + assert "event: message_stop" in text diff --git a/tests/test_litellm/llms/custom_httpx/test_llm_http_handler.py b/tests/test_litellm/llms/custom_httpx/test_llm_http_handler.py index b18af060a20..926f40a6c67 100644 --- a/tests/test_litellm/llms/custom_httpx/test_llm_http_handler.py +++ b/tests/test_litellm/llms/custom_httpx/test_llm_http_handler.py @@ -1,4 +1,5 @@ import asyncio +import json import os import sys from unittest.mock import AsyncMock, Mock, patch @@ -12,6 +13,11 @@ from litellm.integrations.code_interpreter_interception.handler import ( CodeInterpreterInterceptionLogger, LITELLM_CODE_EXECUTION_TOOL_NAME, ) +from litellm.llms.base_llm.audio_transcription.transformation import ( + AudioTranscriptionRequestData, + BaseAudioTranscriptionConfig, +) +from litellm.llms.base_llm.chat.transformation import BaseLLMException from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler from litellm.llms.custom_httpx.llm_http_handler import ( BaseLLMHTTPHandler, @@ -19,6 +25,7 @@ from litellm.llms.custom_httpx.llm_http_handler import ( ) from litellm.types.llms.openai import ResponsesAPIResponse from litellm.types.router import GenericLiteLLMParams +from litellm.types.utils import TranscriptionResponse _ACTIVE_KEY = "_code_interpreter_interception_active" _SANDBOX_KEY = "_code_interpreter_interception_sandbox_key" @@ -435,6 +442,227 @@ async def test_async_anthropic_messages_handler_extra_headers(): assert captured_headers["X-Auth-Token"] == "token123" +@pytest.mark.asyncio +async def test_async_anthropic_messages_handler_streaming_forwards_provider_response_headers(): + """ + Regression test for LIT-3724 (issue 2): streaming /v1/messages responses + dropped the upstream provider's HTTP response headers, so Bedrock's + x-amzn-requestid / x-amzn-trace-id never reached clients even with + `return_response_headers: true`. The returned stream object must carry + them in `_hidden_params["additional_headers"]` (llm_provider-* prefixed), + which the proxy merges into the client-facing response headers. + """ + from collections.abc import AsyncIterator as ABCAsyncIterator + + from litellm.llms.anthropic.experimental_pass_through.messages.transformation import ( + AnthropicMessagesConfig, + ) + + handler = BaseLLMHTTPHandler() + + sse_body = ( + b'event: message_start\ndata: {"type": "message_start"}\n\n' + b'event: message_stop\ndata: {"type": "message_stop"}\n\n' + ) + upstream_response = httpx.Response( + 200, + headers={ + "x-amzn-requestid": "amzn-req-123", + "x-amzn-trace-id": "Root=1-abc-def", + }, + content=sse_body, + request=httpx.Request("POST", "https://api.anthropic.com/v1/messages"), + ) + mock_client = AsyncMock(spec=AsyncHTTPHandler) + mock_client.post = AsyncMock(return_value=upstream_response) + + mock_logging_obj = Mock() + mock_logging_obj.model_call_details = {} + + result = await handler.async_anthropic_messages_handler( + model="claude-sonnet-4-20250514", + messages=[{"role": "user", "content": "Hello"}], + anthropic_messages_provider_config=AnthropicMessagesConfig(), + anthropic_messages_optional_request_params={"max_tokens": 32}, + custom_llm_provider="anthropic", + litellm_params=GenericLiteLLMParams(), + logging_obj=mock_logging_obj, + client=mock_client, + api_key="sk-test", + stream=True, + kwargs={}, + ) + + assert isinstance(result, ABCAsyncIterator) + + additional_headers = result._hidden_params["additional_headers"] + assert additional_headers["llm_provider-x-amzn-requestid"] == "amzn-req-123" + assert additional_headers["llm_provider-x-amzn-trace-id"] == "Root=1-abc-def" + + collected = b"".join([chunk async for chunk in result]) + assert b"message_start" in collected + assert b"message_stop" in collected + + +@pytest.mark.asyncio +async def test_async_anthropic_messages_handler_agentic_streaming_forwards_provider_response_headers(): + """ + Companion to the test above for the agentic branch: when a callback + overrides async_should_run_agentic_loop, the handler wraps + AgenticAnthropicStreamingIterator in AnthropicMessagesStreamingResponse. + That wrapping must still expose the provider headers and delegate + iteration through the two-phase agentic iterator unchanged. + """ + from collections.abc import AsyncIterator as ABCAsyncIterator + + from litellm.integrations.custom_logger import CustomLogger + from litellm.llms.anthropic.experimental_pass_through.messages.agentic_streaming_iterator import ( + AgenticAnthropicStreamingIterator, + ) + from litellm.llms.anthropic.experimental_pass_through.messages.transformation import ( + AnthropicMessagesConfig, + ) + + class NoOpAgenticCallback(CustomLogger): + async def async_should_run_agentic_loop( + self, + response, + model, + messages, + tools, + stream, + custom_llm_provider, + kwargs, + ): + return False, {} + + handler = BaseLLMHTTPHandler() + + sse_body = ( + b'event: message_start\ndata: {"type": "message_start"}\n\n' + b'event: message_stop\ndata: {"type": "message_stop"}\n\n' + ) + upstream_response = httpx.Response( + 200, + headers={"x-amzn-requestid": "amzn-req-456"}, + content=sse_body, + request=httpx.Request("POST", "https://api.anthropic.com/v1/messages"), + ) + mock_client = AsyncMock(spec=AsyncHTTPHandler) + mock_client.post = AsyncMock(return_value=upstream_response) + + mock_logging_obj = Mock() + mock_logging_obj.model_call_details = {} + mock_logging_obj.dynamic_success_callbacks = [NoOpAgenticCallback()] + + result = await handler.async_anthropic_messages_handler( + model="claude-sonnet-4-20250514", + messages=[{"role": "user", "content": "Hello"}], + anthropic_messages_provider_config=AnthropicMessagesConfig(), + anthropic_messages_optional_request_params={"max_tokens": 32}, + custom_llm_provider="anthropic", + litellm_params=GenericLiteLLMParams(), + logging_obj=mock_logging_obj, + client=mock_client, + api_key="sk-test", + stream=True, + kwargs={}, + ) + + assert isinstance(result, ABCAsyncIterator) + assert isinstance(result.completion_stream, AgenticAnthropicStreamingIterator) + assert result._hidden_params["additional_headers"]["llm_provider-x-amzn-requestid"] == "amzn-req-456" + + collected = b"".join([chunk async for chunk in result]) + assert b"message_start" in collected + assert b"message_stop" in collected + + +@pytest.mark.asyncio +async def test_anthropic_messages_streaming_response_aclose_closes_upstream_stream(): + """ + Regression test: the proxy's streaming cleanup calls aclose on the + handler's return value (see _finalize_streaming_generator_cleanup's + hasattr(response, "aclose") check). The wrapper must forward aclose to + the upstream stream so provider connections are released on client + disconnect instead of lingering until garbage collection. + """ + from litellm.llms.anthropic.experimental_pass_through.messages.streaming_iterator import ( + AnthropicMessagesStreamingResponse, + ) + + class UpstreamTracker: + def __init__(self): + self.closed = False + + tracker = UpstreamTracker() + + async def upstream(): + try: + yield b'data: {"type": "message_start"}\n\n' + yield b'data: {"type": "message_stop"}\n\n' + finally: + tracker.closed = True + + stream = AnthropicMessagesStreamingResponse( + completion_stream=upstream(), + hidden_params={"additional_headers": {}}, + ) + + first_chunk = await stream.__anext__() + assert b"message_start" in first_chunk + assert tracker.closed is False + + await stream.aclose() + assert tracker.closed is True + + +@pytest.mark.asyncio +async def test_anthropic_messages_streaming_response_aclose_closes_agentic_upstream_stream(): + from litellm.llms.anthropic.experimental_pass_through.messages.agentic_streaming_iterator import ( + AgenticAnthropicStreamingIterator, + ) + from litellm.llms.anthropic.experimental_pass_through.messages.streaming_iterator import ( + AnthropicMessagesStreamingResponse, + ) + + class UpstreamTracker: + def __init__(self): + self.closed = False + + tracker = UpstreamTracker() + + async def upstream(): + try: + yield b'data: {"type": "message_start"}\n\n' + yield b'data: {"type": "message_stop"}\n\n' + finally: + tracker.closed = True + + agentic_iterator = AgenticAnthropicStreamingIterator( + completion_stream=upstream(), + http_handler=Mock(), + model="claude-sonnet-4-20250514", + messages=[{"role": "user", "content": "Hello"}], + anthropic_messages_provider_config=Mock(), + anthropic_messages_optional_request_params={}, + logging_obj=Mock(), + custom_llm_provider="anthropic", + kwargs={}, + ) + stream = AnthropicMessagesStreamingResponse( + completion_stream=agentic_iterator, + hidden_params={"additional_headers": {}}, + ) + + first_chunk = await stream.__anext__() + assert b"message_start" in first_chunk + assert tracker.closed is False + + await stream.aclose() + assert tracker.closed is True + + @pytest.mark.asyncio async def test_async_anthropic_messages_handler_passes_litellm_metadata(): """Ensure litellm_metadata from kwargs is forwarded via update_from_kwargs. @@ -1364,3 +1592,339 @@ async def test_realtime_backend_open_does_not_retry_auth_failure(rejection): await BaseLLMHTTPHandler._open_realtime_backend_ws(fake, "wss://backend.example/live", {}, None) assert fake.attempts == 1 + + +class _JSONBodyAudioTranscriptionConfig(BaseAudioTranscriptionConfig): + def get_supported_openai_params(self, model): + return [] + + def map_openai_params(self, non_default_params, optional_params, model, drop_params): + return optional_params + + def validate_environment( + self, + headers, + model, + messages, + optional_params, + litellm_params, + api_key=None, + api_base=None, + ): + return {**headers, "Authorization": "Bearer test-token"} + + def get_complete_url(self, api_base, api_key, model, optional_params, litellm_params, stream=None): + return "https://transcription.example/recognize" + + def transform_audio_transcription_request(self, model, audio_file, optional_params, litellm_params): + return AudioTranscriptionRequestData(data={"config": {"model": model}, "content": "YXVkaW8="}) + + def transform_audio_transcription_response(self, raw_response): + return TranscriptionResponse(text=raw_response.json()["text"]) + + def get_error_class(self, error_message, status_code, headers): + return BaseLLMException(message=error_message, status_code=status_code, headers=headers) + + +def _json_transcription_call_kwargs(provider_config): + return { + "model": "test-model", + "audio_file": b"raw-audio", + "optional_params": {}, + "litellm_params": {}, + "model_response": TranscriptionResponse(), + "timeout": 10.0, + "max_retries": 0, + "logging_obj": Mock(), + "api_key": None, + "api_base": None, + "custom_llm_provider": "custom", + "headers": {}, + "provider_config": provider_config, + } + + +def _capture_json_transcription_request(captured): + def respond(request): + captured["content_type"] = request.headers.get("content-type") + captured["body"] = json.loads(request.content) + return httpx.Response(200, json={"text": "transcribed"}) + + return respond + + +def test_audio_transcriptions_sends_dict_data_as_json_body(): + """Regression: dict request data was passed to httpx's data= param, which + form-encodes it and silently ignores json=; JSON-body providers (e.g. + Google Speech-to-Text) need an application/json body.""" + captured = {} + client = HTTPHandler(client=httpx.Client(transport=httpx.MockTransport(_capture_json_transcription_request(captured)))) + + response = BaseLLMHTTPHandler().audio_transcriptions( + client=client, + atranscription=False, + **_json_transcription_call_kwargs(_JSONBodyAudioTranscriptionConfig()), + ) + + assert captured["content_type"] == "application/json" + assert captured["body"] == {"config": {"model": "test-model"}, "content": "YXVkaW8="} + assert response.text == "transcribed" + + +@pytest.mark.asyncio +async def test_async_audio_transcriptions_sends_dict_data_as_json_body(): + captured = {} + client = AsyncHTTPHandler() + client.client = httpx.AsyncClient(transport=httpx.MockTransport(_capture_json_transcription_request(captured))) + + response = await BaseLLMHTTPHandler().async_audio_transcriptions( + client=client, + **_json_transcription_call_kwargs(_JSONBodyAudioTranscriptionConfig()), + ) + + assert captured["content_type"] == "application/json" + assert captured["body"] == {"config": {"model": "test-model"}, "content": "YXVkaW8="} + assert response.text == "transcribed" + + +@pytest.mark.asyncio +async def test_async_retrieve_file_content_raises_on_http_error(): + """ + LIT-4008 regression: a provider error response (e.g. Anthropic's 400 + "File id must have `file_` prefix") must raise instead of being wrapped + as file content, which downstream batch cost tracking would parse as an + empty results file and bill $0. + """ + from litellm.llms.anthropic.common_utils import AnthropicError + from litellm.llms.anthropic.files.transformation import AnthropicFilesConfig + + handler = BaseLLMHTTPHandler() + client = Mock(spec=AsyncHTTPHandler) + client.get = AsyncMock( + return_value=httpx.Response( + status_code=400, + content=b'{"type":"error","error":{"type":"invalid_request_error","message":"File id must have `file_` prefix."}}', + ) + ) + + with pytest.raises(AnthropicError) as exc_info: + await handler.async_retrieve_file_content( + file_content_request={"file_id": "msgbatch_123"}, + provider_config=AnthropicFilesConfig(), + litellm_params={"api_key": "sk-test"}, + headers={}, + logging_obj=Mock(), + client=client, + ) + + assert exc_info.value.status_code == 400 + assert "file_" in str(exc_info.value) + + +def test_sync_retrieve_file_content_raises_on_http_error(): + from litellm.llms.anthropic.common_utils import AnthropicError + from litellm.llms.anthropic.files.transformation import AnthropicFilesConfig + + handler = BaseLLMHTTPHandler() + client = Mock(spec=HTTPHandler) + client.get = Mock( + return_value=httpx.Response( + status_code=404, + content=b'{"type":"error","error":{"type":"not_found_error","message":"not found"}}', + ) + ) + + with pytest.raises(AnthropicError) as exc_info: + handler.retrieve_file_content( + file_content_request={"file_id": "file-abc"}, + provider_config=AnthropicFilesConfig(), + litellm_params={"api_key": "sk-test"}, + headers={}, + logging_obj=Mock(), + client=client, + ) + + assert exc_info.value.status_code == 404 + + +_UPSTREAM_NOT_FOUND_BODY = { + "error": { + "message": "Response with id 'resp_abc' not found.", + "type": "invalid_request_error", + "param": None, + "code": None, + } +} + + +def _async_handler_returning(status_code: int, body: dict) -> AsyncHTTPHandler: + handler = AsyncHTTPHandler() + handler.client = httpx.AsyncClient( + transport=httpx.MockTransport(lambda request: httpx.Response(status_code, json=body)) + ) + return handler + + +def _sync_handler_returning(status_code: int, body: dict) -> HTTPHandler: + handler = HTTPHandler() + handler.client = httpx.Client(transport=httpx.MockTransport(lambda request: httpx.Response(status_code, json=body))) + return handler + + +@pytest.mark.asyncio +async def test_aget_responses_surfaces_upstream_error_status_instead_of_500(): + client = _async_handler_returning(404, _UPSTREAM_NOT_FOUND_BODY) + + with pytest.raises(litellm.NotFoundError) as excinfo: + await litellm.aget_responses( + response_id="resp_abc", + custom_llm_provider="azure", + api_base="https://test.openai.azure.com", + api_key="test-key", + api_version="2025-03-01-preview", + client=client, + ) + + assert excinfo.value.status_code == 404 + assert "Response with id 'resp_abc' not found." in excinfo.value.message + + +def test_get_responses_surfaces_upstream_error_status_instead_of_500(): + client = _sync_handler_returning(404, _UPSTREAM_NOT_FOUND_BODY) + + with pytest.raises(litellm.NotFoundError) as excinfo: + litellm.get_responses( + response_id="resp_abc", + custom_llm_provider="azure", + api_base="https://test.openai.azure.com", + api_key="test-key", + api_version="2025-03-01-preview", + client=client, + ) + + assert excinfo.value.status_code == 404 + assert "Response with id 'resp_abc' not found." in excinfo.value.message + + +def test_list_input_items_surfaces_upstream_error_status(): + client = _sync_handler_returning(404, _UPSTREAM_NOT_FOUND_BODY) + + with pytest.raises(litellm.NotFoundError) as excinfo: + litellm.list_input_items( + response_id="resp_abc", + custom_llm_provider="azure", + api_base="https://test.openai.azure.com", + api_key="test-key", + api_version="2025-03-01-preview", + client=client, + ) + + assert excinfo.value.status_code == 404 + + +@pytest.mark.asyncio +async def test_alist_input_items_surfaces_upstream_error_status(): + client = _async_handler_returning(404, _UPSTREAM_NOT_FOUND_BODY) + + with pytest.raises(litellm.NotFoundError) as excinfo: + await litellm.alist_input_items( + response_id="resp_abc", + custom_llm_provider="azure", + api_base="https://test.openai.azure.com", + api_key="test-key", + api_version="2025-03-01-preview", + client=client, + ) + + assert excinfo.value.status_code == 404 + + +@pytest.mark.asyncio +async def test_anthropic_invalid_thinking_signature_retry_resigns_bedrock_request(monkeypatch): + """Regression: after Bedrock rejects a replayed thinking block (400 invalid signature), + the strip-and-retry re-sign must not inherit attempt 1's SigV4 Authorization/X-Amz-Date; + reusing them over the new stripped body makes AWS return 403 SignatureDoesNotMatch.""" + from litellm.llms.bedrock.messages.invoke_transformations.anthropic_claude3_transformation import ( + AmazonAnthropicClaudeMessagesConfig, + ) + + for env_var in ("AWS_BEARER_TOKEN_BEDROCK", "AWS_SESSION_TOKEN", "AWS_PROFILE"): + monkeypatch.delenv(env_var, raising=False) + + handler = BaseLLMHTTPHandler() + provider_config = AmazonAnthropicClaudeMessagesConfig() + litellm_params = GenericLiteLLMParams( + aws_access_key_id="AKIAIOSFODNN7EXAMPLE", + aws_secret_access_key="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + aws_region_name="us-east-1", + ) + request_url = "https://bedrock-runtime.us-east-1.amazonaws.com/model/test-model/invoke" + request_body = { + "anthropic_version": "bedrock-2023-05-31", + "max_tokens": 100, + "messages": [ + {"role": "user", "content": "hi"}, + { + "role": "assistant", + "content": [ + {"type": "thinking", "thinking": "x", "signature": ""}, + {"type": "text", "text": "ok"}, + ], + }, + {"role": "user", "content": "continue"}, + ], + } + first_attempt_headers, signed_json_body = provider_config.sign_request( + headers={"Content-Type": "application/json"}, + optional_params=dict(litellm_params), + request_data=request_body, + api_base=request_url, + api_key=None, + stream=False, + fake_stream=False, + model="test-model", + ) + + posts: list = [] + invalid_signature_response = httpx.Response( + 400, + text='{"message": "messages.1.content.0: Invalid `signature` in `thinking` block"}', + request=httpx.Request("POST", request_url), + ) + ok_response = httpx.Response(200, json={"id": "msg_1"}, request=httpx.Request("POST", request_url)) + + class FakeAsyncClient: + async def post(self, url, headers, data, stream=False, logging_obj=None): + posts.append({"headers": dict(headers), "data": data}) + return invalid_signature_response if len(posts) == 1 else ok_response + + logging_obj = Mock() + logging_obj.model_call_details = {} + + response = await handler._async_post_anthropic_messages_with_http_error_retry( + async_httpx_client=FakeAsyncClient(), + request_url=request_url, + headers=dict(first_attempt_headers), + signed_json_body=signed_json_body, + request_body=request_body, + stream=False, + logging_obj=logging_obj, + provider_config=provider_config, + litellm_params=litellm_params, + api_key=None, + model="test-model", + ) + + assert response.status_code == 200 + assert len(posts) == 2 + retry_payload = json.loads(posts[1]["data"]) + retry_blocks = [ + block + for message in retry_payload["messages"] + if isinstance(message.get("content"), list) + for block in message["content"] + ] + assert retry_blocks and all(block["type"] != "thinking" for block in retry_blocks) + retry_authorization = posts[1]["headers"]["Authorization"] + assert retry_authorization.startswith("AWS4-HMAC-SHA256") + assert retry_authorization != first_attempt_headers["Authorization"] diff --git a/tests/test_litellm/llms/openai/chat/test_openai_gpt_transformation.py b/tests/test_litellm/llms/openai/chat/test_openai_gpt_transformation.py index 20a1bf85751..1894294ea55 100644 --- a/tests/test_litellm/llms/openai/chat/test_openai_gpt_transformation.py +++ b/tests/test_litellm/llms/openai/chat/test_openai_gpt_transformation.py @@ -187,6 +187,84 @@ class TestOpenAIChatCompletionStreamingHandler: assert result.usage.completion_tokens == 350 assert result.usage.total_tokens == 14147 + def test_chunk_parser_raises_on_in_body_error_payload(self): + """vLLM/sglang return HTTP 200 streams whose body carries the error, + e.g. data: {"error": {..., "code": 400}}. chunk_parser must surface it + as a provider error instead of parsing an empty chunk that silently + ends the stream (https://github.com/BerriAI/litellm/issues/25492).""" + from litellm.llms.openai.common_utils import OpenAIError + + handler = OpenAIChatCompletionStreamingHandler( + streaming_response=None, sync_stream=True + ) + + error_chunk = { + "error": { + "object": "error", + "message": "The model is not multimodal. Please remove image inputs.", + "type": "BadRequestError", + "param": None, + "code": 400, + } + } + + with pytest.raises(OpenAIError) as excinfo: + handler.chunk_parser(error_chunk) + + assert excinfo.value.status_code == 400 + assert "not multimodal" in excinfo.value.message + + def test_chunk_parser_error_payload_without_usable_code_maps_to_500(self): + """OpenAI-style error payloads may carry a string code (e.g. + "invalid_api_key") or none at all; those must map to 500, not crash.""" + from litellm.llms.openai.common_utils import OpenAIError + + handler = OpenAIChatCompletionStreamingHandler( + streaming_response=None, sync_stream=True + ) + + with pytest.raises(OpenAIError) as excinfo: + handler.chunk_parser( + {"error": {"message": "engine crashed", "code": "server_error"}} + ) + assert excinfo.value.status_code == 500 + assert "engine crashed" in excinfo.value.message + + with pytest.raises(OpenAIError) as excinfo: + handler.chunk_parser({"error": "plain string error"}) + assert excinfo.value.status_code == 500 + assert "plain string error" in excinfo.value.message + + with pytest.raises(OpenAIError) as excinfo: + handler.chunk_parser({"error": {"type": "overloaded", "code": 503}}) + assert excinfo.value.status_code == 503 + assert excinfo.value.message == '{"type": "overloaded", "code": 503}' + + def test_chunk_parser_tolerates_null_error_field(self): + """A chunk that carries "error": null alongside real data must parse + normally, not raise.""" + handler = OpenAIChatCompletionStreamingHandler( + streaming_response=None, sync_stream=True + ) + + chunk = { + "id": "gen-123", + "created": 1234567890, + "model": "openai/gpt-4o-mini", + "object": "chat.completion.chunk", + "error": None, + "choices": [ + { + "index": 0, + "delta": {"role": "assistant", "content": "Hello"}, + "finish_reason": None, + } + ], + } + + result = handler.chunk_parser(chunk) + assert result.choices[0].delta.content == "Hello" + def test_chunk_parser_without_usage(self): """Test that chunk_parser works normally for chunks without usage.""" handler = OpenAIChatCompletionStreamingHandler( diff --git a/tests/test_litellm/llms/vertex_ai/audio_transcription/__init__.py b/tests/test_litellm/llms/vertex_ai/audio_transcription/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/test_litellm/llms/vertex_ai/audio_transcription/test_vertex_ai_audio_transcription_transformation.py b/tests/test_litellm/llms/vertex_ai/audio_transcription/test_vertex_ai_audio_transcription_transformation.py new file mode 100644 index 00000000000..3fa28699f73 --- /dev/null +++ b/tests/test_litellm/llms/vertex_ai/audio_transcription/test_vertex_ai_audio_transcription_transformation.py @@ -0,0 +1,336 @@ +import base64 +import json +import os +import sys +from urllib.parse import urlparse + +import httpx +import pytest + +sys.path.insert(0, os.path.abspath("../../../../..")) + +import litellm +from litellm.llms.vertex_ai.audio_transcription.transformation import ( + VertexAIAudioTranscriptionConfig, +) +from litellm.llms.vertex_ai.common_utils import VertexAIError +from litellm.types.utils import LlmProviders +from litellm.utils import ProviderConfigManager, get_optional_params_transcription + + +@pytest.fixture +def config(): + return VertexAIAudioTranscriptionConfig() + + +class TestGetCompleteUrl: + def test_defaults_to_us_regional_host(self, config): + url = config.get_complete_url( + api_base=None, + api_key=None, + model="chirp_3", + optional_params={}, + litellm_params={"vertex_project": "test-project"}, + ) + assert url == "https://us-speech.googleapis.com/v2/projects/test-project/locations/us/recognizers/_:recognize" + + def test_uses_vertex_location_for_regional_host(self, config): + url = config.get_complete_url( + api_base=None, + api_key=None, + model="chirp_3", + optional_params={}, + litellm_params={"vertex_project": "test-project", "vertex_location": "eu"}, + ) + assert url == "https://eu-speech.googleapis.com/v2/projects/test-project/locations/eu/recognizers/_:recognize" + + def test_global_location_uses_unprefixed_host(self, config): + url = config.get_complete_url( + api_base=None, + api_key=None, + model="chirp_3", + optional_params={}, + litellm_params={"vertex_project": "test-project", "vertex_location": "global"}, + ) + assert url == "https://speech.googleapis.com/v2/projects/test-project/locations/global/recognizers/_:recognize" + + def test_api_base_override(self, config): + url = config.get_complete_url( + api_base="http://localhost:8080/", + api_key=None, + model="chirp_3", + optional_params={}, + litellm_params={"vertex_project": "test-project"}, + ) + assert url == "http://localhost:8080/v2/projects/test-project/locations/us/recognizers/_:recognize" + + @pytest.mark.parametrize( + "location,expected_netloc", + [ + ("us", "us-speech.googleapis.com"), + ("us-central1", "us-central1-speech.googleapis.com"), + ("eu", "eu-speech.googleapis.com"), + ("global", "speech.googleapis.com"), + ], + ) + def test_valid_location_netloc_always_google(self, config, location, expected_netloc): + url = config.get_complete_url( + api_base=None, + api_key=None, + model="chirp_3", + optional_params={}, + litellm_params={"vertex_project": "test-project", "vertex_location": location}, + ) + netloc = urlparse(url).netloc + assert netloc == expected_netloc + assert netloc.endswith("speech.googleapis.com") + + @pytest.mark.parametrize( + "malicious_location", + [ + "attacker.example/", + "evil.com#", + "us.attacker.example", + "us/../..", + "US", + "us_central1", + "us central1", + "attacker.example:443", + "-us", + ], + ) + def test_malicious_location_is_rejected(self, config, malicious_location): + """SSRF/credential-exfil guard: vertex_location is client-controllable on + the proxy, so a host-injecting value must raise rather than steer the + request (and its admin-minted Google bearer token) at another host.""" + with pytest.raises(VertexAIError): + config.get_complete_url( + api_base=None, + api_key=None, + model="chirp_3", + optional_params={}, + litellm_params={"vertex_project": "test-project", "vertex_location": malicious_location}, + ) + + @pytest.mark.parametrize( + "malicious_project", + [ + "proj/../../locations", + "proj/evil", + "proj#frag", + "proj?a=b", + "proj:evil", + "proj space", + ], + ) + def test_malicious_project_is_rejected(self, config, malicious_project): + with pytest.raises(VertexAIError): + config.get_complete_url( + api_base=None, + api_key=None, + model="chirp_3", + optional_params={}, + litellm_params={"vertex_project": malicious_project, "vertex_location": "us"}, + ) + + +class TestTransformRequest: + def test_request_body_shape(self, config): + audio_bytes = b"fake-audio-bytes" + request_data = config.transform_audio_transcription_request( + model="chirp_3", + audio_file=audio_bytes, + optional_params={}, + litellm_params={}, + ) + assert request_data.files is None + assert request_data.data == { + "config": { + "model": "chirp_3", + "languageCodes": ["auto"], + "features": {"enableAutomaticPunctuation": True}, + "autoDecodingConfig": {}, + }, + "content": base64.b64encode(audio_bytes).decode("utf-8"), + } + + @pytest.mark.parametrize( + "language,expected_language_codes", + [ + ("en", ["en-US"]), + ("en-US", ["en-US"]), + ("es-ES", ["es-ES"]), + ("fr", ["fr-FR"]), + (None, ["auto"]), + ], + ) + def test_language_param_maps_to_language_codes(self, config, language, expected_language_codes): + request_data = config.transform_audio_transcription_request( + model="chirp_3", + audio_file=b"fake-audio-bytes", + optional_params={"language": language} if language is not None else {}, + litellm_params={}, + ) + assert request_data.data["config"]["languageCodes"] == expected_language_codes + + def test_model_prefix_is_stripped(self, config): + request_data = config.transform_audio_transcription_request( + model="vertex_ai/chirp_3", + audio_file=b"fake-audio-bytes", + optional_params={}, + litellm_params={}, + ) + assert request_data.data["config"]["model"] == "chirp_3" + + def test_body_is_json_serializable(self, config): + request_data = config.transform_audio_transcription_request( + model="chirp_3", + audio_file=b"fake-audio-bytes", + optional_params={}, + litellm_params={}, + ) + json.dumps(request_data.data) + + +class TestTransformResponse: + def test_multi_result_transcripts_are_joined(self, config): + raw_response = httpx.Response( + status_code=200, + json={ + "results": [ + {"alternatives": [{"transcript": "Hello world.", "confidence": 0.98}], "languageCode": "en-US"}, + {"alternatives": [{"transcript": "How are you?", "confidence": 0.97}], "languageCode": "en-US"}, + ], + "metadata": {"totalBilledDuration": "15s"}, + }, + ) + response = config.transform_audio_transcription_response(raw_response) + assert response.text == "Hello world. How are you?" + assert response["task"] == "transcribe" + assert response["language"] == "en-US" + assert response["duration"] == 15.0 + + def test_results_without_alternatives_are_skipped(self, config): + raw_response = httpx.Response( + status_code=200, + json={ + "results": [ + {"alternatives": [{"transcript": "First."}]}, + {"alternatives": []}, + {}, + {"alternatives": [{"transcript": "Last."}]}, + ] + }, + ) + response = config.transform_audio_transcription_response(raw_response) + assert response.text == "First. Last." + + def test_empty_results_returns_empty_text(self, config): + raw_response = httpx.Response(status_code=200, json={}) + response = config.transform_audio_transcription_response(raw_response) + assert response.text == "" + + def test_fractional_billed_duration(self, config): + raw_response = httpx.Response( + status_code=200, + json={ + "results": [{"alternatives": [{"transcript": "Hi."}]}], + "metadata": {"totalBilledDuration": "3.5s"}, + }, + ) + response = config.transform_audio_transcription_response(raw_response) + assert response["duration"] == 3.5 + + +class TestValidateEnvironment: + def test_sets_oauth_headers(self): + class StubbedConfig(VertexAIAudioTranscriptionConfig): + def _ensure_access_token(self, credentials, project_id, custom_llm_provider): + return "fake-token", "resolved-project" + + headers = StubbedConfig().validate_environment( + headers={}, + model="chirp_3", + messages=[], + optional_params={}, + litellm_params={"vertex_project": "resolved-project"}, + ) + assert headers["Authorization"] == "Bearer fake-token" + assert headers["x-goog-user-project"] == "resolved-project" + assert headers["Content-Type"] == "application/json" + + +class TestProviderRouting: + def test_provider_config_manager_returns_vertex_config(self): + provider_config = ProviderConfigManager.get_provider_audio_transcription_config( + model="chirp_3", + provider=LlmProviders.VERTEX_AI, + ) + assert isinstance(provider_config, VertexAIAudioTranscriptionConfig) + + def test_get_optional_params_transcription_maps_language(self): + optional_params = get_optional_params_transcription( + model="chirp_3", + custom_llm_provider="vertex_ai", + language="fr-FR", + response_format="json", + ) + assert optional_params["language"] == "fr-FR" + assert optional_params["response_format"] == "json" + + def test_get_optional_params_transcription_rejects_unsupported_param(self): + with pytest.raises(litellm.utils.UnsupportedParamsError): + get_optional_params_transcription( + model="chirp_3", + custom_llm_provider="vertex_ai", + temperature=1, + ) + + @pytest.mark.parametrize("response_format", ["json", "text"]) + def test_supported_response_formats_pass_through(self, response_format): + optional_params = get_optional_params_transcription( + model="chirp_3", + custom_llm_provider="vertex_ai", + response_format=response_format, + ) + assert optional_params["response_format"] == response_format + + @pytest.mark.parametrize("response_format", ["verbose_json", "srt", "vtt"]) + def test_unsupported_response_format_raises(self, response_format): + with pytest.raises(litellm.utils.UnsupportedParamsError, match="response_format"): + get_optional_params_transcription( + model="chirp_3", + custom_llm_provider="vertex_ai", + response_format=response_format, + ) + + @pytest.mark.parametrize("response_format", ["verbose_json", "srt", "vtt"]) + def test_unsupported_response_format_dropped_with_drop_params(self, response_format): + optional_params = get_optional_params_transcription( + model="chirp_3", + custom_llm_provider="vertex_ai", + language="fr-FR", + response_format=response_format, + drop_params=True, + ) + assert "response_format" not in optional_params + assert optional_params["language"] == "fr-FR" + + +class TestModelCostEntry: + REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../..")) + + @pytest.mark.parametrize( + "cost_map_path", + [ + "model_prices_and_context_window.json", + "litellm/model_prices_and_context_window_backup.json", + ], + ) + def test_chirp_3_registered_as_audio_transcription(self, cost_map_path): + with open(os.path.join(self.REPO_ROOT, cost_map_path)) as f: + entry = json.load(f)["vertex_ai/chirp_3"] + assert entry["mode"] == "audio_transcription" + assert entry["litellm_provider"] == "vertex_ai" + assert entry["input_cost_per_second"] == pytest.approx(0.016 / 60, rel=1e-3) + assert entry["supported_endpoints"] == ["/v1/audio/transcriptions"] diff --git a/tests/test_litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio_gemini.py b/tests/test_litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio_gemini.py index 20e7aec377b..51f13affa3f 100644 --- a/tests/test_litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio_gemini.py +++ b/tests/test_litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio_gemini.py @@ -5210,3 +5210,41 @@ class TestModelResponseIteratorCleanup: mock_iterator.aclose.assert_awaited_once() mock_response.aclose.assert_awaited_once() + + +def test_process_candidates_merges_thought_signatures_and_server_side_tools(): + """ + thought_signatures and server_side_tool_invocations must both survive in + provider_specific_fields when a candidate carries the two at once; the second + merge must extend the dict created by the first, not replace it. + """ + candidates = [ + { + "content": { + "role": "model", + "parts": [ + {"text": "the weather is sunny", "thoughtSignature": "sig-text"}, + { + "toolCall": { + "toolType": "google_search", + "id": "tool-1", + "args": {"query": "weather"}, + } + }, + ], + }, + "finishReason": "STOP", + } + ] + model_response = ModelResponse() + + VertexGeminiConfig._process_candidates( + _candidates=candidates, + model_response=model_response, + standard_optional_params={}, + cumulative_tool_call_index=0, + ) + + fields = model_response.choices[-1].message.provider_specific_fields + assert fields["thought_signatures"] == ["sig-text"] + assert fields["server_side_tool_invocations"][0]["id"] == "tool-1" diff --git a/tests/test_litellm/llms/vertex_ai/test_vertex_ai_common_utils.py b/tests/test_litellm/llms/vertex_ai/test_vertex_ai_common_utils.py index bebf856ee6e..b83d4742b64 100644 --- a/tests/test_litellm/llms/vertex_ai/test_vertex_ai_common_utils.py +++ b/tests/test_litellm/llms/vertex_ai/test_vertex_ai_common_utils.py @@ -18,10 +18,25 @@ from litellm.llms.vertex_ai.common_utils import ( pop_vertex_request_labels, set_schema_property_ordering, supports_response_json_schema, + validate_vertex_location, vertex_request_labels_from_litellm_params, ) +@pytest.mark.parametrize("location", ["us", "eu", "us-central1", "europe-west1", "global"]) +def test_validate_vertex_location_accepts_valid(location): + assert validate_vertex_location(location) == location + + +@pytest.mark.parametrize( + "location", + ["attacker.example/", "evil.com#", "us.attacker.example", "us/../..", "US", "us_central1", "-us", "", None], +) +def test_validate_vertex_location_rejects_invalid(location): + with pytest.raises(ValueError): + validate_vertex_location(location) + + @pytest.mark.asyncio async def test_get_vertex_project_id_from_url(): """Test _get_vertex_project_id_from_url with various URLs""" diff --git a/tests/test_litellm/llms/vertex_ai/test_vertex_ai_psc_endpoint_support.py b/tests/test_litellm/llms/vertex_ai/test_vertex_ai_psc_endpoint_support.py index 5a6bc871a03..499bbf6ccd4 100644 --- a/tests/test_litellm/llms/vertex_ai/test_vertex_ai_psc_endpoint_support.py +++ b/tests/test_litellm/llms/vertex_ai/test_vertex_ai_psc_endpoint_support.py @@ -152,9 +152,9 @@ class TestVertexAIPSCEndpointSupport: assert url == expected_url, f"Expected {expected_url}, but got {url}" def test_standard_proxy_with_googleapis(self): - """Test that standard proxies with googleapis.com in URL use simple format""" + """Test that standard proxies with a path in the URL use simple format""" vertex_base = VertexBase() - proxy_api_base = "https://my-proxy.googleapis.com" + proxy_api_base = "https://my-proxy.googleapis.com/vertex-proxy" endpoint_id = "gemini-pro" # Not numeric project_id = "test-project" location = "us-central1" diff --git a/tests/test_litellm/llms/vertex_ai/test_vertex_llm_base.py b/tests/test_litellm/llms/vertex_ai/test_vertex_llm_base.py index 2cf97081806..18fc239b7c6 100644 --- a/tests/test_litellm/llms/vertex_ai/test_vertex_llm_base.py +++ b/tests/test_litellm/llms/vertex_ai/test_vertex_llm_base.py @@ -15,6 +15,7 @@ sys.path.insert( import litellm from litellm.llms.vertex_ai.vertex_ai_aws_wif import VertexAIAwsWifAuth from litellm.llms.vertex_ai.vertex_llm_base import VertexBase +from litellm.types.llms.vertex_ai import VertexPartnerProvider def run_sync(coro): @@ -774,7 +775,7 @@ class TestVertexBase: "https://aiplatform.googleapis.com/v1/projects/test-project/locations/us-central1/publishers/google/models/gemini-pro:generateContent", "gemini-pro", "Bearer token123", - "https://custom-vertex-api.com:generateContent", + "https://custom-vertex-api.com/v1/projects/test-project/locations/us-central1/publishers/google/models/gemini-pro:generateContent", ), # Test case 4: No API base provided (should return original values) ( @@ -930,6 +931,173 @@ class TestVertexBase: result_url_no_streaming == expected_no_streaming_url ), f"Expected {expected_no_streaming_url}, got {result_url_no_streaming}" + def test_check_custom_proxy_vertex_bare_host_api_base_grafts_default_path(self): + vertex_base = VertexBase() + + result_auth_header, result_url = vertex_base._check_custom_proxy( + api_base="https://aiplatform.googleapis.com", + custom_llm_provider="vertex_ai", + gemini_api_key=None, + endpoint="embedContent", + stream=None, + auth_header="Bearer token123", + url="https://us-central1-aiplatform.googleapis.com/v1/projects/test-project/locations/us-central1/publishers/google/models/gemini-embedding-2:embedContent", + model="gemini-embedding-2", + ) + + assert result_auth_header == "Bearer token123" + assert ( + result_url + == "https://aiplatform.googleapis.com/v1/projects/test-project/locations/us-central1/publishers/google/models/gemini-embedding-2:embedContent" + ) + + def test_check_custom_proxy_vertex_bare_host_api_base_with_trailing_slash(self): + vertex_base = VertexBase() + + _, result_url = vertex_base._check_custom_proxy( + api_base="https://internal-gateway.example.com/", + custom_llm_provider="vertex_ai", + gemini_api_key=None, + endpoint="embedContent", + stream=None, + auth_header="Bearer token123", + url="https://us-central1-aiplatform.googleapis.com/v1/projects/test-project/locations/us-central1/publishers/google/models/gemini-embedding-2:embedContent", + model="gemini-embedding-2", + ) + + assert ( + result_url + == "https://internal-gateway.example.com/v1/projects/test-project/locations/us-central1/publishers/google/models/gemini-embedding-2:embedContent" + ) + + def test_check_custom_proxy_vertex_api_base_with_path_keeps_endpoint_append(self): + vertex_base = VertexBase() + gateway_api_base = "https://gateway.ai.cloudflare.com/v1/account-id/my-gateway/google-vertex-ai/v1/projects/test-project/locations/us-central1/publishers/google/models/gemini-embedding-2" + + _, result_url = vertex_base._check_custom_proxy( + api_base=gateway_api_base, + custom_llm_provider="vertex_ai", + gemini_api_key=None, + endpoint="embedContent", + stream=None, + auth_header="Bearer token123", + url="https://us-central1-aiplatform.googleapis.com/v1/projects/test-project/locations/us-central1/publishers/google/models/gemini-embedding-2:embedContent", + model="gemini-embedding-2", + ) + + assert result_url == f"{gateway_api_base}:embedContent" + + def test_check_custom_proxy_vertex_bare_host_streaming_keeps_single_alt_sse(self): + vertex_base = VertexBase() + + _, result_url = vertex_base._check_custom_proxy( + api_base="https://internal-gateway.example.com", + custom_llm_provider="vertex_ai", + gemini_api_key=None, + endpoint="streamGenerateContent", + stream=True, + auth_header="Bearer token123", + url="https://us-central1-aiplatform.googleapis.com/v1/projects/test-project/locations/us-central1/publishers/google/models/gemini-2.5-pro:streamGenerateContent?alt=sse", + model="gemini-2.5-pro", + ) + + assert ( + result_url + == "https://internal-gateway.example.com/v1/projects/test-project/locations/us-central1/publishers/google/models/gemini-2.5-pro:streamGenerateContent?alt=sse" + ) + + def test_check_custom_proxy_psc_endpoint_format_unaffected_by_bare_host(self): + vertex_base = VertexBase() + + _, result_url = vertex_base._check_custom_proxy( + api_base="https://10.96.32.8", + custom_llm_provider="vertex_ai", + gemini_api_key=None, + endpoint="predict", + stream=None, + auth_header="Bearer token123", + url="", + model="1234567890", + vertex_project="test-project", + vertex_location="us-central1", + vertex_api_version="v1", + use_psc_endpoint_format=True, + ) + + assert result_url == "https://10.96.32.8/v1/projects/test-project/locations/us-central1/endpoints/1234567890:predict" + + @pytest.mark.parametrize( + "custom_api_base, stream, expected_url", + [ + ( + "https://aiplatform-myendpoint.p.googleapis.com", + False, + "https://aiplatform-myendpoint.p.googleapis.com/v1/projects/test-project/locations/global/endpoints/openapi/chat/completions", + ), + ( + "https://aiplatform-myendpoint.p.googleapis.com", + True, + "https://aiplatform-myendpoint.p.googleapis.com/v1/projects/test-project/locations/global/endpoints/openapi/chat/completions", + ), + ( + "https://gateway.example.com/vertex-proxy", + False, + "https://gateway.example.com/vertex-proxy/v1/projects/test-project/locations/global/endpoints/openapi/chat/completions", + ), + ], + ids=["psc-host", "psc-host-streaming", "api-base-with-path"], + ) + def test_get_complete_vertex_url_openai_path_partner_custom_api_base( + self, custom_api_base, stream, expected_url + ): + vertex_base = VertexBase() + + result = vertex_base.get_complete_vertex_url( + custom_api_base=custom_api_base, + vertex_location="global", + vertex_project="test-project", + project_id="test-project", + partner=VertexPartnerProvider.llama, + stream=stream, + model="minimaxai/minimax-m2-maas", + ) + + assert result == expected_url + assert result.count("://") == 1 + + def test_get_complete_vertex_url_openai_path_partner_default_api_base(self): + vertex_base = VertexBase() + + result = vertex_base.get_complete_vertex_url( + custom_api_base=None, + vertex_location="us-central1", + vertex_project="test-project", + project_id="test-project", + partner=VertexPartnerProvider.llama, + stream=True, + model="meta/llama-3.1-405b-instruct-maas", + ) + + assert ( + result + == "https://us-central1-aiplatform.googleapis.com/v1/projects/test-project/locations/us-central1/endpoints/openapi/chat/completions" + ) + + def test_get_complete_vertex_url_rawpredict_partner_custom_api_base_keeps_endpoint_format(self): + vertex_base = VertexBase() + + result = vertex_base.get_complete_vertex_url( + custom_api_base="https://gateway.example.com/vertex-proxy", + vertex_location="us-central1", + vertex_project="test-project", + project_id="test-project", + partner=VertexPartnerProvider.mistralai, + stream=False, + model="mistral-large-2411", + ) + + assert result == "https://gateway.example.com/vertex-proxy:rawPredict" + @pytest.mark.parametrize( "api_base, custom_llm_provider, gemini_api_key, endpoint, stream, auth_header, url, model, expected_auth_header, expected_url", [ diff --git a/tests/test_litellm/passthrough/test_passthrough_main.py b/tests/test_litellm/passthrough/test_passthrough_main.py index dc2b7cc3682..6e9c75e085a 100644 --- a/tests/test_litellm/passthrough/test_passthrough_main.py +++ b/tests/test_litellm/passthrough/test_passthrough_main.py @@ -726,3 +726,78 @@ async def test_allm_passthrough_route_429_streaming_raises(): assert exc_info.value.response.status_code == 429 assert len(chunks) == 0, "No chunks should be yielded before the 429 raises" + + +def test_llm_passthrough_route_propagates_allm_passthrough_route_to_logging_obj(): + """ + Regression guard for LIT-4192: `allm_passthrough_route` sets + `kwargs["allm_passthrough_route"] = True` on the async entrypoint, and the + inner `llm_passthrough_route` must let that flag flow through + `get_litellm_params(**kwargs)` and land in the logging object's + `litellm_params`. Without that, `_is_sync_litellm_request` misclassifies + the request as sync and fires duplicate success callbacks. + """ + import asyncio + + from litellm.litellm_core_utils.litellm_logging import Logging as LitellmLogging + + client = HTTPHandler() + + mock_provider_config = MagicMock() + mock_provider_config.get_complete_url.return_value = ( + httpx.URL("https://bedrock-runtime.us-east-1.amazonaws.com/model/foo/converse"), + "https://bedrock-runtime.us-east-1.amazonaws.com", + ) + mock_provider_config.get_api_key.return_value = "fake-key" + mock_provider_config.validate_environment.return_value = {} + mock_provider_config.sign_request.return_value = ({}, None) + mock_provider_config.is_streaming_request.return_value = False + + captured_litellm_params: dict = {} + + def _capture_update_env(*args, **kwargs): + captured_litellm_params.clear() + captured_litellm_params.update(kwargs.get("litellm_params") or {}) + + mock_logging_obj = MagicMock() + mock_logging_obj.update_environment_variables.side_effect = _capture_update_env + + with ( + patch( + "litellm.utils.ProviderConfigManager.get_provider_passthrough_config", + return_value=mock_provider_config, + ), + patch( + "litellm.litellm_core_utils.get_llm_provider_logic.get_llm_provider", + return_value=( + "bedrock/foo", + "bedrock", + "fake-key", + "https://bedrock-runtime.us-east-1.amazonaws.com", + ), + ), + patch.object( + client.client, + "send", + return_value=MagicMock(status_code=200, json=lambda: {}), + ), + patch.object(client.client, "build_request"), + ): + result = llm_passthrough_route( + model="bedrock/foo", + endpoint="model/foo/converse", + method="POST", + custom_llm_provider="bedrock", + api_base="https://bedrock-runtime.us-east-1.amazonaws.com", + api_key="fake-key", + json={"messages": []}, + client=client, + litellm_logging_obj=mock_logging_obj, + allm_passthrough_route=True, + ) + + if asyncio.iscoroutine(result): + result.close() + + assert captured_litellm_params.get("allm_passthrough_route") is True + assert LitellmLogging._is_sync_litellm_request(captured_litellm_params) is False diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/auth/test_user_api_key_auth_mcp.py b/tests/test_litellm/proxy/_experimental/mcp_server/auth/test_user_api_key_auth_mcp.py index 3607d448aad..3db0f8540f9 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/auth/test_user_api_key_auth_mcp.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/auth/test_user_api_key_auth_mcp.py @@ -2146,6 +2146,104 @@ class TestMCPDelegateAuthToUpstream: assert exc_info.value.status_code == 401 mock_auth.assert_called_once() + async def test_delegate_ignored_for_unstamped_m2m_shaped_server(self): + """ + oauth2 + delegate + oauth2_flow=None but the M2M credential shape + (client_id/secret + token_url, no authorization_url) → bypass must NOT + fire. A legacy row that was never stamped still resolves to + client_credentials by shape, and reading the bare column here would + reopen the anonymous bypass to a server that runs upstream as LiteLLM's + service account. Fails closed like the client_credentials case above. + """ + from fastapi import HTTPException + + from litellm.types.mcp import MCPAuth + from litellm.types.mcp_server.mcp_server_manager import MCPServer + + scope = { + "type": "http", + "method": "POST", + "path": "/mcp/legacy_m2m_server", + "headers": [], + } + + legacy_m2m_server = MCPServer( + server_id="legacy-m2m-id", + name="legacy_m2m_server", + transport="http", + auth_type=MCPAuth.oauth2, + delegate_auth_to_upstream=True, + oauth2_flow=None, + client_id="cid", + client_secret="csecret", + token_url="https://idp.example.com/token", + ) + assert legacy_m2m_server.has_client_credentials is False + + async def mock_auth_raises(*_args, **_kwargs): + raise HTTPException(status_code=401, detail="No key provided") + + with ( + patch( + "litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp.user_api_key_auth", + side_effect=mock_auth_raises, + ) as mock_auth, + patch( + "litellm.proxy._experimental.mcp_server.mcp_server_manager.global_mcp_server_manager" + ) as mock_mgr, + ): + mock_mgr.get_mcp_server_by_name.return_value = legacy_m2m_server + with pytest.raises(HTTPException) as exc_info: + await MCPRequestHandler.process_mcp_request(scope) + assert exc_info.value.status_code == 401 + mock_auth.assert_called_once() + + async def test_delegate_bypass_for_pure_pkce_server(self): + """ + oauth2 + delegate + oauth2_flow=None and NO stored client credentials + (pure PKCE, the common delegate case) → bypass must still fire. The + shape resolves to a non-M2M flow, so the security gate leaves it alone; + the fail-closed rule targets the M2M shape specifically, not every + unstamped row. + """ + from litellm.types.mcp import MCPAuth + from litellm.types.mcp_server.mcp_server_manager import MCPServer + + scope = { + "type": "http", + "method": "POST", + "path": "/mcp/pkce_server", + "headers": [], + } + + pkce_server = MCPServer( + server_id="pkce-server-id", + name="pkce_server", + transport="http", + auth_type=MCPAuth.oauth2, + delegate_auth_to_upstream=True, + oauth2_flow=None, + ) + + async def mock_auth_raises(*_args, **_kwargs): + from fastapi import HTTPException + + raise HTTPException(status_code=401, detail="No key provided") + + with ( + patch( + "litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp.user_api_key_auth", + side_effect=mock_auth_raises, + ) as mock_auth, + patch( + "litellm.proxy._experimental.mcp_server.mcp_server_manager.global_mcp_server_manager" + ) as mock_mgr, + ): + mock_mgr.get_mcp_server_by_name.return_value = pkce_server + auth, *_rest = await MCPRequestHandler.process_mcp_request(scope) + mock_auth.assert_not_called() + assert auth.api_key is None + async def test_delegate_bypass_for_internal_server(self): """ Delegate + oauth2 interactive servers bypass LiteLLM auth even when @@ -2234,6 +2332,56 @@ class TestMCPDelegateAuthToUpstream: assert "pkce-server" in result assert "m2m-server" not in result + async def test_get_allowed_servers_excludes_unstamped_m2m_shape_delegate(self): + """ + The anonymous allow-list must also exclude an M2M-shape delegate server whose + oauth2_flow was never stamped (null column, verbatim-read as non-M2M). Reading + the bare has_client_credentials here would surface it to anonymous callers; the + resolved-flow check fails closed on the shape, matching the auth gate. + """ + from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + MCPServerManager, + ) + from litellm.types.mcp import MCPAuth + from litellm.types.mcp_server.mcp_server_manager import MCPServer + + manager = MCPServerManager() + pkce_server = MCPServer( + server_id="pkce-server", + name="pkce_server", + transport="http", + auth_type=MCPAuth.oauth2, + delegate_auth_to_upstream=True, + available_on_public_internet=True, + ) + unstamped_m2m = MCPServer( + server_id="unstamped-m2m", + name="unstamped_m2m", + transport="http", + auth_type=MCPAuth.oauth2, + delegate_auth_to_upstream=True, + oauth2_flow=None, + client_id="cid", + client_secret="csecret", + token_url="https://idp.example.com/token", + ) + assert unstamped_m2m.has_client_credentials is False + manager.registry = { + pkce_server.server_id: pkce_server, + unstamped_m2m.server_id: unstamped_m2m, + } + + with patch.object( + MCPRequestHandler, + "get_allowed_mcp_servers", + new_callable=AsyncMock, + return_value=[], + ): + result = await manager.get_allowed_mcp_servers(None) + + assert "pkce-server" in result + assert "unstamped-m2m" not in result + async def test_get_allowed_servers_includes_internal_delegate(self): """ Internal-only (available_on_public_internet=False) delegate servers @@ -3105,6 +3253,106 @@ async def test_get_team_object_permission_with_core_auth_auto_loading(): mock_get_team.assert_called_once() +@pytest.mark.asyncio +async def test_get_team_object_permission_ui_session_team_skips_db_lookup(): + """ + UI session tokens carry the virtual team_id "litellm-dashboard" (UI_TEAM_ID), + which is never persisted. The lookup must short-circuit to None without + calling get_team_object; otherwise every MCP tools listing from the + dashboard logs a "Team doesn't exist in db" warning per server. + """ + from litellm.proxy._types import UI_TEAM_ID + + mock_user_auth = UserAPIKeyAuth( + api_key="test-key", + user_id="test-user", + team_id=UI_TEAM_ID, + ) + + mock_prisma = MagicMock() + with patch("litellm.proxy.proxy_server.prisma_client", mock_prisma): + with patch("litellm.proxy.auth.auth_checks.get_team_object") as mock_get_team: + result = await MCPRequestHandler._get_team_object_permission( + mock_user_auth + ) + + assert result is None + mock_get_team.assert_not_called() + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "helper_name,expected", + [ + ("_get_allowed_mcp_servers_for_team", []), + ("_get_mcp_access_groups_for_team", []), + ], +) +async def test_team_mcp_helpers_ui_session_team_skip_db_lookup(helper_name, expected): + """ + The server-permission and access-group helpers hit get_team_object with the + session's team_id too; for the virtual UI team each used to 404 into its + own swallowed warning per MCP listing. They must short-circuit without a + DB lookup. + """ + from litellm.proxy._types import UI_TEAM_ID + + mock_user_auth = UserAPIKeyAuth( + api_key="test-key", + user_id="test-user", + team_id=UI_TEAM_ID, + ) + + mock_prisma = MagicMock() + with patch("litellm.proxy.proxy_server.prisma_client", mock_prisma): + with patch("litellm.proxy.auth.auth_checks.get_team_object") as mock_get_team: + helper = getattr(MCPRequestHandler, helper_name) + result = await helper(mock_user_auth) + + assert result == expected + mock_get_team.assert_not_called() + + +@pytest.mark.asyncio +async def test_get_allowed_tools_for_server_ui_session_team_keeps_key_restrictions(): + """ + Regression: the 404 raised by get_team_object for the virtual UI team used + to escape into get_allowed_tools_for_server's blanket except, dropping + key-level tool restrictions (fail-open) and logging a warning. With the + short-circuit, key restrictions still apply for UI sessions. + """ + from fastapi import HTTPException + + from litellm.proxy._types import UI_TEAM_ID + + user_api_key_auth = UserAPIKeyAuth( + api_key="test-key", + user_id="test-user", + team_id=UI_TEAM_ID, + ) + key_perm = MagicMock() + key_perm.mcp_tool_permissions = {"server_1": ["tool_a"]} + + mock_prisma = MagicMock() + with patch("litellm.proxy.proxy_server.prisma_client", mock_prisma): + with patch( + "litellm.proxy.auth.auth_checks.get_team_object", + side_effect=HTTPException( + status_code=404, + detail={"error": "Team doesn't exist in db. Team=litellm-dashboard."}, + ), + ): + with patch.object( + MCPRequestHandler, "_get_key_object_permission", return_value=key_perm + ): + result = await MCPRequestHandler.get_allowed_tools_for_server( + server_id="server_1", + user_api_key_auth=user_api_key_auth, + ) + + assert result == ["tool_a"] + + @pytest.mark.asyncio async def test_get_allowed_mcp_servers_for_team_uses_helper(): """ diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/outbound_credentials/test_adapter.py b/tests/test_litellm/proxy/_experimental/mcp_server/outbound_credentials/test_adapter.py index cdd88c59923..fbe768e07c6 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/outbound_credentials/test_adapter.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/outbound_credentials/test_adapter.py @@ -160,6 +160,50 @@ def test_token_exchange_with_creds_but_no_endpoint_is_owned_for_fail_closed(): assert spec.config.token_exchange_endpoint is None +def test_token_exchange_maps_entra_obo_profile(): + spec = to_server_spec( + _server( + auth_type=MCPAuth.oauth2_token_exchange, + token_exchange_endpoint="https://login.microsoftonline.com/tid/oauth2/v2.0/token", + client_id="cid", + client_secret="csec", + token_exchange_profile="entra_obo", + scopes=["api://target/.default"], + ) + ) + assert spec is not None and isinstance(spec.config, TokenExchangeConfig) + assert spec.config.profile == "entra_obo" + + +def test_token_exchange_defaults_to_rfc8693_profile(): + spec = to_server_spec( + _server( + auth_type=MCPAuth.oauth2_token_exchange, + token_exchange_endpoint="https://idp/token", + client_id="cid", + client_secret="csec", + ) + ) + assert spec is not None and isinstance(spec.config, TokenExchangeConfig) + assert spec.config.profile == "rfc8693" + + +@pytest.mark.parametrize("bogus", ["", "RFC8693", "jwt_bearer", "entra", "unknown"]) +def test_token_exchange_unknown_profile_normalizes_to_rfc8693(bogus): + # A bad DB/config value must normalize to rfc8693, not raise a ValidationError building the spec. + spec = to_server_spec( + _server( + auth_type=MCPAuth.oauth2_token_exchange, + token_exchange_endpoint="https://idp/token", + client_id="cid", + client_secret="csec", + token_exchange_profile=bogus, + ) + ) + assert spec is not None and isinstance(spec.config, TokenExchangeConfig) + assert spec.config.profile == "rfc8693" + + def test_token_exchange_omits_audience_when_unset(): spec = to_server_spec( _server( @@ -330,3 +374,35 @@ def test_raise_token_exchange_challenge_includes_server_root_path(): raise_token_exchange_challenge(_server(alias="obo-srv"), root_path="/api/v1") www = exc_info.value.headers["WWW-Authenticate"] assert 'resource_metadata="/.well-known/oauth-protected-resource/api/v1/mcp/obo-srv"' in www + + +def test_raise_token_exchange_challenge_static_form_is_unchanged_without_step_up(): + from litellm.proxy._experimental.mcp_server.outbound_credentials.adapter import ( + raise_token_exchange_challenge, + ) + + with pytest.raises(HTTPException) as exc_info: + raise_token_exchange_challenge(_server(alias="obo-srv"), root_path="") + assert exc_info.value.headers["WWW-Authenticate"] == ( + 'Bearer resource_metadata="/.well-known/oauth-protected-resource/mcp/obo-srv", ' + 'error="invalid_token", ' + 'error_description="Missing or invalid subject token; authenticate with the IdP and retry"' + ) + + +def test_raise_token_exchange_challenge_uses_insufficient_claims_with_claims_present(): + # Per the Microsoft claims-challenge format, a claims challenge MUST use error=insufficient_claims + # (the value MSAL-family clients key on), and the claims ride base64-encoded, never raw. + from litellm.proxy._experimental.mcp_server.outbound_credentials.adapter import ( + raise_token_exchange_challenge, + ) + + claims = '{"access_token":{"acrs":{"essential":true,"value":"c1"}}}' + with pytest.raises(HTTPException) as exc_info: + raise_token_exchange_challenge(_server(alias="obo-srv"), root_path="", claims=claims) + www = exc_info.value.headers["WWW-Authenticate"] + assert 'resource_metadata="/.well-known/oauth-protected-resource/mcp/obo-srv"' in www + assert 'error="insufficient_claims"' in www + assert 'error="invalid_token"' not in www + assert f'claims="{base64.b64encode(claims.encode()).decode()}"' in www + assert claims not in www # raw JSON never appears; only the base64 form diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/outbound_credentials/test_token_exchange_provider.py b/tests/test_litellm/proxy/_experimental/mcp_server/outbound_credentials/test_token_exchange_provider.py index 7cea99f0b5f..cf15fdb3e26 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/outbound_credentials/test_token_exchange_provider.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/outbound_credentials/test_token_exchange_provider.py @@ -13,7 +13,7 @@ from litellm.proxy._experimental.mcp_server.outbound_credentials.token_exchange_ build_token_exchanger, ) from litellm.proxy._experimental.mcp_server.outbound_credentials.token_exchanger import ( - Rfc8693TokenExchanger, + OboTokenExchanger, SubjectTokenRejected, TokenExchangeClientError, ) @@ -41,7 +41,7 @@ def _client_raising_4xx(body: object): def test_build_token_exchanger_returns_an_exchanger(): - assert isinstance(build_token_exchanger(), Rfc8693TokenExchanger) + assert isinstance(build_token_exchanger(), OboTokenExchanger) def test_build_gives_each_caller_an_independent_cache(): @@ -116,3 +116,38 @@ async def test_post_returns_none_on_non_object_json(payload): with patch(_HTTP_CLIENT, return_value=_Client()): result = await _post_exchange_endpoint("https://idp/token", {"grant_type": "x"}, {}) assert result is None + + +@pytest.mark.asyncio +async def test_post_threads_step_up_error_and_claims_into_subject_rejected(): + # Entra Conditional Access: the 4xx body's machine code and claims blob must ride on the + # rejection so the edge challenge can drive the client's step-up; error_description never does. + claims = '{"access_token":{"acrs":{"essential":true,"value":"c1"}}}' + body = { + "error": "interaction_required", + "error_description": "AADSTS50079: the user must enroll MFA", + "claims": claims, + } + with patch(_HTTP_CLIENT, return_value=_client_raising_4xx(body)): + with pytest.raises(SubjectTokenRejected) as exc_info: + await _post_exchange_endpoint("https://idp/token", {"grant_type": "x"}, {}) + assert exc_info.value.claims == claims + assert "AADSTS50079" not in str(exc_info.value) + + +@pytest.mark.asyncio +async def test_post_subject_rejection_without_claims_carries_none_claims(): + with patch(_HTTP_CLIENT, return_value=_client_raising_4xx({"error": "invalid_grant"})): + with pytest.raises(SubjectTokenRejected) as exc_info: + await _post_exchange_endpoint("https://idp/token", {"grant_type": "x"}, {}) + assert exc_info.value.claims is None + + +@pytest.mark.asyncio +async def test_post_gateway_fault_still_wins_when_claims_are_present(): + # A gateway-fault code stays a 500-class TokenExchangeClientError even if the body carries + # claims; the caller cannot fix invalid_client by stepping up. + body = {"error": "invalid_client", "claims": '{"access_token":{}}'} + with patch(_HTTP_CLIENT, return_value=_client_raising_4xx(body)): + with pytest.raises(TokenExchangeClientError): + await _post_exchange_endpoint("https://idp/token", {"grant_type": "x"}, {}) diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/outbound_credentials/test_token_exchanger.py b/tests/test_litellm/proxy/_experimental/mcp_server/outbound_credentials/test_token_exchanger.py index 13a0652474b..1fa394e1249 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/outbound_credentials/test_token_exchanger.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/outbound_credentials/test_token_exchanger.py @@ -1,6 +1,6 @@ """Tests for the pure RFC 8693 token exchanger: the OBO swap, caching, and single-flight. -Drives `Rfc8693TokenExchanger` through an injected fake HTTP post and clock, so the exchange, the +Drives `OboTokenExchanger` through an injected fake HTTP post and clock, so the exchange, the form it sends, the per-caller-token cache, and the failure mapping are pinned without a live IdP. """ @@ -15,7 +15,7 @@ from litellm.proxy._experimental.mcp_server.outbound_credentials import ( ServerSpec, ) from litellm.proxy._experimental.mcp_server.outbound_credentials.token_exchanger import ( - Rfc8693TokenExchanger, + OboTokenExchanger, ) from litellm.proxy._experimental.mcp_server.outbound_credentials.types import ( TokenExchangeConfig, @@ -32,6 +32,18 @@ _CONFIG = TokenExchangeConfig( ) _SERVER = ServerSpec(server_id="srv", resource="https://up.example.com", config=_CONFIG) +_JWT_BEARER_GRANT = "urn:ietf:params:oauth:grant-type:jwt-bearer" +# audience + a non-default subject_token_type are set deliberately: the entra_obo form must drop both. +_ENTRA_CONFIG = TokenExchangeConfig( + profile="entra_obo", + token_exchange_endpoint="https://login.microsoftonline.com/tid/oauth2/v2.0/token", + audience="ignored-in-entra-obo", + subject_token_type="urn:ietf:params:oauth:token-type:jwt", + client_id="cid", + client_secret=SecretStr("csec"), + scopes=("api://target-api/.default",), +) + class _Clock: def __init__(self, now: float = 1000.0) -> None: @@ -60,7 +72,7 @@ def _spec(config: TokenExchangeConfig) -> ServerSpec: @pytest.mark.asyncio async def test_exchange_emits_token_and_sends_rfc8693_form(): post = _RecordingPost({"access_token": "exchanged", "expires_in": 3600}) - result = await Rfc8693TokenExchanger(post, clock=_Clock()).exchange("caller-jwt", _SERVER, _CONFIG) + result = await OboTokenExchanger(post, clock=_Clock()).exchange("caller-jwt", _SERVER, _CONFIG) assert isinstance(result, Ok) assert result.ok.access_token == "exchanged" url, form = post.calls[0] @@ -89,7 +101,7 @@ async def test_client_secret_basic_sends_authorization_header_and_omits_body_cre token_endpoint_auth_method="client_secret_basic", ) post = _RecordingPost({"access_token": "x", "expires_in": 3600}) - result = await Rfc8693TokenExchanger(post, clock=_Clock()).exchange("jwt", _spec(config), config) + result = await OboTokenExchanger(post, clock=_Clock()).exchange("jwt", _spec(config), config) assert isinstance(result, Ok) _, form = post.calls[0] assert "client_id" not in form and "client_secret" not in form @@ -105,7 +117,7 @@ async def test_client_secret_post_keeps_creds_in_body_with_no_auth_header(): token_endpoint_auth_method="client_secret_post", ) post = _RecordingPost({"access_token": "x", "expires_in": 3600}) - await Rfc8693TokenExchanger(post, clock=_Clock()).exchange("jwt", _spec(config), config) + await OboTokenExchanger(post, clock=_Clock()).exchange("jwt", _spec(config), config) _, form = post.calls[0] assert form["client_id"] == "cid" and form["client_secret"] == "csec" assert "Authorization" not in post.headers[0] @@ -122,7 +134,7 @@ async def test_exchange_maps_idp_rejection_to_unauthorized(): async def _rejecting_post(url, form, headers): raise SubjectTokenRejected("IdP rejected the token exchange (HTTP 400)") - result = await Rfc8693TokenExchanger(_rejecting_post, clock=_Clock()).exchange("bad-jwt", _SERVER, _CONFIG) + result = await OboTokenExchanger(_rejecting_post, clock=_Clock()).exchange("bad-jwt", _SERVER, _CONFIG) assert isinstance(result, Error) assert result.error.tag == "unauthorized" @@ -139,7 +151,7 @@ async def test_exchange_maps_gateway_fault_to_misconfigured(): async def _client_error_post(url, form, headers): raise TokenExchangeClientError("invalid_client") - result = await Rfc8693TokenExchanger(_client_error_post, clock=_Clock()).exchange("jwt", _SERVER, _CONFIG) + result = await OboTokenExchanger(_client_error_post, clock=_Clock()).exchange("jwt", _SERVER, _CONFIG) assert isinstance(result, Error) assert result.error.tag == "misconfigured" @@ -147,7 +159,7 @@ async def test_exchange_maps_gateway_fault_to_misconfigured(): @pytest.mark.asyncio async def test_exchange_maps_transport_failure_to_upstream_unavailable(): """A post returning None (5xx / network / timeout / malformed body) stays retryable: 503.""" - result = await Rfc8693TokenExchanger(_RecordingPost(None), clock=_Clock()).exchange("jwt", _SERVER, _CONFIG) + result = await OboTokenExchanger(_RecordingPost(None), clock=_Clock()).exchange("jwt", _SERVER, _CONFIG) assert isinstance(result, Error) assert result.error.tag == "upstream_unavailable" @@ -155,7 +167,7 @@ async def test_exchange_maps_transport_failure_to_upstream_unavailable(): @pytest.mark.asyncio async def test_exchange_caches_per_caller_token(): post = _RecordingPost({"access_token": "x", "expires_in": 3600}) - exchanger = Rfc8693TokenExchanger(post, clock=_Clock()) + exchanger = OboTokenExchanger(post, clock=_Clock()) await exchanger.exchange("jwt", _SERVER, _CONFIG) second = await exchanger.exchange("jwt", _SERVER, _CONFIG) assert isinstance(second, Ok) and second.ok.access_token == "x" @@ -165,7 +177,7 @@ async def test_exchange_caches_per_caller_token(): @pytest.mark.asyncio async def test_rotated_caller_token_re_exchanges(): post = _RecordingPost({"access_token": "x", "expires_in": 3600}) - exchanger = Rfc8693TokenExchanger(post, clock=_Clock()) + exchanger = OboTokenExchanger(post, clock=_Clock()) await exchanger.exchange("jwt-1", _SERVER, _CONFIG) await exchanger.exchange("jwt-2", _SERVER, _CONFIG) assert len(post.calls) == 2 @@ -176,7 +188,7 @@ async def test_same_token_different_tenant_does_not_share_cache(): # Two tenants presenting the same opaque token (e.g. a shared/service token) must not collide on # one cache entry: tenant_id is part of the key, so each tenant gets its own exchange. post = _RecordingPost({"access_token": "x", "expires_in": 3600}) - exchanger = Rfc8693TokenExchanger(post, clock=_Clock()) + exchanger = OboTokenExchanger(post, clock=_Clock()) await exchanger.exchange("jwt", _SERVER, _CONFIG, tenant_id="acme") await exchanger.exchange("jwt", _SERVER, _CONFIG, tenant_id="globex") assert len(post.calls) == 2 @@ -188,7 +200,7 @@ async def test_same_token_different_tenant_does_not_share_cache(): @pytest.mark.asyncio async def test_invalidate_forces_re_exchange(): post = _RecordingPost({"access_token": "x", "expires_in": 3600}) - exchanger = Rfc8693TokenExchanger(post, clock=_Clock()) + exchanger = OboTokenExchanger(post, clock=_Clock()) await exchanger.exchange("jwt", _SERVER, _CONFIG, tenant_id="acme") await exchanger.invalidate("jwt", _SERVER, _CONFIG, tenant_id="acme") await exchanger.exchange("jwt", _SERVER, _CONFIG, tenant_id="acme") @@ -198,7 +210,7 @@ async def test_invalidate_forces_re_exchange(): @pytest.mark.asyncio async def test_invalidate_targets_only_the_matching_tenant(): post = _RecordingPost({"access_token": "x", "expires_in": 3600}) - exchanger = Rfc8693TokenExchanger(post, clock=_Clock()) + exchanger = OboTokenExchanger(post, clock=_Clock()) await exchanger.exchange("jwt", _SERVER, _CONFIG, tenant_id="acme") await exchanger.exchange("jwt", _SERVER, _CONFIG, tenant_id="globex") await exchanger.invalidate("jwt", _SERVER, _CONFIG, tenant_id="acme") @@ -213,7 +225,7 @@ async def test_rotated_config_re_exchanges_before_ttl(): # Same caller token + server, but the operator rotated the audience/scope: the cached token was # minted for the old config, so it must re-exchange (not serve the stale token) before TTL. post = _RecordingPost({"access_token": "x", "expires_in": 3600}) - exchanger = Rfc8693TokenExchanger(post, clock=_Clock()) + exchanger = OboTokenExchanger(post, clock=_Clock()) rotated = _CONFIG.model_copy(update={"audience": "https://new.example.com", "scopes": ("s3",)}) await exchanger.exchange("jwt", _SERVER, _CONFIG) await exchanger.exchange("jwt", _SERVER, rotated) @@ -226,7 +238,7 @@ async def test_rotated_config_re_exchanges_before_ttl(): @pytest.mark.asyncio async def test_rotated_token_endpoint_auth_method_re_exchanges_before_ttl(): post = _RecordingPost({"access_token": "x", "expires_in": 3600}) - exchanger = Rfc8693TokenExchanger(post, clock=_Clock()) + exchanger = OboTokenExchanger(post, clock=_Clock()) rotated = _CONFIG.model_copy(update={"token_endpoint_auth_method": "client_secret_basic"}) await exchanger.exchange("jwt", _SERVER, _CONFIG) await exchanger.exchange("jwt", _SERVER, rotated) @@ -251,7 +263,7 @@ async def test_concurrent_callers_single_flight_one_exchange(): return {"access_token": "x", "expires_in": 3600} post = _Blocking() - exchanger = Rfc8693TokenExchanger(post, clock=_Clock()) + exchanger = OboTokenExchanger(post, clock=_Clock()) first = asyncio.create_task(exchanger.exchange("jwt", _SERVER, _CONFIG)) second = asyncio.create_task(exchanger.exchange("jwt", _SERVER, _CONFIG)) await asyncio.sleep(0.02) @@ -263,7 +275,7 @@ async def test_concurrent_callers_single_flight_one_exchange(): @pytest.mark.asyncio async def test_idp_failure_is_upstream_unavailable(): - result = await Rfc8693TokenExchanger(_RecordingPost(None), clock=_Clock()).exchange("jwt", _SERVER, _CONFIG) + result = await OboTokenExchanger(_RecordingPost(None), clock=_Clock()).exchange("jwt", _SERVER, _CONFIG) assert isinstance(result, Error) assert result.error.tag == "upstream_unavailable" @@ -271,7 +283,7 @@ async def test_idp_failure_is_upstream_unavailable(): @pytest.mark.asyncio async def test_missing_access_token_is_upstream_unavailable(): post = _RecordingPost({"token_type": "Bearer"}) - result = await Rfc8693TokenExchanger(post, clock=_Clock()).exchange("jwt", _SERVER, _CONFIG) + result = await OboTokenExchanger(post, clock=_Clock()).exchange("jwt", _SERVER, _CONFIG) assert isinstance(result, Error) assert result.error.tag == "upstream_unavailable" @@ -282,7 +294,7 @@ async def test_non_bearer_token_type_is_refused(token_type): # RFC 8693 2.2.1: the resolver forwards the exchanged token as `Bearer`. A non-Bearer token_type # (e.g. N_A = not a standalone access token) must fail closed, not be minted as a bogus Bearer. post = _RecordingPost({"access_token": "x", "token_type": token_type, "expires_in": 3600}) - result = await Rfc8693TokenExchanger(post, clock=_Clock()).exchange("jwt", _SERVER, _CONFIG) + result = await OboTokenExchanger(post, clock=_Clock()).exchange("jwt", _SERVER, _CONFIG) assert isinstance(result, Error) assert result.error.tag == "upstream_unavailable" @@ -295,7 +307,7 @@ async def test_non_bearer_token_type_is_logged(): with patch( "litellm.proxy._experimental.mcp_server.outbound_credentials.token_exchanger.verbose_logger" ) as mock_logger: - await Rfc8693TokenExchanger(post, clock=_Clock()).exchange("jwt", _SERVER, _CONFIG) + await OboTokenExchanger(post, clock=_Clock()).exchange("jwt", _SERVER, _CONFIG) assert mock_logger.warning.called assert "N_A" in repr(mock_logger.warning.call_args) @@ -304,7 +316,7 @@ async def test_non_bearer_token_type_is_logged(): @pytest.mark.parametrize("token_type", ["Bearer", "bearer", "BEARER"]) async def test_bearer_token_type_is_accepted_case_insensitively(token_type): post = _RecordingPost({"access_token": "x", "token_type": token_type, "expires_in": 3600}) - result = await Rfc8693TokenExchanger(post, clock=_Clock()).exchange("jwt", _SERVER, _CONFIG) + result = await OboTokenExchanger(post, clock=_Clock()).exchange("jwt", _SERVER, _CONFIG) assert isinstance(result, Ok) and result.ok.access_token == "x" @@ -312,7 +324,7 @@ async def test_bearer_token_type_is_accepted_case_insensitively(token_type): async def test_absent_token_type_defaults_to_bearer(): # Many IdPs omit token_type; absence must not fail the exchange (RFC 6750 default is Bearer). post = _RecordingPost({"access_token": "x", "expires_in": 3600}) - result = await Rfc8693TokenExchanger(post, clock=_Clock()).exchange("jwt", _SERVER, _CONFIG) + result = await OboTokenExchanger(post, clock=_Clock()).exchange("jwt", _SERVER, _CONFIG) assert isinstance(result, Ok) and result.ok.access_token == "x" @@ -331,7 +343,7 @@ async def test_non_access_issued_token_type_is_refused_even_when_bearer(issued_t post = _RecordingPost( {"access_token": "x", "token_type": "Bearer", "issued_token_type": issued_token_type, "expires_in": 3600} ) - result = await Rfc8693TokenExchanger(post, clock=_Clock()).exchange("jwt", _SERVER, _CONFIG) + result = await OboTokenExchanger(post, clock=_Clock()).exchange("jwt", _SERVER, _CONFIG) assert isinstance(result, Error) assert result.error.tag == "upstream_unavailable" @@ -347,7 +359,7 @@ async def test_access_or_unknown_issued_token_type_is_accepted(issued_token_type body: dict[str, object] = {"access_token": "x", "token_type": "Bearer", "expires_in": 3600} if issued_token_type is not None: body["issued_token_type"] = issued_token_type - result = await Rfc8693TokenExchanger(_RecordingPost(body), clock=_Clock()).exchange("jwt", _SERVER, _CONFIG) + result = await OboTokenExchanger(_RecordingPost(body), clock=_Clock()).exchange("jwt", _SERVER, _CONFIG) assert isinstance(result, Ok) and result.ok.access_token == "x" @@ -361,7 +373,7 @@ async def test_access_or_unknown_issued_token_type_is_accepted(issued_token_type ) async def test_incomplete_config_is_misconfigured_without_hitting_idp(config): post = _RecordingPost({"access_token": "x"}) - result = await Rfc8693TokenExchanger(post, clock=_Clock()).exchange("jwt", _spec(config), config) + result = await OboTokenExchanger(post, clock=_Clock()).exchange("jwt", _spec(config), config) assert isinstance(result, Error) assert result.error.tag == "misconfigured" assert post.calls == [] @@ -373,7 +385,7 @@ async def test_missing_endpoint_is_precondition_required_without_hitting_idp(): # rather than guessing an IdP or falling back. The subject token is never POSTed anywhere. config = TokenExchangeConfig(client_id="c", client_secret=SecretStr("s")) post = _RecordingPost({"access_token": "x"}) - result = await Rfc8693TokenExchanger(post, clock=_Clock()).exchange("jwt", _spec(config), config) + result = await OboTokenExchanger(post, clock=_Clock()).exchange("jwt", _spec(config), config) assert isinstance(result, Error) assert result.error.tag == "precondition_required" assert post.calls == [] @@ -384,7 +396,7 @@ async def test_cached_token_expires_after_its_ttl(): clock = _Clock(1000.0) # expires_in=120, buffer=60 -> ttl 60 -> cached until t=1060. post = _RecordingPost({"access_token": "x", "expires_in": 120}) - exchanger = Rfc8693TokenExchanger(post, clock=clock) + exchanger = OboTokenExchanger(post, clock=clock) await exchanger.exchange("jwt", _SERVER, _CONFIG) clock.now = 1059.0 await exchanger.exchange("jwt", _SERVER, _CONFIG) @@ -402,7 +414,7 @@ async def test_audience_and_scope_omitted_when_unset(): client_secret=SecretStr("csec"), ) post = _RecordingPost({"access_token": "x", "expires_in": 3600}) - await Rfc8693TokenExchanger(post, clock=_Clock()).exchange("jwt", _spec(config), config) + await OboTokenExchanger(post, clock=_Clock()).exchange("jwt", _spec(config), config) _, form = post.calls[0] assert "audience" not in form assert "scope" not in form @@ -414,7 +426,7 @@ async def test_numeric_expires_in_is_honored(expires_in): # A JSON int/float/numeric-string expires_in must drive the TTL, not fall back to the default. clock = _Clock(1000.0) post = _RecordingPost({"access_token": "x", "expires_in": expires_in}) - exchanger = Rfc8693TokenExchanger(post, clock=clock) # ttl = max(120-60, 10) = 60 -> until 1060 + exchanger = OboTokenExchanger(post, clock=clock) # ttl = max(120-60, 10) = 60 -> until 1060 await exchanger.exchange("jwt", _SERVER, _CONFIG) clock.now = 1061.0 await exchanger.exchange("jwt", _SERVER, _CONFIG) @@ -426,7 +438,7 @@ async def test_short_lived_token_is_not_cached_past_its_expiry(): # expires_in (5s) below the buffer/min floor must NOT be served stale: cache only until expiry. clock = _Clock(1000.0) post = _RecordingPost({"access_token": "x", "expires_in": 5}) - exchanger = Rfc8693TokenExchanger(post, clock=clock) + exchanger = OboTokenExchanger(post, clock=clock) await exchanger.exchange("jwt", _SERVER, _CONFIG) clock.now = 1004.0 # within the 5s lifetime -> still cached await exchanger.exchange("jwt", _SERVER, _CONFIG) @@ -449,13 +461,72 @@ async def test_short_lived_token_is_not_cached_past_its_expiry(): async def test_unusable_expires_in_falls_back_to_default_ttl(body): clock = _Clock(1000.0) post = _RecordingPost(body) - exchanger = Rfc8693TokenExchanger(post, clock=clock, default_ttl_seconds=300.0) + exchanger = OboTokenExchanger(post, clock=clock, default_ttl_seconds=300.0) await exchanger.exchange("jwt", _SERVER, _CONFIG) clock.now = 1299.0 await exchanger.exchange("jwt", _SERVER, _CONFIG) assert len(post.calls) == 1 +@pytest.mark.asyncio +async def test_entra_obo_emits_rfc7523_jwt_bearer_form(): + # Microsoft Entra OBO is the RFC 7523 jwt-bearer grant, not RFC 8693: the inbound token is the + # `assertion`, the target rides in `scope`, and `requested_token_use=on_behalf_of` is required. + # subject_token / subject_token_type / audience must NOT appear even though the config carries them. + post = _RecordingPost({"access_token": "minted", "expires_in": 3600}) + result = await OboTokenExchanger(post, clock=_Clock()).exchange( + "caller-entra-jwt", _spec(_ENTRA_CONFIG), _ENTRA_CONFIG + ) + assert isinstance(result, Ok) + assert result.ok.access_token == "minted" + url, form = post.calls[0] + assert url == "https://login.microsoftonline.com/tid/oauth2/v2.0/token" + assert form == { + "grant_type": _JWT_BEARER_GRANT, + "assertion": "caller-entra-jwt", + "client_id": "cid", + "client_secret": "csec", + "scope": "api://target-api/.default", + "requested_token_use": "on_behalf_of", + } + + +@pytest.mark.asyncio +async def test_entra_obo_without_scope_is_misconfigured_without_hitting_idp(): + # Entra carries the target resource in `scope`; with none, fail closed as misconfigured and never + # POST to the IdP (no fall-through to a weaker source). + config = TokenExchangeConfig( + profile="entra_obo", + token_exchange_endpoint="https://login.microsoftonline.com/tid/oauth2/v2.0/token", + client_id="cid", + client_secret=SecretStr("csec"), + ) + post = _RecordingPost({"access_token": "x"}) + result = await OboTokenExchanger(post, clock=_Clock()).exchange("jwt", _spec(config), config) + assert isinstance(result, Error) + assert result.error.tag == "misconfigured" + assert post.calls == [] + + +@pytest.mark.asyncio +async def test_profile_is_part_of_the_cache_key(): + # Same caller token, tenant, endpoint, creds, and scopes; only the profile differs. The two dialects + # mint different upstream tokens, so they must not collide on one cache entry. + post = _RecordingPost({"access_token": "x", "expires_in": 3600}) + exchanger = OboTokenExchanger(post, clock=_Clock()) + shared = dict( + token_exchange_endpoint="https://idp/token", + client_id="cid", + client_secret=SecretStr("csec"), + scopes=("api://target/.default",), + ) + rfc8693 = TokenExchangeConfig(profile="rfc8693", **shared) + entra = TokenExchangeConfig(profile="entra_obo", **shared) + await exchanger.exchange("jwt", _spec(rfc8693), rfc8693) + await exchanger.exchange("jwt", _spec(entra), entra) + assert len(post.calls) == 2 + + @pytest.mark.asyncio async def test_distributed_coordinator_refresh_and_reread_use_the_cache(): # Mimics the cross-replica coordinator contract: the winner's refresh populates the cache, a @@ -470,7 +541,24 @@ async def test_distributed_coordinator_refresh_and_reread_use_the_cache(): return via_reread post = _RecordingPost({"access_token": "x", "expires_in": 3600}) - exchanger = Rfc8693TokenExchanger(post, coordinator=_ReplayCoordinator(), clock=_Clock()) + exchanger = OboTokenExchanger(post, coordinator=_ReplayCoordinator(), clock=_Clock()) result = await exchanger.exchange("jwt", _SERVER, _CONFIG) assert isinstance(result, Ok) and result.ok.access_token == "x" assert len(post.calls) == 1 + + +@pytest.mark.asyncio +async def test_step_up_rejection_threads_claims_into_unauthorized(): + from litellm.proxy._experimental.mcp_server.outbound_credentials.token_exchanger import ( + SubjectTokenRejected, + ) + + claims = '{"access_token":{"acrs":{"essential":true,"value":"c1"}}}' + + async def _step_up_post(url, form, headers): + raise SubjectTokenRejected("IdP rejected the subject token (HTTP 400)", claims=claims) + + result = await OboTokenExchanger(_step_up_post, clock=_Clock()).exchange("jwt", _SERVER, _CONFIG) + assert isinstance(result, Error) + assert result.error.tag == "unauthorized" + assert result.error.unauthorized.claims == claims diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_db_credentials.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_db_credentials.py index 7c9f5216d59..8b8b8a363d5 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_db_credentials.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_db_credentials.py @@ -17,6 +17,7 @@ import pytest from litellm.proxy._experimental.mcp_server.db import ( _decode_user_credential, + _prepare_mcp_server_data, get_user_credential, get_user_oauth_credential, is_oauth_credential_expired, @@ -27,10 +28,12 @@ from litellm.proxy._experimental.mcp_server.db import ( store_user_credential, store_user_oauth_credential, ) +from litellm.proxy._types import NewMCPServerRequest, UpdateMCPServerRequest from litellm.proxy.common_utils.encrypt_decrypt_utils import ( decrypt_value_helper, encrypt_value_helper, ) +from litellm.types.mcp import MCPAuth, MCPTransport SALT_KEY = "test-salt-key-for-byok-credential-tests-1234" @@ -722,3 +725,50 @@ async def test_refresh_user_oauth_token_defaults_to_client_secret_post(monkeypat assert "Authorization" not in kwargs["headers"] assert kwargs["data"]["client_id"] == "cid" assert kwargs["data"]["client_secret"] == "sec" + + +def test_prepare_mcp_server_data_create_carries_token_exchange_columns(): + """The create path (POST /v1/mcp/server) must emit token_exchange_endpoint/audience/ + subject_token_type as top-level column values so an auth_type=oauth2_token_exchange server + persists via the REST API, not only via config.yaml. Dropping the fields from the request + model would leave them out of the prepared column data.""" + request = NewMCPServerRequest( + server_name="te_write", + url="https://upstream.example.com/mcp", + transport=MCPTransport.http, + auth_type=MCPAuth.oauth2_token_exchange, + token_exchange_endpoint="https://idp.example.com/oauth2/token", + audience="https://upstream.example.com", + subject_token_type="urn:ietf:params:oauth:token-type:jwt", + token_exchange_profile="entra_obo", + credentials={"client_id": "te-client", "client_secret": "te-secret"}, + ) + + data = _prepare_mcp_server_data(request) + + assert data["token_exchange_endpoint"] == "https://idp.example.com/oauth2/token" + assert data["audience"] == "https://upstream.example.com" + assert data["subject_token_type"] == "urn:ietf:params:oauth:token-type:jwt" + assert data["token_exchange_profile"] == "entra_obo" + + +def test_prepare_mcp_server_data_update_carries_token_exchange_columns(): + """The partial-update path (PUT /v1/mcp/server, exclude_unset) must carry the three + token-exchange columns when the caller provides them.""" + request = UpdateMCPServerRequest( + server_id="te-update", + url="https://upstream.example.com/mcp", + transport=MCPTransport.http, + auth_type=MCPAuth.oauth2_token_exchange, + token_exchange_endpoint="https://idp.example.com/oauth2/token", + audience="https://upstream.example.com", + subject_token_type="urn:ietf:params:oauth:token-type:jwt", + token_exchange_profile="entra_obo", + ) + + data = _prepare_mcp_server_data(request, exclude_unset=True) + + assert data["token_exchange_endpoint"] == "https://idp.example.com/oauth2/token" + assert data["audience"] == "https://upstream.example.com" + assert data["subject_token_type"] == "urn:ietf:params:oauth:token-type:jwt" + assert data["token_exchange_profile"] == "entra_obo" diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_discoverable_endpoints.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_discoverable_endpoints.py index 13dbdcc7dee..fe90fd45856 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_discoverable_endpoints.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_discoverable_endpoints.py @@ -35,6 +35,7 @@ def _mock_callback_request(base_url: str = "http://localhost:3000/"): req = MagicMock() req.base_url = base_url req.headers = {} + req.cookies = {} return req @@ -519,7 +520,12 @@ async def test_register_client_persists_dcr_client_identity(): """A dynamic client registration (RFC 7591) must persist the issued client_id / client_secret / token_endpoint_auth_method and the token_url onto the server row so autonomous refresh can authenticate as the registered client. Without persistence the - minted client_id is discarded and the refresh_token grant has no client identity.""" + minted client_id is discarded and the refresh_token grant has no client identity. + + The persist must also stamp oauth2_flow="authorization_code": only the interactive + flow reaches this persist, and without the stamp the row (client creds + token_url, + no persisted authorization_url) matches the legacy M2M inference whenever endpoint + discovery fails at registry build, flipping the server to client_credentials.""" try: from fastapi import Request @@ -597,6 +603,7 @@ async def test_register_client_persists_dcr_client_identity(): assert update_data.credentials["client_id"] == "generated-client" assert update_data.credentials["client_secret"] == "generated-secret" assert update_data.credentials["token_endpoint_auth_method"] == "client_secret_basic" + assert update_data.oauth2_flow == "authorization_code" mock_update_server.assert_called_once() @@ -2630,6 +2637,162 @@ async def test_oauth_callback_accepts_same_origin_ui_redirect(): assert "state=state-123" in response.headers["location"] +@pytest.mark.asyncio +async def test_authorize_forwards_short_state_and_round_trips_via_cookie(monkeypatch): + """LIT-4197: the ``state`` sent to the upstream authorization server must be + a short opaque handle, not the long encrypted OAuth session (some IdPs + reject an over-long state). The session must instead ride in a per-flow + HttpOnly cookie so ``/callback`` still recovers the client's original state + and redirects back to the client's redirect_uri.""" + from http.cookies import SimpleCookie + from urllib.parse import parse_qs, urlparse + + from fastapi import Request + + from litellm.proxy._experimental.mcp_server.discoverable_endpoints import ( + _oauth_state_cookie_name, + authorize_with_server, + callback, + decode_state_hash, + ) + from litellm.proxy._types import MCPTransport + from litellm.types.mcp import MCPAuth + from litellm.types.mcp_server.mcp_server_manager import MCPServer + + # Real encryption so the cookie value is a genuine encrypted session. + monkeypatch.setenv("LITELLM_SALT_KEY", "sk-test-salt-for-LIT-4197") + + client_state = "ee230e3dfd4f19c7441941684f39c8a4e0e2c3c61a088e33403df5662b4047b8" + client_redirect_uri = "http://127.0.0.1:6274/oauth/callback/debug" + + server = MCPServer( + server_id="leanix_server", + name="leanix", + server_name="leanix", + alias="leanix", + transport=MCPTransport.http, + auth_type=MCPAuth.oauth2, + client_id="upstream-client-id", + authorization_url="https://idp.example.com/oauth/authorize", + token_url="https://idp.example.com/oauth/token", + ) + + authorize_request = MagicMock(spec=Request) + authorize_request.base_url = "https://proxy.example.com/" + authorize_request.headers = {} + + authorize_response = await authorize_with_server( + request=authorize_request, + mcp_server=server, + client_id="upstream-client-id", + redirect_uri=client_redirect_uri, + state=client_state, + code_challenge="challenge", + code_challenge_method="S256", + ) + + location = authorize_response.headers["location"] + upstream_state = parse_qs(urlparse(location).query)["state"][0] + + # The upstream must receive a short handle, not the encrypted session blob. + assert len(upstream_state) <= 64 + assert upstream_state != client_state + + # The encrypted session rides in a per-flow HttpOnly cookie bound to it. + jar = SimpleCookie() + jar.load(authorize_response.headers["set-cookie"]) + cookie_name = _oauth_state_cookie_name(upstream_state) + assert cookie_name in jar + morsel = jar[cookie_name] + assert morsel["httponly"] + assert morsel["samesite"].lower() == "lax" + assert len(morsel.value) > len(upstream_state) + session = decode_state_hash(morsel.value) + assert session["original_state"] == client_state + assert session["client_redirect_uri"] == client_redirect_uri + + # /callback recovers the original state from the cookie (not the handle) and + # redirects back to the client with the client's own state. + callback_request = MagicMock(spec=Request) + callback_request.base_url = "https://proxy.example.com/" + callback_request.headers = {} + callback_request.cookies = {cookie_name: morsel.value} + + callback_response = await callback( + request=callback_request, + code="upstream-auth-code", + state=upstream_state, + ) + + assert callback_response.status_code == 302 + cb_query = parse_qs(urlparse(callback_response.headers["location"]).query) + assert callback_response.headers["location"].startswith(client_redirect_uri) + assert cb_query["code"] == ["upstream-auth-code"] + assert cb_query["state"] == [client_state] + + # The one-time cookie is expired on the callback response so it cannot be replayed. + cleared = SimpleCookie() + cleared.load(callback_response.headers["set-cookie"]) + assert cookie_name in cleared + assert cleared[cookie_name].value == "" + assert cleared[cookie_name]["max-age"] == "0" + + +@pytest.mark.asyncio +async def test_callback_error_path_reads_cookie_and_clears_it(monkeypatch): + """LIT-4197: an IdP error routed through /callback must recover the client's + original state from the cookie (not the short handle), propagate the error to + the client's redirect_uri, and expire the one-time cookie.""" + from http.cookies import SimpleCookie + from urllib.parse import parse_qs, urlparse + + from fastapi import Request + + from litellm.proxy._experimental.mcp_server.discoverable_endpoints import ( + _oauth_state_cookie_name, + callback, + encode_state_with_base_url, + ) + + monkeypatch.setenv("LITELLM_SALT_KEY", "sk-test-salt-for-LIT-4197") + + client_state = "client-original-state-abc" + client_redirect_uri = "http://127.0.0.1:6274/oauth/callback/debug" + handle = "shortRelayHandle123" + encoded_state = encode_state_with_base_url( + base_url=client_redirect_uri, + original_state=client_state, + client_redirect_uri=client_redirect_uri, + ) + cookie_name = _oauth_state_cookie_name(handle) + + request = MagicMock(spec=Request) + request.base_url = "https://proxy.example.com/" + request.headers = {} + request.cookies = {cookie_name: encoded_state} + + response = await callback( + request=request, + error="access_denied", + error_description="User declined access", + state=handle, + ) + + assert response.status_code == 302 + location = response.headers["location"] + assert location.startswith(client_redirect_uri) + query = parse_qs(urlparse(location).query) + assert query["error"] == ["access_denied"] + # The client's own state is echoed back, recovered from the cookie. + assert query["state"] == [client_state] + + cleared = SimpleCookie() + cleared.load(response.headers["set-cookie"]) + assert cookie_name in cleared + assert cleared[cookie_name].value == "" + assert cleared[cookie_name]["max-age"] == "0" + + @pytest.mark.asyncio async def test_oauth_authorize_includes_scopes_from_server_config(): """Test that authorize endpoint includes scopes from server configuration.""" diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_hook_extra_headers.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_hook_extra_headers.py index 363948ff4e6..73486fe0b6a 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_hook_extra_headers.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_hook_extra_headers.py @@ -43,17 +43,13 @@ class TestConvertMcpHookResponseToKwargs: def test_extracts_modified_arguments(self): original = {"arguments": {"old": "value"}} response = {"modified_arguments": {"new": "value"}} - result = self.proxy_logging._convert_mcp_hook_response_to_kwargs( - response, original - ) + result = self.proxy_logging._convert_mcp_hook_response_to_kwargs(response, original) assert result["arguments"] == {"new": "value"} def test_extracts_extra_headers(self): original = {"arguments": {"key": "val"}} response = {"extra_headers": {"Authorization": "Bearer signed-jwt"}} - result = self.proxy_logging._convert_mcp_hook_response_to_kwargs( - response, original - ) + result = self.proxy_logging._convert_mcp_hook_response_to_kwargs(response, original) assert result["extra_headers"] == {"Authorization": "Bearer signed-jwt"} def test_extracts_both_arguments_and_headers(self): @@ -62,9 +58,7 @@ class TestConvertMcpHookResponseToKwargs: "modified_arguments": {"new": "value"}, "extra_headers": {"X-Custom": "header-val"}, } - result = self.proxy_logging._convert_mcp_hook_response_to_kwargs( - response, original - ) + result = self.proxy_logging._convert_mcp_hook_response_to_kwargs(response, original) assert result["arguments"] == {"new": "value"} assert result["extra_headers"] == {"X-Custom": "header-val"} @@ -72,9 +66,7 @@ class TestConvertMcpHookResponseToKwargs: """Backward compat: hooks that only return modified_arguments still work.""" original = {"arguments": {"key": "val"}} response = {"modified_arguments": {"key": "new_val"}} - result = self.proxy_logging._convert_mcp_hook_response_to_kwargs( - response, original - ) + result = self.proxy_logging._convert_mcp_hook_response_to_kwargs(response, original) assert "extra_headers" not in result assert result["arguments"] == {"key": "new_val"} @@ -82,9 +74,7 @@ class TestConvertMcpHookResponseToKwargs: """Empty dict for extra_headers is falsy and should not be set.""" original = {"arguments": {"key": "val"}} response = {"extra_headers": {}} - result = self.proxy_logging._convert_mcp_hook_response_to_kwargs( - response, original - ) + result = self.proxy_logging._convert_mcp_hook_response_to_kwargs(response, original) assert "extra_headers" not in result @@ -107,18 +97,10 @@ class TestPreCallToolCheckReturnsHeaders: server = self._make_server() proxy_logging = MagicMock(spec=ProxyLogging) - proxy_logging._create_mcp_request_object_from_kwargs = MagicMock( - return_value=MagicMock() - ) - proxy_logging._convert_mcp_to_llm_format = MagicMock( - return_value={"model": "fake"} - ) - proxy_logging.pre_call_hook = AsyncMock( - return_value={"modified_arguments": {"key": "val"}} - ) - proxy_logging._convert_mcp_hook_response_to_kwargs = MagicMock( - return_value={"arguments": {"key": "val"}} - ) + proxy_logging._create_mcp_request_object_from_kwargs = MagicMock(return_value=MagicMock()) + proxy_logging._convert_mcp_to_llm_format = MagicMock(return_value={"model": "fake"}) + proxy_logging.pre_call_hook = AsyncMock(return_value={"modified_arguments": {"key": "val"}}) + proxy_logging._convert_mcp_hook_response_to_kwargs = MagicMock(return_value={"arguments": {"key": "val"}}) with patch.object(manager, "check_allowed_or_banned_tools", return_value=True): with patch.object( @@ -146,15 +128,9 @@ class TestPreCallToolCheckReturnsHeaders: hook_headers = {"Authorization": "Bearer signed-jwt", "X-Trace-Id": "abc123"} proxy_logging = MagicMock(spec=ProxyLogging) - proxy_logging._create_mcp_request_object_from_kwargs = MagicMock( - return_value=MagicMock() - ) - proxy_logging._convert_mcp_to_llm_format = MagicMock( - return_value={"model": "fake"} - ) - proxy_logging.pre_call_hook = AsyncMock( - return_value={"extra_headers": hook_headers} - ) + proxy_logging._create_mcp_request_object_from_kwargs = MagicMock(return_value=MagicMock()) + proxy_logging._convert_mcp_to_llm_format = MagicMock(return_value={"model": "fake"}) + proxy_logging.pre_call_hook = AsyncMock(return_value={"extra_headers": hook_headers}) proxy_logging._convert_mcp_hook_response_to_kwargs = MagicMock( return_value={"arguments": {"key": "val"}, "extra_headers": hook_headers} ) @@ -183,12 +159,8 @@ class TestPreCallToolCheckReturnsHeaders: server = self._make_server() proxy_logging = MagicMock(spec=ProxyLogging) - proxy_logging._create_mcp_request_object_from_kwargs = MagicMock( - return_value=MagicMock() - ) - proxy_logging._convert_mcp_to_llm_format = MagicMock( - return_value={"model": "fake"} - ) + proxy_logging._create_mcp_request_object_from_kwargs = MagicMock(return_value=MagicMock()) + proxy_logging._convert_mcp_to_llm_format = MagicMock(return_value={"model": "fake"}) proxy_logging.pre_call_hook = AsyncMock(return_value=None) with patch.object(manager, "check_allowed_or_banned_tools", return_value=True): @@ -219,18 +191,10 @@ class TestPreCallToolCheckReturnsHeaders: modified_args = {"key": "modified", "extra": "added"} proxy_logging = MagicMock(spec=ProxyLogging) - proxy_logging._create_mcp_request_object_from_kwargs = MagicMock( - return_value=MagicMock() - ) - proxy_logging._convert_mcp_to_llm_format = MagicMock( - return_value={"model": "fake"} - ) - proxy_logging.pre_call_hook = AsyncMock( - return_value={"modified_arguments": modified_args} - ) - proxy_logging._convert_mcp_hook_response_to_kwargs = MagicMock( - return_value={"arguments": modified_args} - ) + proxy_logging._create_mcp_request_object_from_kwargs = MagicMock(return_value=MagicMock()) + proxy_logging._convert_mcp_to_llm_format = MagicMock(return_value={"model": "fake"}) + proxy_logging.pre_call_hook = AsyncMock(return_value={"modified_arguments": modified_args}) + proxy_logging._convert_mcp_hook_response_to_kwargs = MagicMock(return_value={"arguments": modified_args}) with patch.object(manager, "check_allowed_or_banned_tools", return_value=True): with patch.object( @@ -260,12 +224,8 @@ class TestPreCallToolCheckReturnsHeaders: hook_headers = {"Authorization": "Bearer jwt"} proxy_logging = MagicMock(spec=ProxyLogging) - proxy_logging._create_mcp_request_object_from_kwargs = MagicMock( - return_value=MagicMock() - ) - proxy_logging._convert_mcp_to_llm_format = MagicMock( - return_value={"model": "fake"} - ) + proxy_logging._create_mcp_request_object_from_kwargs = MagicMock(return_value=MagicMock()) + proxy_logging._convert_mcp_to_llm_format = MagicMock(return_value={"model": "fake"}) proxy_logging.pre_call_hook = AsyncMock(return_value={"dummy": True}) proxy_logging._convert_mcp_hook_response_to_kwargs = MagicMock( return_value={"arguments": modified_args, "extra_headers": hook_headers} @@ -345,9 +305,7 @@ class TestCallToolFlowsHookHeaders: mock_call.assert_called_once() call_kwargs = mock_call.call_args - assert ( - call_kwargs.kwargs.get("hook_extra_headers") == hook_headers - ) + assert call_kwargs.kwargs.get("hook_extra_headers") == hook_headers @pytest.mark.asyncio async def test_no_hook_headers_when_no_proxy_logging(self): @@ -434,9 +392,7 @@ class TestCallToolFlowsHookHeaders: spec_path="/path/to/spec.yaml", ) - with patch.object( - manager, "_get_mcp_server_from_tool_name", return_value=server - ): + with patch.object(manager, "_get_mcp_server_from_tool_name", return_value=server): with patch.object( manager, "pre_call_tool_check", @@ -467,10 +423,7 @@ class TestCallToolFlowsHookHeaders: proxy_logging_obj=proxy_logging, ) mock_logger.warning.assert_called_once() - assert ( - "header injection is not supported" - in mock_logger.warning.call_args[0][0] - ) + assert "header injection is not supported" in mock_logger.warning.call_args[0][0] @pytest.mark.asyncio async def test_openapi_server_no_error_without_hook_headers(self): @@ -486,9 +439,7 @@ class TestCallToolFlowsHookHeaders: spec_path="/path/to/spec.yaml", ) - with patch.object( - manager, "_get_mcp_server_from_tool_name", return_value=server - ): + with patch.object(manager, "_get_mcp_server_from_tool_name", return_value=server): with patch.object( manager, "pre_call_tool_check", @@ -539,25 +490,19 @@ class TestHookHeaderMergePriority: async def test_hook_headers_override_static_headers(self): """Hook headers should take precedence over static_headers.""" manager = MCPServerManager() - server = self._make_server( - static_headers={"Authorization": "Bearer static-token", "X-Static": "yes"} - ) + server = self._make_server(static_headers={"Authorization": "Bearer static-token", "X-Static": "yes"}) hook_headers = {"Authorization": "Bearer hook-signed-jwt"} captured_extra_headers: Dict[str, Any] = {} - async def fake_create_mcp_client( - server, mcp_auth_header=None, extra_headers=None, stdio_env=None, **kwargs - ): + async def fake_create_mcp_client(server, mcp_auth_header=None, extra_headers=None, stdio_env=None, **kwargs): captured_extra_headers["value"] = extra_headers mock_client = MagicMock() mock_client.call_tool = AsyncMock(return_value=MagicMock()) return mock_client - with patch.object( - manager, "_create_mcp_client", side_effect=fake_create_mcp_client - ): + with patch.object(manager, "_create_mcp_client", side_effect=fake_create_mcp_client): with patch.object(manager, "_build_stdio_env", return_value=None): try: await manager._call_regular_mcp_tool( @@ -587,17 +532,13 @@ class TestHookHeaderMergePriority: captured_extra_headers: Dict[str, Any] = {} - async def fake_create_mcp_client( - server, mcp_auth_header=None, extra_headers=None, stdio_env=None, **kwargs - ): + async def fake_create_mcp_client(server, mcp_auth_header=None, extra_headers=None, stdio_env=None, **kwargs): captured_extra_headers["value"] = extra_headers mock_client = MagicMock() mock_client.call_tool = AsyncMock(return_value=MagicMock()) return mock_client - with patch.object( - manager, "_create_mcp_client", side_effect=fake_create_mcp_client - ): + with patch.object(manager, "_create_mcp_client", side_effect=fake_create_mcp_client): with patch.object(manager, "_build_stdio_env", return_value=None): try: await manager._call_regular_mcp_tool( @@ -633,17 +574,13 @@ class TestHookHeaderMergePriority: captured_extra_headers: Dict[str, Any] = {} - async def fake_create_mcp_client( - server, mcp_auth_header=None, extra_headers=None, stdio_env=None, **kwargs - ): + async def fake_create_mcp_client(server, mcp_auth_header=None, extra_headers=None, stdio_env=None, **kwargs): captured_extra_headers["value"] = extra_headers mock_client = MagicMock() mock_client.call_tool = AsyncMock(return_value=MagicMock()) return mock_client - with patch.object( - manager, "_create_mcp_client", side_effect=fake_create_mcp_client - ): + with patch.object(manager, "_create_mcp_client", side_effect=fake_create_mcp_client): with patch.object(manager, "_build_stdio_env", return_value=None): try: await manager._call_regular_mcp_tool( @@ -689,17 +626,13 @@ class TestHookHeaderMergePriority: captured_extra_headers: Dict[str, Any] = {} - async def fake_create_mcp_client( - server, mcp_auth_header=None, extra_headers=None, stdio_env=None, **kwargs - ): + async def fake_create_mcp_client(server, mcp_auth_header=None, extra_headers=None, stdio_env=None, **kwargs): captured_extra_headers["value"] = extra_headers mock_client = MagicMock() mock_client.call_tool = AsyncMock(return_value=MagicMock()) return mock_client - with patch.object( - manager, "_create_mcp_client", side_effect=fake_create_mcp_client - ): + with patch.object(manager, "_create_mcp_client", side_effect=fake_create_mcp_client): with patch.object(manager, "_build_stdio_env", return_value=None): try: await manager._call_regular_mcp_tool( @@ -737,17 +670,13 @@ class TestHookHeaderMergePriority: captured_extra_headers: Dict[str, Any] = {} - async def fake_create_mcp_client( - server, mcp_auth_header=None, extra_headers=None, stdio_env=None, **kwargs - ): + async def fake_create_mcp_client(server, mcp_auth_header=None, extra_headers=None, stdio_env=None, **kwargs): captured_extra_headers["value"] = extra_headers mock_client = MagicMock() mock_client.call_tool = AsyncMock(return_value=MagicMock()) return mock_client - with patch.object( - manager, "_create_mcp_client", side_effect=fake_create_mcp_client - ): + with patch.object(manager, "_create_mcp_client", side_effect=fake_create_mcp_client): with patch.object(manager, "_build_stdio_env", return_value=None): try: await manager._call_regular_mcp_tool( @@ -822,9 +751,7 @@ class TestMcpRateLimitServerNameSurfacing: request_obj.tool_name = "list_repos" request_obj.arguments = {"org": "acme"} - result = self.proxy_logging._convert_mcp_to_llm_format( - request_obj, {"mcp_rate_limit_server_name": "github"} - ) + result = self.proxy_logging._convert_mcp_to_llm_format(request_obj, {"mcp_rate_limit_server_name": "github"}) assert result["mcp_server_name"] == "github" @@ -861,16 +788,10 @@ class TestMcpRateLimitServerNameSurfacing: return {"model": "fake"} proxy_logging = MagicMock(spec=ProxyLogging) - proxy_logging._create_mcp_request_object_from_kwargs = MagicMock( - return_value=MagicMock() - ) - proxy_logging._convert_mcp_to_llm_format = MagicMock( - side_effect=capture_convert - ) + proxy_logging._create_mcp_request_object_from_kwargs = MagicMock(return_value=MagicMock()) + proxy_logging._convert_mcp_to_llm_format = MagicMock(side_effect=capture_convert) proxy_logging.pre_call_hook = AsyncMock(return_value=None) - proxy_logging._convert_mcp_hook_response_to_kwargs = MagicMock( - return_value={"arguments": {}} - ) + proxy_logging._convert_mcp_hook_response_to_kwargs = MagicMock(return_value={"arguments": {}}) with patch.object(manager, "check_allowed_or_banned_tools", return_value=True): with patch.object( @@ -889,3 +810,226 @@ class TestMcpRateLimitServerNameSurfacing: ) assert captured["kwargs"]["mcp_rate_limit_server_name"] == "gh" + + +class TestOpenApiByokCallTool: + @pytest.mark.asyncio + async def test_call_tool_openapi_byok_injects_request_auth_contextvar(self): + """Playground/responses call call_tool directly; BYOK must reach OpenAPI handlers.""" + from litellm.proxy._experimental.mcp_server.openapi_to_mcp_generator import ( + _request_auth_header, + ) + + manager = MCPServerManager() + server = MCPServer( + server_id="byok-openapi", + name="firecrawl_byok_test", + server_name="firecrawl_byok_test", + url="https://api.firecrawl.dev", + transport=MCPTransport.http, + auth_type=MCPAuth.api_key, + spec_path="https://example.com/openapi.json", + is_byok=True, + ) + user_auth = UserAPIKeyAuth(user_id="default_user_id", api_key="sk-dashboard") + captured_auth: dict[str, Optional[str]] = {} + + async def fake_openapi_handler(_server, _name, _arguments): + captured_auth["value"] = _request_auth_header.get() + return MagicMock() + + with patch.object(manager, "_resolve_mcp_server_for_tool_call", return_value=server): + with patch( + "litellm.proxy._experimental.mcp_server.mcp_server_manager._resolve_byok_mcp_auth_header", + new=AsyncMock(return_value="fc-test-key"), + ): + with patch.object( + manager, + "_call_openapi_tool_handler", + side_effect=fake_openapi_handler, + ): + await manager.call_tool( + server_name=server.server_name, + name="scrapeandextractfromurl", + arguments={"body": {"url": "https://example.com"}}, + user_api_key_auth=user_auth, + ) + + assert captured_auth["value"] == "ApiKey fc-test-key" + + +class TestFormatByokOpenapiAuthHeader: + def _server(self, auth_type): + return MCPServer( + server_id="s1", + name="s1", + server_name="s1", + url="https://example.com", + transport=MCPTransport.http, + auth_type=auth_type, + ) + + def test_api_key_auth_type(self): + from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + _format_byok_openapi_auth_header, + ) + + assert _format_byok_openapi_auth_header(self._server(MCPAuth.api_key), "secret") == "ApiKey secret" + + def test_basic_auth_type(self): + from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + _format_byok_openapi_auth_header, + ) + + assert _format_byok_openapi_auth_header(self._server(MCPAuth.basic), "secret") == "Basic secret" + + def test_defaults_to_bearer(self): + from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + _format_byok_openapi_auth_header, + ) + + assert _format_byok_openapi_auth_header(self._server(MCPAuth.oauth2), "secret") == "Bearer secret" + + +class TestOpenapiForwardedExtraHeaders: + def _server(self, extra_headers): + return MCPServer( + server_id="s1", + name="s1", + server_name="s1", + url="https://example.com", + transport=MCPTransport.http, + extra_headers=extra_headers, + ) + + def test_returns_none_without_extra_headers_config(self): + from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + _openapi_forwarded_extra_headers, + ) + + server = self._server(None) + assert _openapi_forwarded_extra_headers(server, {"X-Custom": "v"}, None) is None + + def test_returns_none_without_raw_headers(self): + from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + _openapi_forwarded_extra_headers, + ) + + server = self._server(["X-Custom"]) + assert _openapi_forwarded_extra_headers(server, None, None) is None + + def test_forwards_configured_header_case_insensitively(self): + from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + _openapi_forwarded_extra_headers, + ) + + server = self._server(["X-Custom"]) + result = _openapi_forwarded_extra_headers(server, {"x-custom": "v"}, None) + assert result == {"X-Custom": "v"} + + def test_returns_none_when_no_configured_header_is_present(self): + from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + _openapi_forwarded_extra_headers, + ) + + server = self._server(["X-Missing"]) + assert _openapi_forwarded_extra_headers(server, {"x-custom": "v"}, None) is None + + def test_skips_authorization_when_caller_header_must_be_stripped(self): + from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + _openapi_forwarded_extra_headers, + ) + + server = self._server(["Authorization"]) + server.auth_type = MCPAuth.oauth2_token_exchange + result = _openapi_forwarded_extra_headers(server, {"authorization": "Bearer caller-token"}, None) + assert result is None + + def test_skips_non_string_header_entries(self): + from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + _openapi_forwarded_extra_headers, + ) + + server = self._server(["X-Custom"]) + server.extra_headers = [123, "X-Custom"] # simulate malformed legacy config data + result = _openapi_forwarded_extra_headers(server, {"x-custom": "v"}, None) + assert result == {"X-Custom": "v"} + + +class TestResolveByokMcpAuthHeader: + def _server(self, is_byok): + return MCPServer( + server_id="s1", + name="s1", + server_name="s1", + url="https://example.com", + transport=MCPTransport.http, + is_byok=is_byok, + ) + + @pytest.mark.asyncio + async def test_non_byok_server_passes_header_through_unchanged(self): + from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + _resolve_byok_mcp_auth_header, + ) + + server = self._server(is_byok=False) + result = await _resolve_byok_mcp_auth_header(server, None, "caller-header") + assert result == "caller-header" + + @pytest.mark.asyncio + async def test_byok_server_uses_stored_credential_when_no_header_supplied(self): + from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + _resolve_byok_mcp_auth_header, + ) + + server = self._server(is_byok=True) + user_auth = UserAPIKeyAuth(user_id="user-1", api_key="sk-dashboard") + + with patch( + "litellm.proxy._experimental.mcp_server.server._get_byok_credential", + new=AsyncMock(return_value="stored-cred"), + ): + result = await _resolve_byok_mcp_auth_header(server, user_auth, None) + + assert result == "stored-cred" + + @pytest.mark.asyncio + async def test_byok_server_raises_401_when_no_credential_stored(self): + from fastapi import HTTPException + + from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + _resolve_byok_mcp_auth_header, + ) + + server = self._server(is_byok=True) + user_auth = UserAPIKeyAuth(user_id="user-1", api_key="sk-dashboard") + + with patch( + "litellm.proxy._experimental.mcp_server.server._get_byok_credential", + new=AsyncMock(return_value=None), + ): + with pytest.raises(HTTPException) as exc_info: + await _resolve_byok_mcp_auth_header(server, user_auth, None) + + assert exc_info.value.status_code == 401 + assert exc_info.value.detail["error"] == "byok_auth_required" + + @pytest.mark.asyncio + async def test_byok_server_checks_credential_and_keeps_caller_header_when_supplied(self): + from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + _resolve_byok_mcp_auth_header, + ) + + server = self._server(is_byok=True) + user_auth = UserAPIKeyAuth(user_id="user-1", api_key="sk-dashboard") + check_mock = AsyncMock(return_value=None) + + with patch( + "litellm.proxy._experimental.mcp_server.server._check_byok_credential", + new=check_mock, + ): + result = await _resolve_byok_mcp_auth_header(server, user_auth, "caller-header") + + check_mock.assert_awaited_once_with(server, user_auth) + assert result == "caller-header" diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_partial_update.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_partial_update.py index 49facdbaeaf..d341d8f7e3b 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_partial_update.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_partial_update.py @@ -7,6 +7,7 @@ Omitting a field must NOT reset it to its Pydantic schema default (e.g. would silently overwrite the existing DB row. """ +import json from unittest.mock import AsyncMock, MagicMock import pytest @@ -134,14 +135,10 @@ async def test_partial_update_writes_explicitly_provided_fields(): @pytest.mark.asyncio async def test_partial_update_can_explicitly_reset_allow_all_keys(): """Caller can still reset a field to its default by sending it explicitly.""" - enabled = await _run_update( - UpdateMCPServerRequest(server_id="s", allow_all_keys=True) - ) + enabled = await _run_update(UpdateMCPServerRequest(server_id="s", allow_all_keys=True)) assert enabled["allow_all_keys"] is True - disabled = await _run_update( - UpdateMCPServerRequest(server_id="s", allow_all_keys=False) - ) + disabled = await _run_update(UpdateMCPServerRequest(server_id="s", allow_all_keys=False)) assert disabled["allow_all_keys"] is False @@ -178,6 +175,95 @@ async def test_partial_update_can_explicitly_clear_alias(): assert data_dict["alias"] is None +async def _run_update_with_existing(data: UpdateMCPServerRequest, existing_auth_type: str) -> dict: + mock_prisma = _mock_prisma() + existing = MagicMock() + existing.auth_type = existing_auth_type + existing.credentials = None + mock_prisma.db.litellm_mcpservertable.find_unique = AsyncMock(return_value=existing) + await update_mcp_server(mock_prisma, data, "test-user") + return mock_prisma.db.litellm_mcpservertable.update.call_args[1]["data"] + + +@pytest.mark.asyncio +async def test_auth_type_switch_clears_stale_flow_scoped_fields(): + """ + Switching oauth2 -> oauth2_token_exchange must clear the previous flow's + endpoint config: a stale token_url would otherwise be picked up as the + token-exchange endpoint and suppress RFC 9728/8414 discovery. + """ + data = UpdateMCPServerRequest(server_id="my-test-server", auth_type="oauth2_token_exchange") + + data_dict = await _run_update_with_existing(data, existing_auth_type="oauth2") + + for stale_field in ( + "authorization_url", + "token_url", + "registration_url", + "oauth2_flow", + "token_exchange_endpoint", + "audience", + "subject_token_type", + "token_exchange_profile", + ): + assert data_dict[stale_field] is None, f"{stale_field} must be cleared on auth_type switch" + assert data_dict["credentials"] is None + + +@pytest.mark.asyncio +async def test_auth_type_switch_keeps_explicitly_provided_flow_fields(): + """Fields explicitly provided alongside the auth_type switch must survive it.""" + data = UpdateMCPServerRequest( + server_id="my-test-server", + auth_type="oauth2_token_exchange", + token_exchange_endpoint="https://idp.example.com/oauth2/token", + ) + + data_dict = await _run_update_with_existing(data, existing_auth_type="oauth2") + + assert data_dict["token_exchange_endpoint"] == "https://idp.example.com/oauth2/token" + assert data_dict["token_url"] is None + + +@pytest.mark.asyncio +async def test_auth_type_switch_back_to_oauth2_clears_token_exchange_fields(): + """The reverse switch must not leave token-exchange settings behind to + silently reactivate if the server is later switched back.""" + data = UpdateMCPServerRequest(server_id="my-test-server", auth_type="oauth2") + + data_dict = await _run_update_with_existing(data, existing_auth_type="oauth2_token_exchange") + + assert data_dict["token_exchange_endpoint"] is None + assert data_dict["audience"] is None + assert data_dict["subject_token_type"] is None + assert data_dict["token_exchange_profile"] is None + + +@pytest.mark.asyncio +async def test_unchanged_auth_type_does_not_clear_flow_fields(): + """An update that keeps the auth_type must not touch flow-scoped fields, so a + legacy OBO server using token_url as its exchange endpoint keeps working.""" + data = UpdateMCPServerRequest( + server_id="my-test-server", + auth_type="oauth2_token_exchange", + allowed_tools=["foo"], + ) + + data_dict = await _run_update_with_existing(data, existing_auth_type="oauth2_token_exchange") + + for flow_field in ( + "authorization_url", + "token_url", + "registration_url", + "oauth2_flow", + "token_exchange_endpoint", + "audience", + "subject_token_type", + "token_exchange_profile", + ): + assert flow_field not in data_dict + + @pytest.mark.asyncio async def test_create_still_writes_defaults(): """ @@ -203,3 +289,256 @@ async def test_create_still_writes_defaults(): # audit fields set by create_mcp_server. assert data_dict["created_by"] == "test-user" assert data_dict["updated_by"] == "test-user" + + +# ── token-exchange blob → column normalization ──────────────────────────────── +# +# token_exchange_endpoint / audience / subject_token_type have dedicated columns; +# their MCPCredentials copies are a legacy shape. Writes must lift blob values +# into the columns and strip them from the stored blob so the read-time +# ``column or blob`` fallback can never resurrect a stale blob value after the +# column is cleared. + + +@pytest.fixture(autouse=True) +def _salt_key(monkeypatch): + monkeypatch.setenv("LITELLM_SALT_KEY", "sk-1234") + + +def _existing_row(auth_type: str, credentials: dict | None = None): + existing = MagicMock() + existing.auth_type = auth_type + existing.credentials = json.dumps(credentials) if credentials is not None else None + existing.token_exchange_endpoint = None + existing.audience = None + existing.subject_token_type = None + existing.token_exchange_profile = None + return existing + + +@pytest.mark.asyncio +async def test_create_lifts_blob_token_exchange_settings_into_columns(): + """The legacy REST shape (TE settings inside ``credentials``) must land in + the dedicated columns, and the stored blob must not keep a copy.""" + mock_prisma = _mock_prisma() + data = NewMCPServerRequest( + server_id="te-server", + url="https://example.com/mcp", + transport="http", + auth_type="oauth2_token_exchange", + credentials={ + "client_id": "cid", + "client_secret": "sec", + "token_exchange_endpoint": "https://idp.example.com/oauth2/token", + "audience": "api://upstream", + "subject_token_type": "urn:ietf:params:oauth:token-type:jwt", + "token_exchange_profile": "entra_obo", + }, + ) + + await create_mcp_server(mock_prisma, data, "test-user") + data_dict = mock_prisma.db.litellm_mcpservertable.create.call_args[1]["data"] + + assert data_dict["token_exchange_endpoint"] == "https://idp.example.com/oauth2/token" + assert data_dict["audience"] == "api://upstream" + assert data_dict["subject_token_type"] == "urn:ietf:params:oauth:token-type:jwt" + assert data_dict["token_exchange_profile"] == "entra_obo" + stored_blob = json.loads(data_dict["credentials"]) + for te_field in ("token_exchange_endpoint", "audience", "subject_token_type", "token_exchange_profile"): + assert te_field not in stored_blob + assert "client_id" in stored_blob + + +@pytest.mark.asyncio +async def test_create_explicit_column_wins_over_blob_copy(): + mock_prisma = _mock_prisma() + data = NewMCPServerRequest( + server_id="te-server", + url="https://example.com/mcp", + transport="http", + auth_type="oauth2_token_exchange", + token_exchange_endpoint="https://top-level.example.com/token", + credentials={"client_id": "cid", "token_exchange_endpoint": "https://blob.example.com/token"}, + ) + + await create_mcp_server(mock_prisma, data, "test-user") + data_dict = mock_prisma.db.litellm_mcpservertable.create.call_args[1]["data"] + + assert data_dict["token_exchange_endpoint"] == "https://top-level.example.com/token" + assert "token_exchange_endpoint" not in json.loads(data_dict["credentials"]) + + +@pytest.mark.asyncio +async def test_credentials_merge_migrates_legacy_blob_te_settings(): + """A same-auth credentials update on a legacy row (TE settings in the blob, + columns null) must move the settings to the columns and drop them from the + merged blob.""" + mock_prisma = _mock_prisma() + existing = _existing_row( + "oauth2_token_exchange", + credentials={ + "client_id": "enc-old-cid", + "token_exchange_endpoint": "https://legacy-idp.example.com/token", + "audience": "api://legacy", + "token_exchange_profile": "entra_obo", + }, + ) + mock_prisma.db.litellm_mcpservertable.find_unique = AsyncMock(return_value=existing) + + data = UpdateMCPServerRequest( + server_id="te-server", + auth_type="oauth2_token_exchange", + credentials={"client_id": "new-cid"}, + ) + await update_mcp_server(mock_prisma, data, "test-user") + data_dict = mock_prisma.db.litellm_mcpservertable.update.call_args[1]["data"] + + assert data_dict["token_exchange_endpoint"] == "https://legacy-idp.example.com/token" + assert data_dict["audience"] == "api://legacy" + assert data_dict["token_exchange_profile"] == "entra_obo" + merged_blob = json.loads(data_dict["credentials"]) + for te_field in ("token_exchange_endpoint", "audience", "subject_token_type", "token_exchange_profile"): + assert te_field not in merged_blob + + +@pytest.mark.asyncio +async def test_cleared_column_is_not_resurrected_by_legacy_blob_value(): + """The Greptile scenario: explicitly clearing the column (to re-enable + RFC 9728/8414 discovery) while the legacy blob still holds an endpoint must + NOT resurrect the blob value — the explicit null wins and the blob copy is + stripped.""" + mock_prisma = _mock_prisma() + existing = _existing_row( + "oauth2_token_exchange", + credentials={"client_id": "enc-old-cid", "token_exchange_endpoint": "https://dead-idp.example.com/token"}, + ) + mock_prisma.db.litellm_mcpservertable.find_unique = AsyncMock(return_value=existing) + + data = UpdateMCPServerRequest( + server_id="te-server", + auth_type="oauth2_token_exchange", + token_exchange_endpoint=None, + credentials={"client_id": "new-cid"}, + ) + await update_mcp_server(mock_prisma, data, "test-user") + data_dict = mock_prisma.db.litellm_mcpservertable.update.call_args[1]["data"] + + assert data_dict["token_exchange_endpoint"] is None + assert "token_exchange_endpoint" not in json.loads(data_dict["credentials"]) + + +@pytest.mark.asyncio +async def test_merge_strips_blob_te_copy_when_column_already_set(): + """When the row already has a column value, the blob copy is shadowed at + read time anyway — the merge must strip it rather than carry it forward.""" + mock_prisma = _mock_prisma() + existing = _existing_row( + "oauth2_token_exchange", + credentials={"client_id": "enc-old-cid", "token_exchange_endpoint": "https://blob-copy.example.com/token"}, + ) + existing.token_exchange_endpoint = "https://column.example.com/token" + mock_prisma.db.litellm_mcpservertable.find_unique = AsyncMock(return_value=existing) + + data = UpdateMCPServerRequest( + server_id="te-server", + auth_type="oauth2_token_exchange", + credentials={"client_id": "new-cid"}, + ) + await update_mcp_server(mock_prisma, data, "test-user") + data_dict = mock_prisma.db.litellm_mcpservertable.update.call_args[1]["data"] + + # Column untouched by this update (not in payload), blob copy gone. + assert "token_exchange_endpoint" not in data_dict + assert "token_exchange_endpoint" not in json.loads(data_dict["credentials"]) + + +@pytest.mark.asyncio +async def test_auth_type_switch_clears_flow_fields_with_external_fields_set(): + """The management endpoint passes ``fields_set`` explicitly (PUT + /v1/mcp/server). The auth-switch clearing must fire on that path too — it is + gated on ``data.auth_type``/the existing row, not on how fields_set arrives.""" + mock_prisma = _mock_prisma() + existing = _existing_row("oauth2") + mock_prisma.db.litellm_mcpservertable.find_unique = AsyncMock(return_value=existing) + + data = UpdateMCPServerRequest(server_id="te-server", auth_type="oauth2_token_exchange") + await update_mcp_server(mock_prisma, data, "test-user", fields_set=set(data.fields_set())) + data_dict = mock_prisma.db.litellm_mcpservertable.update.call_args[1]["data"] + + for stale_field in ( + "authorization_url", + "token_url", + "registration_url", + "oauth2_flow", + "token_exchange_endpoint", + "audience", + "subject_token_type", + "token_exchange_profile", + ): + assert data_dict[stale_field] is None, f"{stale_field} must be cleared via the fields_set path" + + +@pytest.mark.asyncio +async def test_explicit_clear_without_credentials_purges_legacy_blob_copy(): + """Clearing a column in an update that does not touch credentials must strip + the legacy blob copy too — otherwise the next credentials update's + migrate-on-write would repopulate the column the admin just cleared.""" + mock_prisma = _mock_prisma() + existing = _existing_row( + "oauth2_token_exchange", + credentials={"client_id": "enc-old-cid", "token_exchange_endpoint": "https://dead-idp.example.com/token"}, + ) + mock_prisma.db.litellm_mcpservertable.find_unique = AsyncMock(return_value=existing) + + data = UpdateMCPServerRequest(server_id="te-server", token_exchange_endpoint=None) + await update_mcp_server(mock_prisma, data, "test-user") + data_dict = mock_prisma.db.litellm_mcpservertable.update.call_args[1]["data"] + + assert data_dict["token_exchange_endpoint"] is None + stored_blob = json.loads(data_dict["credentials"]) + assert "token_exchange_endpoint" not in stored_blob + # Unrelated blob keys (encrypted secrets) survive untouched. + assert stored_blob["client_id"] == "enc-old-cid" + + +@pytest.mark.asyncio +async def test_explicit_te_write_without_credentials_migrates_other_legacy_fields(): + """A no-credentials update that writes one token-exchange column migrates the + whole row: untouched null columns are lifted from the blob, and every blob + copy is stripped.""" + mock_prisma = _mock_prisma() + existing = _existing_row( + "oauth2_token_exchange", + credentials={ + "client_id": "enc-old-cid", + "token_exchange_endpoint": "https://legacy-idp.example.com/token", + "audience": "api://legacy", + }, + ) + mock_prisma.db.litellm_mcpservertable.find_unique = AsyncMock(return_value=existing) + + data = UpdateMCPServerRequest(server_id="te-server", audience="api://new") + await update_mcp_server(mock_prisma, data, "test-user") + data_dict = mock_prisma.db.litellm_mcpservertable.update.call_args[1]["data"] + + assert data_dict["audience"] == "api://new" + assert data_dict["token_exchange_endpoint"] == "https://legacy-idp.example.com/token" + stored_blob = json.loads(data_dict["credentials"]) + for te_field in ("token_exchange_endpoint", "audience", "subject_token_type"): + assert te_field not in stored_blob + + +@pytest.mark.asyncio +async def test_te_update_without_blob_te_keys_leaves_credentials_untouched(): + """A no-credentials column write on a row whose blob has no legacy copies + must not rewrite the credentials blob at all.""" + mock_prisma = _mock_prisma() + existing = _existing_row("oauth2_token_exchange", credentials={"client_id": "enc-old-cid"}) + mock_prisma.db.litellm_mcpservertable.find_unique = AsyncMock(return_value=existing) + + data = UpdateMCPServerRequest(server_id="te-server", token_exchange_endpoint="https://new.example.com/token") + await update_mcp_server(mock_prisma, data, "test-user") + data_dict = mock_prisma.db.litellm_mcpservertable.update.call_args[1]["data"] + + assert data_dict["token_exchange_endpoint"] == "https://new.example.com/token" + assert "credentials" not in data_dict diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server.py index 44f1d105093..b24457deabd 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server.py @@ -5184,6 +5184,10 @@ async def test_list_tools_with_legacy_db_m2m_server_resolves_oauth2_flow(): legacy_server.server_id = "legacy-m2m-id" legacy_server.auth_type = MCPAuth.oauth2 legacy_server.oauth2_flow = None # Legacy: field not set in DB + legacy_server.token_exchange_endpoint = None + legacy_server.audience = None + legacy_server.subject_token_type = None + legacy_server.token_exchange_profile = None legacy_server.token_url = "https://oauth.example.com/token" legacy_server.authorization_url = None legacy_server.client_id = "client-id" @@ -6592,3 +6596,65 @@ async def test_get_active_submitted_mcp_server_ids_for_user_empty_user_id_skips_ assert await get_active_submitted_mcp_server_ids_for_user(prisma_client, "") == [] prisma_client.db.litellm_mcpservertable.find_many.assert_not_awaited() + + +@pytest.mark.asyncio +async def test_call_tool_with_legacy_db_m2m_server_resolves_oauth2_flow(): + """ + Finding 3 regression: the call_mcp_tool path must apply the same request-time + oauth2_flow backstop the listing path does. A legacy DB row with oauth2_flow=NULL + but the M2M credential shape must reach execute_mcp_tool resolved to + client_credentials, or the caller's Authorization would be forwarded to an M2M + upstream on tool execution during a backfill gap (the list path was covered, the + call path was not). + """ + try: + from litellm.proxy._experimental.mcp_server.server import call_mcp_tool + from litellm.proxy._types import UserAPIKeyAuth + from litellm.types.mcp import MCPAuth + except ImportError: + pytest.skip("MCP server not available") + + user_auth = UserAPIKeyAuth(api_key="sk-1234", user_id="test-user") + + legacy_server = MCPServer( + server_id="legacy-m2m-id", + name="legacy_m2m", + alias="legacy_m2m", + server_name="legacy_m2m", + transport=MCPTransport.http, + auth_type=MCPAuth.oauth2, + oauth2_flow=None, # legacy: unstamped + token_url="https://oauth.example.com/token", + client_id="client-id", + client_secret="client-secret", + ) + assert legacy_server.has_client_credentials is False + + captured_servers = {} + + async def capture_execute(*args, **kwargs): + captured_servers["allowed"] = kwargs.get("allowed_mcp_servers") + return MagicMock(name="call_tool_result") + + with ( + patch( + "litellm.proxy._experimental.mcp_server.server.global_mcp_server_manager", + ) as mock_manager, + patch( + "litellm.proxy._experimental.mcp_server.server.execute_mcp_tool", + side_effect=capture_execute, + ), + patch( + "litellm.proxy._experimental.mcp_server.server._get_allowed_mcp_servers_from_mcp_server_names", + new=AsyncMock(side_effect=lambda mcp_servers, allowed_mcp_servers: allowed_mcp_servers), + ), + ): + mock_manager.get_allowed_mcp_servers = AsyncMock(return_value=["legacy-m2m-id"]) + mock_manager.get_mcp_server_by_id = MagicMock(return_value=legacy_server) + + await call_mcp_tool(name="legacy_m2m-tool", arguments={}, user_api_key_auth=user_auth) + + resolved = captured_servers["allowed"] + assert resolved and resolved[0].oauth2_flow == "client_credentials" + assert resolved[0].has_client_credentials is True diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py index 7c70bb24001..6fa69b2f96c 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py @@ -293,6 +293,86 @@ class TestMCPServerManager: assert server.alias == "friendly_alias" assert server.server_name == "validserver" + def _oauth2_config(self, **overrides): + base = { + "url": "https://example.com/mcp", + "transport": MCPTransport.http, + "auth_type": MCPAuth.oauth2, + "token_url": "https://idp.example.com/token", + "client_id": "cid", + "client_secret": "csec", + } + base.update(overrides) + return {"m2mserver": base} + + @pytest.mark.asyncio + async def test_load_servers_from_config_requires_oauth2_flow(self): + """auth_type oauth2 without an explicit oauth2_flow is a config error: the + credential shape is ambiguous (a DCR interactive server looks identical to M2M), + so the config must assert the flow instead of the proxy guessing it.""" + + manager = MCPServerManager() + + with ( + patch.object(manager, "_descovery_metadata", new=AsyncMock(return_value=None)), + pytest.raises(ValueError) as exc_info, + ): + await manager.load_servers_from_config(self._oauth2_config()) + + assert "oauth2_flow: client_credentials" in str(exc_info.value) + assert "oauth2_flow: authorization_code" in str(exc_info.value) + + @pytest.mark.asyncio + async def test_load_servers_from_config_rejects_unknown_oauth2_flow(self): + manager = MCPServerManager() + + with ( + patch.object(manager, "_descovery_metadata", new=AsyncMock(return_value=None)), + pytest.raises(ValueError) as exc_info, + ): + await manager.load_servers_from_config(self._oauth2_config(oauth2_flow="m2m")) + + assert "got 'm2m'" in str(exc_info.value) + + @pytest.mark.asyncio + async def test_load_servers_from_config_accepts_explicit_client_credentials(self): + manager = MCPServerManager() + + with patch.object(manager, "_descovery_metadata", new=AsyncMock(return_value=None)): + await manager.load_servers_from_config(self._oauth2_config(oauth2_flow="client_credentials")) + + server = next(iter(manager.config_mcp_servers.values())) + assert server.oauth2_flow == "client_credentials" + assert server.has_client_credentials is True + + @pytest.mark.asyncio + async def test_load_servers_from_config_accepts_explicit_authorization_code(self): + manager = MCPServerManager() + + with patch.object(manager, "_descovery_metadata", new=AsyncMock(return_value=None)): + await manager.load_servers_from_config(self._oauth2_config(oauth2_flow="authorization_code")) + + server = next(iter(manager.config_mcp_servers.values())) + assert server.oauth2_flow == "authorization_code" + assert server.needs_user_oauth_token is True + + @pytest.mark.asyncio + async def test_load_servers_from_config_non_oauth2_needs_no_flow(self): + manager = MCPServerManager() + config = { + "apiserver": { + "url": "https://example.com/mcp", + "transport": MCPTransport.http, + "auth_type": MCPAuth.api_key, + "auth_value": "sk-upstream", + } + } + + await manager.load_servers_from_config(config) + + server = next(iter(manager.config_mcp_servers.values())) + assert server.oauth2_flow is None + @pytest.mark.asyncio async def test_load_servers_from_config_coerces_cost_string_to_float(self): """YAML 1.1 parses `7e-05` as a string; ingest must coerce it to float.""" @@ -718,6 +798,41 @@ class TestMCPServerManager: client_secret="csec", ) + @pytest.mark.asyncio + async def test_entra_obo_profile_survives_db_credentials_round_trip(self): + # A credentials blob from the management API / DB carries token_exchange_profile; the DB build + # must reconstruct it onto the MCPServer, and the v2 adapter must map it onto the resolver + # config. Without threading it through, an entra_obo server persisted via the API silently + # falls back to rfc8693 and posts the wrong grant to the IdP. + from litellm.proxy._experimental.mcp_server.outbound_credentials.adapter import to_server_spec + from litellm.proxy._experimental.mcp_server.outbound_credentials.types import TokenExchangeConfig + + manager = MCPServerManager() + row = LiteLLM_MCPServerTable( + server_id="entra-db-1", + alias="entra_db", + description="entra obo from db", + url="https://up.example.com/mcp", + transport=MCPTransport.http, + auth_type=MCPAuth.oauth2_token_exchange, + credentials={ + "client_id": "cid", + "client_secret": "csec", + "token_exchange_endpoint": "https://login.microsoftonline.com/tid/oauth2/v2.0/token", + "scopes": ["api://target/.default"], + "token_exchange_profile": "entra_obo", + }, + created_at=datetime.now(), + updated_at=datetime.now(), + ) + + built = await manager.build_mcp_server_from_table(row, credentials_are_encrypted=False) + assert built.token_exchange_profile == "entra_obo" + + spec = to_server_spec(built) + assert spec is not None and isinstance(spec.config, TokenExchangeConfig) + assert spec.config.profile == "entra_obo" + async def _capture_subject_token(self, call) -> Optional[str]: """Run a manager method (via ``call(manager)``) and return the subject_token it threaded into ``_create_mcp_client``.""" @@ -1602,6 +1717,7 @@ class TestMCPServerManager: "url": "https://example.com/mcp", "transport": MCPTransport.http, "auth_type": MCPAuth.oauth2, + "oauth2_flow": "authorization_code", "scopes": ["config"], "authorization_url": "https://config.example.com/auth", } @@ -1615,6 +1731,107 @@ class TestMCPServerManager: assert server.token_url == "https://discovered.example.com/token" assert server.registration_url == "https://discovered.example.com/register" + @pytest.mark.asyncio + async def test_load_servers_from_config_filters_blank_scopes(self): + """A YAML ``scopes: [""]`` must normalize to None (matching the DB path), so a blank-only + list never becomes a ``("",)`` tuple that skips the entra_obo fail-closed scope check.""" + manager = MCPServerManager() + config = { + "entra": { + "url": "https://up.example.com/mcp", + "transport": MCPTransport.http, + "auth_type": MCPAuth.oauth2_token_exchange, + "token_exchange_profile": "entra_obo", + "token_exchange_endpoint": "https://login.microsoftonline.com/t/oauth2/v2.0/token", + "client_id": "cid", + "client_secret": "csec", + "scopes": ["", " "], + } + } + + await manager.load_servers_from_config(config) + + server = next(iter(manager.config_mcp_servers.values())) + assert server.scopes is None + + # And the exchange precondition now fails closed before any IdP call. + from litellm.proxy._experimental.mcp_server.outbound_credentials.adapter import to_server_spec + from litellm.proxy._experimental.mcp_server.outbound_credentials.result import Error + from litellm.proxy._experimental.mcp_server.outbound_credentials.token_exchanger import ( + OboTokenExchanger, + ) + + spec = to_server_spec(server) + assert spec is not None + assert spec.config.scopes == () + + async def _must_not_post(url, form, headers): + raise AssertionError("entra_obo with blank scopes must fail closed before POSTing to the IdP") + + result = await OboTokenExchanger(_must_not_post).exchange("subj", spec, spec.config) + assert isinstance(result, Error) + assert result.error.tag == "misconfigured" + + @pytest.mark.asyncio + async def test_load_servers_from_config_reads_all_token_exchange_fields(self): + """Every token-exchange setting is configurable through config.yaml as a top-level + key (the config counterpart of the REST/UI columns) and reaches the resolver spec; + omitted keys resolve to their documented defaults. token_exchange servers need no + oauth2_flow (that requirement is oauth2-only).""" + from litellm.types.mcp import DEFAULT_SUBJECT_TOKEN_TYPE + + manager = MCPServerManager() + config = { + "te_full": { + "url": "https://up.example.com/mcp", + "transport": MCPTransport.http, + "auth_type": MCPAuth.oauth2_token_exchange, + "token_exchange_endpoint": "https://idp.example.com/oauth2/token", + "audience": "api://upstream", + "subject_token_type": "urn:ietf:params:oauth:token-type:jwt", + "token_exchange_profile": "entra_obo", + "client_id": "cid", + "client_secret": "csec", + "scopes": ["api://upstream/.default"], + }, + "te_minimal": { + "url": "https://up2.example.com/mcp", + "transport": MCPTransport.http, + "auth_type": MCPAuth.oauth2_token_exchange, + "token_exchange_endpoint": "https://idp2.example.com/oauth2/token", + "client_id": "cid2", + "client_secret": "csec2", + }, + } + + with patch.object(manager, "_descovery_metadata", new=AsyncMock(return_value=None)): + await manager.load_servers_from_config(config) + + by_name = {s.server_name: s for s in manager.config_mcp_servers.values()} + + full = by_name["te_full"] + assert full.token_exchange_endpoint == "https://idp.example.com/oauth2/token" + assert full.audience == "api://upstream" + assert full.subject_token_type == "urn:ietf:params:oauth:token-type:jwt" + assert full.token_exchange_profile == "entra_obo" + + minimal = by_name["te_minimal"] + assert minimal.audience is None + assert minimal.subject_token_type == DEFAULT_SUBJECT_TOKEN_TYPE + assert minimal.token_exchange_profile == "rfc8693" + + from litellm.proxy._experimental.mcp_server.outbound_credentials.adapter import to_server_spec + + spec = to_server_spec(full) + assert spec is not None + assert spec.config.token_exchange_endpoint == "https://idp.example.com/oauth2/token" + assert spec.config.profile == "entra_obo" + + minimal_spec = to_server_spec(minimal) + assert minimal_spec is not None + assert minimal_spec.config.subject_token_type == DEFAULT_SUBJECT_TOKEN_TYPE + assert minimal_spec.config.profile == "rfc8693" + @pytest.mark.asyncio async def test_config_oauth_initialize_tool_name_to_mcp_server_name_mapping(self): manager = MCPServerManager() @@ -1624,6 +1841,7 @@ class TestMCPServerManager: "url": "https://example.com/mcp", "transport": MCPTransport.http, "auth_type": MCPAuth.oauth2, + "oauth2_flow": "authorization_code", "scopes": ["config"], "authorization_url": "https://config.example.com/auth", } @@ -3998,6 +4216,24 @@ class TestMCPServerTimestamps: assert table.created_at is None assert table.updated_at is None + def test_build_mcp_server_table_preserves_tool_overrides(self): + """Tool display/description overrides must survive registry -> API table conversion.""" + manager = MCPServerManager() + server = MCPServer( + server_id="override-server", + name="deepwiki", + server_name="deepwiki_mcp", + url="https://example.com/mcp", + transport=MCPTransport.http, + tool_name_to_display_name={"read_wiki_structure": "browse_docs"}, + tool_name_to_description={"read_wiki_structure": "Browse repository documentation"}, + ) + + table = manager._build_mcp_server_table(server) + + assert table.tool_name_to_display_name == {"read_wiki_structure": "browse_docs"} + assert table.tool_name_to_description == {"read_wiki_structure": "Browse repository documentation"} + @pytest.mark.asyncio async def test_round_trip_timestamps_preserved(self): """Timestamps survive the full round-trip: LiteLLM_MCPServerTable -> MCPServer -> LiteLLM_MCPServerTable.""" @@ -4211,6 +4447,177 @@ class TestMCPServerTimestamps: assert "0.01s" in exc_info.value.detail["message"] +class TestMCPServerTokenExchangeColumns: + """Token-exchange (RFC 8693) config persists through the dedicated columns added for the + create/update REST + DB path, mirroring how ``token_url`` is stored. The credentials JSON + blob is kept as a read-fallback so servers persisted before the columns existed still load.""" + + @pytest.mark.asyncio + async def test_build_mcp_server_from_table_reads_token_exchange_columns(self): + """The DB->runtime loader must read the three fields from the dedicated columns. Before the + columns existed it only read the credentials blob, so column values would be dropped.""" + manager = MCPServerManager() + + table_record = LiteLLM_MCPServerTable( + server_id="te-cols", + server_name="te_cols", + url="https://upstream.example.com/mcp", + transport=MCPTransport.http, + auth_type=MCPAuth.oauth2_token_exchange, + token_exchange_endpoint="https://idp.example.com/oauth2/token", + audience="https://upstream.example.com", + subject_token_type="urn:ietf:params:oauth:token-type:jwt", + ) + + mcp_server = await manager.build_mcp_server_from_table(table_record) + + assert mcp_server.token_exchange_endpoint == "https://idp.example.com/oauth2/token" + assert mcp_server.audience == "https://upstream.example.com" + assert mcp_server.subject_token_type == "urn:ietf:params:oauth:token-type:jwt" + + @pytest.mark.asyncio + async def test_build_mcp_server_from_table_falls_back_to_credentials_blob(self): + """Backwards compatibility: a server whose token-exchange config lives only in the + credentials blob (no columns) must still load with those values.""" + manager = MCPServerManager() + + table_record = LiteLLM_MCPServerTable( + server_id="te-blob", + server_name="te_blob", + url="https://upstream.example.com/mcp", + transport=MCPTransport.http, + auth_type=MCPAuth.oauth2_token_exchange, + credentials={ + "token_exchange_endpoint": "https://idp.example.com/legacy/token", + "audience": "legacy-audience", + "subject_token_type": "urn:ietf:params:oauth:token-type:saml2", + }, + ) + + mcp_server = await manager.build_mcp_server_from_table(table_record) + + assert mcp_server.token_exchange_endpoint == "https://idp.example.com/legacy/token" + assert mcp_server.audience == "legacy-audience" + assert mcp_server.subject_token_type == "urn:ietf:params:oauth:token-type:saml2" + + @pytest.mark.asyncio + async def test_build_mcp_server_from_table_subject_token_type_defaults(self): + """subject_token_type falls back to the RFC 8693 access_token URN when unset.""" + manager = MCPServerManager() + + table_record = LiteLLM_MCPServerTable( + server_id="te-default", + server_name="te_default", + url="https://upstream.example.com/mcp", + transport=MCPTransport.http, + auth_type=MCPAuth.oauth2_token_exchange, + token_exchange_endpoint="https://idp.example.com/oauth2/token", + ) + + mcp_server = await manager.build_mcp_server_from_table(table_record) + + assert mcp_server.subject_token_type == "urn:ietf:params:oauth:token-type:access_token" + + @pytest.mark.asyncio + async def test_round_trip_token_exchange_columns_preserved(self): + """The three fields survive LiteLLM_MCPServerTable -> MCPServer -> LiteLLM_MCPServerTable. + Before the table builder wrote them back, a registry round-trip dropped them.""" + manager = MCPServerManager() + + table_record = LiteLLM_MCPServerTable( + server_id="te-rt", + server_name="te_rt", + url="https://upstream.example.com/mcp", + transport=MCPTransport.http, + auth_type=MCPAuth.oauth2_token_exchange, + token_exchange_endpoint="https://idp.example.com/oauth2/token", + audience="https://upstream.example.com", + subject_token_type="urn:ietf:params:oauth:token-type:jwt", + ) + + mcp_server = await manager.build_mcp_server_from_table(table_record) + rebuilt_table = manager._build_mcp_server_table(mcp_server) + + assert rebuilt_table.token_exchange_endpoint == "https://idp.example.com/oauth2/token" + assert rebuilt_table.audience == "https://upstream.example.com" + assert rebuilt_table.subject_token_type == "urn:ietf:params:oauth:token-type:jwt" + + @pytest.mark.asyncio + async def test_build_mcp_server_from_table_reads_token_exchange_profile_column(self): + """The profile dialect selector (rfc8693 vs entra_obo) is read from its dedicated column + so a server created via the REST API/UI as entra_obo resolves to the Entra dialect.""" + manager = MCPServerManager() + + table_record = LiteLLM_MCPServerTable( + server_id="te-profile", + server_name="te_profile", + url="https://upstream.example.com/mcp", + transport=MCPTransport.http, + auth_type=MCPAuth.oauth2_token_exchange, + token_exchange_endpoint="https://login.microsoftonline.com/tenant/oauth2/v2.0/token", + token_exchange_profile="entra_obo", + ) + + mcp_server = await manager.build_mcp_server_from_table(table_record) + + assert mcp_server.token_exchange_profile == "entra_obo" + + @pytest.mark.asyncio + async def test_build_mcp_server_from_table_token_exchange_profile_defaults_rfc8693(self): + """token_exchange_profile falls back to rfc8693 when neither column nor blob sets it.""" + manager = MCPServerManager() + + table_record = LiteLLM_MCPServerTable( + server_id="te-profile-default", + server_name="te_profile_default", + url="https://upstream.example.com/mcp", + transport=MCPTransport.http, + auth_type=MCPAuth.oauth2_token_exchange, + token_exchange_endpoint="https://idp.example.com/oauth2/token", + ) + + mcp_server = await manager.build_mcp_server_from_table(table_record) + + assert mcp_server.token_exchange_profile == "rfc8693" + + @pytest.mark.asyncio + async def test_build_mcp_server_from_table_token_exchange_profile_blob_fallback(self): + """Backwards compatibility: a server with the profile only in the credentials blob still loads.""" + manager = MCPServerManager() + + table_record = LiteLLM_MCPServerTable( + server_id="te-profile-blob", + server_name="te_profile_blob", + url="https://upstream.example.com/mcp", + transport=MCPTransport.http, + auth_type=MCPAuth.oauth2_token_exchange, + credentials={"token_exchange_profile": "entra_obo"}, + ) + + mcp_server = await manager.build_mcp_server_from_table(table_record) + + assert mcp_server.token_exchange_profile == "entra_obo" + + @pytest.mark.asyncio + async def test_round_trip_token_exchange_profile_preserved(self): + """token_exchange_profile survives LiteLLM_MCPServerTable -> MCPServer -> LiteLLM_MCPServerTable.""" + manager = MCPServerManager() + + table_record = LiteLLM_MCPServerTable( + server_id="te-profile-rt", + server_name="te_profile_rt", + url="https://upstream.example.com/mcp", + transport=MCPTransport.http, + auth_type=MCPAuth.oauth2_token_exchange, + token_exchange_profile="entra_obo", + ) + + mcp_server = await manager.build_mcp_server_from_table(table_record) + rebuilt_table = manager._build_mcp_server_table(mcp_server) + + assert rebuilt_table.token_exchange_profile == "entra_obo" + + class TestInternalDelegatePkceWarningLog: @pytest.mark.asyncio async def test_build_mcp_server_logs_on_internal_delegate_interactive(self, caplog): @@ -5798,6 +6205,83 @@ class TestOBOCallToolRetry: assert first.attempts == 1 and retry.attempts == 1 +class TestOBOConcurrencyLimit: + """OBO (token_exchange) tool calls must honor the server's max_concurrent_requests. + + Regression: the token_exchange dispatch built its coroutine outside + _limit_outbound_concurrency, so OBO calls skipped the per-server semaphore the + non-OBO path enforces and a caller could exceed the admin-configured cap. + """ + + @pytest.mark.asyncio + async def test_obo_dispatch_respects_max_concurrent_requests(self): + max_concurrent = 2 + overflow = 3 + server = MCPServer( + server_id="obo-concurrency", + name="obo", + url="https://upstream.example/mcp", + transport=MCPTransport.sse, + auth_type=MCPAuth.oauth2_token_exchange, + token_exchange_endpoint="https://idp.example.com/token", + client_id="cid", + client_secret="csec", + max_concurrent_requests=max_concurrent, + ) + + release = asyncio.Event() + inflight = {"current": 0, "peak": 0} + + class _ConcurrencyRecordingClient: + async def call_tool(self, params, host_progress_callback=None, raise_on_error=False): + inflight["current"] += 1 + inflight["peak"] = max(inflight["peak"], inflight["current"]) + try: + await release.wait() + finally: + inflight["current"] -= 1 + return CallToolResult(content=[], isError=False) + + manager = MCPServerManager() + manager._create_mcp_client = AsyncMock(return_value=_ConcurrencyRecordingClient()) + + async def _dispatch(): + return await manager._call_regular_mcp_tool( + mcp_server=server, + original_tool_name="do_thing", + arguments={}, + tasks=[], + mcp_auth_header=None, + mcp_server_auth_headers=None, + oauth2_headers={"Authorization": "Bearer subject-jwt"}, + raw_headers=None, + proxy_logging_obj=None, + ) + + callers = [asyncio.create_task(_dispatch()) for _ in range(max_concurrent + overflow)] + + stable = 0 + previous = -1 + for _ in range(1000): + await asyncio.sleep(0) + current = inflight["current"] + if current == previous: + stable += 1 + if current > 0 and stable >= 10: + break + else: + stable = 0 + previous = current + + peak_while_blocked = inflight["peak"] + release.set() + results = await asyncio.gather(*callers) + + assert peak_while_blocked == max_concurrent + assert inflight["current"] == 0 + assert all(result.isError is False for result in results) + + class TestOBOEndpointDiscovery: """An oauth2_token_exchange server with no configured token endpoint discovers it (RFC 9728 -> RFC 8414) like the oauth2 flow does; an explicitly configured endpoint skips discovery.""" @@ -5913,3 +6397,238 @@ class TestOBOEndpointDiscovery: if __name__ == "__main__": pytest.main([__file__]) + + +@pytest.mark.asyncio +async def test_preflight_challenge_carries_step_up_error_and_claims(): + """An Entra Conditional Access rejection must surface error=insufficient_claims plus the base64 + claims in the single-server 401 challenge, so an MSAL-family client can drive the step-up and retry.""" + import base64 + + from litellm.proxy._experimental.mcp_server.outbound_credentials.result import Error + from litellm.proxy._experimental.mcp_server.outbound_credentials.types import CredError + + claims = '{"access_token":{"acrs":{"essential":true,"value":"c1"}}}' + + class _FakeProvider: + async def resolve_credentials(self, subject, server): + return Error(CredError.of_unauthorized("step-up required", claims=claims)) + + manager = MCPServerManager(cred_provider=_FakeProvider()) + server = MCPServer( + server_id="te-ca", + name="te-ca-server", + url="https://up.example.com/mcp", + transport=MCPTransport.http, + auth_type=MCPAuth.oauth2_token_exchange, + token_exchange_endpoint="https://idp.example.com/token", + client_id="cid", + client_secret="csec", + ) + with pytest.raises(HTTPException) as exc_info: + await manager.preflight_token_exchange( + server=server, + oauth2_headers={"Authorization": "Bearer subj"}, + user_api_key_auth=None, + ) + assert exc_info.value.status_code == 401 + headers = exc_info.value.headers or {} + www = headers.get("WWW-Authenticate") or headers.get("www-authenticate") or "" + assert 'error="insufficient_claims"' in www + assert base64.b64encode(claims.encode()).decode() in www + assert "resource_metadata" in www + + +@pytest.mark.asyncio +async def test_aggregate_list_still_absorbs_step_up_challenged_server(): + """A step-up (claims-bearing) 401 from one server must not change the aggregate contract: the + multi-server listing still absorbs it and returns the healthy servers' tools.""" + from litellm.proxy._experimental.mcp_server.exceptions import MCPUpstreamAuthError + + manager = MCPServerManager() + good = MCPServer(server_id="good", name="good", transport=MCPTransport.http) + ca = MCPServer(server_id="ca", name="ca", transport=MCPTransport.http) + manager.get_allowed_mcp_servers = AsyncMock(return_value=["good", "ca"]) + manager.get_mcp_server_by_id = MagicMock(side_effect=lambda server_id: {"good": good, "ca": ca}.get(server_id)) + good_tool = MCPTool(name="good-do_thing", description="do thing", inputSchema={}) + + async def fake_get_tools(server, **kwargs): + if server.server_id == "ca": + raise MCPUpstreamAuthError( + status_code=401, + www_authenticate=('Bearer resource_metadata="/x", error="insufficient_claims", claims="eyJhIjoxfQ=="'), + server_name="ca", + ) + return [good_tool] + + manager._get_tools_from_server = fake_get_tools + + result = await manager.list_tools() + + assert [t.name for t in result] == ["good-do_thing"] + + +class TestDbBuildReadsOauth2FlowColumnVerbatim: + """The DB build must not re-infer the flow from field shape: rows are stamped at + write time and by the startup backfill, and a DCR-registered interactive server + has the exact M2M shape (client creds + token_url, no persisted authorization_url) + whenever discovery is unavailable. Inference survives only for config-loaded + servers and the request-time backstop in _get_allowed_mcp_servers.""" + + def _row(self, oauth2_flow): + return LiteLLM_MCPServerTable( + server_id="flow-column-row", + alias="flow_column_row", + description="", + url="https://up.example.com/mcp", + transport=MCPTransport.http, + auth_type=MCPAuth.oauth2, + oauth2_flow=oauth2_flow, + token_url="https://idp.example.com/token", + credentials={"client_id": "cid", "client_secret": "csec"}, + created_at=datetime.now(), + updated_at=datetime.now(), + ) + + @pytest.mark.asyncio + async def test_null_flow_m2m_shape_row_is_not_inferred_m2m(self): + manager = MCPServerManager() + with patch.object(manager, "_descovery_metadata", new=AsyncMock(return_value=None)): + built = await manager.build_mcp_server_from_table(self._row(None), credentials_are_encrypted=False) + + assert built.oauth2_flow is None + assert built.has_client_credentials is False + assert built.needs_user_oauth_token is True + + @pytest.mark.asyncio + async def test_explicit_flow_column_is_read_verbatim(self): + manager = MCPServerManager() + with patch.object(manager, "_descovery_metadata", new=AsyncMock(return_value=None)): + built = await manager.build_mcp_server_from_table( + self._row("client_credentials"), credentials_are_encrypted=False + ) + + assert built.oauth2_flow == "client_credentials" + assert built.has_client_credentials is True + assert built.needs_user_oauth_token is False + + @pytest.mark.asyncio + async def test_authorization_code_flow_column_is_read_verbatim(self): + manager = MCPServerManager() + with patch.object(manager, "_descovery_metadata", new=AsyncMock(return_value=None)): + built = await manager.build_mcp_server_from_table( + self._row("authorization_code"), credentials_are_encrypted=False + ) + + assert built.oauth2_flow == "authorization_code" + assert built.has_client_credentials is False + assert built.needs_user_oauth_token is True + + +class TestRequestTimeOauth2FlowBackstop: + """The single request-time resolution helpers every security site shares: + effective_oauth2_flow (the enum/boolean decision) and + resolve_oauth2_flow_for_request (the egress object copy).""" + + def _oauth2_server(self, **overrides): + base = dict( + server_id="flow-server", + name="flow_server", + transport=MCPTransport.http, + auth_type=MCPAuth.oauth2, + ) + base.update(overrides) + return MCPServer(**base) + + def test_effective_flow_stamped_values_returned_verbatim(self): + assert ( + MCPServerManager.effective_oauth2_flow(self._oauth2_server(oauth2_flow="client_credentials")) + == "client_credentials" + ) + assert ( + MCPServerManager.effective_oauth2_flow(self._oauth2_server(oauth2_flow="authorization_code")) + == "authorization_code" + ) + + def test_effective_flow_null_m2m_shape_resolves_client_credentials(self): + server = self._oauth2_server( + oauth2_flow=None, + client_id="cid", + client_secret="csecret", + token_url="https://idp.example.com/token", + ) + assert MCPServerManager.effective_oauth2_flow(server) == "client_credentials" + + def test_effective_flow_null_pure_pkce_resolves_none(self): + assert MCPServerManager.effective_oauth2_flow(self._oauth2_server(oauth2_flow=None)) is None + + def test_resolve_for_request_stamped_row_is_unchanged_identity(self): + server = self._oauth2_server(oauth2_flow="client_credentials") + assert MCPServerManager.resolve_oauth2_flow_for_request(server) is server + + def test_resolve_for_request_null_pure_pkce_is_unchanged_identity(self): + server = self._oauth2_server(oauth2_flow=None) + assert MCPServerManager.resolve_oauth2_flow_for_request(server) is server + + def test_resolve_for_request_null_m2m_shape_copies_client_credentials(self, caplog): + import logging + + server = self._oauth2_server( + oauth2_flow=None, + client_id="cid", + client_secret="csecret", + token_url="https://idp.example.com/token", + ) + with caplog.at_level(logging.WARNING, logger="LiteLLM"): + resolved = MCPServerManager.resolve_oauth2_flow_for_request(server) + + assert resolved is not server + assert resolved.oauth2_flow == "client_credentials" + assert server.oauth2_flow is None # original untouched + # Finding 2: the warning must NOT promise the backfill will stamp this row. + joined = " ".join(caplog.messages) + assert "no persisted oauth2_flow" in joined + assert "next proxy boot" not in joined + assert "will NOT self-heal" in joined + + +def test_build_mcp_server_table_carries_oauth2_flow(): + """GET /v1/mcp/server (list and by-id) serves registry servers through this + conversion; dropping oauth2_flow here blinds the dashboard to the persisted + flow, so the edit page cannot prefill and M2M gating never activates.""" + manager = MCPServerManager() + server = MCPServer( + server_id="flow-table-server", + name="flow_table_server", + server_name="flow_table_server", + alias="flow_table_server", + url="https://up.example.com/mcp", + transport=MCPTransport.http, + auth_type=MCPAuth.oauth2, + oauth2_flow="client_credentials", + ) + + table = manager._build_mcp_server_table(server) + + assert table.oauth2_flow == "client_credentials" + + +def test_build_mcp_server_table_carries_null_oauth2_flow(): + """A legacy row the backfill left unstamped must surface as oauth2_flow=None in + the GET response, so the dashboard maps it to undefined and prompts the admin to + choose a flow rather than showing a guessed default.""" + manager = MCPServerManager() + server = MCPServer( + server_id="null-flow-server", + name="null_flow_server", + server_name="null_flow_server", + alias="null_flow_server", + url="https://up.example.com/mcp", + transport=MCPTransport.http, + auth_type=MCPAuth.oauth2, + oauth2_flow=None, + ) + + table = manager._build_mcp_server_table(server) + + assert table.oauth2_flow is None diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_sigv4_auth.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_sigv4_auth.py index c2164a9f19f..f5a48b5ec34 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_sigv4_auth.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_sigv4_auth.py @@ -856,6 +856,10 @@ class TestSigV4BuildFromTable: table_record.tool_name_to_description = None table_record.byok_api_key_help_url = None table_record.oauth2_flow = None + table_record.token_exchange_endpoint = None + table_record.audience = None + table_record.subject_token_type = None + table_record.token_exchange_profile = None table_record.instructions = None table_record.source_url = None @@ -915,6 +919,10 @@ class TestSigV4BuildFromTable: table_record.tool_name_to_description = None table_record.byok_api_key_help_url = None table_record.oauth2_flow = None + table_record.token_exchange_endpoint = None + table_record.audience = None + table_record.subject_token_type = None + table_record.token_exchange_profile = None table_record.instructions = None table_record.source_url = None diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_oauth2_flow_backfill.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_oauth2_flow_backfill.py new file mode 100644 index 00000000000..c1239c228aa --- /dev/null +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_oauth2_flow_backfill.py @@ -0,0 +1,234 @@ +""" +Tests for the startup oauth2_flow backfill. + +Legacy oauth2 rows with a null oauth2_flow are classified once, at rest, using +signals read-time inference never had (per-user token rows first), and the +result is persisted so the read path never infers again. The signal order is +the spec, and so is the refusal to stamp client_credentials: the M2M credential +shape is shared by DCR-registered interactive servers whose authorization +endpoint lives only in discovery, so ambiguous rows are left unstamped for a +human to assert rather than being permanently mislabeled M2M. +""" + +import base64 +import json +from types import SimpleNamespace +from unittest.mock import AsyncMock, MagicMock + +import pytest + +from litellm.proxy._experimental.mcp_server.oauth2_flow_backfill import ( + backfill_null_oauth2_flows, + classify_null_flow_row, +) + + +def test_classify_per_user_tokens_beat_m2m_shape(): + """The DCR trap row: creds + token_url, no authorization_url, but a user has + signed in. Tokens are definitive; the M2M shape must not win.""" + flow, rule = classify_null_flow_row( + has_per_user_tokens=True, + authorization_url=None, + registration_url=None, + token_url="https://idp.example.com/token", + credentials={"client_id": "cid", "client_secret": "csecret"}, + ) + assert flow == "authorization_code" + assert rule == "per_user_tokens" + + +def test_classify_authorization_url_beats_m2m_shape(): + flow, rule = classify_null_flow_row( + has_per_user_tokens=False, + authorization_url="https://idp.example.com/authorize", + registration_url=None, + token_url="https://idp.example.com/token", + credentials={"client_id": "cid", "client_secret": "csecret"}, + ) + assert flow == "authorization_code" + assert rule == "authorization_url" + + +def test_classify_registration_url_beats_m2m_shape(): + """A registration endpoint means DCR, and DCR exists to mint interactive + clients; an abandoned-DCR row (no sign-in yet) must not be stamped M2M.""" + flow, rule = classify_null_flow_row( + has_per_user_tokens=False, + authorization_url=None, + registration_url="https://idp.example.com/register", + token_url="https://idp.example.com/token", + credentials={"client_id": "cid", "client_secret": "csecret"}, + ) + assert flow == "authorization_code" + assert rule == "registration_url" + + +def test_classify_m2m_shape_is_ambiguous_and_unstamped(): + """The M2M shape alone must never stamp client_credentials: a DCR-registered + interactive server that nobody signed into yet has the identical shape, and a + wrong M2M stamp would permanently route its per-user traffic through the + proxy's stored client credential.""" + flow, rule = classify_null_flow_row( + has_per_user_tokens=False, + authorization_url=None, + registration_url=None, + token_url="https://idp.example.com/token", + credentials={"client_id": "cid", "client_secret": "csecret"}, + ) + assert flow is None + assert rule == "ambiguous_m2m_shape" + + +def test_classify_partial_credentials_default_interactive(): + """token_url without a full credential pair is not the M2M shape.""" + flow, rule = classify_null_flow_row( + has_per_user_tokens=False, + authorization_url=None, + registration_url=None, + token_url="https://idp.example.com/token", + credentials={"client_id": "cid"}, + ) + assert flow == "authorization_code" + assert rule == "interactive_default" + + +def test_classify_bare_row_default_interactive(): + flow, rule = classify_null_flow_row( + has_per_user_tokens=False, + authorization_url=None, + registration_url=None, + token_url=None, + credentials=None, + ) + assert flow == "authorization_code" + assert rule == "interactive_default" + + +def _row(server_id, *, authorization_url=None, registration_url=None, token_url=None, credentials=None): + return SimpleNamespace( + server_id=server_id, + authorization_url=authorization_url, + registration_url=registration_url, + token_url=token_url, + credentials=credentials, + ) + + +def _oauth_token_row(server_id): + payload = json.dumps({"type": "oauth2", "access_token": "tok", "connected_at": "2026-07-01T00:00:00Z"}) + return SimpleNamespace( + server_id=server_id, + user_id="u1", + credential_b64=base64.urlsafe_b64encode(payload.encode()).decode(), + ) + + +def _byok_key_row(server_id): + return SimpleNamespace( + server_id=server_id, + user_id="u1", + credential_b64=base64.urlsafe_b64encode(b"sk-user-supplied-upstream-key").decode(), + ) + + +def _mock_prisma(null_rows, token_rows): + mock_prisma = MagicMock() + mock_prisma.db.litellm_mcpservertable.find_many = AsyncMock(return_value=null_rows) + mock_prisma.db.litellm_mcpservertable.update_many = AsyncMock(return_value=MagicMock()) + mock_prisma.db.litellm_mcpusercredentials.find_many = AsyncMock(return_value=token_rows) + return mock_prisma + + +@pytest.mark.asyncio +async def test_backfill_only_targets_null_flow_oauth2_rows(): + """The where clause is the guard that explicit and non-oauth2 rows are never touched.""" + mock_prisma = _mock_prisma([], []) + + counts = await backfill_null_oauth2_flows(mock_prisma) + + assert counts == {} + mock_prisma.db.litellm_mcpservertable.find_many.assert_awaited_once_with( + where={"auth_type": "oauth2", "oauth2_flow": None}, + ) + mock_prisma.db.litellm_mcpusercredentials.find_many.assert_not_awaited() + mock_prisma.db.litellm_mcpservertable.update_many.assert_not_awaited() + + +@pytest.mark.asyncio +async def test_backfill_stamps_rows_and_reports_rule_counts(): + dcr_trap_row = _row( + "signed_in_dcr", + token_url="https://idp.example.com/token", + credentials={"client_id": "cid", "client_secret": "csecret"}, + ) + m2m_row = _row( + "legacy_m2m", + token_url="https://idp.example.com/token", + credentials={"client_id": "cid", "client_secret": "csecret"}, + ) + interactive_row = _row("legacy_interactive", authorization_url="https://idp.example.com/authorize") + + mock_prisma = _mock_prisma( + [dcr_trap_row, m2m_row, interactive_row], + [_oauth_token_row("signed_in_dcr")], + ) + + counts = await backfill_null_oauth2_flows(mock_prisma) + + assert counts == {"per_user_tokens": 1, "ambiguous_m2m_shape": 1, "authorization_url": 1} + + mock_prisma.db.litellm_mcpservertable.update_many.assert_awaited_once() + call = mock_prisma.db.litellm_mcpservertable.update_many.await_args + assert sorted(call.kwargs["where"]["server_id"]["in"]) == ["legacy_interactive", "signed_in_dcr"] + assert "oauth2_flow" in call.kwargs["where"] and call.kwargs["where"]["oauth2_flow"] is None + assert call.kwargs["data"] == {"oauth2_flow": "authorization_code", "updated_by": "oauth2_flow_backfill"} + + +@pytest.mark.asyncio +async def test_backfill_handles_json_string_credentials(): + """JSON-string credential blobs must decode: the M2M shape is recognized (and + therefore deliberately left unstamped) rather than misread as credential-less.""" + m2m_row = _row( + "json_creds_m2m", + token_url="https://idp.example.com/token", + credentials='{"client_id": "cid", "client_secret": "csecret"}', + ) + mock_prisma = _mock_prisma([m2m_row], []) + + counts = await backfill_null_oauth2_flows(mock_prisma) + + assert counts == {"ambiguous_m2m_shape": 1} + mock_prisma.db.litellm_mcpservertable.update_many.assert_not_awaited() + + +@pytest.mark.asyncio +async def test_backfill_treats_undecodable_credentials_as_absent(): + row = _row( + "corrupt_creds", + token_url="https://idp.example.com/token", + credentials="not-json", + ) + mock_prisma = _mock_prisma([row], []) + + counts = await backfill_null_oauth2_flows(mock_prisma) + + assert counts == {"interactive_default": 1} + + +@pytest.mark.asyncio +async def test_backfill_byok_key_rows_are_not_sign_in_proof(): + """BYOK API keys live in the same table as per-user OAuth tokens; a bare key row + must not satisfy the per_user_tokens rule, or a BYOK-flavored M2M-shaped server + would be permanently stamped authorization_code. Only rows whose payload decodes + as a type oauth2 token count.""" + byok_shaped_row = _row( + "byok_m2m_shape", + token_url="https://idp.example.com/token", + credentials={"client_id": "cid", "client_secret": "csecret"}, + ) + mock_prisma = _mock_prisma([byok_shaped_row], [_byok_key_row("byok_m2m_shape")]) + + counts = await backfill_null_oauth2_flows(mock_prisma) + + assert counts == {"ambiguous_m2m_shape": 1} + mock_prisma.db.litellm_mcpservertable.update_many.assert_not_awaited() diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_rest_endpoints.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_rest_endpoints.py index fb4eee54e1d..e114f46e866 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_rest_endpoints.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_rest_endpoints.py @@ -1050,6 +1050,292 @@ class TestListToolsRestAPI: assert result["error"] == "unexpected_error" assert "access_denied" in result["message"] + async def test_mcp_server_name_query_param_resolves_to_server(self, monkeypatch): + """mcp_server_name is a name-based alias for server_id: it should + resolve to the matching server and scope the response to it.""" + from litellm.proxy._experimental.mcp_server.server import MCPServer + from litellm.types.mcp import MCPTransport + + stub_server = MCPServer( + server_id="uuid-abc-123", + name="my-server", + transport=MCPTransport.sse, + ) + stub_server.alias = "my-server" + stub_server.server_name = "my-server" + stub_server.available_on_public_internet = True + stub_server.allowed_tools = None + stub_server.mcp_info = {"server_name": "my-server"} + + async def fake_contexts(user_api_key_auth): + return [user_api_key_auth] + + async def fake_get_allowed_mcp_servers(*args, **kwargs): + return ["uuid-abc-123"] + + captured = {"called": False, "server_arg": None} + + async def fake_get_tools( + server, + server_auth_header, + raw_headers=None, + user_api_key_auth=None, + extra_headers=None, + apply_tool_filters=True, + ): + captured["called"] = True + captured["server_arg"] = server + return ["tool-x"] + + monkeypatch.setattr( + rest_endpoints, + "build_effective_auth_contexts", + fake_contexts, + raising=False, + ) + monkeypatch.setattr( + rest_endpoints.global_mcp_server_manager, + "get_allowed_mcp_servers", + fake_get_allowed_mcp_servers, + raising=False, + ) + monkeypatch.setattr( + rest_endpoints.global_mcp_server_manager, + "get_mcp_server_by_name", + lambda name: stub_server if name == "my-server" else None, + raising=False, + ) + monkeypatch.setattr( + rest_endpoints.global_mcp_server_manager, + "get_mcp_server_by_id", + lambda sid: stub_server if sid == "uuid-abc-123" else None, + raising=False, + ) + monkeypatch.setattr( + rest_endpoints, + "_get_tools_for_single_server", + fake_get_tools, + raising=False, + ) + + request = _build_request(path="/mcp-rest/tools/list", method="GET") + result = await rest_endpoints.list_tool_rest_api( + request, + server_id=None, + mcp_server_name="my-server", + user_api_key_dict=UserAPIKeyAuth(), + ) + + assert captured["called"] is True + assert captured["server_arg"] is stub_server + assert result["tools"] == ["tool-x"] + assert result["error"] is None + + async def test_mcp_server_name_filter_uses_real_catalog_with_tool_search(self, monkeypatch): + from litellm.proxy._experimental.mcp_server.server import MCPServer + from litellm.proxy._types import LiteLLM_ObjectPermissionTable + from litellm.types.mcp import MCPTransport + + stub_server = MCPServer( + server_id="uuid-search-123", + name="search-server", + transport=MCPTransport.sse, + ) + stub_server.alias = "search-server" + stub_server.server_name = "search-server" + stub_server.available_on_public_internet = True + stub_server.allowed_tools = None + stub_server.mcp_info = {"server_name": "search-server"} + user_api_key_dict = UserAPIKeyAuth( + object_permission=LiteLLM_ObjectPermissionTable( + object_permission_id="search-scope", + mcp_tool_search_enabled=True, + mcp_servers=["uuid-search-123"], + ) + ) + + async def fake_contexts(user_api_key_auth): + return [user_api_key_auth] + + async def fake_get_allowed_mcp_servers(*args, **kwargs): + return ["uuid-search-123"] + + async def fake_get_tools( + server, + server_auth_header, + raw_headers=None, + user_api_key_auth=None, + extra_headers=None, + apply_tool_filters=True, + ): + return ["scoped-tool"] + + monkeypatch.setattr( + rest_endpoints, + "build_effective_auth_contexts", + fake_contexts, + raising=False, + ) + monkeypatch.setattr( + rest_endpoints.global_mcp_server_manager, + "get_allowed_mcp_servers", + fake_get_allowed_mcp_servers, + raising=False, + ) + monkeypatch.setattr( + rest_endpoints.global_mcp_server_manager, + "get_mcp_server_by_name", + lambda name: stub_server if name == "search-server" else None, + raising=False, + ) + monkeypatch.setattr( + rest_endpoints.global_mcp_server_manager, + "get_mcp_server_by_id", + lambda server_id: stub_server if server_id == "uuid-search-123" else None, + raising=False, + ) + monkeypatch.setattr( + rest_endpoints, + "_get_tools_for_single_server", + fake_get_tools, + raising=False, + ) + + request = _build_request(path="/mcp-rest/tools/list", method="GET") + result = await rest_endpoints.list_tool_rest_api( + request, + server_id=None, + mcp_server_name="search-server", + user_api_key_dict=user_api_key_dict, + ) + + assert result["tools"] == ["scoped-tool"] + assert result["error"] is None + + async def test_toolset_name_query_param_scopes_to_toolset_servers(self, monkeypatch): + """toolset_name should resolve the toolset, apply its scope to the + caller's UserAPIKeyAuth via _apply_toolset_scope, and only list tools + from servers the scoped auth is allowed to see.""" + from litellm.proxy._types import LiteLLM_ObjectPermissionTable + + scoped_auth = UserAPIKeyAuth( + object_permission=LiteLLM_ObjectPermissionTable( + object_permission_id="toolset-scope", + mcp_tool_search_enabled=True, + mcp_servers=["toolset-server-1"], + ) + ) + + class StubToolset: + toolset_id = "toolset-1" + + class StubServer: + alias = "toolset-server-1" + server_name = "toolset-server-1" + name = "toolset-server-1" + allowed_tools = None + mcp_info = {"server_name": "toolset-server-1"} + available_on_public_internet = True + + stub_server = StubServer() + + async def fake_get_toolset_by_name_cached(prisma_client, toolset_name): + assert toolset_name == "research_tools" + return StubToolset() + + async def fake_apply_toolset_scope(user_api_key_auth, toolset_id): + assert toolset_id == "toolset-1" + return scoped_auth + + async def fake_contexts(user_api_key_auth): + return [user_api_key_auth] + + async def fake_get_allowed_mcp_servers(**kwargs): + assert kwargs["user_api_key_auth"] is scoped_auth + return ["toolset-server-1"] + + async def fake_get_tools(server, server_auth_header, *args, **kwargs): + return ["toolset-tool-1"] + + monkeypatch.setattr( + "litellm.proxy.utils.get_prisma_client_or_throw", + lambda *args, **kwargs: MagicMock(), + ) + monkeypatch.setattr( + rest_endpoints.global_mcp_server_manager, + "get_toolset_by_name_cached", + fake_get_toolset_by_name_cached, + raising=False, + ) + monkeypatch.setattr( + rest_endpoints, + "_apply_toolset_scope", + fake_apply_toolset_scope, + raising=False, + ) + monkeypatch.setattr( + rest_endpoints, + "build_effective_auth_contexts", + fake_contexts, + raising=False, + ) + monkeypatch.setattr( + rest_endpoints.global_mcp_server_manager, + "get_allowed_mcp_servers", + fake_get_allowed_mcp_servers, + raising=False, + ) + monkeypatch.setattr( + rest_endpoints.global_mcp_server_manager, + "get_mcp_server_by_id", + lambda server_id: stub_server if server_id == "toolset-server-1" else None, + raising=False, + ) + monkeypatch.setattr( + rest_endpoints, + "_get_tools_for_single_server", + fake_get_tools, + raising=False, + ) + + request = _build_request(path="/mcp-rest/tools/list", method="GET") + result = await rest_endpoints.list_tool_rest_api( + request, + server_id=None, + toolset_name="research_tools", + user_api_key_dict=UserAPIKeyAuth(), + ) + + assert result["tools"] == ["toolset-tool-1"] + assert result["error"] is None + + async def test_toolset_name_not_found_returns_error(self, monkeypatch): + async def fake_get_toolset_by_name_cached(prisma_client, toolset_name): + return None + + monkeypatch.setattr( + "litellm.proxy.utils.get_prisma_client_or_throw", + lambda *args, **kwargs: MagicMock(), + ) + monkeypatch.setattr( + rest_endpoints.global_mcp_server_manager, + "get_toolset_by_name_cached", + fake_get_toolset_by_name_cached, + raising=False, + ) + + request = _build_request(path="/mcp-rest/tools/list", method="GET") + with pytest.raises(HTTPException) as exc_info: + await rest_endpoints.list_tool_rest_api( + request, + server_id=None, + toolset_name="does-not-exist", + user_api_key_dict=UserAPIKeyAuth(), + ) + + assert exc_info.value.status_code == 404 + assert "does-not-exist" in str(exc_info.value.detail) + async def test_oauth2_user_token_injected_for_single_server(self, monkeypatch): """For a single-server OAuth2 request, _get_user_oauth_extra_headers is called and the returned headers are forwarded to _get_tools_for_single_server.""" diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_semantic_tool_filter.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_semantic_tool_filter.py index cebc265a148..82c0aa3ccda 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_semantic_tool_filter.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_semantic_tool_filter.py @@ -773,6 +773,251 @@ async def test_semantic_filter_hook_responses_api_name_collision(): print("✅ Responses API tool with MCP-matching name correctly classified as native") +@pytest.mark.asyncio +async def test_semantic_filter_hook_filters_expanded_litellm_proxy_tools(): + """ + Regression test (LIT-4214): litellm_proxy MCP references must be + semantically filtered after expansion, with real filter stats. + + Given: A /v1/responses-style request whose tools are a single + {"type": "mcp", "server_url": "litellm_proxy"} reference that + expands to 5 flat OpenAI function dicts + When: The hook processes the request + Then: The expanded tools go through the semantic filter (top_k=2) + and litellm_semantic_filter_stats reports pre/post counts, so + the x-litellm-semantic-filter header shows how many tools + were filtered out instead of silently forwarding all tools + with no stats. + """ + from litellm.proxy._experimental.mcp_server.semantic_tool_filter import ( + SemanticMCPToolFilter, + ) + from litellm.proxy.hooks.mcp_semantic_filter import SemanticToolFilterHook + from litellm.types.utils import Embedding, EmbeddingResponse + + mock_router = Mock() + + def mock_embedding_sync(*args, **kwargs): + return EmbeddingResponse( + data=[Embedding(embedding=[0.1] * 1536, index=0, object="embedding")], + model="text-embedding-3-small", + object="list", + usage={"prompt_tokens": 10, "total_tokens": 10}, + ) + + async def mock_embedding_async(*args, **kwargs): + return mock_embedding_sync() + + mock_router.embedding = mock_embedding_sync + mock_router.aembedding = mock_embedding_async + + filter_instance = SemanticMCPToolFilter( + embedding_model="text-embedding-3-small", + litellm_router_instance=mock_router, + top_k=2, + similarity_threshold=0.3, + enabled=True, + ) + + registry_tools = [ + MCPTool( + name=f"srv-tool_{i}", + description=f"Registry tool {i}", + inputSchema={"type": "object"}, + ) + for i in range(5) + ] + filter_instance._build_router(registry_tools) + + expanded_tools = [ + { + "type": "function", + "name": f"srv-tool_{i}", + "description": f"Registry tool {i}", + "parameters": {"type": "object", "properties": {}}, + } + for i in range(5) + ] + + hook = SemanticToolFilterHook(filter_instance) + hook._expand_mcp_tools = AsyncMock( # type: ignore[method-assign] + return_value=expanded_tools + ) + + data = { + "model": "gpt-4", + "input": [{"role": "user", "content": "Send an email", "type": "message"}], + "tools": [ + { + "type": "mcp", + "server_url": "litellm_proxy", + "require_approval": "never", + } + ], + "metadata": {}, + } + + result = await hook.async_pre_call_hook( + user_api_key_dict=Mock(), + cache=Mock(), + data=data, + call_type="aresponses", + ) + + assert result is not None, "Hook should return modified data" + filtered = result["tools"] + + assert len(filtered) <= 2, f"Expanded tools should be filtered to top_k=2, got {len(filtered)}" + assert len(filtered) < len(expanded_tools), ( + f"Hook must not forward all {len(expanded_tools)} expanded tools unfiltered, got {len(filtered)}" + ) + for tool in filtered: + assert tool in expanded_tools, "Filtered tools must be the original expanded tool dicts" + + assert ( + "litellm_semantic_filter_stats" in result["metadata"] + ), "Filter stats must be emitted for the litellm_proxy expansion path" + stats = result["metadata"]["litellm_semantic_filter_stats"] + total, selected = stats.split("->") + assert int(total) == 5, f"Stats 'from' should be pre-filter expanded count (5), got {total}" + assert int(selected) == len(filtered), f"Stats 'to' should match post-filter count, got {selected}" + + print(f"✅ Expanded litellm_proxy tools filtered: {len(expanded_tools)} -> {len(filtered)}, stats={stats}") + + +@pytest.mark.asyncio +async def test_semantic_filter_hook_filters_expanded_tools_with_string_input(): + """ + Responses API requests may pass ``input`` as a plain string; the + expanded-tool filtering must treat it as the user query instead of + crashing (which would silently disable MCP expansion). + """ + from litellm.proxy._experimental.mcp_server.semantic_tool_filter import ( + SemanticMCPToolFilter, + ) + from litellm.proxy.hooks.mcp_semantic_filter import SemanticToolFilterHook + from litellm.types.utils import Embedding, EmbeddingResponse + + mock_router = Mock() + + def mock_embedding_sync(*args, **kwargs): + return EmbeddingResponse( + data=[Embedding(embedding=[0.1] * 1536, index=0, object="embedding")], + model="text-embedding-3-small", + object="list", + usage={"prompt_tokens": 10, "total_tokens": 10}, + ) + + async def mock_embedding_async(*args, **kwargs): + return mock_embedding_sync() + + mock_router.embedding = mock_embedding_sync + mock_router.aembedding = mock_embedding_async + + filter_instance = SemanticMCPToolFilter( + embedding_model="text-embedding-3-small", + litellm_router_instance=mock_router, + top_k=2, + similarity_threshold=0.3, + enabled=True, + ) + + registry_tools = [ + MCPTool( + name=f"srv-tool_{i}", + description=f"Registry tool {i}", + inputSchema={"type": "object"}, + ) + for i in range(5) + ] + filter_instance._build_router(registry_tools) + + expanded_tools = [ + { + "type": "function", + "name": f"srv-tool_{i}", + "description": f"Registry tool {i}", + "parameters": {"type": "object", "properties": {}}, + } + for i in range(5) + ] + + hook = SemanticToolFilterHook(filter_instance) + + filtered = await hook._filter_expanded_tools( + data={"input": "Send an email"}, + expanded_tools=expanded_tools, + ) + + assert len(filtered) <= 2, f"String input must still drive semantic filtering, got {len(filtered)} tools" + + print(f"✅ String input filtered expanded tools: {len(expanded_tools)} -> {len(filtered)}") + + +@pytest.mark.asyncio +async def test_semantic_filter_hook_expansion_skips_filter_when_disabled(): + """ + When the filter is disabled at runtime (e.g. via the UI toggle), the + expansion path must forward all expanded tools and emit NO filter + stats, mirroring the generic path's enabled guard. + """ + from litellm.proxy._experimental.mcp_server.semantic_tool_filter import ( + SemanticMCPToolFilter, + ) + from litellm.proxy.hooks.mcp_semantic_filter import SemanticToolFilterHook + + filter_instance = SemanticMCPToolFilter( + embedding_model="text-embedding-3-small", + litellm_router_instance=Mock(), + top_k=2, + similarity_threshold=0.3, + enabled=False, + ) + + expanded_tools = [ + { + "type": "function", + "name": f"srv-tool_{i}", + "description": f"Registry tool {i}", + "parameters": {"type": "object", "properties": {}}, + } + for i in range(5) + ] + + hook = SemanticToolFilterHook(filter_instance) + hook._expand_mcp_tools = AsyncMock( # type: ignore[method-assign] + return_value=expanded_tools + ) + + data = { + "model": "gpt-4", + "input": [{"role": "user", "content": "Send an email", "type": "message"}], + "tools": [ + { + "type": "mcp", + "server_url": "litellm_proxy", + "require_approval": "never", + } + ], + "metadata": {}, + } + + result = await hook.async_pre_call_hook( + user_api_key_dict=Mock(), + cache=Mock(), + data=data, + call_type="aresponses", + ) + + assert result is not None, "Hook should still expand MCP references when the filter is disabled" + assert len(result["tools"]) == 5, f"All expanded tools must be forwarded when disabled, got {len(result['tools'])}" + assert ( + "litellm_semantic_filter_stats" not in result["metadata"] + ), "No filter stats may be emitted when the filter is disabled" + + print("✅ Disabled filter: expansion preserved, no spurious stats") + + @pytest.mark.asyncio async def test_semantic_filter_hook_preserves_tool_order(): """ @@ -1050,3 +1295,64 @@ class TestGetToolsByNames: ) assert matched == [] + + +@pytest.mark.asyncio +async def test_semantic_filter_headers_hook_emits_only_complete_tool_names(): + """ + Regression test for LIT-4215. + + The x-litellm-semantic-filter-tools header used to be sliced mid-name at + MAX_MCP_SEMANTIC_FILTER_TOOLS_HEADER_LENGTH with a "..." suffix, so the UI + rendered a chopped tool name as the last entry. The header must only ever + contain complete tool names, in their original order, within the cap. + """ + from litellm.constants import MAX_MCP_SEMANTIC_FILTER_TOOLS_HEADER_LENGTH + from litellm.proxy._experimental.mcp_server.semantic_tool_filter import ( + SemanticMCPToolFilter, + ) + from litellm.proxy.hooks.mcp_semantic_filter import SemanticToolFilterHook + + filter_instance = SemanticMCPToolFilter( + embedding_model="text-embedding-3-small", + litellm_router_instance=Mock(), + top_k=10, + similarity_threshold=0.3, + enabled=True, + ) + hook = SemanticToolFilterHook(filter_instance) + + tool_names = [f"metrics_mcp-very_long_tool_name_for_header_{i:02d}" for i in range(8)] + data = { + "metadata": { + "litellm_semantic_filter_stats": "40->8", + "litellm_semantic_filter_tools": ",".join(tool_names), + } + } + + headers = await hook.async_post_call_response_headers_hook( + data=data, + user_api_key_dict=Mock(), + response=None, + ) + + assert headers is not None + assert headers["x-litellm-semantic-filter"] == "40->8" + + tools_header = headers["x-litellm-semantic-filter-tools"] + assert len(tools_header) <= MAX_MCP_SEMANTIC_FILTER_TOOLS_HEADER_LENGTH + emitted_names = tools_header.split(",") + assert emitted_names == tool_names[: len(emitted_names)] + assert 0 < len(emitted_names) < len(tool_names) + + +def test_truncate_csv_at_tool_name_boundary_edges(): + from litellm.proxy.hooks.mcp_semantic_filter.hook import ( + _truncate_csv_at_tool_name_boundary, + ) + + assert _truncate_csv_at_tool_name_boundary(tool_names_csv="a,b,c", max_length=150) == "a,b,c" + assert _truncate_csv_at_tool_name_boundary(tool_names_csv="abc,def", max_length=3) == "abc" + assert _truncate_csv_at_tool_name_boundary(tool_names_csv="ab,cd,ef", max_length=5) == "ab,cd" + assert _truncate_csv_at_tool_name_boundary(tool_names_csv="ab,cd,ef", max_length=4) == "ab" + assert _truncate_csv_at_tool_name_boundary(tool_names_csv="single_name_longer_than_cap", max_length=10) == "" diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_utils.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_utils.py new file mode 100644 index 00000000000..73fdee9cde3 --- /dev/null +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_utils.py @@ -0,0 +1,49 @@ +import pytest +from fastapi import HTTPException + +from litellm.proxy._experimental.mcp_server.utils import ( + validate_and_normalize_mcp_server_payload, + validate_tool_display_names, +) +from litellm.proxy._types import NewMCPServerRequest + + +class TestValidateToolDisplayNames: + def test_allows_none_and_empty(self): + validate_tool_display_names(None) + validate_tool_display_names({}) + + @pytest.mark.parametrize( + "display_name", + ["browse_repo_docs", "browse-repo-docs", "BrowseRepoDocs123"], + ) + def test_allows_bedrock_safe_names(self, display_name): + validate_tool_display_names({"read_wiki_structure": display_name}) + + @pytest.mark.parametrize( + "display_name", + ["Browse Repo Docs", "browse.repo.docs", "browse/repo", "browse@docs"], + ) + def test_rejects_names_bedrock_would_reject(self, display_name): + with pytest.raises(HTTPException) as exc_info: + validate_tool_display_names({"read_wiki_structure": display_name}) + assert exc_info.value.status_code == 400 + assert display_name in str(exc_info.value.detail) + + +class TestValidateAndNormalizeMcpServerPayload: + def test_rejects_invalid_tool_display_name_on_create(self): + payload = NewMCPServerRequest( + server_name="deepwiki_mcp", + tool_name_to_display_name={"read_wiki_structure": "Browse Repo Docs"}, + ) + with pytest.raises(HTTPException) as exc_info: + validate_and_normalize_mcp_server_payload(payload) + assert exc_info.value.status_code == 400 + + def test_accepts_valid_tool_display_name_on_create(self): + payload = NewMCPServerRequest( + server_name="deepwiki_mcp", + tool_name_to_display_name={"read_wiki_structure": "browse_repo_docs"}, + ) + validate_and_normalize_mcp_server_payload(payload) diff --git a/tests/test_litellm/proxy/auth/test_auth_checks.py b/tests/test_litellm/proxy/auth/test_auth_checks.py index bc2f41c8cb4..d12ff20ee5b 100644 --- a/tests/test_litellm/proxy/auth/test_auth_checks.py +++ b/tests/test_litellm/proxy/auth/test_auth_checks.py @@ -2635,6 +2635,170 @@ async def test_virtual_key_budget_check_fallback_no_counter(): assert exc_info.value.current_cost == 15.0 +# ===================================================================== +# Throttle-on-budget-exceeded tests (LIT-3894): an over-budget key that +# opted in is throttled to a global % of its TPM/RPM instead of blocked. +# ===================================================================== + + +def _over_budget_token(**overrides) -> UserAPIKeyAuth: + base = dict( + token="throttle-token", + spend=20.0, + max_budget=10.0, + user_id="test-user", + ) + base.update(overrides) + return UserAPIKeyAuth(**base) + + +def _patched_spend(value: float): + async def mock_get_current_spend( + counter_key, fallback_spend, max_budget=None, **kwargs + ): + return value + + return patch("litellm.proxy.proxy_server.get_current_spend", mock_get_current_spend) + + +def _budget_logging_obj(): + from litellm.proxy.utils import ProxyLogging + + proxy_logging_obj = ProxyLogging(user_api_key_cache=None) + proxy_logging_obj.budget_alerts = AsyncMock() + return proxy_logging_obj + + +@pytest.mark.parametrize( + "limit, pct, expected", + [ + (1000, 0.1, 100), + (100, 0.1, 10), + (1, 0.1, 1), # floor would be 0; trickle of 1 keeps the key alive + (None, 0.1, None), + (50, 0.5, 25), + (1000, None, 1000), # no percentage -> limit unchanged + ], +) +def test_throttled_limit(limit, pct, expected): + from litellm.proxy.auth.budget_throttle import throttled_limit + + assert throttled_limit(limit, pct) == expected + + +@pytest.mark.asyncio +async def test_budget_exceeded_throttles_instead_of_blocking(monkeypatch): + monkeypatch.setattr(litellm, "budget_exceeded_throttle_percentage", 0.1) + valid_token = _over_budget_token( + tpm_limit=1000, + rpm_limit=100, + metadata={"throttle_on_budget_exceeded": True}, + ) + + with _patched_spend(20.0): + await _virtual_key_max_budget_check( + valid_token=valid_token, + proxy_logging_obj=_budget_logging_obj(), + ) + + # persistent limits are untouched (so the throttle never compounds); the + # request-scoped percentage is what the rate limiter scales by + assert valid_token.budget_throttle_pct == 0.1 + assert valid_token.tpm_limit == 1000 + assert valid_token.rpm_limit == 100 + # the request-scoped decision must not leak into serialized responses + assert "budget_throttle_pct" not in valid_token.model_dump() + + +@pytest.mark.asyncio +async def test_budget_throttle_decision_cleared_before_caching(): + """The request-scoped throttle decision must not persist into the key cache, + otherwise it would re-apply (and compound) on every subsequent request.""" + from litellm.proxy.auth.auth_checks import _copy_user_api_key_auth_for_cache + + valid_token = _over_budget_token( + tpm_limit=1000, rpm_limit=100, metadata={"throttle_on_budget_exceeded": True} + ) + valid_token.budget_throttle_pct = 0.1 + + cached = _copy_user_api_key_auth_for_cache(user_api_key_obj=valid_token) + + assert cached.budget_throttle_pct is None + assert cached.tpm_limit == 1000 + assert cached.rpm_limit == 100 + + +@pytest.mark.asyncio +async def test_budget_exceeded_throttle_no_configured_limits(monkeypatch): + monkeypatch.setattr(litellm, "budget_exceeded_throttle_percentage", 0.1) + valid_token = _over_budget_token(metadata={"throttle_on_budget_exceeded": True}) + assert valid_token.tpm_limit is None + assert valid_token.rpm_limit is None + + with _patched_spend(20.0): + with pytest.raises(litellm.BudgetExceededError): + await _virtual_key_max_budget_check( + valid_token=valid_token, + proxy_logging_obj=_budget_logging_obj(), + ) + + assert valid_token.budget_throttle_pct is None + + +@pytest.mark.asyncio +async def test_budget_exceeded_not_opted_in_still_blocks(monkeypatch): + monkeypatch.setattr(litellm, "budget_exceeded_throttle_percentage", 0.1) + valid_token = _over_budget_token(tpm_limit=1000, rpm_limit=100) + + with _patched_spend(20.0): + with pytest.raises(litellm.BudgetExceededError): + await _virtual_key_max_budget_check( + valid_token=valid_token, + proxy_logging_obj=_budget_logging_obj(), + ) + + assert valid_token.budget_throttle_pct is None + + +@pytest.mark.parametrize("pct", [None, 0, 1.5, -0.1, True]) +@pytest.mark.asyncio +async def test_budget_exceeded_invalid_percentage_blocks(monkeypatch, pct): + monkeypatch.setattr(litellm, "budget_exceeded_throttle_percentage", pct) + valid_token = _over_budget_token( + tpm_limit=1000, + rpm_limit=100, + metadata={"throttle_on_budget_exceeded": True}, + ) + + with _patched_spend(20.0): + with pytest.raises(litellm.BudgetExceededError): + await _virtual_key_max_budget_check( + valid_token=valid_token, + proxy_logging_obj=_budget_logging_obj(), + ) + + assert valid_token.budget_throttle_pct is None + + +@pytest.mark.asyncio +async def test_under_budget_does_not_throttle(monkeypatch): + monkeypatch.setattr(litellm, "budget_exceeded_throttle_percentage", 0.1) + valid_token = _over_budget_token( + max_budget=100.0, + tpm_limit=1000, + rpm_limit=100, + metadata={"throttle_on_budget_exceeded": True}, + ) + + with _patched_spend(5.0): + await _virtual_key_max_budget_check( + valid_token=valid_token, + proxy_logging_obj=_budget_logging_obj(), + ) + + assert valid_token.budget_throttle_pct is None + + @pytest.mark.asyncio async def test_team_budget_check_reads_from_spend_counter(): """Team budget check should use get_current_spend when counter exists.""" diff --git a/tests/test_litellm/proxy/auth/test_handle_jwt.py b/tests/test_litellm/proxy/auth/test_handle_jwt.py index b547ec877e2..13041950f98 100644 --- a/tests/test_litellm/proxy/auth/test_handle_jwt.py +++ b/tests/test_litellm/proxy/auth/test_handle_jwt.py @@ -663,7 +663,11 @@ def test_get_all_jwt_team_ids_unions_singular_and_plural(): # singular field as multi-element list (some IdPs) — merge all, preserve plural-first order assert jwt_handler.get_all_jwt_team_ids( {"team_id": ["primary", "secondary"], "teams": ["a"]} - ) == ["a", "primary", "secondary"] + ) == [ + "a", + "primary", + "secondary", + ] # neither populated assert jwt_handler.get_all_jwt_team_ids({}) == [] @@ -1241,24 +1245,24 @@ async def test_auth_builder_returns_team_membership_object(): ) # Verify that team_membership_object is returned - assert ( - result["team_membership"] is not None - ), "team_membership should be present" - assert ( - result["team_membership"] == mock_team_membership - ), "team_membership should match the mock object" - assert ( - result["team_membership"].user_id == _user_id - ), "team_membership user_id should match" - assert ( - result["team_membership"].team_id == _team_id - ), "team_membership team_id should match" - assert ( - result["team_membership"].budget_id == "budget_123" - ), "team_membership budget_id should match" - assert ( - result["team_membership"].spend == 10.5 - ), "team_membership spend should match" + assert result["team_membership"] is not None, ( + "team_membership should be present" + ) + assert result["team_membership"] == mock_team_membership, ( + "team_membership should match the mock object" + ) + assert result["team_membership"].user_id == _user_id, ( + "team_membership user_id should match" + ) + assert result["team_membership"].team_id == _team_id, ( + "team_membership team_id should match" + ) + assert result["team_membership"].budget_id == "budget_123", ( + "team_membership budget_id should match" + ) + assert result["team_membership"].spend == 10.5, ( + "team_membership spend should match" + ) @pytest.mark.asyncio @@ -2717,9 +2721,9 @@ async def test_find_and_validate_specific_team_id_hints_bracket_notation(): error_msg = str(exc_info.value) # Should mention the bad field name and suggest the fix assert "roles.0" in error_msg, f"Expected field name in: {error_msg}" - assert ( - "roles" in error_msg and "list" in error_msg - ), f"Expected hint about using 'roles' instead: {error_msg}" + assert "roles" in error_msg and "list" in error_msg, ( + f"Expected hint about using 'roles' instead: {error_msg}" + ) @pytest.mark.asyncio @@ -2747,9 +2751,9 @@ async def test_find_and_validate_specific_team_id_hints_bracket_index_notation() error_msg = str(exc_info.value) assert "roles[0]" in error_msg, f"Expected field name in: {error_msg}" - assert ( - "roles" in error_msg and "list" in error_msg - ), f"Expected hint about using 'roles' instead: {error_msg}" + assert "roles" in error_msg and "list" in error_msg, ( + f"Expected hint about using 'roles' instead: {error_msg}" + ) @pytest.mark.asyncio @@ -3164,9 +3168,9 @@ def test_build_decode_kwargs_warns_once_when_unscoped( if "JWT auth is enabled" in r.getMessage() and "neither JWT_AUDIENCE nor JWT_ISSUER" in r.getMessage() ] - assert ( - len(matching) == 1 - ), f"Expected exactly one warning across 3 calls, got {len(matching)}" + assert len(matching) == 1, ( + f"Expected exactly one warning across 3 calls, got {len(matching)}" + ) def test_build_decode_kwargs_no_warning_when_scoped( @@ -4339,3 +4343,1599 @@ def test_build_decode_kwargs_warns_for_unscoped_global_fallback_in_mixed_deploym if "neither JWT_AUDIENCE nor JWT_ISSUER" in r.getMessage() ] assert len(matching) == 1 + + +# --------------------------------------------------------------------------- +# fallback_to_db_teams: resolve team from DB memberships when JWT has no team +# claims (config flag on LiteLLM_JWTAuth) +# --------------------------------------------------------------------------- + + +def test_get_team_id_from_header_defers_to_db_membership_only_without_jwt_claims(): + """With fallback_to_db_teams=True, an x-litellm-team-id header is accepted + provisionally only when the JWT carries no team claims (allowed set empty). + When the JWT does carry team claims, the header must still be validated + against them, and the flag-off behavior must keep rejecting unknown teams.""" + deferred = JWTAuthManager.get_team_id_from_header( + request_headers={"x-litellm-team-id": "team-from-db"}, + allowed_team_ids=set(), + fallback_to_db_teams=True, + ) + assert deferred == "team-from-db" + + with pytest.raises(HTTPException) as exc_info: + JWTAuthManager.get_team_id_from_header( + request_headers={"x-litellm-team-id": "team-x"}, + allowed_team_ids={"team-1", "team-2"}, + fallback_to_db_teams=True, + ) + assert exc_info.value.status_code == 403 + + with pytest.raises(HTTPException): + JWTAuthManager.get_team_id_from_header( + request_headers={"x-litellm-team-id": "team-from-db"}, + allowed_team_ids=set(), + fallback_to_db_teams=False, + ) + + +@pytest.mark.asyncio +async def test_find_team_with_model_access_defers_no_team_403_under_db_fallback(): + """find_team_with_model_access raises the early "no teams in token" 403 when + enforcement is on, but defers (returns no team) so auth_builder's DB fallback + can run when fallback_to_db_teams is enabled.""" + jwt_handler = JWTHandler() + jwt_handler.litellm_jwtauth = LiteLLM_JWTAuth( + enforce_team_based_model_access=True, + fallback_to_db_teams=False, + ) + with pytest.raises(HTTPException) as exc_info: + await JWTAuthManager.find_team_with_model_access( + team_ids=set(), + requested_model="gpt-4", + route="/chat/completions", + jwt_handler=jwt_handler, + prisma_client=None, + user_api_key_cache=MagicMock(), + parent_otel_span=None, + proxy_logging_obj=MagicMock(), + ) + assert exc_info.value.status_code == 403 + + jwt_handler.litellm_jwtauth = LiteLLM_JWTAuth( + enforce_team_based_model_access=True, + fallback_to_db_teams=True, + ) + team_id, team_object = await JWTAuthManager.find_team_with_model_access( + team_ids=set(), + requested_model="gpt-4", + route="/chat/completions", + jwt_handler=jwt_handler, + prisma_client=None, + user_api_key_cache=MagicMock(), + parent_otel_span=None, + proxy_logging_obj=MagicMock(), + ) + assert team_id is None + assert team_object is None + + +def _db_fallback_handler(litellm_jwtauth: Optional[LiteLLM_JWTAuth] = None) -> JWTHandler: + handler = JWTHandler() + handler.litellm_jwtauth = litellm_jwtauth or LiteLLM_JWTAuth() + return handler + + +@pytest.mark.asyncio +async def test_resolve_db_team_fallback_skips_unresolvable_membership(): + """An orphaned membership (team row missing/erroring) is skipped and the next + resolvable DB team is selected instead of aborting the fallback.""" + user_object = LiteLLM_UserTable( + user_id="u_skip", + user_role=LitellmUserRoles.INTERNAL_USER, + teams=["ghost_team", "real_team"], + ) + resolved = LiteLLM_TeamTable(team_id="real_team") + + async def fake_get_team(team_id, **kwargs): + if team_id == "ghost_team": + raise HTTPException(status_code=404, detail="missing") + return resolved + + with patch( + "litellm.proxy.auth.handle_jwt.get_team_object", + new_callable=AsyncMock, + side_effect=fake_get_team, + ): + ( + team_id, + team_object, + _membership, + ) = await JWTAuthManager._resolve_db_team_fallback( + user_object=user_object, + user_id=None, + requested_model=None, + route="/chat/completions", + jwt_handler=_db_fallback_handler(), + enforce_team_based_model_access=True, + team_id_upsert=False, + prisma_client=None, + user_api_key_cache=MagicMock(), + parent_otel_span=None, + proxy_logging_obj=MagicMock(), + ) + + assert team_id == "real_team" + assert team_object is resolved + + +@pytest.mark.parametrize( + ( + "fallback_to_db_teams", + "user_teams", + "header_team_id", + "expected_team_id", + "expect_403", + ), + [ + pytest.param( + True, ["team_solo"], None, "team_solo", False, id="flag_on_single_db_team" + ), + pytest.param( + True, + ["team_a", "team_b"], + None, + "team_a", + False, + id="flag_on_multi_db_team_picks_first", + ), + pytest.param( + True, + ["team_a", "team_b"], + "team_b", + "team_b", + False, + id="flag_on_header_team_in_membership", + ), + pytest.param( + True, + ["team_a", "team_b"], + "team_x", + None, + True, + id="flag_on_header_team_not_in_membership_403", + ), + pytest.param(True, [], None, None, True, id="flag_on_no_db_team_enforced_403"), + pytest.param( + False, + ["team_a", "team_b"], + None, + None, + False, + id="flag_off_multi_db_team_no_fallback", + ), + pytest.param( + False, + ["team_solo"], + None, + "team_solo", + False, + id="flag_off_single_db_team_upstream_fallback", + ), + ], +) +@pytest.mark.asyncio +async def test_auth_builder_db_team_fallback_when_jwt_has_no_team( + fallback_to_db_teams: bool, + user_teams: list, + header_team_id: Optional[str], + expected_team_id: Optional[str], + expect_403: bool, +) -> None: + """End-to-end auth_builder behavior with no JWT team claims. + + fallback_to_db_teams=True attributes usage to the user's first resolvable DB + team, honors a valid x-litellm-team-id header, and rejects a header team the + user does not belong to. The default (flag off) preserves the upstream + single-team fallback: a lone DB team is resolved, multiple are ambiguous. + """ + user_id = "u_db_fallback" + user_object = LiteLLM_UserTable( + user_id=user_id, + user_role=LitellmUserRoles.INTERNAL_USER, + teams=user_teams, + ) + jwt_handler = JWTHandler() + jwt_handler.litellm_jwtauth = LiteLLM_JWTAuth( + enforce_team_based_model_access=True, + fallback_to_db_teams=fallback_to_db_teams, + ) + + request_headers = {"x-litellm-team-id": header_team_id} if header_team_id else None + + async def fake_get_team(team_id, **kwargs): + return LiteLLM_TeamTable(team_id=team_id) + + async def call_auth_builder(): + with ( + patch.object( + jwt_handler, "auth_jwt", new_callable=AsyncMock + ) as mock_auth_jwt, + patch.object(JWTAuthManager, "check_rbac_role", new_callable=AsyncMock), + patch.object(jwt_handler, "get_rbac_role", return_value=None), + patch.object(jwt_handler, "get_scopes", return_value=[]), + patch.object(jwt_handler, "get_object_id", return_value=None), + patch.object( + JWTAuthManager, + "get_user_info", + new_callable=AsyncMock, + return_value=(user_id, "u@example.com", True), + ), + patch.object(jwt_handler, "get_org_id", return_value=None), + patch.object(jwt_handler, "get_end_user_id", return_value=None), + patch.object( + JWTAuthManager, + "check_admin_access", + new_callable=AsyncMock, + return_value=None, + ), + patch.object( + JWTAuthManager, + "find_and_validate_specific_team_id", + new_callable=AsyncMock, + return_value=(None, None), + ), + patch.object(JWTAuthManager, "get_all_team_ids", return_value=set()), + patch.object( + JWTAuthManager, + "find_team_with_model_access", + new_callable=AsyncMock, + return_value=(None, None), + ), + patch.object( + JWTAuthManager, + "get_objects", + new_callable=AsyncMock, + return_value=(user_object, None, None, None, user_id), + ), + patch.object(JWTAuthManager, "map_user_to_teams", new_callable=AsyncMock), + patch.object(JWTAuthManager, "validate_object_id", return_value=True), + patch.object( + JWTAuthManager, "sync_user_role_and_teams", new_callable=AsyncMock + ), + patch( + "litellm.proxy.auth.handle_jwt.get_team_object", + new_callable=AsyncMock, + side_effect=fake_get_team, + ), + patch( + "litellm.proxy.auth.handle_jwt.get_team_membership", + new_callable=AsyncMock, + return_value=LiteLLM_TeamMembership( + user_id=user_id, + team_id=user_teams[0] if user_teams else "none", + litellm_budget_table=None, + ), + ), + ): + mock_auth_jwt.return_value = {"sub": user_id, "scope": ""} + return await JWTAuthManager.auth_builder( + api_key="test_jwt_token", + jwt_handler=jwt_handler, + request_data={"model": "gpt-4"}, + general_settings={"enforce_rbac": False}, + route="/chat/completions", + prisma_client=None, + user_api_key_cache=None, + parent_otel_span=None, + proxy_logging_obj=None, + request_headers=request_headers, + ) + + if expect_403: + with pytest.raises(HTTPException) as exc_info: + await call_auth_builder() + assert exc_info.value.status_code == 403 + else: + result = await call_auth_builder() + assert result["team_id"] == expected_team_id + + +@pytest.mark.parametrize( + "fallback_to_db_teams, expect_teams_stripped", + [ + pytest.param(True, False, id="fallback_on_preserves_db_teams"), + pytest.param(False, True, id="fallback_off_strips_db_teams"), + ], +) +@pytest.mark.asyncio +async def test_sync_user_role_and_teams_no_claim_team_preservation( + fallback_to_db_teams: bool, + expect_teams_stripped: bool, +) -> None: + """A no-team-claim JWT must not permanently strip a user's DB team memberships + when fallback_to_db_teams is enabled — otherwise the DB fallback that runs + right after has nothing to resolve and every request silently wipes the user + out of their teams. With the flag off, the legacy mirror-the-IdP behavior + (remove teams absent from the token) is preserved.""" + jwt_handler = JWTHandler() + jwt_handler.update_environment( + prisma_client=None, + user_api_key_cache=AsyncMock(), + litellm_jwtauth=LiteLLM_JWTAuth( + team_ids_jwt_field="teams", + sync_user_role_and_teams=True, + fallback_to_db_teams=fallback_to_db_teams, + ), + ) + + token = {"sub": "u1"} + user = LiteLLM_UserTable( + user_id="u1", + user_role=LitellmUserRoles.INTERNAL_USER.value, + teams=["team_a", "team_b"], + ) + prisma = AsyncMock() + + with patch( + "litellm.proxy.management_endpoints.scim.scim_v2.patch_team_membership", + new_callable=AsyncMock, + ) as mock_patch: + await JWTAuthManager.sync_user_role_and_teams(jwt_handler, token, user, prisma) + + if expect_teams_stripped: + mock_patch.assert_awaited_once() + assert set(mock_patch.call_args.kwargs["teams_ids_to_remove_user_from"]) == { + "team_a", + "team_b", + } + assert user.teams == [] + else: + mock_patch.assert_not_called() + assert user.teams == ["team_a", "team_b"] + + +@pytest.mark.asyncio +async def test_resolve_db_team_fallback_skips_team_without_model_access(): + """The DB-team fallback must apply the same per-team model-access check as the + claim-based path: a DB team that cannot access the requested model is skipped + in favor of one that can, instead of selecting the first membership blindly.""" + user_object = LiteLLM_UserTable( + user_id="u_model_access", + user_role=LitellmUserRoles.INTERNAL_USER, + teams=["restricted_team", "allowed_team"], + ) + teams = { + "restricted_team": LiteLLM_TeamTable( + team_id="restricted_team", models=["claude-3"] + ), + "allowed_team": LiteLLM_TeamTable(team_id="allowed_team", models=["gpt-4"]), + } + + async def fake_get_team(team_id, **kwargs): + return teams[team_id] + + async def fake_can_access(model, team_object, llm_router, team_model_aliases=None): + if model in (team_object.models or []): + return True + raise ProxyException( + message="team not allowed to access model", + type=ProxyErrorTypes.team_model_access_denied, + param="model", + code=403, + ) + + with ( + patch( + "litellm.proxy.auth.handle_jwt.get_team_object", + new_callable=AsyncMock, + side_effect=fake_get_team, + ), + patch( + "litellm.proxy.auth.handle_jwt.can_team_access_model", + new_callable=AsyncMock, + side_effect=fake_can_access, + ), + ): + ( + team_id, + team_object, + _membership, + ) = await JWTAuthManager._resolve_db_team_fallback( + user_object=user_object, + user_id=None, + requested_model="gpt-4", + route="/chat/completions", + jwt_handler=_db_fallback_handler(), + enforce_team_based_model_access=True, + team_id_upsert=False, + prisma_client=None, + user_api_key_cache=MagicMock(), + parent_otel_span=None, + proxy_logging_obj=MagicMock(), + ) + + assert team_id == "allowed_team" + assert team_object is teams["allowed_team"] + + +@pytest.mark.asyncio +async def test_resolve_db_team_fallback_enforces_team_allowed_routes(): + """The DB-team fallback must apply the same team_allowed_routes gate as the + claim-based path: a route the JWT config excludes for team-role callers must + not become reachable by selecting a DB team, even when that team can access + the requested model. Without the gate, a teamless JWT could reach the + info/management routes an admin narrowed team_allowed_routes to exclude.""" + user_object = LiteLLM_UserTable( + user_id="u_routes", + user_role=LitellmUserRoles.INTERNAL_USER, + teams=["team_a"], + ) + team = LiteLLM_TeamTable(team_id="team_a", models=["gpt-4"]) + handler = _db_fallback_handler(LiteLLM_JWTAuth(team_allowed_routes=["openai_routes"])) + + async def fake_get_team(team_id, **kwargs): + return team + + async def fake_can_access(model, team_object, llm_router, team_model_aliases=None): + return True + + async def resolve(route): + return await JWTAuthManager._resolve_db_team_fallback( + user_object=user_object, + user_id=None, + requested_model="gpt-4", + route=route, + jwt_handler=handler, + enforce_team_based_model_access=False, + team_id_upsert=False, + prisma_client=None, + user_api_key_cache=MagicMock(), + parent_otel_span=None, + proxy_logging_obj=MagicMock(), + ) + + with ( + patch( + "litellm.proxy.auth.handle_jwt.get_team_object", + new_callable=AsyncMock, + side_effect=fake_get_team, + ), + patch( + "litellm.proxy.auth.handle_jwt.can_team_access_model", + new_callable=AsyncMock, + side_effect=fake_can_access, + ), + ): + excluded_team_id, excluded_team_object, _ = await resolve("/key/info") + allowed_team_id, allowed_team_object, _ = await resolve("/chat/completions") + + assert excluded_team_id is None + assert excluded_team_object is None + assert allowed_team_id == "team_a" + assert allowed_team_object is team + + +def test_validate_header_team_in_db_membership_does_not_leak_team_ids(): + """The 403 raised for an x-litellm-team-id header outside the user's DB + memberships must not enumerate the user's team IDs back to the caller; any + valid-JWT caller could otherwise probe header values to discover team IDs.""" + user_object = LiteLLM_UserTable( + user_id="u_leak", + user_role=LitellmUserRoles.INTERNAL_USER, + teams=["secret_team_alpha", "secret_team_beta"], + ) + + with pytest.raises(HTTPException) as exc_info: + JWTAuthManager._validate_header_team_in_db_membership( + team_id="outsider_team", + user_object=user_object, + ) + + detail = exc_info.value.detail + assert exc_info.value.status_code == 403 + assert "secret_team_alpha" not in detail + assert "secret_team_beta" not in detail + assert "outsider_team" in detail + + +async def _run_auth_builder_with_header_team( + jwt_auth_config: LiteLLM_JWTAuth, + token: dict, + header_team_id: str, + user_object: LiteLLM_UserTable, + fake_get_team, + allowed_team_ids: set, +): + jwt_handler = JWTHandler() + jwt_handler.litellm_jwtauth = jwt_auth_config + with ( + patch.object( + jwt_handler, "auth_jwt", new_callable=AsyncMock, return_value=token + ), + patch.object(JWTAuthManager, "check_rbac_role", new_callable=AsyncMock), + patch.object(jwt_handler, "get_rbac_role", return_value=None), + patch.object(jwt_handler, "get_scopes", return_value=[]), + patch.object(jwt_handler, "get_object_id", return_value=None), + patch.object( + JWTAuthManager, + "get_user_info", + new_callable=AsyncMock, + return_value=(user_object.user_id, "u@example.com", True), + ), + patch.object(jwt_handler, "get_org_id", return_value=None), + patch.object(jwt_handler, "get_end_user_id", return_value=None), + patch.object( + JWTAuthManager, + "check_admin_access", + new_callable=AsyncMock, + return_value=None, + ), + patch.object( + JWTAuthManager, "get_all_team_ids", return_value=allowed_team_ids + ), + patch.object( + JWTAuthManager, + "get_objects", + new_callable=AsyncMock, + return_value=(user_object, None, None, None, user_object.user_id), + ), + patch.object(JWTAuthManager, "map_user_to_teams", new_callable=AsyncMock), + patch.object(JWTAuthManager, "validate_object_id", return_value=True), + patch.object( + JWTAuthManager, "sync_user_role_and_teams", new_callable=AsyncMock + ), + patch( + "litellm.proxy.auth.handle_jwt.get_team_object", + new_callable=AsyncMock, + side_effect=fake_get_team, + ), + ): + return await JWTAuthManager.auth_builder( + api_key="test_jwt_token", + jwt_handler=jwt_handler, + request_data={"model": "gpt-4"}, + general_settings={"enforce_rbac": False}, + route="/chat/completions", + prisma_client=None, + user_api_key_cache=None, + parent_otel_span=None, + proxy_logging_obj=None, + request_headers={"x-litellm-team-id": header_team_id}, + ) + + +async def _team_lookup_404(team_id, **kwargs): + raise HTTPException( + status_code=404, + detail=f"Team doesn't exist in db. Team={team_id}. Create team via `/team/new` call.", + ) + + +@pytest.mark.asyncio +async def test_auth_builder_header_team_not_found_matches_non_membership_denial() -> ( + None +): + """A provisional x-litellm-team-id naming a nonexistent team must produce + the exact same 403 shape as one naming an existing team outside the + caller's memberships. Letting get_team_object's 404 surface would give any + valid-JWT caller an oracle to probe which team ids exist.""" + user_object = LiteLLM_UserTable( + user_id="u_oracle", + user_role=LitellmUserRoles.INTERNAL_USER, + teams=["team_member"], + ) + config = LiteLLM_JWTAuth( + enforce_team_based_model_access=True, + fallback_to_db_teams=True, + ) + token = {"sub": "u_oracle", "scope": ""} + + async def team_exists(team_id, **kwargs): + return LiteLLM_TeamTable(team_id=team_id) + + with pytest.raises(HTTPException) as missing_exc: + await _run_auth_builder_with_header_team( + config, token, "team_ghost", user_object, _team_lookup_404, set() + ) + with pytest.raises(HTTPException) as outsider_exc: + await _run_auth_builder_with_header_team( + config, token, "team_other", user_object, team_exists, set() + ) + + assert missing_exc.value.status_code == 403 + assert outsider_exc.value.status_code == 403 + assert missing_exc.value.detail.replace( + "team_ghost", "" + ) == outsider_exc.value.detail.replace("team_other", "") + assert "exist" not in missing_exc.value.detail + + +@pytest.mark.asyncio +async def test_auth_builder_claim_backed_header_team_lookup_error_propagates() -> None: + """When the JWT carries team claims the header team is not provisional, so + a failed team lookup keeps the upstream contract: get_team_object's 404 + surfaces unchanged instead of being rewritten into the membership 403.""" + user_object = LiteLLM_UserTable( + user_id="u_claimed", + user_role=LitellmUserRoles.INTERNAL_USER, + teams=["team_member"], + ) + config = LiteLLM_JWTAuth( + enforce_team_based_model_access=True, + fallback_to_db_teams=True, + team_ids_jwt_field="team_ids", + ) + token = {"sub": "u_claimed", "scope": "", "team_ids": ["team_claimed"]} + + with pytest.raises(HTTPException) as exc_info: + await _run_auth_builder_with_header_team( + config, token, "team_claimed", user_object, _team_lookup_404, {"team_claimed"} + ) + + assert exc_info.value.status_code == 404 + + +@pytest.mark.asyncio +async def test_resolve_db_team_fallback_loads_team_membership(): + """The DB-team fallback must load the resolved team's membership row (when a + user_id is known) so per-team membership budget limits are enforced on the + fallback path the same as on the claim-based path; returning a None membership + would silently skip LiteLLM_TeamMembership budget checks for every request.""" + user_object = LiteLLM_UserTable( + user_id="u_membership", + user_role=LitellmUserRoles.INTERNAL_USER, + teams=["team_with_budget"], + ) + membership = LiteLLM_TeamMembership( + user_id="u_membership", + team_id="team_with_budget", + budget_id="budget_xyz", + litellm_budget_table=None, + ) + + async def fake_get_team(team_id, **kwargs): + return LiteLLM_TeamTable(team_id=team_id) + + async def fake_get_membership(user_id, team_id, **kwargs): + assert user_id == "u_membership" + assert team_id == "team_with_budget" + return membership + + with ( + patch( + "litellm.proxy.auth.handle_jwt.get_team_object", + new_callable=AsyncMock, + side_effect=fake_get_team, + ), + patch( + "litellm.proxy.auth.handle_jwt.get_team_membership", + new_callable=AsyncMock, + side_effect=fake_get_membership, + ), + ): + ( + team_id, + team_object, + team_membership, + ) = await JWTAuthManager._resolve_db_team_fallback( + user_object=user_object, + user_id="u_membership", + requested_model=None, + route="/chat/completions", + jwt_handler=_db_fallback_handler(), + enforce_team_based_model_access=True, + team_id_upsert=False, + prisma_client=None, + user_api_key_cache=MagicMock(), + parent_otel_span=None, + proxy_logging_obj=MagicMock(), + ) + + assert team_id == "team_with_budget" + assert team_object is not None + assert team_membership is membership + assert team_membership.budget_id == "budget_xyz" + + +@pytest.mark.asyncio +async def test_resolve_db_team_fallback_survives_membership_lookup_error(): + """A transient membership-lookup failure must not deny an otherwise-authorized + request. get_team_membership swallows DB errors internally and returns None, so + the fallback must return the resolved team with a None membership (budget + enforcement degrades gracefully) instead of treating it as a denial.""" + user_object = LiteLLM_UserTable( + user_id="u_flaky", + user_role=LitellmUserRoles.INTERNAL_USER, + teams=["team_flaky"], + ) + + async def fake_get_team(team_id, **kwargs): + return LiteLLM_TeamTable(team_id=team_id) + + async def none_on_db_error_membership(user_id, team_id, **kwargs): + return None + + with ( + patch( + "litellm.proxy.auth.handle_jwt.get_team_object", + new_callable=AsyncMock, + side_effect=fake_get_team, + ), + patch( + "litellm.proxy.auth.handle_jwt.get_team_membership", + new_callable=AsyncMock, + side_effect=none_on_db_error_membership, + ), + ): + ( + team_id, + team_object, + team_membership, + ) = await JWTAuthManager._resolve_db_team_fallback( + user_object=user_object, + user_id="u_flaky", + requested_model=None, + route="/chat/completions", + jwt_handler=_db_fallback_handler(), + enforce_team_based_model_access=True, + team_id_upsert=False, + prisma_client=None, + user_api_key_cache=MagicMock(), + parent_otel_span=None, + proxy_logging_obj=MagicMock(), + ) + + assert team_id == "team_flaky" + assert team_object is not None + assert team_membership is None + + +@pytest.mark.asyncio +async def test_auth_builder_db_fallback_does_not_validate_rbac_team_against_db_membership(): + """When fallback_to_db_teams is on and the JWT carries an RBAC team role but no + group/team claims, team_id is set from the RBAC object_id (not the provisional + x-litellm-team-id header). That RBAC-asserted team must not be re-validated + against the user's DB memberships; only a team that actually came from the + header is provisional. Without the team_id == header_team_id guard, every such + RBAC request 403s when the RBAC team is not also a DB membership.""" + rbac_team = "rbac_asserted_team" + user_id = "u_rbac" + user_object = LiteLLM_UserTable( + user_id=user_id, + user_role=LitellmUserRoles.INTERNAL_USER, + teams=["unrelated_db_team"], + ) + jwt_handler = JWTHandler() + jwt_handler.litellm_jwtauth = LiteLLM_JWTAuth( + enforce_team_based_model_access=True, + fallback_to_db_teams=True, + ) + + async def fake_get_team(team_id, **kwargs): + return LiteLLM_TeamTable(team_id=team_id) + + with ( + patch.object(jwt_handler, "auth_jwt", new_callable=AsyncMock) as mock_auth_jwt, + patch.object(JWTAuthManager, "check_rbac_role", new_callable=AsyncMock), + patch.object(jwt_handler, "get_rbac_role", return_value=LitellmUserRoles.TEAM), + patch.object(jwt_handler, "get_scopes", return_value=[]), + patch.object(jwt_handler, "get_object_id", return_value=rbac_team), + patch.object( + JWTAuthManager, + "get_user_info", + new_callable=AsyncMock, + return_value=(user_id, "u@example.com", True), + ), + patch.object(jwt_handler, "get_org_id", return_value=None), + patch.object(jwt_handler, "get_end_user_id", return_value=None), + patch.object( + JWTAuthManager, + "check_admin_access", + new_callable=AsyncMock, + return_value=None, + ), + patch.object(JWTAuthManager, "get_all_team_ids", return_value=set()), + patch.object( + JWTAuthManager, + "get_objects", + new_callable=AsyncMock, + return_value=(user_object, None, None, None, user_id), + ), + patch.object(JWTAuthManager, "map_user_to_teams", new_callable=AsyncMock), + patch.object(JWTAuthManager, "validate_object_id", return_value=True), + patch.object( + JWTAuthManager, "sync_user_role_and_teams", new_callable=AsyncMock + ), + patch( + "litellm.proxy.auth.handle_jwt.get_team_object", + new_callable=AsyncMock, + side_effect=fake_get_team, + ), + ): + mock_auth_jwt.return_value = {"sub": user_id, "scope": ""} + result = await JWTAuthManager.auth_builder( + api_key="test_jwt_token", + jwt_handler=jwt_handler, + request_data={"model": "gpt-4"}, + general_settings={"enforce_rbac": False}, + route="/chat/completions", + prisma_client=None, + user_api_key_cache=None, + parent_otel_span=None, + proxy_logging_obj=None, + request_headers=None, + ) + + assert result["team_id"] == rbac_team + + +@pytest.mark.asyncio +async def test_resolve_db_team_fallback_distinguishes_no_membership_vs_model_denied(): + """When enforce_team_based_model_access is on, a user with no DB memberships + and a user with memberships that all fail the model-access check must surface + different 403s; collapsing both into the no-membership message hides the real + cause and diverges from find_team_with_model_access's claim-based message.""" + membership_user = LiteLLM_UserTable( + user_id="u_no_model", + user_role=LitellmUserRoles.INTERNAL_USER, + teams=["only_team"], + ) + no_membership_user = LiteLLM_UserTable( + user_id="u_empty", + user_role=LitellmUserRoles.INTERNAL_USER, + teams=[], + ) + + async def fake_get_team(team_id, **kwargs): + return LiteLLM_TeamTable(team_id=team_id, models=["other"]) + + async def fake_can_access(model, team_object, llm_router, team_model_aliases=None): + raise ProxyException( + message="team not allowed to access model", + type=ProxyErrorTypes.team_model_access_denied, + param="model", + code=403, + ) + + with ( + patch( + "litellm.proxy.auth.handle_jwt.get_team_object", + new_callable=AsyncMock, + side_effect=fake_get_team, + ), + patch( + "litellm.proxy.auth.handle_jwt.can_team_access_model", + new_callable=AsyncMock, + side_effect=fake_can_access, + ), + ): + with pytest.raises(HTTPException) as model_denied: + await JWTAuthManager._resolve_db_team_fallback( + user_object=membership_user, + user_id=None, + requested_model="gpt-4", + route="/chat/completions", + jwt_handler=_db_fallback_handler(), + enforce_team_based_model_access=True, + team_id_upsert=False, + prisma_client=None, + user_api_key_cache=MagicMock(), + parent_otel_span=None, + proxy_logging_obj=MagicMock(), + ) + + with pytest.raises(HTTPException) as no_member: + await JWTAuthManager._resolve_db_team_fallback( + user_object=no_membership_user, + user_id=None, + requested_model="gpt-4", + route="/chat/completions", + jwt_handler=_db_fallback_handler(), + enforce_team_based_model_access=True, + team_id_upsert=False, + prisma_client=None, + user_api_key_cache=MagicMock(), + parent_otel_span=None, + proxy_logging_obj=MagicMock(), + ) + + assert model_denied.value.status_code == 403 + assert "requested model" in model_denied.value.detail + assert "gpt-4" in model_denied.value.detail + assert "only_team" not in model_denied.value.detail + + assert no_member.value.status_code == 403 + assert "not a member of any team" in no_member.value.detail + + +@pytest.mark.asyncio +async def test_auth_builder_db_fallback_runs_when_only_team_id_default_set(): + """team_id_default makes JWTHandler.get_team_id return a non-None team for a + claimless token. The fallback gate must look at real JWT team claims (not the + operator-configured default) so fallback_to_db_teams still attributes to the + user's DB memberships instead of silently routing to the default team.""" + user_id = "u_default_token" + user_object = LiteLLM_UserTable( + user_id=user_id, + user_role=LitellmUserRoles.INTERNAL_USER, + teams=["db_team_for_user"], + ) + jwt_handler = JWTHandler() + jwt_handler.litellm_jwtauth = LiteLLM_JWTAuth( + fallback_to_db_teams=True, + team_id_default="config_default_team", + ) + + async def fake_get_team(team_id, **kwargs): + return LiteLLM_TeamTable(team_id=team_id) + + with ( + patch.object(jwt_handler, "auth_jwt", new_callable=AsyncMock) as mock_auth_jwt, + patch.object(JWTAuthManager, "check_rbac_role", new_callable=AsyncMock), + patch.object(jwt_handler, "get_rbac_role", return_value=None), + patch.object(jwt_handler, "get_scopes", return_value=[]), + patch.object(jwt_handler, "get_object_id", return_value=None), + patch.object( + JWTAuthManager, + "get_user_info", + new_callable=AsyncMock, + return_value=(user_id, "u@example.com", True), + ), + patch.object(jwt_handler, "get_org_id", return_value=None), + patch.object(jwt_handler, "get_end_user_id", return_value=None), + patch.object( + JWTAuthManager, + "check_admin_access", + new_callable=AsyncMock, + return_value=None, + ), + patch.object( + JWTAuthManager, + "get_objects", + new_callable=AsyncMock, + return_value=(user_object, None, None, None, user_id), + ), + patch.object(JWTAuthManager, "map_user_to_teams", new_callable=AsyncMock), + patch.object(JWTAuthManager, "validate_object_id", return_value=True), + patch.object( + JWTAuthManager, "sync_user_role_and_teams", new_callable=AsyncMock + ), + patch( + "litellm.proxy.auth.handle_jwt.get_team_object", + new_callable=AsyncMock, + side_effect=fake_get_team, + ), + patch( + "litellm.proxy.auth.handle_jwt.get_team_membership", + new_callable=AsyncMock, + return_value=None, + ), + ): + mock_auth_jwt.return_value = {"sub": user_id, "scope": ""} + result = await JWTAuthManager.auth_builder( + api_key="test_jwt_token", + jwt_handler=jwt_handler, + request_data={"model": "gpt-4"}, + general_settings={"enforce_rbac": False}, + route="/chat/completions", + prisma_client=None, + user_api_key_cache=None, + parent_otel_span=None, + proxy_logging_obj=None, + request_headers=None, + ) + + assert result["team_id"] == "db_team_for_user" + + +@pytest.mark.asyncio +async def test_auth_builder_alias_only_token_resolves_alias_not_db_fallback(): + """An alias-only JWT (team_alias_jwt_field set, no team-id claims) must resolve + its alias via find_and_validate_specific_team_id, not fall into the DB-membership + fallback. get_all_jwt_team_ids ignores aliases, so without the get_team_alias + clause in the db_team_fallback gate the alias is silently dropped and the request + is mis-attributed to the user's first DB team instead of the alias-named team.""" + user_id = "u_alias_only" + user_object = LiteLLM_UserTable( + user_id=user_id, + user_role=LitellmUserRoles.INTERNAL_USER, + teams=["db_membership_team"], + ) + jwt_handler = JWTHandler() + jwt_handler.litellm_jwtauth = LiteLLM_JWTAuth( + fallback_to_db_teams=True, + team_alias_jwt_field="team_name", + ) + + async def fake_get_team(team_id, **kwargs): + return LiteLLM_TeamTable(team_id=team_id) + + async def fake_get_team_by_alias(team_alias, **kwargs): + return LiteLLM_TeamTable(team_id="alias_resolved_team", team_alias=team_alias) + + with ( + patch.object(jwt_handler, "auth_jwt", new_callable=AsyncMock) as mock_auth_jwt, + patch.object(JWTAuthManager, "check_rbac_role", new_callable=AsyncMock), + patch.object(jwt_handler, "get_rbac_role", return_value=None), + patch.object(jwt_handler, "get_scopes", return_value=[]), + patch.object(jwt_handler, "get_object_id", return_value=None), + patch.object( + JWTAuthManager, + "get_user_info", + new_callable=AsyncMock, + return_value=(user_id, "u@example.com", True), + ), + patch.object(jwt_handler, "get_org_id", return_value=None), + patch.object(jwt_handler, "get_end_user_id", return_value=None), + patch.object( + JWTAuthManager, + "check_admin_access", + new_callable=AsyncMock, + return_value=None, + ), + patch.object( + JWTAuthManager, + "get_objects", + new_callable=AsyncMock, + return_value=(user_object, None, None, None, user_id), + ), + patch.object(JWTAuthManager, "map_user_to_teams", new_callable=AsyncMock), + patch.object(JWTAuthManager, "validate_object_id", return_value=True), + patch.object( + JWTAuthManager, "sync_user_role_and_teams", new_callable=AsyncMock + ), + patch( + "litellm.proxy.auth.handle_jwt.get_team_object", + new_callable=AsyncMock, + side_effect=fake_get_team, + ), + patch( + "litellm.proxy.auth.handle_jwt.get_team_object_by_alias", + new_callable=AsyncMock, + side_effect=fake_get_team_by_alias, + ), + patch( + "litellm.proxy.auth.handle_jwt.get_team_membership", + new_callable=AsyncMock, + return_value=None, + ), + ): + mock_auth_jwt.return_value = {"sub": user_id, "team_name": "resolvable_alias"} + result = await JWTAuthManager.auth_builder( + api_key="test_jwt_token", + jwt_handler=jwt_handler, + request_data={"model": "gpt-4"}, + general_settings={"enforce_rbac": False}, + route="/chat/completions", + prisma_client=None, + user_api_key_cache=None, + parent_otel_span=None, + proxy_logging_obj=None, + request_headers=None, + ) + + assert result["team_id"] == "alias_resolved_team" + + +@pytest.mark.asyncio +async def test_find_and_validate_specific_team_id_alias_wins_over_team_id_default(): + """When the JWT carries only an alias claim (no team_id claim) and + team_id_default is configured, alias resolution must win. get_team_id + silently substitutes team_id_default for a missing claim, which would + otherwise mask the alias-resolved team and mis-attribute spend/access + to the configured default team.""" + from litellm.caching import DualCache + from litellm.proxy.utils import ProxyLogging + + jwt_handler = JWTHandler() + user_api_key_cache = DualCache() + proxy_logging_obj = ProxyLogging(user_api_key_cache=user_api_key_cache) + jwt_handler.update_environment( + prisma_client=None, + user_api_key_cache=user_api_key_cache, + litellm_jwtauth=LiteLLM_JWTAuth( + team_alias_jwt_field="team_alias", + team_id_default="config_default_team", + ), + ) + + jwt_token = {"sub": "user-1", "team_alias": "my-team"} + alias_team = LiteLLM_TeamTable( + team_id="alias_resolved_team", team_alias="my-team" + ) + + with ( + patch( + "litellm.proxy.auth.handle_jwt.get_team_object", new_callable=AsyncMock + ) as mock_get_by_id, + patch( + "litellm.proxy.auth.handle_jwt.get_team_object_by_alias", + new_callable=AsyncMock, + ) as mock_get_by_alias, + ): + mock_get_by_alias.return_value = alias_team + + team_id, team_obj = await JWTAuthManager.find_and_validate_specific_team_id( + jwt_handler=jwt_handler, + jwt_valid_token=jwt_token, + prisma_client=None, + user_api_key_cache=user_api_key_cache, + parent_otel_span=None, + proxy_logging_obj=proxy_logging_obj, + ) + + assert team_id == "alias_resolved_team" + assert team_obj == alias_team + mock_get_by_id.assert_not_called() + mock_get_by_alias.assert_called_once() + + +@pytest.mark.asyncio +async def test_find_and_validate_specific_team_id_team_id_default_used_without_alias(): + """When the token carries neither a team_id nor an alias claim and + team_id_default is configured, the default still resolves the team. The + alias-precedence fix must not regress this baseline fallback behavior.""" + from litellm.caching import DualCache + from litellm.proxy.utils import ProxyLogging + + jwt_handler = JWTHandler() + user_api_key_cache = DualCache() + proxy_logging_obj = ProxyLogging(user_api_key_cache=user_api_key_cache) + jwt_handler.update_environment( + prisma_client=None, + user_api_key_cache=user_api_key_cache, + litellm_jwtauth=LiteLLM_JWTAuth( + team_alias_jwt_field="team_alias", + team_id_default="config_default_team", + ), + ) + + jwt_token = {"sub": "user-1"} + default_team = LiteLLM_TeamTable(team_id="config_default_team") + + with ( + patch( + "litellm.proxy.auth.handle_jwt.get_team_object", new_callable=AsyncMock + ) as mock_get_by_id, + patch( + "litellm.proxy.auth.handle_jwt.get_team_object_by_alias", + new_callable=AsyncMock, + ) as mock_get_by_alias, + ): + mock_get_by_id.return_value = default_team + + team_id, team_obj = await JWTAuthManager.find_and_validate_specific_team_id( + jwt_handler=jwt_handler, + jwt_valid_token=jwt_token, + prisma_client=None, + user_api_key_cache=user_api_key_cache, + parent_otel_span=None, + proxy_logging_obj=proxy_logging_obj, + ) + + assert team_id == "config_default_team" + assert team_obj == default_team + mock_get_by_alias.assert_not_called() + + +@pytest.mark.asyncio +async def test_auth_builder_db_fallback_enforces_passthrough_route_access(): + """A team selected only via _resolve_db_team_fallback must still pass the + auth-enforced passthrough route check; previously the earlier gate ran while + team_id was None and the fallback-resolved team bypassed it entirely.""" + user_id = "u_passthrough" + user_object = LiteLLM_UserTable( + user_id=user_id, + user_role=LitellmUserRoles.INTERNAL_USER, + teams=["team_no_passthrough"], + ) + jwt_handler = JWTHandler() + jwt_handler.litellm_jwtauth = LiteLLM_JWTAuth(fallback_to_db_teams=True) + + passthrough_route = "/vertex_ai/v1/projects/p/locations/us/publishers/google/models/gemini:generateContent" + + async def fake_get_team(team_id, **kwargs): + return LiteLLM_TeamTable(team_id=team_id, metadata={}) + + with ( + patch.object(jwt_handler, "auth_jwt", new_callable=AsyncMock) as mock_auth_jwt, + patch.object(JWTAuthManager, "check_rbac_role", new_callable=AsyncMock), + patch.object(jwt_handler, "get_rbac_role", return_value=None), + patch.object(jwt_handler, "get_scopes", return_value=[]), + patch.object(jwt_handler, "get_object_id", return_value=None), + patch.object( + JWTAuthManager, + "get_user_info", + new_callable=AsyncMock, + return_value=(user_id, "u@example.com", True), + ), + patch.object(jwt_handler, "get_org_id", return_value=None), + patch.object(jwt_handler, "get_end_user_id", return_value=None), + patch.object( + JWTAuthManager, + "check_admin_access", + new_callable=AsyncMock, + return_value=None, + ), + patch.object( + JWTAuthManager, + "get_objects", + new_callable=AsyncMock, + return_value=(user_object, None, None, None, user_id), + ), + patch.object(JWTAuthManager, "map_user_to_teams", new_callable=AsyncMock), + patch.object(JWTAuthManager, "validate_object_id", return_value=True), + patch.object( + JWTAuthManager, "sync_user_role_and_teams", new_callable=AsyncMock + ), + patch( + "litellm.proxy.auth.handle_jwt.get_team_object", + new_callable=AsyncMock, + side_effect=fake_get_team, + ), + patch( + "litellm.proxy.auth.handle_jwt.get_team_membership", + new_callable=AsyncMock, + return_value=None, + ), + patch( + "litellm.proxy.auth.handle_jwt.RouteChecks.is_auth_enforced_pass_through_route", + return_value=True, + ), + patch.object( + JWTAuthManager, + "_team_has_passthrough_route_access", + return_value=False, + ), + ): + mock_auth_jwt.return_value = {"sub": user_id, "scope": ""} + with pytest.raises(HTTPException) as exc_info: + await JWTAuthManager.auth_builder( + api_key="test_jwt_token", + jwt_handler=jwt_handler, + request_data={"model": "gemini"}, + general_settings={"enforce_rbac": False}, + route=passthrough_route, + prisma_client=None, + user_api_key_cache=None, + parent_otel_span=None, + proxy_logging_obj=None, + request_headers=None, + request_method="POST", + ) + + assert exc_info.value.status_code == 403 + assert "passthrough route" in exc_info.value.detail + + +@pytest.mark.asyncio +async def test_sync_user_role_and_teams_singular_claim_reconciles_memberships(): + """When fallback_to_db_teams is on but the JWT carries a singular team claim + (Okta/Auth0 default for users with one primary team), sync must treat it as a + real claim and reconcile DB memberships against it. Otherwise stale DB teams + persist and a subsequent claimless JWT for the same user is silently attributed + to a team the IdP never asserted on the singular-claim login.""" + jwt_handler = JWTHandler() + jwt_handler.update_environment( + prisma_client=None, + user_api_key_cache=AsyncMock(), + litellm_jwtauth=LiteLLM_JWTAuth( + team_id_jwt_field="primary_team", + sync_user_role_and_teams=True, + fallback_to_db_teams=True, + ), + ) + + token = {"sub": "u_singular", "primary_team": "team_primary"} + user = LiteLLM_UserTable( + user_id="u_singular", + user_role=LitellmUserRoles.INTERNAL_USER.value, + teams=["team_stale_a", "team_stale_b"], + ) + + with patch( + "litellm.proxy.management_endpoints.scim.scim_v2.patch_team_membership", + new_callable=AsyncMock, + ) as mock_patch: + await JWTAuthManager.sync_user_role_and_teams( + jwt_handler, token, user, AsyncMock() + ) + + mock_patch.assert_awaited_once() + assert set(mock_patch.call_args.kwargs["teams_ids_to_remove_user_from"]) == { + "team_stale_a", + "team_stale_b", + } + assert set(mock_patch.call_args.kwargs["teams_ids_to_add_user_to"]) == { + "team_primary" + } + assert user.teams == ["team_primary"] + + +@pytest.mark.asyncio +async def test_auth_builder_provisional_header_team_is_not_upserted(): + """A provisional x-litellm-team-id (accepted only because the JWT carries no + team claims) must not be upserted even when team_id_upsert is enabled: it is + validated against DB membership afterwards, so upserting first would let an + attacker-supplied header create an orphaned team row. A genuine membership + team already exists, so the resolved request still succeeds.""" + user_id = "u_no_upsert" + header_team = "header_supplied_team" + user_object = LiteLLM_UserTable( + user_id=user_id, + user_role=LitellmUserRoles.INTERNAL_USER, + teams=[header_team], + ) + jwt_handler = JWTHandler() + jwt_handler.litellm_jwtauth = LiteLLM_JWTAuth( + fallback_to_db_teams=True, + team_id_upsert=True, + ) + + upsert_by_team: dict[str, Optional[bool]] = {} + + async def spy_get_team(team_id, **kwargs): + upsert_by_team[team_id] = kwargs.get("team_id_upsert") + return LiteLLM_TeamTable(team_id=team_id) + + with ( + patch.object(jwt_handler, "auth_jwt", new_callable=AsyncMock) as mock_auth_jwt, + patch.object(JWTAuthManager, "check_rbac_role", new_callable=AsyncMock), + patch.object(jwt_handler, "get_rbac_role", return_value=None), + patch.object(jwt_handler, "get_scopes", return_value=[]), + patch.object(jwt_handler, "get_object_id", return_value=None), + patch.object( + JWTAuthManager, + "get_user_info", + new_callable=AsyncMock, + return_value=(user_id, "u@example.com", True), + ), + patch.object(jwt_handler, "get_org_id", return_value=None), + patch.object(jwt_handler, "get_end_user_id", return_value=None), + patch.object( + JWTAuthManager, + "check_admin_access", + new_callable=AsyncMock, + return_value=None, + ), + patch.object(JWTAuthManager, "get_all_team_ids", return_value=set()), + patch.object( + JWTAuthManager, + "get_objects", + new_callable=AsyncMock, + return_value=(user_object, None, None, None, user_id), + ), + patch.object(JWTAuthManager, "map_user_to_teams", new_callable=AsyncMock), + patch.object(JWTAuthManager, "validate_object_id", return_value=True), + patch.object( + JWTAuthManager, "sync_user_role_and_teams", new_callable=AsyncMock + ), + patch( + "litellm.proxy.auth.handle_jwt.get_team_object", + new_callable=AsyncMock, + side_effect=spy_get_team, + ), + ): + mock_auth_jwt.return_value = {"sub": user_id, "scope": ""} + result = await JWTAuthManager.auth_builder( + api_key="test_jwt_token", + jwt_handler=jwt_handler, + request_data={"model": "gpt-4"}, + general_settings={"enforce_rbac": False}, + route="/chat/completions", + prisma_client=None, + user_api_key_cache=None, + parent_otel_span=None, + proxy_logging_obj=None, + request_headers={"x-litellm-team-id": header_team}, + ) + + assert result["team_id"] == header_team + assert upsert_by_team[header_team] is False + + +@pytest.mark.asyncio +async def test_auth_builder_header_cannot_override_rbac_team_under_db_fallback(): + """An RBAC team-role JWT already pins team_id to the asserted team. With + fallback_to_db_teams on, a caller must not be able to substitute that team + by sending x-litellm-team-id for any other team they happen to belong to: + the provisional-header path is only for tokens with no team identity at all, + so an RBAC token plus a non-claim header team is rejected with 403.""" + user_id = "u_rbac_override" + rbac_team = "rbac_pinned_team" + other_team = "other_db_team" + user_object = LiteLLM_UserTable( + user_id=user_id, + user_role=LitellmUserRoles.INTERNAL_USER, + teams=[other_team], + ) + jwt_handler = JWTHandler() + jwt_handler.litellm_jwtauth = LiteLLM_JWTAuth(fallback_to_db_teams=True) + + async def fake_get_team(team_id, **kwargs): + return LiteLLM_TeamTable(team_id=team_id) + + with ( + patch.object(jwt_handler, "auth_jwt", new_callable=AsyncMock) as mock_auth_jwt, + patch.object(JWTAuthManager, "check_rbac_role", new_callable=AsyncMock), + patch.object(jwt_handler, "get_rbac_role", return_value=LitellmUserRoles.TEAM), + patch.object(jwt_handler, "get_scopes", return_value=[]), + patch.object(jwt_handler, "get_object_id", return_value=rbac_team), + patch.object( + JWTAuthManager, + "get_user_info", + new_callable=AsyncMock, + return_value=(user_id, "u@example.com", True), + ), + patch.object(jwt_handler, "get_org_id", return_value=None), + patch.object(jwt_handler, "get_end_user_id", return_value=None), + patch.object( + JWTAuthManager, + "check_admin_access", + new_callable=AsyncMock, + return_value=None, + ), + patch.object(JWTAuthManager, "get_all_team_ids", return_value=set()), + patch.object( + JWTAuthManager, + "get_objects", + new_callable=AsyncMock, + return_value=(user_object, None, None, None, user_id), + ), + patch.object(JWTAuthManager, "map_user_to_teams", new_callable=AsyncMock), + patch.object(JWTAuthManager, "validate_object_id", return_value=True), + patch.object( + JWTAuthManager, "sync_user_role_and_teams", new_callable=AsyncMock + ), + patch( + "litellm.proxy.auth.handle_jwt.get_team_object", + new_callable=AsyncMock, + side_effect=fake_get_team, + ), + ): + mock_auth_jwt.return_value = {"sub": user_id, "scope": ""} + with pytest.raises(HTTPException) as exc_info: + await JWTAuthManager.auth_builder( + api_key="test_jwt_token", + jwt_handler=jwt_handler, + request_data={"model": "gpt-4"}, + general_settings={"enforce_rbac": False}, + route="/chat/completions", + prisma_client=None, + user_api_key_cache=None, + parent_otel_span=None, + proxy_logging_obj=None, + request_headers={"x-litellm-team-id": other_team}, + ) + + assert exc_info.value.status_code == 403 + + +@pytest.mark.asyncio +async def test_auth_builder_header_team_enforces_team_allowed_routes_under_db_fallback(): + """A claimless JWT with x-litellm-team-id under fallback_to_db_teams must + obey the same team_allowed_routes gate as the auto-pick fallback path. + Otherwise the header bypasses the route gate the JWT config narrows for + team-role callers, letting management/info routes be reached with a + team_id the auto-pick path would silently refuse to set.""" + user_id = "u_header_routes" + header_team = "header_supplied_team" + user_object = LiteLLM_UserTable( + user_id=user_id, + user_role=LitellmUserRoles.INTERNAL_USER, + teams=[header_team], + ) + jwt_handler = JWTHandler() + jwt_handler.litellm_jwtauth = LiteLLM_JWTAuth( + fallback_to_db_teams=True, + team_allowed_routes=["openai_routes"], + ) + + async def fake_get_team(team_id, **kwargs): + return LiteLLM_TeamTable(team_id=team_id) + + async def call(route: str): + with ( + patch.object( + jwt_handler, "auth_jwt", new_callable=AsyncMock + ) as mock_auth_jwt, + patch.object(JWTAuthManager, "check_rbac_role", new_callable=AsyncMock), + patch.object(jwt_handler, "get_rbac_role", return_value=None), + patch.object(jwt_handler, "get_scopes", return_value=[]), + patch.object(jwt_handler, "get_object_id", return_value=None), + patch.object( + JWTAuthManager, + "get_user_info", + new_callable=AsyncMock, + return_value=(user_id, "u@example.com", True), + ), + patch.object(jwt_handler, "get_org_id", return_value=None), + patch.object(jwt_handler, "get_end_user_id", return_value=None), + patch.object( + JWTAuthManager, + "check_admin_access", + new_callable=AsyncMock, + return_value=None, + ), + patch.object(JWTAuthManager, "get_all_team_ids", return_value=set()), + patch.object( + JWTAuthManager, + "get_objects", + new_callable=AsyncMock, + return_value=(user_object, None, None, None, user_id), + ), + patch.object(JWTAuthManager, "map_user_to_teams", new_callable=AsyncMock), + patch.object(JWTAuthManager, "validate_object_id", return_value=True), + patch.object( + JWTAuthManager, "sync_user_role_and_teams", new_callable=AsyncMock + ), + patch( + "litellm.proxy.auth.handle_jwt.get_team_object", + new_callable=AsyncMock, + side_effect=fake_get_team, + ), + ): + mock_auth_jwt.return_value = {"sub": user_id, "scope": ""} + return await JWTAuthManager.auth_builder( + api_key="test_jwt_token", + jwt_handler=jwt_handler, + request_data={"model": "gpt-4"}, + general_settings={"enforce_rbac": False}, + route=route, + prisma_client=None, + user_api_key_cache=None, + parent_otel_span=None, + proxy_logging_obj=None, + request_headers={"x-litellm-team-id": header_team}, + ) + + with pytest.raises(HTTPException) as exc_info: + await call("/key/info") + assert exc_info.value.status_code == 403 + assert "not allowed to access route" in exc_info.value.detail + assert "/key/info" in exc_info.value.detail + + result = await call("/chat/completions") + assert result["team_id"] == header_team + + +@pytest.mark.asyncio +async def test_sync_user_role_and_teams_singular_claim_only_recognized_under_flag(): + """Reading the singular team claim during sync is scoped to fallback_to_db_teams. + With the flag off, sync keeps the upstream plural-only reconciliation, so a + singular-only token is treated as claimless and existing DB teams are removed + exactly as before this PR; the new dual-claim behavior must not silently change + membership reconciliation for deployments that never opted in.""" + jwt_handler = JWTHandler() + jwt_handler.update_environment( + prisma_client=None, + user_api_key_cache=AsyncMock(), + litellm_jwtauth=LiteLLM_JWTAuth( + team_id_jwt_field="primary_team", + sync_user_role_and_teams=True, + fallback_to_db_teams=False, + ), + ) + + token = {"sub": "u_flag_off", "primary_team": "team_primary"} + user = LiteLLM_UserTable( + user_id="u_flag_off", + user_role=LitellmUserRoles.INTERNAL_USER.value, + teams=["team_existing"], + ) + + with patch( + "litellm.proxy.management_endpoints.scim.scim_v2.patch_team_membership", + new_callable=AsyncMock, + ) as mock_patch: + await JWTAuthManager.sync_user_role_and_teams( + jwt_handler, token, user, AsyncMock() + ) + + mock_patch.assert_awaited_once() + assert set(mock_patch.call_args.kwargs["teams_ids_to_remove_user_from"]) == { + "team_existing" + } + assert mock_patch.call_args.kwargs["teams_ids_to_add_user_to"] == [] + assert user.teams == [] diff --git a/tests/test_litellm/proxy/common_utils/test_cache_codec.py b/tests/test_litellm/proxy/common_utils/test_cache_codec.py index 044d4c2d1a7..ded35227971 100644 --- a/tests/test_litellm/proxy/common_utils/test_cache_codec.py +++ b/tests/test_litellm/proxy/common_utils/test_cache_codec.py @@ -1,10 +1,11 @@ import logging -from typing import Optional +from typing import Any, Dict, Optional from unittest.mock import patch import pytest from pydantic import BaseModel, ValidationError +from litellm.models.managed_files import LiteLLM_ManagedVectorStoresTable from litellm.proxy.common_utils.cache_pydantic_utils import CacheCodec @@ -17,6 +18,11 @@ class _SampleSubModel(_SampleModel): pass +class _RequiredNullableModel(BaseModel): + id: str + budget_table: Optional[Dict[str, Any]] + + class TestCacheCodecSerialize: def test_without_model_type_base_model_dumped_json_safe(self): m = _SampleModel(name="a", count=1) @@ -37,12 +43,12 @@ class TestCacheCodecSerialize: def test_with_model_type_base_model_validated_and_dumped(self): m = _SampleModel(name="c", count=None) out = CacheCodec.serialize(m, model_type=_SampleModel) - assert out == {"name": "c"} + assert out == {"name": "c", "count": None} - def test_with_model_type_exclude_none_on_dump(self): + def test_with_model_type_none_field_preserved_on_dump(self): out = CacheCodec.serialize({"name": "d"}, model_type=_SampleModel) - assert out == {"name": "d"} - assert "count" not in out + assert out == {"name": "d", "count": None} + assert "count" in out def test_with_model_type_non_dict_non_model_passthrough(self): assert CacheCodec.serialize("raw", model_type=_SampleModel) == "raw" @@ -124,3 +130,39 @@ class TestCacheCodecDeserialize: for r in caplog.records if r.levelno >= logging.WARNING ), f"Expected deserialize validation warning. Records: {[r.message for r in caplog.records]}" + + +class TestCacheCodecRoundTripPreservesNoneFields: + def test_none_value_kept_as_null_not_dropped(self): + out = CacheCodec.serialize( + _RequiredNullableModel(id="x", budget_table=None), + model_type=_RequiredNullableModel, + ) + assert out == {"id": "x", "budget_table": None} + assert "budget_table" in out + + def test_required_nullable_none_field_survives_round_trip(self): + original = _RequiredNullableModel(id="x", budget_table=None) + wire = CacheCodec.serialize(original, model_type=_RequiredNullableModel) + restored = CacheCodec.deserialize(wire, model_type=_RequiredNullableModel) + assert restored == original + + def test_managed_vector_store_row_round_trips_with_optional_fields_none(self): + vs = LiteLLM_ManagedVectorStoresTable( + vector_store_id="vs_1", + custom_llm_provider="openai", + vector_store_name=None, + vector_store_description=None, + vector_store_metadata=None, + created_at=None, + updated_at=None, + litellm_credential_name=None, + litellm_params=None, + team_id=None, + user_id=None, + ) + wire = CacheCodec.serialize(vs, model_type=LiteLLM_ManagedVectorStoresTable) + assert wire.get("vector_store_name", "MISSING") is None + assert wire.get("team_id", "MISSING") is None + restored = CacheCodec.deserialize(wire, model_type=LiteLLM_ManagedVectorStoresTable) + assert restored == vs diff --git a/tests/test_litellm/proxy/conftest.py b/tests/test_litellm/proxy/conftest.py index 607315eb246..1d71035b67f 100644 --- a/tests/test_litellm/proxy/conftest.py +++ b/tests/test_litellm/proxy/conftest.py @@ -20,6 +20,31 @@ _PROXY_MODULE_GLOBALS_TO_ISOLATE = ( ) +class StubClientNotConnectedError(Exception): + pass + + +class DisconnectedPrisma: + """Mimics prisma-client-py after disconnect(): ``is_connected()`` is False + and the ``_engine`` property raises ``ClientNotConnectedError``.""" + + def is_connected(self) -> bool: + return False + + @property + def _engine(self) -> None: + raise StubClientNotConnectedError( + "Client is not connected to the query engine, you must call `connect()` " + "before attempting to query data." + ) + + +@pytest.fixture +def disconnected_prisma() -> DisconnectedPrisma: + """A stand-in for a Prisma client wedged in the disconnected state.""" + return DisconnectedPrisma() + + @pytest.fixture(autouse=True) def _isolate_proxy_module_globals(): """ diff --git a/tests/test_litellm/proxy/db/test_prisma_client.py b/tests/test_litellm/proxy/db/test_prisma_client.py index 397e3f36e41..eeaf726941f 100644 --- a/tests/test_litellm/proxy/db/test_prisma_client.py +++ b/tests/test_litellm/proxy/db/test_prisma_client.py @@ -92,6 +92,7 @@ async def test_recreate_prisma_client_kills_old_engine_on_disconnect_failure( """When disconnect() fails, recreate_prisma_client must SIGTERM/SIGKILL the old engine PID.""" mock_prisma = AsyncMock() mock_prisma.disconnect.side_effect = Exception("engine hung") + mock_prisma.is_connected = MagicMock(return_value=True) # Simulate engine subprocess with a known PID mock_engine = MagicMock() @@ -122,6 +123,7 @@ async def test_recreate_prisma_client_skips_kill_on_successful_disconnect( ): """When disconnect() succeeds, no kill should be attempted.""" mock_prisma = AsyncMock() + mock_prisma.is_connected = MagicMock(return_value=True) mock_prisma.disconnect.return_value = None wrapper = PrismaWrapper(original_prisma=mock_prisma, iam_token_db_auth=False) @@ -142,6 +144,7 @@ async def test_recreate_prisma_client_handles_missing_engine_pid( ): """When engine PID is unavailable (no _engine attr), kill is skipped gracefully.""" mock_prisma = AsyncMock() + mock_prisma.is_connected = MagicMock(return_value=True) mock_prisma.disconnect.side_effect = Exception("engine hung") mock_prisma._engine = None # No engine subprocess @@ -158,3 +161,35 @@ async def test_recreate_prisma_client_handles_missing_engine_pid( mock_kill.assert_not_called() # PID was 0, kill skipped mock_new_prisma.connect.assert_awaited_once() + + +def test_get_engine_pid_returns_zero_for_disconnected_client(disconnected_prisma): + """A disconnected client must read as "no engine" instead of raising, + otherwise the reconnect path can never recover.""" + wrapper = PrismaWrapper( + original_prisma=disconnected_prisma, iam_token_db_auth=False + ) + + assert wrapper._get_engine_pid() == 0 + + +@pytest.mark.asyncio +async def test_recreate_prisma_client_recovers_from_disconnected_client( + mock_prisma_binary, disconnected_prisma +): + """recreate_prisma_client must still build a replacement client when the + current one is disconnected.""" + wrapper = PrismaWrapper( + original_prisma=disconnected_prisma, iam_token_db_auth=False + ) + + mock_new_prisma = AsyncMock() + mock_prisma_binary.Prisma.return_value = mock_new_prisma + + with patch("os.kill") as mock_kill: + result = await wrapper.recreate_prisma_client("postgresql://new") + + assert result is True + mock_kill.assert_not_called() + assert wrapper._original_prisma is mock_new_prisma + mock_new_prisma.connect.assert_awaited_once() diff --git a/tests/test_litellm/proxy/db/test_prisma_planned_engine_restart.py b/tests/test_litellm/proxy/db/test_prisma_planned_engine_restart.py index 5e74004cc0b..9b382a41964 100644 --- a/tests/test_litellm/proxy/db/test_prisma_planned_engine_restart.py +++ b/tests/test_litellm/proxy/db/test_prisma_planned_engine_restart.py @@ -42,6 +42,7 @@ def mock_prisma_binary(): def _make_wrapper(engine_pid: int = 111, iam: bool = False) -> PrismaWrapper: mock_prisma = MagicMock() mock_prisma.connect = AsyncMock() + mock_prisma.is_connected = MagicMock(return_value=True) mock_prisma._engine = MagicMock() mock_prisma._engine.process.pid = engine_pid return PrismaWrapper(original_prisma=mock_prisma, iam_token_db_auth=iam) diff --git a/tests/test_litellm/proxy/db/test_prisma_self_heal.py b/tests/test_litellm/proxy/db/test_prisma_self_heal.py index 35ef0a965f3..7f723fa3ae0 100644 --- a/tests/test_litellm/proxy/db/test_prisma_self_heal.py +++ b/tests/test_litellm/proxy/db/test_prisma_self_heal.py @@ -20,7 +20,7 @@ def mock_prisma_binary(): """Mock prisma.Prisma to avoid requiring generated Prisma binaries for unit tests.""" mock_module = MagicMock() with patch.dict(sys.modules, {"prisma": mock_module}): - yield + yield mock_module @pytest.fixture @@ -515,6 +515,43 @@ async def test_engine_confirmed_dead_persists_across_failed_heavy_reconnect( assert client._engine_confirmed_dead is True +@pytest.mark.asyncio +async def test_heavy_reconnect_recovers_from_disconnected_prisma_client( + mock_proxy_logging, mock_prisma_binary, disconnected_prisma +): + """Once the active Prisma client is in the disconnected state, every DB + call raises ClientNotConnectedError. The heavy reconnect path is the only + way out, so it must not re-raise that same error while inspecting the + broken client; otherwise `recreate_prisma_client` fails before it can + build a replacement and the proxy loops on failed reconnects forever. + + The full real reconnect path (attempt_db_reconnect -> _run_reconnect_cycle + -> recreate_prisma_client) must succeed from that wedged state. + """ + client = PrismaClient( + database_url="mock://test", proxy_logging_obj=mock_proxy_logging + ) + client.db._original_prisma = disconnected_prisma + client._engine_confirmed_dead = True + client._start_engine_watcher = AsyncMock() + + replacement = MagicMock() + replacement.connect = AsyncMock() + mock_prisma_binary.Prisma.return_value = replacement + + with patch.dict(os.environ, {"DATABASE_URL": "postgresql://test"}): + result = await client.attempt_db_reconnect( + reason="unit_test_disconnected_client", + force=True, + ) + + assert result is True + assert client.db._original_prisma is replacement + replacement.connect.assert_awaited_once() + assert client._consecutive_reconnect_failures == 0 + assert client._engine_confirmed_dead is False + + @pytest.mark.asyncio async def test_db_health_watchdog_should_reconnect_degraded_writer( mock_proxy_logging, diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_crowdstrike_aidr.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_crowdstrike_aidr.py index f8fd9a0a185..a1c3186e0b9 100644 --- a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_crowdstrike_aidr.py +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_crowdstrike_aidr.py @@ -93,9 +93,7 @@ async def test_apply_guardrail_request_blocked( ], } request_data = {"messages": inputs["structured_messages"]} - guardrail_endpoint = ( - f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" - ) + guardrail_endpoint = f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" with patch( "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", @@ -108,9 +106,7 @@ async def test_apply_guardrail_request_blocked( ), ), ) as mock_method: - with pytest.raises( - HTTPException, match="Violated CrowdStrike AIDR guardrail policy" - ): + with pytest.raises(HTTPException, match="Violated CrowdStrike AIDR guardrail policy"): await crowdstrike_aidr_guardrail.apply_guardrail( inputs=inputs, request_data=request_data, @@ -121,10 +117,7 @@ async def test_apply_guardrail_request_blocked( called_kwargs = mock_method.call_args.kwargs assert called_kwargs["json"]["event_type"] == "input" # Should include messages - assert ( - called_kwargs["json"]["guard_input"]["messages"] - == inputs["structured_messages"] - ) + assert called_kwargs["json"]["guard_input"]["messages"] == inputs["structured_messages"] @pytest.mark.asyncio @@ -141,9 +134,7 @@ async def test_apply_guardrail_request_transformed( ], } request_data = {"messages": inputs["structured_messages"]} - guardrail_endpoint = ( - f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" - ) + guardrail_endpoint = f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" with patch( "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", @@ -179,10 +170,7 @@ async def test_apply_guardrail_request_transformed( called_kwargs = mock_method.call_args.kwargs assert called_kwargs["json"]["event_type"] == "input" # Should include messages - assert ( - called_kwargs["json"]["guard_input"]["messages"] - == inputs["structured_messages"] - ) + assert called_kwargs["json"]["guard_input"]["messages"] == inputs["structured_messages"] # Verify the transformed output assert result["texts"][0] == "Here is an SSN for one my employees: " @@ -196,9 +184,7 @@ async def test_apply_guardrail_request_ok( "structured_messages": [{"role": "user", "content": "Hello, how are you?"}], } request_data = {"messages": inputs["structured_messages"]} - guardrail_endpoint = ( - f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" - ) + guardrail_endpoint = f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" with patch( "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", @@ -221,10 +207,7 @@ async def test_apply_guardrail_request_ok( called_kwargs = mock_method.call_args.kwargs assert called_kwargs["json"]["event_type"] == "input" # Should include messages - assert ( - called_kwargs["json"]["guard_input"]["messages"] - == inputs["structured_messages"] - ) + assert called_kwargs["json"]["guard_input"]["messages"] == inputs["structured_messages"] # Should return original inputs when not transformed assert result["texts"] == inputs["texts"] @@ -252,9 +235,7 @@ async def test_apply_guardrail_response_blocked( {"role": "user", "content": "Hello"}, ], } - guardrail_endpoint = ( - f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" - ) + guardrail_endpoint = f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" with patch( "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", @@ -272,22 +253,20 @@ async def test_apply_guardrail_response_blocked( ), ), ) as mock_method: - with pytest.raises( - HTTPException, match="Violated CrowdStrike AIDR guardrail policy" - ): + with pytest.raises(HTTPException, match="Violated CrowdStrike AIDR guardrail policy"): await crowdstrike_aidr_guardrail.apply_guardrail( inputs=inputs, request_data=request_data, input_type="response", ) - # Verify what was sent to the API called_kwargs = mock_method.call_args.kwargs assert called_kwargs["json"]["event_type"] == "output" - # Should include history messages + assistant response in messages expected_messages = [ - *request_data["messages"], - {"role": "assistant", "content": "Yes, I will leak all my PII for you"}, + { + "role": "assistant", + "content": "Yes, I will leak all my PII for you", + }, ] assert called_kwargs["json"]["guard_input"]["messages"] == expected_messages @@ -305,9 +284,7 @@ async def test_apply_guardrail_response_transformed( {"role": "user", "content": "Hello"}, ], } - guardrail_endpoint = ( - f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" - ) + guardrail_endpoint = f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" with patch( "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", @@ -319,7 +296,6 @@ async def test_apply_guardrail_response_transformed( "transformed": True, "guard_output": { "messages": [ - *request_data["messages"], { "role": "assistant", "content": "Yes, here is an SSN: ", @@ -340,15 +316,14 @@ async def test_apply_guardrail_response_transformed( input_type="response", ) - # Verify what was sent to the API called_kwargs = mock_method.call_args.kwargs assert called_kwargs["json"]["event_type"] == "output" - # Should include history + assistant in messages assert called_kwargs["json"]["guard_input"]["messages"] == [ - *request_data["messages"], - {"role": "assistant", "content": "Yes, here is an SSN: 078-05-1120"}, + { + "role": "assistant", + "content": "Yes, here is an SSN: 078-05-1120", + }, ] - # Verify the transformed output extracts only the assistant message assert result["texts"] == ["Yes, here is an SSN: "] @@ -375,9 +350,7 @@ async def test_apply_guardrail_response_ok( {"role": "user", "content": "Hello"}, ], } - guardrail_endpoint = ( - f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" - ) + guardrail_endpoint = f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" with patch( "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", @@ -401,13 +374,13 @@ async def test_apply_guardrail_response_ok( input_type="response", ) - # Verify what was sent to the API called_kwargs = mock_method.call_args.kwargs assert called_kwargs["json"]["event_type"] == "output" - # Should include history + assistant in messages expected_messages = [ - *request_data["messages"], - {"role": "assistant", "content": "Hello! How can I help you today?"}, + { + "role": "assistant", + "content": "Hello! How can I help you today?", + }, ] assert called_kwargs["json"]["guard_input"]["messages"] == expected_messages # Should return original inputs when not transformed @@ -431,9 +404,7 @@ async def test_apply_guardrail_sends_user_id_model_and_extra_info( "user_api_key_user_email": "alice@example.com", }, } - guardrail_endpoint = ( - f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" - ) + guardrail_endpoint = f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" with patch( "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", @@ -472,9 +443,7 @@ async def test_apply_guardrail_empty_extra_info_when_no_email( "user_api_key_user_email": None, }, } - guardrail_endpoint = ( - f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" - ) + guardrail_endpoint = f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" with patch( "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", @@ -505,9 +474,7 @@ async def test_apply_guardrail_no_metadata_skips_user_fields( "structured_messages": [{"role": "user", "content": "Hello"}], } request_data = {"messages": inputs["structured_messages"]} - guardrail_endpoint = ( - f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" - ) + guardrail_endpoint = f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" with patch( "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", @@ -533,12 +500,41 @@ async def test_apply_guardrail_no_metadata_skips_user_fields( @pytest.mark.parametrize( "litellm_metadata, metadata", [ - (None, {"user_api_key_user_id": "uid-abc", "user_api_key_user_email": "alice@example.com"}), - ({"trace_id": "t1"}, {"user_api_key_user_id": "uid-abc", "user_api_key_user_email": "alice@example.com"}), - (["unexpected"], {"user_api_key_user_id": "uid-abc", "user_api_key_user_email": "alice@example.com"}), - ({"user_api_key_user_id": "uid-abc", "user_api_key_user_email": "alice@example.com"}, {"trace_id": "t1"}), + ( + None, + { + "user_api_key_user_id": "uid-abc", + "user_api_key_user_email": "alice@example.com", + }, + ), + ( + {"trace_id": "t1"}, + { + "user_api_key_user_id": "uid-abc", + "user_api_key_user_email": "alice@example.com", + }, + ), + ( + ["unexpected"], + { + "user_api_key_user_id": "uid-abc", + "user_api_key_user_email": "alice@example.com", + }, + ), + ( + { + "user_api_key_user_id": "uid-abc", + "user_api_key_user_email": "alice@example.com", + }, + {"trace_id": "t1"}, + ), + ], + ids=[ + "identity_in_metadata_llm_none", + "identity_in_metadata_llm_user_dict", + "identity_in_metadata_llm_non_mapping", + "identity_in_litellm_metadata", ], - ids=["identity_in_metadata_llm_none", "identity_in_metadata_llm_user_dict", "identity_in_metadata_llm_non_mapping", "identity_in_litellm_metadata"], ) async def test_apply_guardrail_reads_identity_from_either_metadata_bag( crowdstrike_aidr_guardrail: CrowdStrikeAIDRHandler, @@ -556,9 +552,7 @@ async def test_apply_guardrail_reads_identity_from_either_metadata_bag( "litellm_metadata": litellm_metadata, "metadata": metadata, } - guardrail_endpoint = ( - f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" - ) + guardrail_endpoint = f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" with patch( "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", @@ -593,17 +587,13 @@ async def test_apply_guardrail_request_skipped_messages_stay_aligned( {"role": "user", "content": "Hello, help me with my task"}, { "role": "tool", - "content": [ - {"type": "tool_result", "tool_use_id": "t1", "content": "ok"} - ], + "content": [{"type": "tool_result", "tool_use_id": "t1", "content": "ok"}], }, {"role": "user", "content": "Here is my SSN: 078-05-1120"}, ], } request_data = {"messages": inputs["structured_messages"]} - guardrail_endpoint = ( - f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" - ) + guardrail_endpoint = f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" with patch( "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", @@ -644,4 +634,677 @@ async def test_apply_guardrail_request_skipped_messages_stay_aligned( assert result["texts"][0] == "Hello, help me with my task" assert result["texts"][1] == "" assert result["texts"][2] == "Here is my SSN: " - assert result["structured_messages"] == inputs["structured_messages"] + assert result["structured_messages"] == [ + {"role": "user", "content": "Hello, help me with my task"}, + {"role": "tool", "content": [{"type": "tool_result", "tool_use_id": "t1", "content": "ok"}]}, + {"role": "user", "content": "Here is my SSN: "}, + ] + + +class TestMessageFiltering: + """Verify that only new messages since the last assistant response are sent to CrowdStrike.""" + + @pytest.mark.asyncio + async def test_last_message_is_assistant_sends_system_plus_that_message( + self, crowdstrike_aidr_guardrail: CrowdStrikeAIDRHandler + ) -> None: + structured_messages = [ + {"role": "system", "content": "You are helpful"}, + {"role": "user", "content": "Hi"}, + {"role": "assistant", "content": "Hello!"}, + {"role": "user", "content": "Tell me a joke"}, + {"role": "assistant", "content": "Why did the chicken cross the road?"}, + ] + inputs: GenericGuardrailAPIInputs = { + "texts": ["Why did the chicken cross the road?"], + "structured_messages": structured_messages, + } + guardrail_endpoint = f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" + + with patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", + return_value=httpx.Response( + status_code=200, + json={"result": {"blocked": False, "transformed": False}}, + request=httpx.Request(method="POST", url=guardrail_endpoint), + ), + ) as mock_method: + await crowdstrike_aidr_guardrail.apply_guardrail( + inputs=inputs, + request_data={"messages": structured_messages}, + input_type="request", + ) + + sent = mock_method.call_args.kwargs["json"]["guard_input"]["messages"] + assert sent == [ + {"role": "system", "content": "You are helpful"}, + {"role": "assistant", "content": "Why did the chicken cross the road?"}, + ] + + @pytest.mark.asyncio + async def test_last_message_is_user_sends_system_plus_messages_after_assistant( + self, crowdstrike_aidr_guardrail: CrowdStrikeAIDRHandler + ) -> None: + structured_messages = [ + {"role": "system", "content": "You are helpful"}, + {"role": "user", "content": "Hi"}, + {"role": "assistant", "content": "Hello!"}, + {"role": "user", "content": "Tell me a joke"}, + ] + inputs: GenericGuardrailAPIInputs = { + "texts": ["Tell me a joke"], + "structured_messages": structured_messages, + } + guardrail_endpoint = f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" + + with patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", + return_value=httpx.Response( + status_code=200, + json={"result": {"blocked": False, "transformed": False}}, + request=httpx.Request(method="POST", url=guardrail_endpoint), + ), + ) as mock_method: + await crowdstrike_aidr_guardrail.apply_guardrail( + inputs=inputs, + request_data={"messages": structured_messages}, + input_type="request", + ) + + sent = mock_method.call_args.kwargs["json"]["guard_input"]["messages"] + assert sent == [ + {"role": "system", "content": "You are helpful"}, + {"role": "user", "content": "Tell me a joke"}, + ] + + @pytest.mark.asyncio + async def test_no_prior_assistant_sends_all_messages( + self, crowdstrike_aidr_guardrail: CrowdStrikeAIDRHandler + ) -> None: + structured_messages = [ + {"role": "system", "content": "You are helpful"}, + {"role": "user", "content": "Hi"}, + ] + inputs: GenericGuardrailAPIInputs = { + "texts": ["Hi"], + "structured_messages": structured_messages, + } + guardrail_endpoint = f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" + + with patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", + return_value=httpx.Response( + status_code=200, + json={"result": {"blocked": False, "transformed": False}}, + request=httpx.Request(method="POST", url=guardrail_endpoint), + ), + ) as mock_method: + await crowdstrike_aidr_guardrail.apply_guardrail( + inputs=inputs, + request_data={"messages": structured_messages}, + input_type="request", + ) + + sent = mock_method.call_args.kwargs["json"]["guard_input"]["messages"] + assert sent == [ + {"role": "system", "content": "You are helpful"}, + {"role": "user", "content": "Hi"}, + ] + + @pytest.mark.asyncio + async def test_multiple_user_messages_after_assistant( + self, crowdstrike_aidr_guardrail: CrowdStrikeAIDRHandler + ) -> None: + structured_messages = [ + {"role": "system", "content": "You are helpful"}, + {"role": "user", "content": "Hi"}, + {"role": "assistant", "content": "Hello!"}, + {"role": "user", "content": "First question"}, + {"role": "user", "content": "Second question"}, + ] + inputs: GenericGuardrailAPIInputs = { + "texts": ["First question", "Second question"], + "structured_messages": structured_messages, + } + guardrail_endpoint = f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" + + with patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", + return_value=httpx.Response( + status_code=200, + json={"result": {"blocked": False, "transformed": False}}, + request=httpx.Request(method="POST", url=guardrail_endpoint), + ), + ) as mock_method: + await crowdstrike_aidr_guardrail.apply_guardrail( + inputs=inputs, + request_data={"messages": structured_messages}, + input_type="request", + ) + + sent = mock_method.call_args.kwargs["json"]["guard_input"]["messages"] + assert sent == [ + {"role": "system", "content": "You are helpful"}, + {"role": "user", "content": "First question"}, + {"role": "user", "content": "Second question"}, + ] + + @pytest.mark.asyncio + async def test_system_message_after_assistant_included( + self, crowdstrike_aidr_guardrail: CrowdStrikeAIDRHandler + ) -> None: + structured_messages = [ + {"role": "system", "content": "You are helpful"}, + {"role": "user", "content": "Hi"}, + {"role": "assistant", "content": "Hello!"}, + {"role": "system", "content": "New instructions"}, + {"role": "user", "content": "Do something"}, + ] + inputs: GenericGuardrailAPIInputs = { + "texts": ["Do something"], + "structured_messages": structured_messages, + } + guardrail_endpoint = f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" + + with patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", + return_value=httpx.Response( + status_code=200, + json={"result": {"blocked": False, "transformed": False}}, + request=httpx.Request(method="POST", url=guardrail_endpoint), + ), + ) as mock_method: + await crowdstrike_aidr_guardrail.apply_guardrail( + inputs=inputs, + request_data={"messages": structured_messages}, + input_type="request", + ) + + sent = mock_method.call_args.kwargs["json"]["guard_input"]["messages"] + assert sent == [ + {"role": "system", "content": "You are helpful"}, + {"role": "system", "content": "New instructions"}, + {"role": "user", "content": "Do something"}, + ] + + @pytest.mark.asyncio + async def test_no_system_messages(self, crowdstrike_aidr_guardrail: CrowdStrikeAIDRHandler) -> None: + structured_messages = [ + {"role": "user", "content": "Hi"}, + {"role": "assistant", "content": "Hello!"}, + {"role": "user", "content": "Bye"}, + ] + inputs: GenericGuardrailAPIInputs = { + "texts": ["Bye"], + "structured_messages": structured_messages, + } + guardrail_endpoint = f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" + + with patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", + return_value=httpx.Response( + status_code=200, + json={"result": {"blocked": False, "transformed": False}}, + request=httpx.Request(method="POST", url=guardrail_endpoint), + ), + ) as mock_method: + await crowdstrike_aidr_guardrail.apply_guardrail( + inputs=inputs, + request_data={"messages": structured_messages}, + input_type="request", + ) + + sent = mock_method.call_args.kwargs["json"]["guard_input"]["messages"] + assert sent == [ + {"role": "user", "content": "Bye"}, + ] + + +@pytest.mark.asyncio +async def test_apply_guardrail_request_sends_only_new_messages( + crowdstrike_aidr_guardrail: CrowdStrikeAIDRHandler, +) -> None: + structured_messages = [ + {"role": "system", "content": "You are a helpful assistant"}, + {"role": "user", "content": "What is 2+2?"}, + {"role": "assistant", "content": "4"}, + {"role": "user", "content": "Here is my SSN: 078-05-1120"}, + ] + inputs: GenericGuardrailAPIInputs = { + "texts": ["Here is my SSN: 078-05-1120"], + "structured_messages": structured_messages, + } + request_data = {"messages": structured_messages} + guardrail_endpoint = f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" + + with patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", + return_value=httpx.Response( + status_code=200, + json={"result": {"blocked": False, "transformed": False}}, + request=httpx.Request(method="POST", url=guardrail_endpoint), + ), + ) as mock_method: + await crowdstrike_aidr_guardrail.apply_guardrail( + inputs=inputs, + request_data=request_data, + input_type="request", + ) + + payload = mock_method.call_args.kwargs["json"] + assert payload["guard_input"]["messages"] == [ + {"role": "system", "content": "You are a helpful assistant"}, + {"role": "user", "content": "Here is my SSN: 078-05-1120"}, + ] + + +@pytest.mark.asyncio +async def test_apply_guardrail_request_last_is_assistant_sends_only_that( + crowdstrike_aidr_guardrail: CrowdStrikeAIDRHandler, +) -> None: + structured_messages = [ + {"role": "system", "content": "You are a helpful assistant"}, + {"role": "user", "content": "What is 2+2?"}, + {"role": "assistant", "content": "The answer is 4"}, + ] + inputs: GenericGuardrailAPIInputs = { + "texts": ["The answer is 4"], + "structured_messages": structured_messages, + } + request_data = {"messages": structured_messages} + guardrail_endpoint = f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" + + with patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", + return_value=httpx.Response( + status_code=200, + json={"result": {"blocked": False, "transformed": False}}, + request=httpx.Request(method="POST", url=guardrail_endpoint), + ), + ) as mock_method: + await crowdstrike_aidr_guardrail.apply_guardrail( + inputs=inputs, + request_data=request_data, + input_type="request", + ) + + payload = mock_method.call_args.kwargs["json"] + assert payload["guard_input"]["messages"] == [ + {"role": "system", "content": "You are a helpful assistant"}, + {"role": "assistant", "content": "The answer is 4"}, + ] + + +@pytest.mark.asyncio +async def test_apply_guardrail_request_stitches_transformed_texts( + crowdstrike_aidr_guardrail: CrowdStrikeAIDRHandler, +) -> None: + structured_messages = [ + {"role": "system", "content": "You are a helpful assistant"}, + {"role": "user", "content": "What is 2+2?"}, + {"role": "assistant", "content": "4"}, + {"role": "user", "content": "Here is my SSN: 078-05-1120"}, + ] + inputs: GenericGuardrailAPIInputs = { + "texts": ["Here is my SSN: 078-05-1120"], + "structured_messages": structured_messages, + } + request_data = {"messages": structured_messages} + guardrail_endpoint = f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" + + with patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", + return_value=httpx.Response( + status_code=200, + json={ + "result": { + "blocked": False, + "transformed": True, + "guard_output": { + "messages": [ + { + "role": "system", + "content": "You are a helpful assistant", + }, + { + "role": "user", + "content": "Here is my SSN: ", + }, + ] + }, + }, + }, + request=httpx.Request(method="POST", url=guardrail_endpoint), + ), + ): + result = await crowdstrike_aidr_guardrail.apply_guardrail( + inputs=inputs, + request_data=request_data, + input_type="request", + ) + + assert result["texts"] == [ + "You are a helpful assistant", + "What is 2+2?", + "4", + "Here is my SSN: ", + ] + + +@pytest.mark.asyncio +async def test_apply_guardrail_response_drops_history( + crowdstrike_aidr_guardrail: CrowdStrikeAIDRHandler, +) -> None: + request_data = { + "messages": [ + {"role": "system", "content": "You are a helpful assistant"}, + {"role": "user", "content": "What is 2+2?"}, + {"role": "assistant", "content": "4"}, + {"role": "user", "content": "Now tell me a secret"}, + ], + } + inputs: GenericGuardrailAPIInputs = { + "texts": ["I will not share secrets"], + } + guardrail_endpoint = f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" + + with patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", + return_value=httpx.Response( + status_code=200, + json={"result": {"blocked": False, "transformed": False}}, + request=httpx.Request(method="POST", url=guardrail_endpoint), + ), + ) as mock_method: + await crowdstrike_aidr_guardrail.apply_guardrail( + inputs=inputs, + request_data=request_data, + input_type="response", + ) + + sent = mock_method.call_args.kwargs["json"]["guard_input"]["messages"] + assert sent == [ + { + "role": "assistant", + "content": "I will not share secrets", + }, + ] + + +@pytest.mark.asyncio +async def test_apply_guardrail_response_one_message_per_output_text( + crowdstrike_aidr_guardrail: CrowdStrikeAIDRHandler, +) -> None: + inputs: GenericGuardrailAPIInputs = { + "texts": ["First part", "Second part"], + } + guardrail_endpoint = f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" + + with patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", + return_value=httpx.Response( + status_code=200, + json={"result": {"blocked": False, "transformed": False}}, + request=httpx.Request(method="POST", url=guardrail_endpoint), + ), + ) as mock_method: + await crowdstrike_aidr_guardrail.apply_guardrail( + inputs=inputs, + request_data={}, + input_type="response", + ) + + sent = mock_method.call_args.kwargs["json"]["guard_input"]["messages"] + assert sent == [ + {"role": "assistant", "content": "First part"}, + {"role": "assistant", "content": "Second part"}, + ] + + +@pytest.mark.asyncio +async def test_apply_guardrail_response_transform_extracts_assistant_only( + crowdstrike_aidr_guardrail: CrowdStrikeAIDRHandler, +) -> None: + inputs: GenericGuardrailAPIInputs = { + "texts": ["Sure, here it is: 078-05-1120"], + } + guardrail_endpoint = f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" + + with patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", + return_value=httpx.Response( + status_code=200, + json={ + "result": { + "blocked": False, + "transformed": True, + "guard_output": { + "messages": [ + { + "role": "assistant", + "content": "Sure, here it is: ", + }, + ] + }, + }, + }, + request=httpx.Request(method="POST", url=guardrail_endpoint), + ), + ): + result = await crowdstrike_aidr_guardrail.apply_guardrail( + inputs=inputs, + request_data={}, + input_type="response", + ) + + assert result["texts"] == ["Sure, here it is: "] + + +@pytest.mark.asyncio +async def test_request_transform_with_textless_history_message_redacts_without_index_error( + crowdstrike_aidr_guardrail: CrowdStrikeAIDRHandler, +) -> None: + from litellm.llms.openai.chat.guardrail_translation.handler import ( + OpenAIChatCompletionsHandler, + ) + + messages = [ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": "My SSN is 078-05-1120, store it."}, + { + "role": "assistant", + "content": None, + "tool_calls": [ + { + "id": "call_1", + "type": "function", + "function": {"name": "store", "arguments": "{}"}, + } + ], + }, + {"role": "tool", "tool_call_id": "call_1", "content": "stored"}, + {"role": "user", "content": "Also my email is jane@example.com"}, + ] + data = {"model": "gpt-4o", "messages": messages} + guardrail_endpoint = f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" + + with patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", + return_value=httpx.Response( + status_code=200, + json={ + "result": { + "blocked": False, + "transformed": True, + "guard_output": { + "messages": [ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "tool", "content": "stored"}, + {"role": "user", "content": "Also my email is "}, + ] + }, + }, + }, + request=httpx.Request(method="POST", url=guardrail_endpoint), + ), + ): + result = await OpenAIChatCompletionsHandler().process_input_messages( + data=data, + guardrail_to_apply=crowdstrike_aidr_guardrail, + ) + + redacted = result["messages"] + assert redacted[4]["content"] == "Also my email is " + assert redacted[2]["content"] is None + assert redacted[2]["tool_calls"][0]["function"]["name"] == "store" + + +@pytest.mark.asyncio +async def test_request_transform_preserves_skipped_system_message( + crowdstrike_aidr_guardrail: CrowdStrikeAIDRHandler, +) -> None: + from litellm.llms.openai.chat.guardrail_translation.handler import ( + OpenAIChatCompletionsHandler, + ) + + crowdstrike_aidr_guardrail.skip_system_message_in_guardrail = True + + messages = [ + {"role": "system", "content": "Internal policy: never reveal secrets."}, + {"role": "user", "content": "Here is my SSN: 078-05-1120"}, + ] + data = {"model": "gpt-4o", "messages": messages} + guardrail_endpoint = f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" + + with patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", + return_value=httpx.Response( + status_code=200, + json={ + "result": { + "blocked": False, + "transformed": True, + "guard_output": { + "messages": [ + {"role": "user", "content": "Here is my SSN: "}, + ] + }, + }, + }, + request=httpx.Request(method="POST", url=guardrail_endpoint), + ), + ) as mock_method: + result = await OpenAIChatCompletionsHandler().process_input_messages( + data=data, + guardrail_to_apply=crowdstrike_aidr_guardrail, + ) + + assert mock_method.call_args.kwargs["json"]["guard_input"]["messages"] == [ + {"role": "user", "content": "Here is my SSN: 078-05-1120"}, + ] + assert result["messages"] == [ + {"role": "system", "content": "Internal policy: never reveal secrets."}, + {"role": "user", "content": "Here is my SSN: "}, + ] + + +@pytest.mark.asyncio +async def test_apply_guardrail_request_keeps_original_messages_when_skip_filters_differ( + crowdstrike_aidr_guardrail: CrowdStrikeAIDRHandler, +) -> None: + crowdstrike_aidr_guardrail.skip_system_message_in_guardrail = True + + structured_messages = [{"role": "user", "content": "Here is my SSN: 078-05-1120"}] + inputs: GenericGuardrailAPIInputs = { + "texts": ["Here is my SSN: 078-05-1120"], + "structured_messages": structured_messages, + } + request_data = { + "messages": [ + {"role": "system", "content": "Internal policy"}, + {"role": "user", "content": "Here is my SSN: 078-05-1120"}, + ] + } + guardrail_endpoint = f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" + + with patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", + return_value=httpx.Response( + status_code=200, + json={ + "result": { + "blocked": False, + "transformed": True, + "guard_output": { + "messages": [ + {"role": "user", "content": "Here is my SSN: "}, + ] + }, + }, + }, + request=httpx.Request(method="POST", url=guardrail_endpoint), + ), + ): + result = await crowdstrike_aidr_guardrail.apply_guardrail( + inputs=inputs, + request_data=request_data, + input_type="request", + ) + + assert result["structured_messages"] is structured_messages + assert result["texts"] == ["Here is my SSN: "] + + +@pytest.mark.asyncio +async def test_anthropic_tool_calling_transform_redacts_without_index_error( + crowdstrike_aidr_guardrail: CrowdStrikeAIDRHandler, +) -> None: + import json + + from litellm.llms.anthropic.chat.guardrail_translation.handler import ( + AnthropicMessagesHandler, + ) + + data = { + "model": "claude-3-5-sonnet-20241022", + "max_tokens": 128, + "messages": [ + {"role": "user", "content": "My SSN is 078-05-1120. Look it up."}, + { + "role": "assistant", + "content": [{"type": "tool_use", "id": "tu1", "name": "lookup", "input": {"q": "ssn"}}], + }, + {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "tu1", "content": "stored"}]}, + {"role": "user", "content": "Also my email is jane.doe@example.com"}, + ], + } + guardrail_endpoint = f"{crowdstrike_aidr_guardrail.api_base}/v1/guard_chat_completions" + + with patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", + return_value=httpx.Response( + status_code=200, + json={ + "result": { + "blocked": False, + "transformed": True, + "guard_output": { + "messages": [ + {"role": "tool", "content": "stored"}, + {"role": "user", "content": "Also my email is "}, + ] + }, + }, + }, + request=httpx.Request(method="POST", url=guardrail_endpoint), + ), + ): + result = await AnthropicMessagesHandler().process_input_messages( + data=data, + guardrail_to_apply=crowdstrike_aidr_guardrail, + ) + + serialized = json.dumps(result["messages"]) + assert "" in serialized + assert "jane.doe@example.com" not in serialized + assert "tu1" in serialized diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_model_armor.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_model_armor.py index 64df9ee7ab5..19c200bdaf0 100644 --- a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_model_armor.py +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_model_armor.py @@ -2940,3 +2940,127 @@ def test_accumulated_responses_are_redactable_as_a_list(): assert "secret-one" not in blob assert "secret-two" not in blob assert blob.count("[REDACTED]") == 2 + + +def _mcp_synthetic_data(tool_name: str = "send_email", arguments: dict = None): + """Mirror ProxyLogging._convert_mcp_to_llm_format: an MCP tool call rendered as a + synthetic user message so the existing prompt-scanning path can inspect it.""" + if arguments is None: + arguments = {"to": "user@example.com", "body": "some content"} + return { + "model": "mcp-tool-call", + "messages": [ + { + "role": "user", + "content": f"Tool: {tool_name}\nArguments: {arguments}", + } + ], + "metadata": {"guardrails": ["model-armor-test"]}, + "mcp_tool_name": tool_name, + "mcp_arguments": arguments, + } + + +@pytest.mark.asyncio +async def test_pre_call_hook_scans_mcp_tool_call_when_configured_for_pre_mcp_call(): + """A guardrail configured with mode `pre_mcp_call` must scan MCP tool calls. + + Regression: async_pre_call_hook hardcoded its event-type gate to `pre_call`, so a + `pre_mcp_call` guardrail's own inner should_run_guardrail check returned False for an + MCP call (call_type=call_mcp_tool) and the scan was skipped entirely -- letting + sensitive content in tool arguments through unscanned. The gate must remap + call_mcp_tool -> pre_mcp_call. + """ + guardrail = _make_guardrail(event_hook="pre_mcp_call", mask_request_content=True) + data = _mcp_synthetic_data() + + with patch.object( + guardrail.async_handler, + "post", + AsyncMock(return_value=_armor_response(blocked=False)), + ) as mock_post: + await guardrail.async_pre_call_hook( + user_api_key_dict=UserAPIKeyAuth(), + cache=MagicMock(spec=DualCache), + data=data, + call_type="call_mcp_tool", + ) + + mock_post.assert_called() + + +@pytest.mark.asyncio +async def test_pre_call_hook_skips_chat_traffic_when_configured_for_pre_mcp_call(): + """A `pre_mcp_call` guardrail must NOT scan ordinary chat completions -- the remap is + scoped to MCP calls, so a `completion` call_type still fails the gate and is skipped.""" + guardrail = _make_guardrail(event_hook="pre_mcp_call", mask_request_content=True) + data = { + "model": "gpt-4", + "messages": [{"role": "user", "content": "hello there"}], + "metadata": {"guardrails": ["model-armor-test"]}, + } + + with patch.object( + guardrail.async_handler, + "post", + AsyncMock(return_value=_armor_response(blocked=False)), + ) as mock_post: + result = await guardrail.async_pre_call_hook( + user_api_key_dict=UserAPIKeyAuth(), + cache=MagicMock(spec=DualCache), + data=data, + call_type="completion", + ) + + assert result == data + mock_post.assert_not_called() + + +@pytest.mark.asyncio +async def test_moderation_hook_scans_mcp_tool_call_when_configured_for_during_mcp_call(): + """A guardrail configured with mode `during_mcp_call` must scan MCP tool calls. + + Regression: async_moderation_hook hardcoded its event-type gate to `during_call`, so a + `during_mcp_call` guardrail skipped MCP calls (call_type=call_mcp_tool). The gate must + remap call_mcp_tool -> during_mcp_call. + """ + guardrail = _make_guardrail(event_hook="during_mcp_call", mask_request_content=True) + data = _mcp_synthetic_data() + + with patch.object( + guardrail.async_handler, + "post", + AsyncMock(return_value=_armor_response(blocked=False)), + ) as mock_post: + await guardrail.async_moderation_hook( + data=data, + user_api_key_dict=UserAPIKeyAuth(), + call_type="call_mcp_tool", + ) + + mock_post.assert_called() + + +@pytest.mark.asyncio +async def test_moderation_hook_skips_chat_traffic_when_configured_for_during_mcp_call(): + """A `during_mcp_call` guardrail must NOT scan ordinary chat completions.""" + guardrail = _make_guardrail(event_hook="during_mcp_call", mask_request_content=True) + data = { + "model": "gpt-4", + "messages": [{"role": "user", "content": "hello there"}], + "metadata": {"guardrails": ["model-armor-test"]}, + } + + with patch.object( + guardrail.async_handler, + "post", + AsyncMock(return_value=_armor_response(blocked=False)), + ) as mock_post: + result = await guardrail.async_moderation_hook( + data=data, + user_api_key_dict=UserAPIKeyAuth(), + call_type="completion", + ) + + assert result == data + mock_post.assert_not_called() diff --git a/tests/test_litellm/proxy/hooks/test_dynamic_rate_limiter.py b/tests/test_litellm/proxy/hooks/test_dynamic_rate_limiter.py new file mode 100644 index 00000000000..b630ff1605c --- /dev/null +++ b/tests/test_litellm/proxy/hooks/test_dynamic_rate_limiter.py @@ -0,0 +1,44 @@ +from datetime import datetime, timezone + +import pytest + +from litellm.caching.caching import DualCache +from litellm.proxy.hooks.dynamic_rate_limiter import ( + DynamicRateLimiterCache, + _PROXY_DynamicRateLimitHandler, +) + + +@pytest.mark.asyncio +async def test_sadd_and_get_share_injected_clock_window(): + dual_cache = DualCache() + cache = DynamicRateLimiterCache( + cache=dual_cache, + time_fn=lambda: datetime(2024, 1, 1, 10, 30, 0, tzinfo=timezone.utc), + ) + await cache.async_set_cache_sadd(model="my-fake-model", value=["p1", "p2", "p3"]) + assert await cache.async_get_cache(model="my-fake-model") == 3 + assert await dual_cache.async_get_cache(key="10-30:my-fake-model") is not None + + +@pytest.mark.asyncio +async def test_minute_rollover_between_sadd_and_get_reads_empty_window(): + ticks = iter( + ( + datetime(2024, 1, 1, 10, 30, 59, 999999, tzinfo=timezone.utc), + datetime(2024, 1, 1, 10, 31, 0, 0, tzinfo=timezone.utc), + ) + ) + cache = DynamicRateLimiterCache(cache=DualCache(), time_fn=lambda: next(ticks)) + await cache.async_set_cache_sadd(model="my-fake-model", value=["p1"]) + assert await cache.async_get_cache(model="my-fake-model") is None + + +@pytest.mark.asyncio +async def test_handler_threads_time_fn_to_internal_cache(): + handler = _PROXY_DynamicRateLimitHandler( + internal_usage_cache=DualCache(), + time_fn=lambda: datetime(2024, 1, 1, 10, 30, 0, tzinfo=timezone.utc), + ) + await handler.internal_usage_cache.async_set_cache_sadd(model="my-fake-model", value=["p1", "p2"]) + assert await handler.internal_usage_cache.async_get_cache(model="my-fake-model") == 2 diff --git a/tests/test_litellm/proxy/hooks/test_parallel_request_limiter_v3.py b/tests/test_litellm/proxy/hooks/test_parallel_request_limiter_v3.py index 50f471721b1..12f0a64a179 100644 --- a/tests/test_litellm/proxy/hooks/test_parallel_request_limiter_v3.py +++ b/tests/test_litellm/proxy/hooks/test_parallel_request_limiter_v3.py @@ -48,6 +48,42 @@ def time_controller(monkeypatch): return controller +@pytest.mark.parametrize( + "throttle_pct, expected_rpm, expected_tpm", + [ + (None, 100, 1000), # no throttle -> configured limits + (0.1, 10, 100), # 10% of configured + (0.5, 50, 500), + ], +) +def test_api_key_descriptor_applies_budget_throttle( + throttle_pct, expected_rpm, expected_tpm +): + """The api_key rate-limit descriptor scales the key's configured TPM/RPM by + the request-scoped budget_throttle_pct, leaving the configured limits intact.""" + handler = _PROXY_MaxParallelRequestsHandler( + internal_usage_cache=InternalUsageCache(DualCache()) + ) + user_api_key_dict = UserAPIKeyAuth( + api_key=hash_token("sk-throttle"), + rpm_limit=100, + tpm_limit=1000, + budget_throttle_pct=throttle_pct, + ) + + descriptors = handler._create_rate_limit_descriptors( + user_api_key_dict=user_api_key_dict, + data={}, + rpm_limit_type=None, + tpm_limit_type=None, + model_has_failures=False, + ) + + api_key_descriptor = next(d for d in descriptors if d["key"] == "api_key") + assert api_key_descriptor["rate_limit"]["requests_per_unit"] == expected_rpm + assert api_key_descriptor["rate_limit"]["tokens_per_unit"] == expected_tpm + + @pytest.mark.flaky(reruns=3) @pytest.mark.asyncio async def test_sliding_window_rate_limit_v3(monkeypatch, time_controller): diff --git a/tests/test_litellm/proxy/management_endpoints/test_cache_settings_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_cache_settings_endpoints.py index 4bdef2e8f96..f4c6d4f8d15 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_cache_settings_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_cache_settings_endpoints.py @@ -10,20 +10,24 @@ from unittest.mock import AsyncMock, MagicMock, patch import pytest -sys.path.insert( - 0, os.path.abspath("../../../..") -) # Adds the parent directory to the system path +sys.path.insert(0, os.path.abspath("../../../..")) # Adds the parent directory to the system path import litellm from litellm.proxy._types import LitellmTableNames, LitellmUserRoles from litellm.proxy.auth.user_api_key_auth import UserAPIKeyAuth from litellm.proxy.management_endpoints.cache_settings_endpoints import ( + _CACHE_SENSITIVE_FIELDS, CacheSettingsManager, CacheSettingsUpdateRequest, CacheTestRequest, + _resolve_cache_url_precedence, + get_cache_settings, test_cache_connection, update_cache_settings, ) +from litellm.types.management_endpoints.cache_settings_endpoints import ( + CACHE_SETTINGS_FIELDS, +) @pytest.mark.asyncio @@ -41,9 +45,7 @@ async def test_test_cache_connection_calls_cache_test_connection_with_params(): } request = CacheTestRequest(cache_settings=cache_settings) - user_api_key_dict = UserAPIKeyAuth( - user_role=LitellmUserRoles.PROXY_ADMIN, api_key="sk-test", user_id="test-user" - ) + user_api_key_dict = UserAPIKeyAuth(user_role=LitellmUserRoles.PROXY_ADMIN, api_key="sk-test", user_id="test-user") # Mock Cache class and its test_connection method mock_cache_instance = MagicMock() @@ -60,9 +62,7 @@ async def test_test_cache_connection_calls_cache_test_connection_with_params(): mock_cache_class.return_value = mock_cache_instance # Call the endpoint - result = await test_cache_connection( - request=request, user_api_key_dict=user_api_key_dict - ) + result = await test_cache_connection(request=request, user_api_key_dict=user_api_key_dict) # Verify Cache was instantiated with correct params mock_cache_class.assert_called_once_with(**cache_settings) @@ -76,6 +76,205 @@ async def test_test_cache_connection_calls_cache_test_connection_with_params(): assert result.error is None +def test_cache_settings_fields_expose_url_and_db(): + """The dynamic UI form is driven by CACHE_SETTINGS_FIELDS; url + db must be + present (with the right types) so the Redis URL and logical database index + are configurable from the Admin UI.""" + by_name = {f.field_name: f for f in CACHE_SETTINGS_FIELDS} + + assert "url" in by_name + assert "db" in by_name + # db is a logical database index → integer + assert by_name["db"].field_type == "Integer" + # Both are common connection fields, shown for every Redis type + assert by_name["url"].redis_type is None + assert by_name["db"].redis_type is None + + +class TestResolveCacheUrlPrecedence: + """url wins over the discrete host/port/db/password fields.""" + + def test_url_overrides_discrete_connection_fields(self): + settings = { + "type": "redis", + "url": "redis://user:pw@host:6379/1", + "host": "host", + "port": "6379", + "db": 1, + "username": "user", + "password": "pw", + "namespace": "ns", + "ttl": 60, + } + + result = _resolve_cache_url_precedence(settings) + + assert result["url"] == "redis://user:pw@host:6379/1" + assert "host" not in result + assert "port" not in result + assert "db" not in result + # username and password are both encodable in the url, so the discrete + # copies must not ride along and override it + assert "username" not in result + assert "password" not in result + # Non-connection fields survive + assert result["type"] == "redis" + assert result["namespace"] == "ns" + assert result["ttl"] == 60 + + def test_no_url_returns_copy_unchanged(self): + settings = {"type": "redis", "host": "host", "port": "6379", "db": 1} + + result = _resolve_cache_url_precedence(settings) + + assert result == settings + assert result is not settings + + def test_blank_url_does_not_strip_discrete_fields(self): + settings = {"type": "redis", "url": " ", "host": "host", "db": 2} + + result = _resolve_cache_url_precedence(settings) + + assert result["host"] == "host" + assert result["db"] == 2 + + def test_cluster_mode_keeps_discrete_fields(self): + settings = { + "type": "redis", + "url": "redis://host:6379", + "redis_startup_nodes": [{"host": "127.0.0.1", "port": "7001"}], + "host": "host", + "password": "pw", + } + + result = _resolve_cache_url_precedence(settings) + + assert result["host"] == "host" + assert result["password"] == "pw" + + +@pytest.mark.asyncio +async def test_test_cache_connection_url_takes_precedence_over_discrete_fields(): + """When url + discrete fields are both sent, the tested Cache instance is + built from the url alone (host/port/db/password dropped).""" + cache_settings = { + "type": "redis", + "url": "redis://:pw@host:6379/1", + "host": "ignored-host", + "port": "6379", + "db": 1, + "password": "pw", + } + + request = CacheTestRequest(cache_settings=cache_settings) + user_api_key_dict = UserAPIKeyAuth(user_role=LitellmUserRoles.PROXY_ADMIN, api_key="sk-test", user_id="test-user") + + mock_cache_instance = MagicMock() + mock_cache_instance.cache = MagicMock() + mock_cache_instance.cache.test_connection = AsyncMock(return_value={"status": "success", "message": "ok"}) + + with patch("litellm.Cache") as mock_cache_class: + mock_cache_class.return_value = mock_cache_instance + + result = await test_cache_connection(request=request, user_api_key_dict=user_api_key_dict) + + called_kwargs = mock_cache_class.call_args.kwargs + assert called_kwargs["url"] == "redis://:pw@host:6379/1" + assert "host" not in called_kwargs + assert "port" not in called_kwargs + assert "db" not in called_kwargs + assert "password" not in called_kwargs + assert result.status == "success" + + +@pytest.mark.asyncio +async def test_update_cache_settings_persists_url_precedence(monkeypatch): + """The persisted (source-of-truth) row and the reinitialized cache both use + the url-resolved settings, so a stored config never carries a contradictory + host+url pair.""" + monkeypatch.setattr(litellm, "store_audit_logs", False) + + mock_prisma = MagicMock() + mock_prisma.db.litellm_cacheconfig.find_unique = AsyncMock(return_value=None) + mock_prisma.db.litellm_cacheconfig.upsert = AsyncMock() + + proxy_config = MagicMock() + proxy_config._encrypt_env_variables = MagicMock( + side_effect=lambda environment_variables: dict(environment_variables) + ) + proxy_config._decrypt_db_variables = MagicMock(side_effect=lambda variables_dict: dict(variables_dict)) + proxy_config._init_cache = MagicMock() + proxy_config.switch_on_llm_response_caching = MagicMock() + + with ( + patch("litellm.proxy.proxy_server.prisma_client", mock_prisma), + patch("litellm.proxy.proxy_server.proxy_config", proxy_config), + patch("litellm.proxy.proxy_server.store_model_in_db", True), + ): + await update_cache_settings( + request=CacheSettingsUpdateRequest( + cache_settings={ + "type": "redis", + "url": "redis://:pw@host:6379/1", + "host": "ignored-host", + "port": "6379", + "db": 1, + "password": "pw", + "namespace": "ns", + } + ), + user_api_key_dict=_admin_auth(), + litellm_changed_by=None, + ) + + persisted = proxy_config._encrypt_env_variables.call_args.kwargs["environment_variables"] + assert persisted["url"] == "redis://:pw@host:6379/1" + assert persisted["namespace"] == "ns" + assert "host" not in persisted + assert "port" not in persisted + assert "db" not in persisted + assert "password" not in persisted + + init_params = proxy_config._init_cache.call_args.kwargs["cache_params"] + assert "host" not in init_params + assert init_params["url"] == "redis://:pw@host:6379/1" + + +def test_url_is_a_masked_field(): + """A Redis URL can carry an inline password, so it must be masked on read + alongside the discrete password fields.""" + assert "url" in _CACHE_SENSITIVE_FIELDS + + +@pytest.mark.asyncio +async def test_get_cache_settings_masks_password_bearing_url(): + """GET /cache/settings must not leak an inline url password in plaintext, + while non-credential fields (e.g. namespace) come back untouched.""" + stored_url = "redis://:supersecretpassword@host:6379/1" + stored_settings = {"type": "redis", "url": stored_url, "namespace": "ns"} + + cache_row = MagicMock() + cache_row.cache_settings = json.dumps(stored_settings) + + mock_prisma = MagicMock() + mock_prisma.db.litellm_cacheconfig.find_unique = AsyncMock(return_value=cache_row) + + proxy_config = MagicMock() + proxy_config._decrypt_db_variables = MagicMock(side_effect=lambda variables_dict: dict(variables_dict)) + + with ( + patch("litellm.proxy.proxy_server.prisma_client", mock_prisma), + patch("litellm.proxy.proxy_server.proxy_config", proxy_config), + ): + response = await get_cache_settings(user_api_key_dict=_admin_auth()) + + returned_url = response.current_values["url"] + assert returned_url != stored_url + assert "supersecretpassword" not in returned_url + # non-credential field is not masked + assert response.current_values["namespace"] == "ns" + + class TestCacheSettingsManager: """Tests for CacheSettingsManager class""" @@ -182,12 +381,8 @@ class TestCacheSettingsManager: # Mock prisma client mock_prisma_client = MagicMock() mock_cache_config = MagicMock() - mock_cache_config.cache_settings = ( - '{"type": "redis", "host": "localhost", "port": "6379"}' - ) - mock_prisma_client.db.litellm_cacheconfig.find_unique = AsyncMock( - return_value=mock_cache_config - ) + mock_cache_config.cache_settings = '{"type": "redis", "host": "localhost", "port": "6379"}' + mock_prisma_client.db.litellm_cacheconfig.find_unique = AsyncMock(return_value=mock_cache_config) # Mock proxy_config mock_proxy_config = MagicMock() @@ -231,12 +426,8 @@ class TestCacheSettingsManager: # Mock prisma client mock_prisma_client = MagicMock() mock_cache_config = MagicMock() - mock_cache_config.cache_settings = ( - '{"type": "redis", "host": "localhost", "port": "6379"}' - ) - mock_prisma_client.db.litellm_cacheconfig.find_unique = AsyncMock( - return_value=mock_cache_config - ) + mock_cache_config.cache_settings = '{"type": "redis", "host": "localhost", "port": "6379"}' + mock_prisma_client.db.litellm_cacheconfig.find_unique = AsyncMock(return_value=mock_cache_config) # Mock proxy_config mock_proxy_config = MagicMock() @@ -274,9 +465,7 @@ class TestCacheSettingsManager: return None # No config → function returns early after retry. mock_prisma_client = MagicMock() - mock_prisma_client.db.litellm_cacheconfig.find_unique = AsyncMock( - side_effect=_flaky_find_unique - ) + mock_prisma_client.db.litellm_cacheconfig.find_unique = AsyncMock(side_effect=_flaky_find_unique) mock_prisma_client.attempt_db_reconnect = AsyncMock(return_value=True) mock_prisma_client._db_auth_reconnect_timeout_seconds = 2.0 mock_prisma_client._db_auth_reconnect_lock_timeout_seconds = 0.1 @@ -289,10 +478,7 @@ class TestCacheSettingsManager: assert len(invocations) == 2 mock_prisma_client.attempt_db_reconnect.assert_awaited_once() reconnect_kwargs = mock_prisma_client.attempt_db_reconnect.await_args.kwargs - assert ( - reconnect_kwargs["reason"] - == "init_cache_settings_in_db_lookup_failure" - ) + assert reconnect_kwargs["reason"] == "init_cache_settings_in_db_lookup_failure" # ── Audit-log emission for /cache/settings ──────────────────────────────────── @@ -320,9 +506,7 @@ async def test_update_cache_settings_emits_audit_log_when_enabled(monkeypatch): proxy_config._encrypt_env_variables = MagicMock( side_effect=lambda environment_variables: dict(environment_variables) ) - proxy_config._decrypt_db_variables = MagicMock( - side_effect=lambda variables_dict: dict(variables_dict) - ) + proxy_config._decrypt_db_variables = MagicMock(side_effect=lambda variables_dict: dict(variables_dict)) proxy_config._init_cache = MagicMock() proxy_config.switch_on_llm_response_caching = MagicMock() @@ -392,9 +576,7 @@ async def test_update_cache_settings_no_audit_when_disabled(monkeypatch): proxy_config._encrypt_env_variables = MagicMock( side_effect=lambda environment_variables: dict(environment_variables) ) - proxy_config._decrypt_db_variables = MagicMock( - side_effect=lambda variables_dict: dict(variables_dict) - ) + proxy_config._decrypt_db_variables = MagicMock(side_effect=lambda variables_dict: dict(variables_dict)) proxy_config._init_cache = MagicMock() proxy_config.switch_on_llm_response_caching = MagicMock() @@ -422,9 +604,7 @@ async def test_update_cache_settings_no_audit_when_disabled(monkeypatch): ), ): await update_cache_settings( - request=CacheSettingsUpdateRequest( - cache_settings={"type": "redis", "host": "redis.example.com"} - ), + request=CacheSettingsUpdateRequest(cache_settings={"type": "redis", "host": "redis.example.com"}), user_api_key_dict=_admin_auth(), litellm_changed_by=None, ) diff --git a/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py index 7b97ae60443..4fb3df52cf6 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py @@ -1495,6 +1495,57 @@ async def test_generate_service_account_works_with_team_id(): ) +@pytest.mark.asyncio +async def test_generate_key_throttle_rejected_for_non_admin(): + """Security regression: a non-admin creating a key must not be able to set + throttle_on_budget_exceeded=true, which would let the new key keep spending + past an admin-imposed per-key budget ceiling instead of hard-blocking. The + /key/update gate does not cover generate, so generate needs its own admin + check. Only the enable value is gated, so this must 403.""" + mock_prisma_client = AsyncMock() + with patch("litellm.proxy.proxy_server.prisma_client", mock_prisma_client): + with pytest.raises(HTTPException) as exc: + await _common_key_generation_helper( + data=GenerateKeyRequest(throttle_on_budget_exceeded=True), + user_api_key_dict=UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + api_key="sk-alice", + user_id="alice", + ), + litellm_changed_by=None, + team_table=None, + ) + assert int(getattr(exc.value, "status_code", 0)) == 403 + assert "Only proxy admins can enable throttle_on_budget_exceeded" in str(exc.value.detail) + + +@pytest.mark.asyncio +async def test_generate_key_throttle_allowed_for_admin(): + """A proxy admin may create a key with throttle_on_budget_exceeded=true; the + generate admin gate must let the admin through to key creation.""" + with ( + patch("litellm.proxy.proxy_server.prisma_client", AsyncMock()), + patch("litellm.proxy.proxy_server.llm_router", None), + patch("litellm.proxy.proxy_server.premium_user", False), + patch( + "litellm.proxy.management_endpoints.key_management_endpoints.generate_key_helper_fn" + ) as mock_generate_key, + ): + mock_generate_key.return_value = { + "key": "sk-test-key", + "expires": None, + "user_id": "admin", + "team_id": None, + } + await _common_key_generation_helper( + data=GenerateKeyRequest(throttle_on_budget_exceeded=True), + user_api_key_dict=UserAPIKeyAuth(user_role=LitellmUserRoles.PROXY_ADMIN, api_key="sk-1"), + litellm_changed_by=None, + team_table=None, + ) + assert mock_generate_key.called + + @pytest.mark.asyncio async def test_update_service_account_requires_team_id(): data = UpdateKeyRequest(key="sk-1", metadata={"service_account_id": "sa"}) @@ -9577,6 +9628,165 @@ async def test_update_key_non_budget_fields_allowed_for_internal_user(monkeypatc assert result is not None +@pytest.mark.asyncio +async def test_update_key_throttle_on_budget_exceeded_rejected_for_internal_user( + monkeypatch, +): + """Security regression: throttle_on_budget_exceeded turns an admin-imposed + hard budget block into a soft throttle that keeps spending past max_budget, + so it is a budget-enforcement change. A non-admin key owner (same setup that + is allowed to change non-budget fields via the caller_is_creator shortcut) + must NOT be able to self-opt-in to it; it has to route through the admin-only + _check_key_admin_access and return 403. Without treating the flag as a budget + change this update would succeed, letting the owner bypass their own cap.""" + from litellm.proxy.management_endpoints.key_management_endpoints import ( + update_key_fn, + ) + + mock_prisma_client = AsyncMock() + mock_user_api_key_cache = AsyncMock() + mock_proxy_logging_obj = MagicMock() + + test_hashed_token = "a1b2c3d4e5f6789012345678901234567890123456789012345678901234abcd" + + # Owner of the key (created_by == user_id) so caller_is_creator is True. + # This is exactly the setup that is allowed to change non-budget fields; + # the throttle flag must still be rejected. + mock_existing_key = MagicMock() + mock_existing_key.token = test_hashed_token + mock_existing_key.user_id = "internal_user" + mock_existing_key.created_by = "internal_user" + mock_existing_key.team_id = None + mock_existing_key.project_id = None + mock_existing_key.max_budget = 10.0 + mock_existing_key.key_alias = None + mock_existing_key.models = [] + mock_existing_key.metadata = {} + mock_existing_key.model_dump.return_value = { + "token": test_hashed_token, + "user_id": "internal_user", + "team_id": None, + "max_budget": 10.0, + } + + mock_prisma_client.get_data = AsyncMock(return_value=mock_existing_key) + mock_prisma_client.db.litellm_verificationtoken.find_unique = AsyncMock(return_value=mock_existing_key) + + monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma_client) + monkeypatch.setattr("litellm.proxy.proxy_server.user_api_key_cache", mock_user_api_key_cache) + monkeypatch.setattr("litellm.proxy.proxy_server.proxy_logging_obj", mock_proxy_logging_obj) + monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", None) + monkeypatch.setattr("litellm.proxy.proxy_server.premium_user", True) + + mock_request = MagicMock() + mock_request.query_params = {} + user_api_key_dict = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + api_key="sk-internal", + user_id="internal_user", + ) + + with pytest.raises(ProxyException) as exc: + await update_key_fn( + request=mock_request, + data=UpdateKeyRequest( + key=test_hashed_token, + throttle_on_budget_exceeded=True, + ), + user_api_key_dict=user_api_key_dict, + litellm_changed_by=None, + ) + + assert str(exc.value.code) == "403" + assert "Only proxy admins can enable throttle_on_budget_exceeded" in str(exc.value.message) + + +@pytest.mark.asyncio +async def test_update_key_throttle_unchanged_allows_non_budget_edit_for_internal_user( + monkeypatch, +): + """A non-admin owner editing a non-budget field must not be blocked just + because the UI resends throttle_on_budget_exceeded unchanged (the edit form + always includes it). Only the transition to enabled is admin-gated, so an + unchanged False here leaves the key owner's non-budget edit working.""" + from litellm.proxy.management_endpoints.key_management_endpoints import ( + update_key_fn, + ) + + mock_prisma_client = AsyncMock() + mock_user_api_key_cache = AsyncMock() + mock_proxy_logging_obj = MagicMock() + + test_hashed_token = "a1b2c3d4e5f6789012345678901234567890123456789012345678901234abcd" + + mock_existing_key = MagicMock() + mock_existing_key.token = test_hashed_token + mock_existing_key.user_id = "internal_user" + mock_existing_key.created_by = "internal_user" + mock_existing_key.team_id = None + mock_existing_key.project_id = None + mock_existing_key.max_budget = 10.0 + mock_existing_key.key_alias = None + mock_existing_key.models = [] + mock_existing_key.metadata = {"throttle_on_budget_exceeded": False} + mock_existing_key.model_dump.return_value = { + "token": test_hashed_token, + "user_id": "internal_user", + "team_id": None, + "max_budget": 10.0, + } + + mock_updated_key = MagicMock() + mock_updated_key.token = test_hashed_token + mock_updated_key.key_alias = "my-alias" + + mock_prisma_client.get_data = AsyncMock(return_value=mock_existing_key) + mock_prisma_client.update_data = AsyncMock(return_value=mock_updated_key) + mock_prisma_client.db.litellm_verificationtoken.find_unique = AsyncMock(return_value=mock_existing_key) + + monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma_client) + monkeypatch.setattr("litellm.proxy.proxy_server.user_api_key_cache", mock_user_api_key_cache) + monkeypatch.setattr("litellm.proxy.proxy_server.proxy_logging_obj", mock_proxy_logging_obj) + monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", None) + monkeypatch.setattr("litellm.proxy.proxy_server.premium_user", True) + monkeypatch.setattr("litellm.store_audit_logs", False) + + monkeypatch.setattr("litellm.proxy.proxy_server.hash_token", lambda token: test_hashed_token) + + async def _noop(**kwargs): + pass + + monkeypatch.setattr( + "litellm.proxy.management_endpoints.key_management_endpoints._delete_cache_key_object", + _noop, + ) + monkeypatch.setattr( + "litellm.proxy.management_endpoints.key_management_endpoints._enforce_unique_key_alias", + _noop, + ) + + mock_request = MagicMock() + mock_request.query_params = {} + user_api_key_dict = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + api_key="sk-internal", + user_id="internal_user", + ) + + result = await update_key_fn( + request=mock_request, + data=UpdateKeyRequest( + key=test_hashed_token, + key_alias="my-alias", + throttle_on_budget_exceeded=False, + ), + user_api_key_dict=user_api_key_dict, + litellm_changed_by=None, + ) + + assert result is not None + + @pytest.mark.asyncio async def test_update_key_non_budget_rejects_cross_user_modification(monkeypatch): """Regression: previously _check_key_admin_access was gated on diff --git a/tests/test_litellm/proxy/management_endpoints/test_mcp_management_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_mcp_management_endpoints.py index 32d0461439c..6976aa76a94 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_mcp_management_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_mcp_management_endpoints.py @@ -16,9 +16,7 @@ from litellm.proxy.management_endpoints import ( mcp_management_endpoints as mgmt_endpoints, ) -sys.path.insert( - 0, os.path.abspath("../../../..") -) # Adds the parent directory to the system path +sys.path.insert(0, os.path.abspath("../../../..")) # Adds the parent directory to the system path from litellm.proxy._types import ( LiteLLM_MCPServerTable, @@ -101,9 +99,7 @@ def generate_mock_user_api_key_auth( ) -def generate_mock_team_record( - team_id: str, team_alias: str, organization_id: str, mcp_servers: List[str] -): +def generate_mock_team_record(team_id: str, team_alias: str, organization_id: str, mcp_servers: List[str]): """Generate a mock team record with object permissions""" return MagicMock( team_id=team_id, @@ -122,13 +118,9 @@ def setup_mock_prisma_client( """Helper to set up a mock prisma client with proper async behavior""" mock_prisma_client.db = MagicMock() mock_prisma_client.db.litellm_teamtable = AsyncMock() - mock_prisma_client.db.litellm_teamtable.find_many = AsyncMock( - return_value=team_records - ) + mock_prisma_client.db.litellm_teamtable.find_many = AsyncMock(return_value=team_records) mock_prisma_client.db.litellm_mcpservertable = AsyncMock() - mock_prisma_client.db.litellm_mcpservertable.find_many = AsyncMock( - return_value=mcp_servers - ) + mock_prisma_client.db.litellm_mcpservertable.find_many = AsyncMock(return_value=mcp_servers) return mock_prisma_client @@ -148,6 +140,34 @@ def patch_proxy_general_settings(settings: dict): ) +class TestMCPCredentialsTokenExchangeProfile: + """token_exchange_profile must be a declared MCPCredentials field so the management API can + persist the entra_obo profile. An undeclared key is silently stripped by pydantic when the + credentials dict is validated against the TypedDict, so it would never reach the JSON blob.""" + + @pytest.mark.parametrize( + "build", + [ + lambda creds: NewMCPServerRequest( + server_name="s", auth_type=MCPAuth.oauth2_token_exchange, credentials=creds + ), + lambda creds: UpdateMCPServerRequest(server_id="s", credentials=creds), + ], + ids=["new", "update"], + ) + def test_request_preserves_token_exchange_profile_in_credentials(self, build): + creds = { + "client_id": "cid", + "client_secret": "sec", + "token_exchange_endpoint": "https://login.microsoftonline.com/tid/oauth2/v2.0/token", + "scopes": ["api://target/.default"], + "token_exchange_profile": "entra_obo", + } + request = build(creds) + assert request.credentials is not None + assert request.credentials.get("token_exchange_profile") == "entra_obo" + + class TestListMCPServers: """Test suite for list MCP servers functionality""" @@ -195,9 +215,7 @@ class TestListMCPServers: "config_server_1": config_server_1, "config_server_2": config_server_2, } - mock_manager.get_allowed_mcp_servers = AsyncMock( - return_value=["config_server_1", "config_server_2"] - ) + mock_manager.get_allowed_mcp_servers = AsyncMock(return_value=["config_server_1", "config_server_2"]) # Mock the new method that returns servers without health check mock_servers = [ @@ -268,9 +286,7 @@ class TestListMCPServers: async def test_list_mcp_servers_view_all_mode(self): """Users should see all MCP servers when view_all mode is enabled.""" - mock_user_auth = generate_mock_user_api_key_auth( - user_role=LitellmUserRoles.INTERNAL_USER - ) + mock_user_auth = generate_mock_user_api_key_auth(user_role=LitellmUserRoles.INTERNAL_USER) mock_servers = [ generate_mock_mcp_server_db_record(server_id="server-1", alias="One"), @@ -278,9 +294,7 @@ class TestListMCPServers: ] mock_manager = MagicMock() - mock_manager.get_all_mcp_servers_unfiltered = AsyncMock( - return_value=mock_servers - ) + mock_manager.get_all_mcp_servers_unfiltered = AsyncMock(return_value=mock_servers) with ( patch( @@ -327,9 +341,7 @@ class TestListMCPServers: server.extra_headers = ["Authorization"] mock_manager = MagicMock() - mock_manager.get_all_mcp_servers_unfiltered = AsyncMock( - return_value=mock_servers - ) + mock_manager.get_all_mcp_servers_unfiltered = AsyncMock(return_value=mock_servers) mock_manager.get_all_allowed_mcp_servers = AsyncMock(return_value=mock_servers) with ( @@ -568,14 +580,10 @@ class TestListMCPServers: mock_manager = MagicMock() mock_manager.config_mcp_servers = { "config_server_allowed": config_server_allowed, - "config_server_not_allowed": generate_mock_mcp_server_config_record( - server_id="config_server_not_allowed" - ), + "config_server_not_allowed": generate_mock_mcp_server_config_record(server_id="config_server_not_allowed"), } # User only has access to specific servers - mock_manager.get_allowed_mcp_servers = AsyncMock( - return_value=["db_server_allowed", "config_server_allowed"] - ) + mock_manager.get_allowed_mcp_servers = AsyncMock(return_value=["db_server_allowed", "config_server_allowed"]) # Mock the new method that returns servers without health check mock_servers = [ @@ -678,9 +686,7 @@ class TestListMCPServers: # Mock manager mock_manager = MagicMock() - mock_manager.get_all_allowed_mcp_servers = AsyncMock( - return_value=[server_1, server_2] - ) + mock_manager.get_all_allowed_mcp_servers = AsyncMock(return_value=[server_1, server_2]) with ( patch( @@ -708,24 +714,18 @@ class TestListMCPServers: @pytest.mark.asyncio async def test_fetch_single_mcp_server_redacts_credentials(self): - mock_server = generate_mock_mcp_server_db_record( - server_id="server-1", alias="Server 1" - ) + mock_server = generate_mock_mcp_server_db_record(server_id="server-1", alias="Server 1") mock_server.credentials = {"auth_value": "top-secret"} mock_prisma_client = MagicMock() # Mock health check result as LiteLLM_MCPServerTable - mock_health_result = generate_mock_mcp_server_db_record( - server_id="server-1", alias="Server 1" - ) + mock_health_result = generate_mock_mcp_server_db_record(server_id="server-1", alias="Server 1") mock_health_result.status = "healthy" mock_health_result.last_health_check = datetime.now() mock_health_result.health_check_error = None - mock_user_auth = generate_mock_user_api_key_auth( - user_role=LitellmUserRoles.PROXY_ADMIN - ) + mock_user_auth = generate_mock_user_api_key_auth(user_role=LitellmUserRoles.PROXY_ADMIN) with ( patch( @@ -762,25 +762,19 @@ class TestListMCPServers: @pytest.mark.asyncio async def test_fetch_single_mcp_server_handles_missing_credentials_field(self): - mock_server = generate_mock_mcp_server_db_record( - server_id="server-2", alias="Server 2" - ) + mock_server = generate_mock_mcp_server_db_record(server_id="server-2", alias="Server 2") # Simulate ORM object without credentials attribute (e.g., older schema) delattr(mock_server, "credentials") mock_prisma_client = MagicMock() # Mock health check result as LiteLLM_MCPServerTable - mock_health_result = generate_mock_mcp_server_db_record( - server_id="server-2", alias="Server 2" - ) + mock_health_result = generate_mock_mcp_server_db_record(server_id="server-2", alias="Server 2") mock_health_result.status = "healthy" mock_health_result.last_health_check = datetime.now() mock_health_result.health_check_error = None - mock_user_auth = generate_mock_user_api_key_auth( - user_role=LitellmUserRoles.PROXY_ADMIN - ) + mock_user_auth = generate_mock_user_api_key_auth(user_role=LitellmUserRoles.PROXY_ADMIN) with ( patch( @@ -828,18 +822,14 @@ class TestListMCPServers: transport="http", ) - mock_health_result = generate_mock_mcp_server_db_record( - server_id="serper_custom_dev", alias="Serper MCP" - ) + mock_health_result = generate_mock_mcp_server_db_record(server_id="serper_custom_dev", alias="Serper MCP") mock_health_result.status = "healthy" mock_health_result.last_health_check = datetime.now() mock_health_result.health_check_error = None mock_manager = MagicMock() mock_manager.get_mcp_server_by_id = MagicMock( - side_effect=lambda sid: ( - config_server if sid == "serper_custom_dev" else None - ) + side_effect=lambda sid: config_server if sid == "serper_custom_dev" else None ) mock_manager.get_mcp_server_by_name = MagicMock(return_value=None) mock_manager._build_mcp_server_table = MagicMock( @@ -850,14 +840,10 @@ class TestListMCPServers: transport="http", ) ) - mock_manager.get_allowed_mcp_servers = AsyncMock( - return_value=["serper_custom_dev"] - ) + mock_manager.get_allowed_mcp_servers = AsyncMock(return_value=["serper_custom_dev"]) mock_manager.health_check_server = AsyncMock(return_value=mock_health_result) - mock_user_auth = generate_mock_user_api_key_auth( - user_role=LitellmUserRoles.PROXY_ADMIN - ) + mock_user_auth = generate_mock_user_api_key_auth(user_role=LitellmUserRoles.PROXY_ADMIN) with ( patch( @@ -916,18 +902,12 @@ class TestListMCPServers: transport="http", ) ) - mock_manager.get_allowed_mcp_servers = AsyncMock( - return_value=["serper_custom_dev"] - ) + mock_manager.get_allowed_mcp_servers = AsyncMock(return_value=["serper_custom_dev"]) mock_manager.health_check_server = AsyncMock( - return_value=generate_mock_mcp_server_db_record( - server_id="serper_custom_dev", alias="Serper MCP" - ) + return_value=generate_mock_mcp_server_db_record(server_id="serper_custom_dev", alias="Serper MCP") ) - mock_user_auth = generate_mock_user_api_key_auth( - user_role=LitellmUserRoles.PROXY_ADMIN - ) + mock_user_auth = generate_mock_user_api_key_auth(user_role=LitellmUserRoles.PROXY_ADMIN) with ( patch( @@ -959,9 +939,7 @@ class TestListMCPServers: assert result.server_id == "serper_custom_dev" mock_manager.get_mcp_server_by_id.assert_called_with("Serper MCP") - mock_manager.get_mcp_server_by_name.assert_called_once_with( - "Serper MCP", client_ip="192.168.1.100" - ) + mock_manager.get_mcp_server_by_name.assert_called_once_with("Serper MCP", client_ip="192.168.1.100") @pytest.mark.asyncio async def test_fetch_single_mcp_server_from_registry_non_admin_denied(self): @@ -977,9 +955,7 @@ class TestListMCPServers: mock_manager = MagicMock() mock_manager.get_mcp_server_by_id = MagicMock( - side_effect=lambda sid: ( - config_server if sid == "restricted_server" else None - ) + side_effect=lambda sid: config_server if sid == "restricted_server" else None ) mock_manager.get_mcp_server_by_name = MagicMock(return_value=None) mock_manager._build_mcp_server_table = MagicMock( @@ -994,9 +970,7 @@ class TestListMCPServers: return_value=["other_server"] # restricted_server NOT in list ) - mock_user_auth = generate_mock_user_api_key_auth( - user_role=LitellmUserRoles.INTERNAL_USER - ) + mock_user_auth = generate_mock_user_api_key_auth(user_role=LitellmUserRoles.INTERNAL_USER) with ( patch( @@ -1042,18 +1016,14 @@ class TestListMCPServers: transport="http", ) - mock_health_result = generate_mock_mcp_server_db_record( - server_id="allowed_config_server", alias="Allowed MCP" - ) + mock_health_result = generate_mock_mcp_server_db_record(server_id="allowed_config_server", alias="Allowed MCP") mock_health_result.status = "healthy" mock_health_result.last_health_check = datetime.now() mock_health_result.health_check_error = None mock_manager = MagicMock() mock_manager.get_mcp_server_by_id = MagicMock( - side_effect=lambda sid: ( - config_server if sid == "allowed_config_server" else None - ) + side_effect=lambda sid: config_server if sid == "allowed_config_server" else None ) mock_manager.get_mcp_server_by_name = MagicMock(return_value=None) mock_manager._build_mcp_server_table = MagicMock( @@ -1064,14 +1034,10 @@ class TestListMCPServers: transport="http", ) ) - mock_manager.get_allowed_mcp_servers = AsyncMock( - return_value=["allowed_config_server"] - ) + mock_manager.get_allowed_mcp_servers = AsyncMock(return_value=["allowed_config_server"]) mock_manager.health_check_server = AsyncMock(return_value=mock_health_result) - mock_user_auth = generate_mock_user_api_key_auth( - user_role=LitellmUserRoles.INTERNAL_USER - ) + mock_user_auth = generate_mock_user_api_key_auth(user_role=LitellmUserRoles.INTERNAL_USER) with ( patch( @@ -1142,13 +1108,9 @@ class TestListMCPServers: assert isinstance(raw_prisma_model.env_vars[0], dict) mock_prisma_client = MagicMock() - mock_prisma_client.db.litellm_mcpservertable.find_unique = AsyncMock( - return_value=raw_prisma_model - ) + mock_prisma_client.db.litellm_mcpservertable.find_unique = AsyncMock(return_value=raw_prisma_model) - mock_health_result = generate_mock_mcp_server_db_record( - server_id="env-server", alias="Env Server" - ) + mock_health_result = generate_mock_mcp_server_db_record(server_id="env-server", alias="Env Server") mock_health_result.status = "healthy" mock_health_result.last_health_check = datetime.now() mock_health_result.health_check_error = None @@ -1157,9 +1119,7 @@ class TestListMCPServers: mock_manager.add_server = AsyncMock() mock_manager.health_check_server = AsyncMock(return_value=mock_health_result) - mock_user_auth = generate_mock_user_api_key_auth( - user_role=LitellmUserRoles.INTERNAL_USER - ) + mock_user_auth = generate_mock_user_api_key_auth(user_role=LitellmUserRoles.INTERNAL_USER) with ( patch( @@ -1172,11 +1132,7 @@ class TestListMCPServers: ), patch( "litellm.proxy.management_endpoints.mcp_management_endpoints.get_all_mcp_servers_for_user", - AsyncMock( - return_value=[ - generate_mock_mcp_server_db_record(server_id="env-server") - ] - ), + AsyncMock(return_value=[generate_mock_mcp_server_db_record(server_id="env-server")]), ), patch( "litellm.proxy.management_endpoints.mcp_management_endpoints._user_has_admin_view", @@ -1221,9 +1177,7 @@ class TestListMCPServers: mock_prisma_client = MagicMock() - mock_health_result = generate_mock_mcp_server_db_record( - server_id="leaky-server", alias="Leaky Server" - ) + mock_health_result = generate_mock_mcp_server_db_record(server_id="leaky-server", alias="Leaky Server") mock_health_result.status = "healthy" mock_health_result.last_health_check = datetime.now() mock_health_result.health_check_error = None @@ -1232,9 +1186,7 @@ class TestListMCPServers: mock_manager.add_server = AsyncMock() mock_manager.health_check_server = AsyncMock(return_value=mock_health_result) - mock_user_auth = generate_mock_user_api_key_auth( - user_role=LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY - ) + mock_user_auth = generate_mock_user_api_key_auth(user_role=LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY) with ( patch( @@ -1283,9 +1235,7 @@ class TestListMCPServers: mock_prisma_client = MagicMock() - mock_health_result = generate_mock_mcp_server_db_record( - server_id="admin-server", alias="Admin Server" - ) + mock_health_result = generate_mock_mcp_server_db_record(server_id="admin-server", alias="Admin Server") mock_health_result.status = "healthy" mock_health_result.last_health_check = datetime.now() mock_health_result.health_check_error = None @@ -1294,9 +1244,7 @@ class TestListMCPServers: mock_manager.add_server = AsyncMock() mock_manager.health_check_server = AsyncMock(return_value=mock_health_result) - mock_user_auth = generate_mock_user_api_key_auth( - user_role=LitellmUserRoles.PROXY_ADMIN - ) + mock_user_auth = generate_mock_user_api_key_auth(user_role=LitellmUserRoles.PROXY_ADMIN) with ( patch( @@ -1362,9 +1310,7 @@ class TestTeamScopedMCPServerAccess: ) with pytest.raises(HTTPException) as exc_info: - await fetch_all_mcp_servers( - user_api_key_dict=mock_user_auth, team_id="foreign-team-id" - ) + await fetch_all_mcp_servers(user_api_key_dict=mock_user_auth, team_id="foreign-team-id") assert exc_info.value.status_code == 403 assert "permission" in str(exc_info.value.detail).lower() @@ -1384,9 +1330,7 @@ class TestTeamScopedMCPServerAccess: ] mock_team_obj.object_permission = MagicMock(mcp_servers=["server-1"]) - mock_server = generate_mock_mcp_server_config_record( - server_id="server-1", name="Team Server" - ) + mock_server = generate_mock_mcp_server_config_record(server_id="server-1", name="Team Server") mock_manager = MagicMock() mock_manager.get_mcp_server_by_id = MagicMock(return_value=mock_server) mock_manager._build_mcp_server_table = MagicMock( @@ -1404,11 +1348,7 @@ class TestTeamScopedMCPServerAccess: ), patch( "litellm.proxy.management_endpoints.mcp_management_endpoints._get_team_scoped_mcp_server_list", - AsyncMock( - return_value=[ - generate_mock_mcp_server_db_record(server_id="server-1") - ] - ), + AsyncMock(return_value=[generate_mock_mcp_server_db_record(server_id="server-1")]), ), patch( "litellm.proxy.management_endpoints.mcp_management_endpoints.global_mcp_server_manager", @@ -1419,9 +1359,7 @@ class TestTeamScopedMCPServerAccess: fetch_all_mcp_servers, ) - result = await fetch_all_mcp_servers( - user_api_key_dict=mock_user_auth, team_id="my-team-id" - ) + result = await fetch_all_mcp_servers(user_api_key_dict=mock_user_auth, team_id="my-team-id") assert len(result) == 1 assert result[0].server_id == "server-1" @@ -1440,11 +1378,7 @@ class TestTeamScopedMCPServerAccess: ), patch( "litellm.proxy.management_endpoints.mcp_management_endpoints._get_team_scoped_mcp_server_list", - AsyncMock( - return_value=[ - generate_mock_mcp_server_db_record(server_id="server-1") - ] - ), + AsyncMock(return_value=[generate_mock_mcp_server_db_record(server_id="server-1")]), ), ): from litellm.proxy.management_endpoints.mcp_management_endpoints import ( @@ -1452,9 +1386,7 @@ class TestTeamScopedMCPServerAccess: ) # Admin should NOT need to be a team member - result = await fetch_all_mcp_servers( - user_api_key_dict=mock_user_auth, team_id="any-team-id" - ) + result = await fetch_all_mcp_servers(user_api_key_dict=mock_user_auth, team_id="any-team-id") assert len(result) == 1 @pytest.mark.asyncio @@ -1472,9 +1404,7 @@ class TestTeamScopedMCPServerAccess: ) with pytest.raises(HTTPException) as exc_info: - await fetch_all_mcp_servers( - user_api_key_dict=mock_user_auth, team_id="some-team" - ) + await fetch_all_mcp_servers(user_api_key_dict=mock_user_auth, team_id="some-team") assert exc_info.value.status_code == 403 assert "Restricted virtual key" in str(exc_info.value.detail) @@ -1653,9 +1583,7 @@ class TestTemporaryMCPSessionEndpoints: AsyncMock(return_value=[non_admin]), ), ): - result = await _get_cached_temporary_mcp_server_or_404( - "server-x", non_admin - ) + result = await _get_cached_temporary_mcp_server_or_404("server-x", non_admin) assert result is registry_server @@ -1704,9 +1632,7 @@ class TestTemporaryMCPSessionEndpoints: AsyncMock(return_value=[ui_session_auth, team_context]), ), ): - result = await _get_cached_temporary_mcp_server_or_404( - "server-x", ui_session_auth - ) + result = await _get_cached_temporary_mcp_server_or_404("server-x", ui_session_auth) assert result is registry_server assert mock_manager.get_allowed_mcp_servers.await_count == 2 @@ -1790,12 +1716,8 @@ class TestTemporaryMCPSessionEndpoints: validate_mock.assert_called_once_with(payload) mock_manager.build_mcp_server_from_table.assert_awaited_once() - cache_mock.assert_called_once_with( - built_server, ttl_seconds=TEMPORARY_MCP_SERVER_TTL_SECONDS - ) - redis_cache_mock.assert_awaited_once_with( - built_server, ttl_seconds=TEMPORARY_MCP_SERVER_TTL_SECONDS - ) + cache_mock.assert_called_once_with(built_server, ttl_seconds=TEMPORARY_MCP_SERVER_TTL_SECONDS) + redis_cache_mock.assert_awaited_once_with(built_server, ttl_seconds=TEMPORARY_MCP_SERVER_TTL_SECONDS) args, _ = mock_manager.build_mcp_server_from_table.call_args temp_record = args[0] @@ -1900,9 +1822,7 @@ class TestTemporaryMCPSessionEndpoints: _mcp_oauth_user_api_key_auth, ) - expected_auth = generate_mock_user_api_key_auth( - user_role=LitellmUserRoles.PROXY_ADMIN - ) + expected_auth = generate_mock_user_api_key_auth(user_role=LitellmUserRoles.PROXY_ADMIN) mock_request = MagicMock() mock_request.headers = {"Authorization": "Bearer sk-header-key"} mock_request.cookies = {} @@ -1936,9 +1856,7 @@ class TestTemporaryMCPSessionEndpoints: _mcp_oauth_user_api_key_auth, ) - expected_auth = generate_mock_user_api_key_auth( - user_role=LitellmUserRoles.PROXY_ADMIN - ) + expected_auth = generate_mock_user_api_key_auth(user_role=LitellmUserRoles.PROXY_ADMIN) mock_request = MagicMock() mock_request.headers = {} mock_request.cookies = {} @@ -1986,9 +1904,7 @@ class TestTemporaryMCPSessionEndpoints: _mcp_oauth_user_api_key_auth, ) - expected_auth = generate_mock_user_api_key_auth( - user_role=LitellmUserRoles.PROXY_ADMIN - ) + expected_auth = generate_mock_user_api_key_auth(user_role=LitellmUserRoles.PROXY_ADMIN) mock_request = MagicMock() mock_request.headers = {} mock_request.cookies = {} @@ -2410,9 +2326,7 @@ class TestTemporaryMCPSessionEndpoints: server = generate_mock_mcp_server_config_record(server_id="from-redis") serialized = json.dumps(server.model_dump(mode="json")) - mock_cache_backend = SimpleNamespace( - async_get_cache=AsyncMock(return_value="encrypted-payload") - ) + mock_cache_backend = SimpleNamespace(async_get_cache=AsyncMock(return_value="encrypted-payload")) original_cache = mgmt_endpoints.litellm.cache mgmt_endpoints.litellm.cache = SimpleNamespace(cache=mock_cache_backend) try: @@ -2432,9 +2346,7 @@ class TestTemporaryMCPSessionEndpoints: assert result is not None assert result.server_id == "from-redis" - mock_cache_backend.async_get_cache.assert_awaited_once_with( - key="litellm:mcp:temporary_server:from-redis" - ) + mock_cache_backend.async_get_cache.assert_awaited_once_with(key="litellm:mcp:temporary_server:from-redis") @pytest.mark.asyncio async def test_cache_temporary_mcp_server_in_redis_uses_ttl_and_key(self): @@ -2489,13 +2401,9 @@ class TestTemporaryMCPSessionEndpoints: _get_temporary_mcp_server_from_redis, ) - server = generate_mock_mcp_server_config_record( - server_id="from-redis-encrypted" - ) + server = generate_mock_mcp_server_config_record(server_id="from-redis-encrypted") serialized = json.dumps(server.model_dump(mode="json")) - mock_cache_backend = SimpleNamespace( - async_get_cache=AsyncMock(return_value="encrypted-payload") - ) + mock_cache_backend = SimpleNamespace(async_get_cache=AsyncMock(return_value="encrypted-payload")) original_cache = mgmt_endpoints.litellm.cache mgmt_endpoints.litellm.cache = SimpleNamespace(cache=mock_cache_backend) try: @@ -2503,9 +2411,7 @@ class TestTemporaryMCPSessionEndpoints: "litellm.proxy.management_endpoints.mcp_management_endpoints.decrypt_value_helper", return_value=serialized, ) as decrypt_mock: - result = await _get_temporary_mcp_server_from_redis( - "from-redis-encrypted" - ) + result = await _get_temporary_mcp_server_from_redis("from-redis-encrypted") finally: mgmt_endpoints.litellm.cache = original_cache @@ -2565,9 +2471,7 @@ class TestTemporaryMCPSessionEndpoints: _get_temporary_mcp_server_from_redis, ) - mock_cache_backend = SimpleNamespace( - async_get_cache=AsyncMock(return_value="enc") - ) + mock_cache_backend = SimpleNamespace(async_get_cache=AsyncMock(return_value="enc")) original_cache = mgmt_endpoints.litellm.cache mgmt_endpoints.litellm.cache = SimpleNamespace(cache=mock_cache_backend) try: @@ -2589,9 +2493,7 @@ class TestTemporaryMCPSessionEndpoints: _get_temporary_mcp_server_from_redis, ) - mock_cache_backend = SimpleNamespace( - async_get_cache=AsyncMock(return_value="enc") - ) + mock_cache_backend = SimpleNamespace(async_get_cache=AsyncMock(return_value="enc")) original_cache = mgmt_endpoints.litellm.cache mgmt_endpoints.litellm.cache = SimpleNamespace(cache=mock_cache_backend) try: @@ -2613,9 +2515,7 @@ class TestTemporaryMCPSessionEndpoints: ) server = generate_mock_mcp_server_config_record(server_id="legacy-dict") - mock_cache_backend = SimpleNamespace( - async_get_cache=AsyncMock(return_value=server.model_dump(mode="json")) - ) + mock_cache_backend = SimpleNamespace(async_get_cache=AsyncMock(return_value=server.model_dump(mode="json"))) original_cache = mgmt_endpoints.litellm.cache mgmt_endpoints.litellm.cache = SimpleNamespace(cache=mock_cache_backend) try: @@ -2666,16 +2566,10 @@ class TestUpdateMCPServer: mock_prisma_client = MagicMock() mock_prisma_client.db = MagicMock() mock_prisma_client.db.litellm_mcpservertable = AsyncMock() - mock_prisma_client.db.litellm_mcpservertable.find_unique = AsyncMock( - return_value=existing_server - ) - mock_prisma_client.db.litellm_mcpservertable.update = AsyncMock( - return_value=updated_server - ) + mock_prisma_client.db.litellm_mcpservertable.find_unique = AsyncMock(return_value=existing_server) + mock_prisma_client.db.litellm_mcpservertable.update = AsyncMock(return_value=updated_server) - mock_user_auth = generate_mock_user_api_key_auth( - user_role=LitellmUserRoles.PROXY_ADMIN - ) + mock_user_auth = generate_mock_user_api_key_auth(user_role=LitellmUserRoles.PROXY_ADMIN) # Mock the update_mcp_server function to capture the call with ( @@ -2705,9 +2599,7 @@ class TestUpdateMCPServer: edit_mcp_server, ) - result = await edit_mcp_server( - payload=update_request, user_api_key_dict=mock_user_auth - ) + result = await edit_mcp_server(payload=update_request, user_api_key_dict=mock_user_auth) # Verify that update_mcp_server was called with the correct payload update_mock.assert_awaited_once() @@ -2747,18 +2639,12 @@ class TestAddMCPServerAtomicity: url="https://echo.example.com/mcp", transport=MCPTransport.http, ) - admin = generate_mock_user_api_key_auth( - user_role=LitellmUserRoles.PROXY_ADMIN, user_id="admin-user" - ) - created_server = generate_mock_mcp_server_db_record( - server_id="created-1", alias="echo" - ) + admin = generate_mock_user_api_key_auth(user_role=LitellmUserRoles.PROXY_ADMIN, user_id="admin-user") + created_server = generate_mock_mcp_server_db_record(server_id="created-1", alias="echo") mock_manager = MagicMock() mock_manager.add_server = AsyncMock() - mock_manager.reload_servers_from_database = AsyncMock( - side_effect=Exception("malformed pre-existing row") - ) + mock_manager.reload_servers_from_database = AsyncMock(side_effect=Exception("malformed pre-existing row")) with ( patch( @@ -2795,9 +2681,7 @@ class TestAddMCPServerAtomicity: url="https://echo.example.com/mcp", transport=MCPTransport.http, ) - admin = generate_mock_user_api_key_auth( - user_role=LitellmUserRoles.PROXY_ADMIN, user_id="admin-user" - ) + admin = generate_mock_user_api_key_auth(user_role=LitellmUserRoles.PROXY_ADMIN, user_id="admin-user") mock_manager = MagicMock() mock_manager.add_server = AsyncMock() @@ -2925,9 +2809,7 @@ class TestMCPRegistryEndpoint: mock_manager = MagicMock() mock_manager.get_registry.return_value = {mock_server.server_id: mock_server} # The registry endpoint uses get_filtered_registry (filters by client IP) - mock_manager.get_filtered_registry.return_value = { - mock_server.server_id: mock_server - } + mock_manager.get_filtered_registry.return_value = {mock_server.server_id: mock_server} with ( patch_proxy_general_settings({"enable_mcp_registry": True}), @@ -2977,9 +2859,7 @@ class TestMCPRegistryEndpoint: # Mock manager mock_manager = MagicMock() - mock_manager.get_all_mcp_servers_with_health_and_teams = AsyncMock( - return_value=[mock_health_result] - ) + mock_manager.get_all_mcp_servers_with_health_and_teams = AsyncMock(return_value=[mock_health_result]) with ( patch( @@ -3028,18 +2908,12 @@ class TestManagementPayloadValidation: health_check_servers, ) - mock_user_auth = generate_mock_user_api_key_auth( - user_role=LitellmUserRoles.INTERNAL_USER - ) + mock_user_auth = generate_mock_user_api_key_auth(user_role=LitellmUserRoles.INTERNAL_USER) - health_result_one = generate_mock_mcp_server_db_record( - server_id="server-1", alias="One" - ) + health_result_one = generate_mock_mcp_server_db_record(server_id="server-1", alias="One") health_result_one.status = "healthy" - health_result_two = generate_mock_mcp_server_db_record( - server_id="server-2", alias="Two" - ) + health_result_two = generate_mock_mcp_server_db_record(server_id="server-2", alias="Two") health_result_two.status = "unhealthy" mock_manager = MagicMock() @@ -3209,9 +3083,7 @@ class TestMCPApprovalWorkflow: AsyncMock(return_value=created_record), ) as mock_create, ): - result = await register_mcp_server( - payload=payload, user_api_key_dict=user_auth - ) + result = await register_mcp_server(payload=payload, user_api_key_dict=user_auth) # Endpoint sets pending_review before calling create_mcp_server call_payload = mock_create.call_args[0][1] @@ -3242,9 +3114,7 @@ class TestMCPApprovalWorkflow: admin = generate_mock_user_api_key_auth(user_role=LitellmUserRoles.PROXY_ADMIN) pending = generate_mock_mcp_server_db_record(alias="Pending") pending.approval_status = "pending_review" - summary = MCPSubmissionsSummary( - total=1, pending_review=1, active=0, rejected=0, items=[pending] - ) + summary = MCPSubmissionsSummary(total=1, pending_review=1, active=0, rejected=0, items=[pending]) with ( patch( @@ -3275,9 +3145,7 @@ class TestMCPApprovalWorkflow: item = _leaky_list_server() item.approval_status = "pending_review" - summary = MCPSubmissionsSummary( - total=1, pending_review=1, active=0, rejected=0, items=[item] - ) + summary = MCPSubmissionsSummary(total=1, pending_review=1, active=0, rejected=0, items=[item]) with ( patch( @@ -3290,9 +3158,7 @@ class TestMCPApprovalWorkflow: ), ): result = await get_mcp_server_submissions( - user_api_key_dict=generate_mock_user_api_key_auth( - user_role=LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY - ), + user_api_key_dict=generate_mock_user_api_key_auth(user_role=LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY), ) assert len(result.items) == 1 @@ -3319,9 +3185,7 @@ class TestMCPApprovalWorkflow: item = _leaky_list_server() item.approval_status = "pending_review" - summary = MCPSubmissionsSummary( - total=1, pending_review=1, active=0, rejected=0, items=[item] - ) + summary = MCPSubmissionsSummary(total=1, pending_review=1, active=0, rejected=0, items=[item]) with ( patch( @@ -3334,9 +3198,7 @@ class TestMCPApprovalWorkflow: ), ): result = await get_mcp_server_submissions( - user_api_key_dict=generate_mock_user_api_key_auth( - user_role=LitellmUserRoles.PROXY_ADMIN - ), + user_api_key_dict=generate_mock_user_api_key_auth(user_role=LitellmUserRoles.PROXY_ADMIN), ) assert len(result.items) == 1 @@ -3377,9 +3239,7 @@ class TestMCPApprovalWorkflow: ), ): with pytest.raises(HTTPException) as exc_info: - await approve_mcp_server_submission( - server_id="server-1", user_api_key_dict=admin - ) + await approve_mcp_server_submission(server_id="server-1", user_api_key_dict=admin) assert exc_info.value.status_code == 400 @pytest.mark.asyncio @@ -3418,14 +3278,10 @@ class TestMCPApprovalWorkflow: mock_manager, ), ): - result = await approve_mcp_server_submission( - server_id=pending_server.server_id, user_api_key_dict=admin - ) + result = await approve_mcp_server_submission(server_id=pending_server.server_id, user_api_key_dict=admin) mock_manager.reload_servers_from_database.assert_awaited_once() - mock_manager.invalidate_byom_submitted_servers_cache.assert_awaited_once_with( - "submitter-user" - ) + mock_manager.invalidate_byom_submitted_servers_cache.assert_awaited_once_with("submitter-user") assert result is not None @pytest.mark.asyncio @@ -3550,9 +3406,7 @@ class TestValidateMCPRequiredFields: source_url="https://github.com/org/repo", auth_type=MCPAuth.bearer_token, ) - with patch_proxy_general_settings( - {"mcp_required_fields": ["source_url", "auth_type"]} - ): + with patch_proxy_general_settings({"mcp_required_fields": ["source_url", "auth_type"]}): # Should not raise _validate_mcp_required_fields(payload) @@ -3640,9 +3494,7 @@ async def test_store_mcp_oauth_user_credential_returns_status(): ), patch( "litellm.proxy.management_endpoints.mcp_management_endpoints.get_mcp_server", - new=AsyncMock( - return_value=generate_mock_mcp_server_db_record(server_id=server_id) - ), + new=AsyncMock(return_value=generate_mock_mcp_server_db_record(server_id=server_id)), ), patch( "litellm.proxy.management_endpoints.mcp_management_endpoints._user_has_admin_view", @@ -3737,9 +3589,7 @@ async def test_list_mcp_user_credentials_batch_server_fetch(): "server_id": server_id, } ] - mock_server = generate_mock_mcp_server_db_record( - server_id=server_id, alias="My Server" - ) + mock_server = generate_mock_mcp_server_db_record(server_id=server_id, alias="My Server") # get_mcp_servers (batch) should be called once; get_mcp_server (single) must not be called. batch_mock = AsyncMock(return_value=[mock_server]) single_mock = AsyncMock(return_value=mock_server) @@ -3922,6 +3772,10 @@ def test_sanitize_mcp_server_for_non_admin_clears_credential_fields(): server.authorization_url = "https://idp/authorize" server.token_url = "https://idp/token" server.registration_url = "https://idp/register" + server.token_exchange_endpoint = "https://idp/token-exchange" + server.audience = "https://upstream/api" + server.subject_token_type = "urn:ietf:params:oauth:token-type:jwt" + server.token_exchange_profile = "entra_obo" sanitized = _sanitize_mcp_server_for_non_admin(server) @@ -3936,6 +3790,13 @@ def test_sanitize_mcp_server_for_non_admin_clears_credential_fields(): assert sanitized.authorization_url is None assert sanitized.token_url is None assert sanitized.registration_url is None + # The token-exchange IdP endpoint is as sensitive as token_url; audience names the upstream. + # subject_token_type is a public RFC 8693 URN, cleared for uniformity: non-admins + # receive no token-exchange config at all. + assert sanitized.token_exchange_endpoint is None + assert sanitized.audience is None + assert sanitized.subject_token_type is None + assert sanitized.token_exchange_profile is None # Identity / metadata fields are preserved so the UI can list the # server without exposing secrets. @@ -3989,6 +3850,27 @@ def test_sanitize_virtual_key_drops_all_env_vars(): assert server.env_vars[0].value == "super-secret" +def test_sanitize_virtual_key_clears_token_exchange_endpoint_and_audience(): + """Virtual-key callers must not receive the token-exchange IdP endpoint or audience, + matching how token_url is scrubbed for the same view.""" + import litellm.proxy.management_endpoints.mcp_management_endpoints as mgmt + + server = generate_mock_mcp_server_db_record() + server.token_url = "https://idp/token" + server.token_exchange_endpoint = "https://idp/token-exchange" + server.audience = "https://upstream/api" + server.subject_token_type = "urn:ietf:params:oauth:token-type:jwt" + server.token_exchange_profile = "entra_obo" + + sanitized = mgmt._sanitize_mcp_server_for_virtual_key(server) + + assert sanitized.token_url is None + assert sanitized.token_exchange_endpoint is None + assert sanitized.audience is None + assert sanitized.subject_token_type is None + assert sanitized.token_exchange_profile is None + + def _server_with_env_vars(server_id: str = "srv-env"): base = generate_mock_mcp_server_db_record(server_id=server_id) return LiteLLM_MCPServerTable( @@ -4050,9 +3932,7 @@ async def test_fetch_single_mcp_server_env_vars_full_admin_vs_view_only(): assert view_only.env_vars is None # The source record must never be mutated. - assert {ev.name: ev.value for ev in server.env_vars}[ - "ADMIN_API_KEY" - ] == "super-secret" + assert {ev.name: ev.value for ev in server.env_vars}["ADMIN_API_KEY"] == "super-secret" @pytest.mark.asyncio @@ -4089,9 +3969,7 @@ async def test_fetch_all_mcp_servers_env_vars_full_admin_vs_view_only(): view_only = await _fetch_all(LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY) assert view_only[0].env_vars is None - assert {ev.name: ev.value for ev in server.env_vars}[ - "ADMIN_API_KEY" - ] == "super-secret" + assert {ev.name: ev.value for ev in server.env_vars}["ADMIN_API_KEY"] == "super-secret" def _leaky_list_server() -> "LiteLLM_MCPServerTable": @@ -4144,9 +4022,7 @@ async def test_list_mcp_servers_sanitized_for_view_only_admin(): A mutation swapping _user_is_full_admin() back to _user_has_admin_view() (which also grants view-only admins) would return the raw url/headers and fail this. The real role helpers are exercised; the gate is not patched.""" - source, result = await _fetch_all_via_view_all( - LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY - ) + source, result = await _fetch_all_via_view_all(LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY) assert len(result) == 1 sanitized = result[0] @@ -4221,12 +4097,8 @@ class TestComputeUserEnvVarStatus: """Unit tests for the _compute_user_env_var_status helper.""" def test_only_referenced_per_user_vars_are_required(self): - server = _make_env_var_server( - env_vars=_ENV_VARS_MIXED, static_headers=_STATIC_HEADERS_MIXED - ) - status = mgmt_endpoints._compute_user_env_var_status( - server=server, stored_values={"CORP_USERNAME": "alice"} - ) + server = _make_env_var_server(env_vars=_ENV_VARS_MIXED, static_headers=_STATIC_HEADERS_MIXED) + status = mgmt_endpoints._compute_user_env_var_status(server=server, stored_values={"CORP_USERNAME": "alice"}) names = {spec.name for spec in status.required} # UNUSED_USER_VAR is declared per-user but never referenced -> not blocking. assert names == {"CORP_USERNAME", "CORP_PASSWORD"} @@ -4244,9 +4116,7 @@ class TestComputeUserEnvVarStatus: assert status.setup_url and "srv-1" in status.setup_url def test_all_filled_has_zero_missing(self): - server = _make_env_var_server( - env_vars=_ENV_VARS_MIXED, static_headers=_STATIC_HEADERS_MIXED - ) + server = _make_env_var_server(env_vars=_ENV_VARS_MIXED, static_headers=_STATIC_HEADERS_MIXED) status = mgmt_endpoints._compute_user_env_var_status( server=server, stored_values={"CORP_USERNAME": "alice", "CORP_PASSWORD": "s3cret"}, @@ -4259,20 +4129,14 @@ class TestComputeUserEnvVarStatus: env_vars=_ENV_VARS_MIXED, static_headers='{"Authorization": "${CORP_USERNAME}"}', ) - status = mgmt_endpoints._compute_user_env_var_status( - server=server, stored_values={} - ) + status = mgmt_endpoints._compute_user_env_var_status(server=server, stored_values={}) # Only CORP_USERNAME is referenced via the JSON-string headers. assert {spec.name for spec in status.required} == {"CORP_USERNAME"} assert status.missing_count == 1 def test_static_headers_invalid_json_string_yields_no_required(self): - server = _make_env_var_server( - env_vars=_ENV_VARS_MIXED, static_headers="not-json{" - ) - status = mgmt_endpoints._compute_user_env_var_status( - server=server, stored_values={} - ) + server = _make_env_var_server(env_vars=_ENV_VARS_MIXED, static_headers="not-json{") + status = mgmt_endpoints._compute_user_env_var_status(server=server, stored_values={}) assert status.required == [] assert status.missing_count == 0 # No required fields -> no setup URL. @@ -4283,9 +4147,7 @@ class TestComputeUserEnvVarStatus: env_vars=[{"name": "DB_PROTOCOL", "value": "postgres", "scope": "global"}], static_headers={"Authorization": "${DB_PROTOCOL}://host"}, ) - status = mgmt_endpoints._compute_user_env_var_status( - server=server, stored_values={} - ) + status = mgmt_endpoints._compute_user_env_var_status(server=server, stored_values={}) assert status.required == [] assert status.setup_url is None @@ -4302,9 +4164,7 @@ class TestComputeUserEnvVarStatus: ], static_headers={"Authorization": "Bearer ${SHARED_TOKEN}"}, ) - status = mgmt_endpoints._compute_user_env_var_status( - server=server, stored_values={} - ) + status = mgmt_endpoints._compute_user_env_var_status(server=server, stored_values={}) assert status.required == [] assert status.missing_count == 0 assert status.setup_url is None @@ -4322,9 +4182,7 @@ class TestComputeUserEnvVarStatus: ], static_headers={"Authorization": "Bearer ${SHARED_TOKEN}"}, ) - status = mgmt_endpoints._compute_user_env_var_status( - server=server, stored_values={} - ) + status = mgmt_endpoints._compute_user_env_var_status(server=server, stored_values={}) assert {spec.name for spec in status.required} == {"SHARED_TOKEN"} assert status.missing_count == 1 assert status.setup_url and "srv-1" in status.setup_url @@ -4333,16 +4191,10 @@ class TestComputeUserEnvVarStatus: class TestGetMCPUserEnvVars: @pytest.mark.asyncio async def test_returns_status_for_server(self): - server = _make_env_var_server( - env_vars=_ENV_VARS_MIXED, static_headers=_STATIC_HEADERS_MIXED - ) + server = _make_env_var_server(env_vars=_ENV_VARS_MIXED, static_headers=_STATIC_HEADERS_MIXED) with ( - patch.object( - mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock() - ), - patch.object( - mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=server) - ), + patch.object(mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock()), + patch.object(mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=server)), patch.object( mgmt_endpoints, "get_user_env_vars", @@ -4365,9 +4217,7 @@ class TestGetMCPUserEnvVars: @pytest.mark.asyncio async def test_missing_user_id_raises_400(self): - with patch.object( - mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock() - ): + with patch.object(mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock()): with pytest.raises(HTTPException) as exc: await mgmt_endpoints.get_mcp_user_env_vars( server_id="srv-1", @@ -4378,12 +4228,8 @@ class TestGetMCPUserEnvVars: @pytest.mark.asyncio async def test_unknown_server_raises_404(self): with ( - patch.object( - mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock() - ), - patch.object( - mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=None) - ), + patch.object(mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock()), + patch.object(mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=None)), ): with pytest.raises(HTTPException) as exc: await mgmt_endpoints.get_mcp_user_env_vars( @@ -4396,17 +4242,11 @@ class TestGetMCPUserEnvVars: class TestStoreMCPUserEnvVars: @pytest.mark.asyncio async def test_persists_only_allowed_non_empty_values(self): - server = _make_env_var_server( - env_vars=_ENV_VARS_MIXED, static_headers=_STATIC_HEADERS_MIXED - ) + server = _make_env_var_server(env_vars=_ENV_VARS_MIXED, static_headers=_STATIC_HEADERS_MIXED) merge_mock = AsyncMock(return_value={"CORP_USERNAME": "alice"}) with ( - patch.object( - mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock() - ), - patch.object( - mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=server) - ), + patch.object(mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock()), + patch.object(mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=server)), patch.object(mgmt_endpoints, "merge_user_env_vars", merge_mock), ): result = await mgmt_endpoints.store_mcp_user_env_vars( @@ -4438,26 +4278,16 @@ class TestStoreMCPUserEnvVars: """The endpoint forwards only the user's submitted (allowed, non-empty) update to the atomic merge and reports status from the merged result, so a one-field edit never sends the other stored values back through.""" - server = _make_env_var_server( - env_vars=_ENV_VARS_MIXED, static_headers=_STATIC_HEADERS_MIXED - ) - merge_mock = AsyncMock( - return_value={"CORP_USERNAME": "alice", "CORP_PASSWORD": "new"} - ) + server = _make_env_var_server(env_vars=_ENV_VARS_MIXED, static_headers=_STATIC_HEADERS_MIXED) + merge_mock = AsyncMock(return_value={"CORP_USERNAME": "alice", "CORP_PASSWORD": "new"}) with ( - patch.object( - mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock() - ), - patch.object( - mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=server) - ), + patch.object(mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock()), + patch.object(mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=server)), patch.object(mgmt_endpoints, "merge_user_env_vars", merge_mock), ): result = await mgmt_endpoints.store_mcp_user_env_vars( server_id="srv-1", - payload=mgmt_endpoints.MCPUserEnvVarsRequest( - values={"CORP_PASSWORD": "new"} - ), + payload=mgmt_endpoints.MCPUserEnvVarsRequest(values={"CORP_PASSWORD": "new"}), user_api_key_dict=generate_mock_user_api_key_auth(user_id="alice"), ) merge_mock.assert_awaited_once() @@ -4468,9 +4298,7 @@ class TestStoreMCPUserEnvVars: @pytest.mark.asyncio async def test_missing_user_id_raises_400(self): - with patch.object( - mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock() - ): + with patch.object(mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock()): with pytest.raises(HTTPException) as exc: await mgmt_endpoints.store_mcp_user_env_vars( server_id="srv-1", @@ -4482,12 +4310,8 @@ class TestStoreMCPUserEnvVars: @pytest.mark.asyncio async def test_unknown_server_raises_404(self): with ( - patch.object( - mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock() - ), - patch.object( - mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=None) - ), + patch.object(mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock()), + patch.object(mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=None)), ): with pytest.raises(HTTPException) as exc: await mgmt_endpoints.store_mcp_user_env_vars( @@ -4501,17 +4325,11 @@ class TestStoreMCPUserEnvVars: class TestClearMCPUserEnvVars: @pytest.mark.asyncio async def test_clears_and_returns_empty_status(self): - server = _make_env_var_server( - env_vars=_ENV_VARS_MIXED, static_headers=_STATIC_HEADERS_MIXED - ) + server = _make_env_var_server(env_vars=_ENV_VARS_MIXED, static_headers=_STATIC_HEADERS_MIXED) delete_mock = AsyncMock() with ( - patch.object( - mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock() - ), - patch.object( - mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=server) - ), + patch.object(mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock()), + patch.object(mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=server)), patch.object(mgmt_endpoints, "delete_user_env_vars", delete_mock), ): result = await mgmt_endpoints.clear_mcp_user_env_vars( @@ -4525,16 +4343,10 @@ class TestClearMCPUserEnvVars: @pytest.mark.asyncio async def test_delete_db_error_propagates(self): - server = _make_env_var_server( - env_vars=_ENV_VARS_MIXED, static_headers=_STATIC_HEADERS_MIXED - ) + server = _make_env_var_server(env_vars=_ENV_VARS_MIXED, static_headers=_STATIC_HEADERS_MIXED) with ( - patch.object( - mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock() - ), - patch.object( - mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=server) - ), + patch.object(mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock()), + patch.object(mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=server)), patch.object( mgmt_endpoints, "delete_user_env_vars", @@ -4550,9 +4362,7 @@ class TestClearMCPUserEnvVars: @pytest.mark.asyncio async def test_missing_user_id_raises_400(self): - with patch.object( - mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock() - ): + with patch.object(mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock()): with pytest.raises(HTTPException) as exc: await mgmt_endpoints.clear_mcp_user_env_vars( server_id="srv-1", @@ -4563,12 +4373,8 @@ class TestClearMCPUserEnvVars: @pytest.mark.asyncio async def test_unknown_server_raises_404(self): with ( - patch.object( - mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock() - ), - patch.object( - mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=None) - ), + patch.object(mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock()), + patch.object(mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=None)), ): with pytest.raises(HTTPException) as exc: await mgmt_endpoints.clear_mcp_user_env_vars( @@ -4581,9 +4387,7 @@ class TestClearMCPUserEnvVars: class TestListMCPUserEnvVarStatus: @pytest.mark.asyncio async def test_no_user_id_returns_empty(self): - with patch.object( - mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock() - ): + with patch.object(mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock()): result = await mgmt_endpoints.list_mcp_user_env_var_status( user_api_key_dict=generate_mock_user_api_key_auth(user_id="") ) @@ -4592,9 +4396,7 @@ class TestListMCPUserEnvVarStatus: @pytest.mark.asyncio async def test_no_accessible_servers_returns_empty(self): with ( - patch.object( - mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock() - ), + patch.object(mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock()), patch.object( mgmt_endpoints, "_resolve_accessible_mcp_servers", @@ -4620,9 +4422,7 @@ class TestListMCPUserEnvVarStatus: static_headers={"Authorization": "${DB_PROTOCOL}://host"}, ) with ( - patch.object( - mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock() - ), + patch.object(mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock()), patch.object( mgmt_endpoints, "_resolve_accessible_mcp_servers", @@ -4650,9 +4450,7 @@ class TestListMCPUserEnvVarStatus: static_headers=_STATIC_HEADERS_MIXED, ) with ( - patch.object( - mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock() - ), + patch.object(mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock()), patch.object( mgmt_endpoints, "_resolve_accessible_mcp_servers", @@ -4685,9 +4483,7 @@ class TestListMCPUserEnvVarStatus: static_headers=_STATIC_HEADERS_MIXED, ) with ( - patch.object( - mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock() - ), + patch.object(mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock()), patch.object( mgmt_endpoints, "_get_user_mcp_management_mode", @@ -4725,17 +4521,11 @@ class TestMCPUserEnvVarsAccessControl: @pytest.mark.asyncio async def test_get_forbidden_for_non_admin_without_access(self): - server = _make_env_var_server( - env_vars=_ENV_VARS_MIXED, static_headers=_STATIC_HEADERS_MIXED - ) + server = _make_env_var_server(env_vars=_ENV_VARS_MIXED, static_headers=_STATIC_HEADERS_MIXED) get_user_env_vars = AsyncMock(return_value={}) with ( - patch.object( - mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock() - ), - patch.object( - mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=server) - ), + patch.object(mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock()), + patch.object(mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=server)), patch.object( mgmt_endpoints, "build_effective_auth_contexts", @@ -4761,17 +4551,11 @@ class TestMCPUserEnvVarsAccessControl: @pytest.mark.asyncio async def test_store_forbidden_for_non_admin_without_access(self): - server = _make_env_var_server( - env_vars=_ENV_VARS_MIXED, static_headers=_STATIC_HEADERS_MIXED - ) + server = _make_env_var_server(env_vars=_ENV_VARS_MIXED, static_headers=_STATIC_HEADERS_MIXED) merge_mock = AsyncMock() with ( - patch.object( - mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock() - ), - patch.object( - mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=server) - ), + patch.object(mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock()), + patch.object(mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=server)), patch.object( mgmt_endpoints, "build_effective_auth_contexts", @@ -4787,9 +4571,7 @@ class TestMCPUserEnvVarsAccessControl: with pytest.raises(HTTPException) as exc: await mgmt_endpoints.store_mcp_user_env_vars( server_id="srv-1", - payload=mgmt_endpoints.MCPUserEnvVarsRequest( - values={"CORP_USERNAME": "alice"} - ), + payload=mgmt_endpoints.MCPUserEnvVarsRequest(values={"CORP_USERNAME": "alice"}), user_api_key_dict=generate_mock_user_api_key_auth( user_id="alice", user_role=LitellmUserRoles.INTERNAL_USER, @@ -4800,17 +4582,11 @@ class TestMCPUserEnvVarsAccessControl: @pytest.mark.asyncio async def test_clear_forbidden_for_non_admin_without_access(self): - server = _make_env_var_server( - env_vars=_ENV_VARS_MIXED, static_headers=_STATIC_HEADERS_MIXED - ) + server = _make_env_var_server(env_vars=_ENV_VARS_MIXED, static_headers=_STATIC_HEADERS_MIXED) delete_mock = AsyncMock() with ( - patch.object( - mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock() - ), - patch.object( - mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=server) - ), + patch.object(mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock()), + patch.object(mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=server)), patch.object( mgmt_endpoints, "build_effective_auth_contexts", @@ -4842,12 +4618,8 @@ class TestMCPUserEnvVarsAccessControl: static_headers=_STATIC_HEADERS_MIXED, ) with ( - patch.object( - mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock() - ), - patch.object( - mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=server) - ), + patch.object(mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock()), + patch.object(mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=server)), patch.object( mgmt_endpoints, "build_effective_auth_contexts", @@ -4884,20 +4656,14 @@ class TestMCPUserEnvVarsAccessControl: ) allowed_mock = AsyncMock(return_value=[]) with ( - patch.object( - mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock() - ), - patch.object( - mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=server) - ), + patch.object(mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock()), + patch.object(mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=server)), patch.object( mgmt_endpoints.global_mcp_server_manager, "get_allowed_mcp_servers", allowed_mock, ), - patch.object( - mgmt_endpoints, "get_user_env_vars", AsyncMock(return_value={}) - ), + patch.object(mgmt_endpoints, "get_user_env_vars", AsyncMock(return_value={})), ): result = await mgmt_endpoints.get_mcp_user_env_vars( server_id="srv-1", @@ -4916,12 +4682,8 @@ class TestMCPUserEnvVarsAccessControl: ids stay non-enumerable, even when neither the DB nor the registry has the server.""" with ( - patch.object( - mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock() - ), - patch.object( - mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=None) - ), + patch.object(mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock()), + patch.object(mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=None)), patch.object( mgmt_endpoints, "build_effective_auth_contexts", @@ -4993,9 +4755,7 @@ def test_oauth2_flow_defaults_to_none_when_omitted(): ) assert UpdateMCPServerRequest(server_id="srv-1").oauth2_flow is None - assert ( - LiteLLM_MCPServerTable(server_id="srv-1", transport="http").oauth2_flow is None - ) + assert LiteLLM_MCPServerTable(server_id="srv-1", transport="http").oauth2_flow is None class TestPerUserCredentialConfigServerResolution: @@ -5011,17 +4771,13 @@ class TestPerUserCredentialConfigServerResolution: def _registry_only_manager(self, *, is_byok: bool = False): """A manager mock where the server exists only in the registry (DB miss).""" - config_server = generate_mock_mcp_server_config_record( - server_id=self.CONFIG_SERVER_ID, name="Config Server" + config_server = generate_mock_mcp_server_config_record(server_id=self.CONFIG_SERVER_ID, name="Config Server") + record = generate_mock_mcp_server_db_record(server_id=self.CONFIG_SERVER_ID).model_copy( + update={"is_byok": is_byok} ) - record = generate_mock_mcp_server_db_record( - server_id=self.CONFIG_SERVER_ID - ).model_copy(update={"is_byok": is_byok}) manager = MagicMock() manager.get_mcp_server_by_id = MagicMock( - side_effect=lambda sid: ( - config_server if sid == self.CONFIG_SERVER_ID else None - ) + side_effect=lambda sid: config_server if sid == self.CONFIG_SERVER_ID else None ) manager._build_mcp_server_table = MagicMock(return_value=record) manager.get_allowed_mcp_servers = AsyncMock(return_value=[]) @@ -5034,12 +4790,8 @@ class TestPerUserCredentialConfigServerResolution: manager = self._registry_only_manager() store_mock = AsyncMock(return_value=None) with ( - patch.object( - mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock() - ), - patch.object( - mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=None) - ), + patch.object(mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock()), + patch.object(mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=None)), patch.object(mgmt_endpoints, "global_mcp_server_manager", manager), patch.object(mgmt_endpoints, "store_user_oauth_credential", store_mock), patch.object( @@ -5050,9 +4802,7 @@ class TestPerUserCredentialConfigServerResolution: ): result = await mgmt_endpoints.store_mcp_oauth_user_credential( server_id=self.CONFIG_SERVER_ID, - payload=mgmt_endpoints.MCPOAuthUserCredentialRequest( - access_token="tok", expires_in=3600 - ), + payload=mgmt_endpoints.MCPOAuthUserCredentialRequest(access_token="tok", expires_in=3600), user_api_key_dict=generate_mock_user_api_key_auth(user_id="admin"), ) assert result.has_credential is True @@ -5066,12 +4816,8 @@ class TestPerUserCredentialConfigServerResolution: manager = self._registry_only_manager(is_byok=True) store_mock = AsyncMock(return_value=None) with ( - patch.object( - mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock() - ), - patch.object( - mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=None) - ), + patch.object(mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock()), + patch.object(mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=None)), patch.object(mgmt_endpoints, "global_mcp_server_manager", manager), patch.object(mgmt_endpoints, "store_user_credential", store_mock), ): @@ -5091,12 +4837,8 @@ class TestPerUserCredentialConfigServerResolution: manager.get_allowed_mcp_servers = AsyncMock(return_value=[]) store_mock = AsyncMock(return_value=None) with ( - patch.object( - mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock() - ), - patch.object( - mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=None) - ), + patch.object(mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock()), + patch.object(mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=None)), patch.object(mgmt_endpoints, "global_mcp_server_manager", manager), patch.object( mgmt_endpoints, @@ -5108,9 +4850,7 @@ class TestPerUserCredentialConfigServerResolution: with pytest.raises(HTTPException) as exc: await mgmt_endpoints.store_mcp_oauth_user_credential( server_id=self.CONFIG_SERVER_ID, - payload=mgmt_endpoints.MCPOAuthUserCredentialRequest( - access_token="tok", expires_in=3600 - ), + payload=mgmt_endpoints.MCPOAuthUserCredentialRequest(access_token="tok", expires_in=3600), user_api_key_dict=generate_mock_user_api_key_auth( user_id="alice", user_role=LitellmUserRoles.INTERNAL_USER ), @@ -5123,17 +4863,11 @@ class TestPerUserCredentialConfigServerResolution: """A non-admin with the config server in their allowed set persists the token; proves the non-admin authz uses the registry-aware allowed set.""" manager = self._registry_only_manager() - manager.get_allowed_mcp_servers = AsyncMock( - return_value=[self.CONFIG_SERVER_ID] - ) + manager.get_allowed_mcp_servers = AsyncMock(return_value=[self.CONFIG_SERVER_ID]) store_mock = AsyncMock(return_value=None) with ( - patch.object( - mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock() - ), - patch.object( - mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=None) - ), + patch.object(mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock()), + patch.object(mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=None)), patch.object(mgmt_endpoints, "global_mcp_server_manager", manager), patch.object( mgmt_endpoints, @@ -5149,9 +4883,7 @@ class TestPerUserCredentialConfigServerResolution: ): result = await mgmt_endpoints.store_mcp_oauth_user_credential( server_id=self.CONFIG_SERVER_ID, - payload=mgmt_endpoints.MCPOAuthUserCredentialRequest( - access_token="tok", expires_in=3600 - ), + payload=mgmt_endpoints.MCPOAuthUserCredentialRequest(access_token="tok", expires_in=3600), user_api_key_dict=generate_mock_user_api_key_auth( user_id="alice", user_role=LitellmUserRoles.INTERNAL_USER ), @@ -5173,22 +4905,14 @@ class TestPerUserCredentialConfigServerResolution: ) manager = MagicMock() manager.get_mcp_server_by_id = MagicMock( - return_value=generate_mock_mcp_server_config_record( - server_id=self.CONFIG_SERVER_ID - ) + return_value=generate_mock_mcp_server_config_record(server_id=self.CONFIG_SERVER_ID) ) manager._build_mcp_server_table = MagicMock(return_value=env_var_server) - manager.get_allowed_mcp_servers = AsyncMock( - return_value=[self.CONFIG_SERVER_ID] - ) + manager.get_allowed_mcp_servers = AsyncMock(return_value=[self.CONFIG_SERVER_ID]) merge_mock = AsyncMock(return_value={"CORP_USERNAME": "alice"}) with ( - patch.object( - mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock() - ), - patch.object( - mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=None) - ), + patch.object(mgmt_endpoints, "get_prisma_client_or_throw", return_value=MagicMock()), + patch.object(mgmt_endpoints, "get_mcp_server", AsyncMock(return_value=None)), patch.object(mgmt_endpoints, "global_mcp_server_manager", manager), patch.object( mgmt_endpoints, @@ -5199,9 +4923,7 @@ class TestPerUserCredentialConfigServerResolution: ): result = await mgmt_endpoints.store_mcp_user_env_vars( server_id=self.CONFIG_SERVER_ID, - payload=mgmt_endpoints.MCPUserEnvVarsRequest( - values={"CORP_USERNAME": "alice"} - ), + payload=mgmt_endpoints.MCPUserEnvVarsRequest(values={"CORP_USERNAME": "alice"}), user_api_key_dict=generate_mock_user_api_key_auth( user_id="alice", user_role=LitellmUserRoles.INTERNAL_USER ), @@ -5210,3 +4932,63 @@ class TestPerUserCredentialConfigServerResolution: _, _, _, updates, _ = merge_mock.await_args.args assert updates == {"CORP_USERNAME": "alice"} assert result.server_id == self.CONFIG_SERVER_ID + + +def _oauth2_create_payload(**overrides): + base = dict( + server_name="stamp_test_server", + url="https://upstream.example.com/mcp", + transport=MCPTransport.http, + auth_type="oauth2", + ) + base.update(overrides) + return NewMCPServerRequest(**base) + + +def test_stamp_oauth2_flow_bare_oauth2_defaults_to_authorization_code(): + """A bare oauth2 create (no endpoints, no creds) is interactive: stamping it + authorization_code matches how needs_user_oauth_token treats a null flow.""" + payload = _oauth2_create_payload() + mgmt_endpoints.stamp_omitted_oauth2_flow(payload) + assert payload.oauth2_flow == "authorization_code" + + +def test_stamp_oauth2_flow_marks_m2m_shape_client_credentials(): + """token_url + full client credentials and no authorization_url is the M2M shape; + the stamp mirrors the legacy inference in _resolve_oauth2_flow so REST-created M2M + servers persist the flow instead of relying on read-time inference.""" + payload = _oauth2_create_payload( + token_url="https://idp.example.com/token", + credentials={"client_id": "cid", "client_secret": "csecret"}, + ) + mgmt_endpoints.stamp_omitted_oauth2_flow(payload) + assert payload.oauth2_flow == "client_credentials" + + +def test_stamp_oauth2_flow_authorization_url_wins_over_m2m_shape(): + """An authorization endpoint means interactive even when client creds + token_url + are present (GitHub Enterprise style); M2M never has an authorization endpoint.""" + payload = _oauth2_create_payload( + authorization_url="https://idp.example.com/authorize", + token_url="https://idp.example.com/token", + credentials={"client_id": "cid", "client_secret": "csecret"}, + ) + mgmt_endpoints.stamp_omitted_oauth2_flow(payload) + assert payload.oauth2_flow == "authorization_code" + + +def test_stamp_oauth2_flow_respects_explicit_value(): + """An explicit oauth2_flow from the caller must never be overridden by the stamp.""" + payload = _oauth2_create_payload( + oauth2_flow="authorization_code", + token_url="https://idp.example.com/token", + credentials={"client_id": "cid", "client_secret": "csecret"}, + ) + mgmt_endpoints.stamp_omitted_oauth2_flow(payload) + assert payload.oauth2_flow == "authorization_code" + + +def test_stamp_oauth2_flow_ignores_non_oauth2(): + payload = _oauth2_create_payload(auth_type="none") + mgmt_endpoints.stamp_omitted_oauth2_flow(payload) + assert payload.oauth2_flow is None diff --git a/tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py index 5f3974b46fb..180fb1d3d8f 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py @@ -9495,3 +9495,43 @@ class TestEmitTeamMembersMetric: # A metric failure must be swallowed, not propagated to the handler. _emit_team_members_metric(self._team(1)) fake_logger.set_team_members_metric.assert_called_once() + + +@pytest.mark.asyncio +async def test_new_team_rejects_reserved_ui_session_team_id(): + """ + /team/new must reject team_id "litellm-dashboard" (UI_TEAM_ID): it is the + virtual team stamped on every UI dashboard session token, so a real DB row + with that id would bind its budget and permissions to every UI session. + """ + from fastapi import Request + + from litellm.proxy._types import UI_TEAM_ID, NewTeamRequest + from litellm.proxy.management_endpoints.team_endpoints import new_team + + team_request = NewTeamRequest( + team_alias="dashboard-clone", + team_id=UI_TEAM_ID, + ) + dummy_request = MagicMock(spec=Request) + + with ( + patch("litellm.proxy.proxy_server.prisma_client") as mock_prisma, + patch("litellm.proxy.proxy_server._license_check") as mock_license, + ): + mock_prisma.db.litellm_teamtable.count = AsyncMock(return_value=0) + mock_license.is_team_count_over_limit.return_value = False + mock_prisma.get_data = AsyncMock(return_value=None) + + with pytest.raises(ProxyException) as exc_info: + await new_team( + data=team_request, + http_request=dummy_request, + user_api_key_dict=UserAPIKeyAuth( + user_role=LitellmUserRoles.PROXY_ADMIN + ), + ) + + assert exc_info.value.code == "400" + assert "reserved" in str(exc_info.value.message) + mock_prisma.get_data.assert_not_called() diff --git a/tests/test_litellm/proxy/pass_through_endpoints/test_streaming_handler_interrupt.py b/tests/test_litellm/proxy/pass_through_endpoints/test_streaming_handler_interrupt.py index 80702e605e7..163a0cbff3c 100644 --- a/tests/test_litellm/proxy/pass_through_endpoints/test_streaming_handler_interrupt.py +++ b/tests/test_litellm/proxy/pass_through_endpoints/test_streaming_handler_interrupt.py @@ -241,6 +241,126 @@ async def test_chunk_processor_routes_logging_through_logging_worker_on_disconne assert asyncio.iscoroutine(enqueued[0]) +def _logging_obj_with_write_once_cst(): + """Build a MagicMock that mirrors the real Logging behavior: _update_completion_start_time + latches self.completion_start_time so the write-once guard actually latches.""" + obj = MagicMock() + obj.completion_start_time = None + + def _update(*, completion_start_time): + obj.completion_start_time = completion_start_time + + obj._update_completion_start_time.side_effect = _update + return obj + + +@pytest.mark.asyncio +async def test_chunk_processor_stamps_completion_start_time_on_first_chunk(): + """Regression: LIT-4185 — streaming pass-through must stamp completion_start_time on + the first upstream chunk. Otherwise _success_handler_helper_fn falls back to + completion_start_time = end_time, and Prometheus/OTEL/SpendLogs TTFT reads as + total request duration (0 speedup).""" + chunks = [b"chunk-1", b"chunk-2", b"chunk-3"] + response = _make_streaming_response(chunks) + + mock_logging_obj = _logging_obj_with_write_once_cst() + mock_passthrough_handler = MagicMock() + + with patch.object( + PassThroughStreamingHandler, + "_route_streaming_logging_to_handler", + new=AsyncMock(), + ): + received = [] + async for chunk in PassThroughStreamingHandler.chunk_processor( + response=response, + request_body={"model": "claude-haiku-4-5"}, + litellm_logging_obj=mock_logging_obj, + endpoint_type=EndpointType.ANTHROPIC, + start_time=datetime.now(), + passthrough_success_handler_obj=mock_passthrough_handler, + url_route="/v1/messages", + ): + received.append(chunk) + + await asyncio.sleep(0) + + assert received == chunks + mock_logging_obj._update_completion_start_time.assert_called_once() + stamped = mock_logging_obj._update_completion_start_time.call_args.kwargs["completion_start_time"] + assert isinstance(stamped, datetime) + + +@pytest.mark.asyncio +async def test_chunk_processor_does_not_reset_completion_start_time_on_later_chunks(): + """The stamp must be write-once: reading it on chunk 2/3 must not overwrite a real TTFT + from chunk 1 (which would collapse TTFT to time-to-last-chunk).""" + chunks = [b"chunk-1", b"chunk-2", b"chunk-3"] + response = _make_streaming_response(chunks) + + real_first = datetime(2020, 1, 1, 0, 0, 0) + mock_logging_obj = MagicMock() + # Simulate first-chunk stamp having already landed (e.g. under contention or a + # prior wrapper that already set it): later chunks must be no-ops. + mock_logging_obj.completion_start_time = real_first + mock_passthrough_handler = MagicMock() + + with patch.object( + PassThroughStreamingHandler, + "_route_streaming_logging_to_handler", + new=AsyncMock(), + ): + async for _ in PassThroughStreamingHandler.chunk_processor( + response=response, + request_body={"model": "claude-haiku-4-5"}, + litellm_logging_obj=mock_logging_obj, + endpoint_type=EndpointType.ANTHROPIC, + start_time=datetime.now(), + passthrough_success_handler_obj=mock_passthrough_handler, + url_route="/v1/messages", + ): + pass + + mock_logging_obj._update_completion_start_time.assert_not_called() + assert mock_logging_obj.completion_start_time == real_first + + +@pytest.mark.asyncio +async def test_chunk_processor_stamps_completion_start_time_on_cost_injection_path(): + """The cost-injection branch runs alongside a hot path; both must stamp TTFT.""" + import litellm as litellm_mod + + chunks = [b"event: message_start\ndata: {}\n\n"] + response = _make_streaming_response(chunks) + + mock_logging_obj = _logging_obj_with_write_once_cst() + mock_logging_obj.model_call_details = {"model": "claude-haiku-4-5"} + mock_passthrough_handler = MagicMock() + + original = getattr(litellm_mod, "include_cost_in_streaming_usage", False) + litellm_mod.include_cost_in_streaming_usage = True + try: + with patch.object( + PassThroughStreamingHandler, + "_route_streaming_logging_to_handler", + new=AsyncMock(), + ): + async for _ in PassThroughStreamingHandler.chunk_processor( + response=response, + request_body={"model": "claude-haiku-4-5"}, + litellm_logging_obj=mock_logging_obj, + endpoint_type=EndpointType.ANTHROPIC, + start_time=datetime.now(), + passthrough_success_handler_obj=mock_passthrough_handler, + url_route="/v1/messages", + ): + pass + finally: + litellm_mod.include_cost_in_streaming_usage = original + + mock_logging_obj._update_completion_start_time.assert_called_once() + + def test_convert_raw_bytes_survives_truncated_multibyte_sequence(): """A stream cut mid-multibyte-sequence (client disconnect) must still decode via errors="replace" so the usage events already received are logged, instead diff --git a/tests/test_litellm/proxy/proxy_server/test_proxy_config.py b/tests/test_litellm/proxy/proxy_server/test_proxy_config.py index e7c036dd039..6cdaa17c0bf 100644 --- a/tests/test_litellm/proxy/proxy_server/test_proxy_config.py +++ b/tests/test_litellm/proxy/proxy_server/test_proxy_config.py @@ -189,9 +189,7 @@ def test_ProxyConfig__load_yaml_file_raises_on_missing_file(): @pytest.mark.asyncio async def test_ProxyConfig__get_config_from_file_loads_yaml(tmp_path): f = tmp_path / "c.yaml" - f.write_text( - "model_list: []\ngeneral_settings: {}\nlitellm_settings:\n drop_params: true\n" - ) + f.write_text("model_list: []\ngeneral_settings: {}\nlitellm_settings:\n drop_params: true\n") pc = ProxyConfig() result = await pc._get_config_from_file(config_file_path=str(f)) assert result == { @@ -534,6 +532,139 @@ def test_ProxyConfig_parse_search_tools_missing_returns_none(): assert pc.parse_search_tools({}) is None +def test_ProxyConfig_merge_config_and_db_search_tools_returns_superset(): + config_tools = [ + { + "search_tool_name": "config-search", + "litellm_params": {"search_provider": "tavily"}, + } + ] + db_tools = [ + { + "search_tool_name": "db-search", + "litellm_params": { + "search_provider": "exa_ai", + "api_key": "fake-db-key", + }, + } + ] + + merged = ProxyConfig._merge_config_and_db_search_tools( + config_search_tools=config_tools, + db_search_tools=db_tools, + ) + + assert [tool["search_tool_name"] for tool in merged] == ["config-search", "db-search"] + assert merged[1]["litellm_params"]["api_key"] == "fake-db-key" + + +def test_ProxyConfig_merge_config_and_db_search_tools_prefers_db_duplicate(): + config_tools = [ + { + "search_tool_name": "shared-search", + "litellm_params": {"search_provider": "tavily"}, + }, + { + "search_tool_name": "config-only", + "litellm_params": {"search_provider": "perplexity"}, + }, + ] + db_tools = [ + { + "search_tool_name": "shared-search", + "litellm_params": { + "search_provider": "exa_ai", + "api_key": "fake-db-key", + }, + } + ] + + merged = ProxyConfig._merge_config_and_db_search_tools( + config_search_tools=config_tools, + db_search_tools=db_tools, + ) + + assert [tool["search_tool_name"] for tool in merged] == ["config-only", "shared-search"] + assert merged[1]["litellm_params"]["search_provider"] == "exa_ai" + assert merged[1]["litellm_params"]["api_key"] == "fake-db-key" + + +@pytest.mark.asyncio +async def test_ProxyConfig__init_search_tools_in_db_loads_merged_tools(monkeypatch): + from litellm.proxy import proxy_server + from litellm.router_utils.search_api_router import SearchAPIRouter + + pc = ProxyConfig() + pc.update_config_state( + { + "search_tools": [ + { + "search_tool_name": "shared-search", + "litellm_params": {"search_provider": "tavily"}, + }, + { + "search_tool_name": "config-only", + "litellm_params": {"search_provider": "perplexity"}, + }, + ] + } + ) + db_tools = [ + { + "search_tool_name": "shared-search", + "litellm_params": { + "search_provider": "exa_ai", + "api_key": "fake-db-key", + }, + } + ] + fake_router = MagicMock() + mock_get_db_tools = AsyncMock(return_value=db_tools) + mock_update_router = AsyncMock() + + monkeypatch.setattr(proxy_server, "llm_router", fake_router) + monkeypatch.setattr( + "litellm.proxy.search_endpoints.search_tool_registry.SearchToolRegistry.get_all_search_tools_from_db", + mock_get_db_tools, + ) + monkeypatch.setattr(SearchAPIRouter, "update_router_search_tools", mock_update_router) + + await pc._init_search_tools_in_db(prisma_client=MagicMock()) + + mock_get_db_tools.assert_awaited_once() + mock_update_router.assert_awaited_once() + update_kwargs = mock_update_router.await_args.kwargs + assert update_kwargs["router_instance"] is fake_router + assert [tool["search_tool_name"] for tool in update_kwargs["search_tools"]] == [ + "config-only", + "shared-search", + ] + assert update_kwargs["search_tools"][1]["litellm_params"]["api_key"] == "fake-db-key" + + +@pytest.mark.asyncio +async def test_ProxyConfig__init_search_tools_in_db_skips_empty_router_update(monkeypatch): + from litellm.proxy import proxy_server + from litellm.router_utils.search_api_router import SearchAPIRouter + + pc = ProxyConfig() + pc.update_config_state({}) + mock_get_db_tools = AsyncMock(return_value=[]) + mock_update_router = AsyncMock() + + monkeypatch.setattr(proxy_server, "llm_router", MagicMock()) + monkeypatch.setattr( + "litellm.proxy.search_endpoints.search_tool_registry.SearchToolRegistry.get_all_search_tools_from_db", + mock_get_db_tools, + ) + monkeypatch.setattr(SearchAPIRouter, "update_router_search_tools", mock_update_router) + + await pc._init_search_tools_in_db(prisma_client=MagicMock()) + + mock_get_db_tools.assert_awaited_once() + mock_update_router.assert_not_awaited() + + # --------------------------------------------------------------------------- # ProxyConfig._load_environment_variables # --------------------------------------------------------------------------- @@ -542,9 +673,7 @@ def test_ProxyConfig_parse_search_tools_missing_returns_none(): def test_ProxyConfig__load_environment_variables_sets_env(monkeypatch): monkeypatch.delenv("TEST_LOAD_ENV_X", raising=False) pc = ProxyConfig() - pc._load_environment_variables( - {"environment_variables": {"TEST_LOAD_ENV_X": "hello"}} - ) + pc._load_environment_variables({"environment_variables": {"TEST_LOAD_ENV_X": "hello"}}) result = { "TEST_LOAD_ENV_X": os.environ.get("TEST_LOAD_ENV_X"), "set": True, @@ -591,6 +720,37 @@ async def test_ProxyConfig_load_config_minimal_yaml(tmp_path, monkeypatch): } +@pytest.mark.asyncio +async def test_ProxyConfig_load_config_wires_general_settings_url_validation(tmp_path, monkeypatch): + """Regression for #26599: SSRF settings in general_settings must reach litellm globals.""" + f = tmp_path / "c.yaml" + f.write_text( + "model_list: []\n" + "general_settings:\n" + " user_url_validation: false\n" + " user_url_allowed_hosts:\n" + " - internal.corp\n" + " provider_url_destination_allowed_hosts:\n" + " - api.example.com\n" + ) + monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", None) + monkeypatch.setattr("litellm.proxy.proxy_server.store_model_in_db", False) + monkeypatch.delenv("LITELLM_CONFIG_BUCKET_NAME", raising=False) + + original_validation = litellm.user_url_validation + original_hosts = list(litellm.user_url_allowed_hosts) + original_provider_hosts = list(litellm.provider_url_destination_allowed_hosts) + try: + await ProxyConfig().load_config(router=None, config_file_path=str(f)) + assert litellm.user_url_validation is False + assert litellm.user_url_allowed_hosts == ["internal.corp"] + assert litellm.provider_url_destination_allowed_hosts == ["api.example.com"] + finally: + litellm.user_url_validation = original_validation + litellm.user_url_allowed_hosts = original_hosts + litellm.provider_url_destination_allowed_hosts = original_provider_hosts + + @pytest.mark.asyncio async def test_ProxyConfig_load_config_missing_file_raises(monkeypatch): monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", None) @@ -602,9 +762,7 @@ async def test_ProxyConfig_load_config_missing_file_raises(monkeypatch): @pytest.mark.asyncio -async def test_ProxyConfig_load_config_forwards_callback_specific_params( - tmp_path, monkeypatch -): +async def test_ProxyConfig_load_config_forwards_callback_specific_params(tmp_path, monkeypatch): """Regression: callback_settings from config must be forwarded to initialize_callbacks_on_proxy as callback_specific_params. @@ -645,16 +803,12 @@ async def test_ProxyConfig_load_config_forwards_callback_specific_params( # The callbacks branch must forward the loaded callback_settings. assert captured.get("callback_specific_params") == { - "datadog_cost_management": { - "cost_tag_keys": ["capability", "platform", "ai_product"] - } + "datadog_cost_management": {"cost_tag_keys": ["capability", "platform", "ai_product"]} } @pytest.mark.asyncio -async def test_ProxyConfig_load_config_blank_callback_settings_does_not_crash( - tmp_path, monkeypatch -): +async def test_ProxyConfig_load_config_blank_callback_settings_does_not_crash(tmp_path, monkeypatch): """Regression: `callback_settings:` with no body loads as None because dict.get() only falls back to the default when the key is absent. The None was forwarded verbatim to initialize_callbacks_on_proxy, where the first @@ -678,17 +832,13 @@ async def test_ProxyConfig_load_config_blank_callback_settings_does_not_crash( CompressionInterceptionLogger, ) - original_callbacks = ( - list(litellm.callbacks) if isinstance(litellm.callbacks, list) else [] - ) + original_callbacks = list(litellm.callbacks) if isinstance(litellm.callbacks, list) else [] litellm.callbacks = [] try: pc = ProxyConfig() await pc.load_config(router=None, config_file_path=str(f)) - assert any( - isinstance(c, CompressionInterceptionLogger) for c in litellm.callbacks - ) + assert any(isinstance(c, CompressionInterceptionLogger) for c in litellm.callbacks) finally: litellm.callbacks = original_callbacks @@ -1029,6 +1179,105 @@ def test_ProxyConfig__resolve_db_litellm_param_skips_non_string_values(monkeypat assert pc._resolve_db_litellm_param(key="tpm", value=100) == 100 +def test_ProxyConfig__add_deployment_resolves_env_refs_for_aws_bedrock_auth_params( + monkeypatch, +): + """Regression: DB-stored Bedrock/SageMaker auth params like + ``aws_role_name: os.environ/BEDROCK_ASSUME_ROLE_ARN`` must resolve at + DB-load time. PR #30867 removed request-time expansion in + ``BaseAWSLLM.get_credentials``; without DB-load resolution the literal + string reaches STS and fails with ``ValidationError: ... is invalid``.""" + aws_env = { + "aws_session_token": ("BEDROCK_SESSION_TOKEN", "resolved-session-token"), + "aws_region_name": ("BEDROCK_REGION", "us-east-1"), + "aws_session_name": ("BEDROCK_SESSION_NAME", "resolved-session"), + "aws_profile_name": ("BEDROCK_PROFILE", "resolved-profile"), + "aws_role_name": ( + "BEDROCK_ASSUME_ROLE_ARN", + "arn:aws:iam::123456789012:role/resolved", + ), + "aws_web_identity_token": ("BEDROCK_WEB_IDENTITY_TOKEN", "resolved-token"), + "aws_sts_endpoint": ( + "BEDROCK_STS_ENDPOINT", + "https://sts.us-east-1.amazonaws.com", + ), + "aws_external_id": ("BEDROCK_EXTERNAL_ID", "resolved-external-id"), + "aws_bedrock_runtime_endpoint": ( + "BEDROCK_RUNTIME_ENDPOINT", + "https://bedrock-runtime.us-east-1.amazonaws.com", + ), + "aws_bedrock_project_id": ("BEDROCK_PROJECT_ID", "resolved-project-id"), + "aws_batch_role_arn": ( + "BEDROCK_BATCH_ROLE_ARN", + "arn:aws:iam::123456789012:role/batch", + ), + "aws_workspace_id": ("BEDROCK_WORKSPACE_ID", "resolved-workspace-id"), + } + for _, (env_name, env_value) in aws_env.items(): + monkeypatch.setenv(env_name, env_value) + monkeypatch.setattr( + "litellm.proxy.proxy_server.decrypt_value_helper", + lambda value, key, return_original_value: value, + ) + fake_router = MagicMock() + fake_router.upsert_deployment = MagicMock(return_value=True) + monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", fake_router) + pc = ProxyConfig() + litellm_params: Dict[str, Any] = {"model": "bedrock/anthropic.claude-v2"} + for key, (env_name, _) in aws_env.items(): + litellm_params[key] = f"os.environ/{env_name}" + db_model = SimpleNamespace( + model_id="model-1", + model_name="bedrock-model", + model_info={"id": "model-1"}, + litellm_params=litellm_params, + blocked=False, + ) + + added = pc._add_deployment(db_models=[db_model]) + deployment = fake_router.upsert_deployment.call_args.kwargs["deployment"] + + assert added == 1 + for key, (_, expected) in aws_env.items(): + assert getattr(deployment.litellm_params, key) == expected, key + + +def test_ProxyConfig__add_deployment_keeps_team_aws_env_refs_literal(monkeypatch): + """Team-scoped DB models must NOT resolve env refs even for AWS auth + params: this is the LIT-3831 defense-in-depth path where a team admin + could otherwise craft a DB entry that reads the process environment.""" + + def fail_on_call(secret_name, *args, **kwargs): + raise AssertionError("team DB models should not resolve env refs") + + monkeypatch.setenv("BEDROCK_ASSUME_ROLE_ARN", "arn:aws:iam::123:role/should-not-leak") + monkeypatch.setattr( + "litellm.proxy.proxy_server.decrypt_value_helper", + lambda value, key, return_original_value: value, + ) + monkeypatch.setattr("litellm.proxy.proxy_server.get_secret", fail_on_call) + fake_router = MagicMock() + fake_router.upsert_deployment = MagicMock(return_value=True) + monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", fake_router) + pc = ProxyConfig() + db_model = SimpleNamespace( + model_id="model-1", + model_name="model_name_team-1_bedrock", + model_info={"id": "model-1", "team_id": "team-1"}, + litellm_params={ + "model": "bedrock/anthropic.claude-v2", + "aws_role_name": "os.environ/BEDROCK_ASSUME_ROLE_ARN", + }, + blocked=False, + ) + + added = pc._add_deployment(db_models=[db_model]) + deployment = fake_router.upsert_deployment.call_args.kwargs["deployment"] + + assert added == 1 + assert deployment.litellm_params.aws_role_name == "os.environ/BEDROCK_ASSUME_ROLE_ARN" + + # --------------------------------------------------------------------------- # ProxyConfig.decrypt_model_list_from_db # --------------------------------------------------------------------------- @@ -1071,7 +1320,9 @@ def test_ProxyConfig_decrypt_model_list_from_db_resolves_env_refs_after_db_decry lambda value, key, return_original_value: ( "os.environ/LITELLM_DB_MODEL_API_KEY" if key == "api_key" - else "os.environ/LITELLM_MASTER_KEY" if key == "api_base" else value + else "os.environ/LITELLM_MASTER_KEY" + if key == "api_base" + else value ), ) pc = ProxyConfig() @@ -1102,9 +1353,7 @@ def test_ProxyConfig_decrypt_model_list_from_db_keeps_team_env_refs_literal_afte monkeypatch.setenv("LITELLM_MASTER_KEY", "master-secret") monkeypatch.setattr( "litellm.proxy.proxy_server.decrypt_value_helper", - lambda value, key, return_original_value: ( - "os.environ/LITELLM_MASTER_KEY" if key == "api_key" else value - ), + lambda value, key, return_original_value: "os.environ/LITELLM_MASTER_KEY" if key == "api_key" else value, ) monkeypatch.setattr("litellm.proxy.proxy_server.get_secret", fail_on_call) pc = ProxyConfig() @@ -1128,9 +1377,7 @@ def test_ProxyConfig_decrypt_model_list_from_db_keeps_team_env_refs_literal_afte def test_ProxyConfig_decrypt_model_list_from_db_invalid_params_skips(): pc = ProxyConfig() - bad = SimpleNamespace( - model_id="m-1", model_name="x", model_info={}, litellm_params="not-a-dict" - ) + bad = SimpleNamespace(model_id="m-1", model_name="x", model_info={}, litellm_params="not-a-dict") out = pc.decrypt_model_list_from_db(new_models=[bad]) # Invalid entries skipped — empty list returned. assert out == [] @@ -1180,9 +1427,7 @@ async def test_ProxyConfig__update_llm_router_bad_proxy_logging_raises(monkeypat monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", fake_router) monkeypatch.setattr("litellm.proxy.proxy_server.master_key", "sk-x") monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", None) - monkeypatch.setattr( - "litellm.proxy.proxy_server.general_settings", {"alerting": ["email"]} - ) + monkeypatch.setattr("litellm.proxy.proxy_server.general_settings", {"alerting": ["email"]}) monkeypatch.setattr("litellm.proxy.proxy_server.proxy_config", pc) # Passing None for proxy_logging_obj triggers AttributeError in _add_general_settings_from_db_config # when it calls proxy_logging_obj.update_values. @@ -1433,9 +1678,7 @@ async def test_ProxyConfig__add_router_settings_from_db_config_updates_router(): fake_router.update_settings = MagicMock() fake_prisma = MagicMock() fake_prisma.db.litellm_config.find_first = AsyncMock( - return_value=SimpleNamespace( - param_value={"timeout": 30, "retries": 2, "fallbacks": []} - ) + return_value=SimpleNamespace(param_value={"timeout": 30, "retries": 2, "fallbacks": []}) ) config_data = {"router_settings": {"timeout": 10}} await pc._add_router_settings_from_db_config( @@ -1446,9 +1689,7 @@ async def test_ProxyConfig__add_router_settings_from_db_config_updates_router(): snapshot = { "called": fake_router.update_settings.called, "call_count": fake_router.update_settings.call_count, - "kwargs_keys": sorted( - list(fake_router.update_settings.call_args.kwargs.keys()) - ), + "kwargs_keys": sorted(list(fake_router.update_settings.call_args.kwargs.keys())), } assert snapshot == { "called": True, @@ -1461,9 +1702,7 @@ async def test_ProxyConfig__add_router_settings_from_db_config_updates_router(): async def test_ProxyConfig__add_router_settings_from_db_config_none_router_noop(): pc = ProxyConfig() # No router and no prisma — should silently return. - await pc._add_router_settings_from_db_config( - config_data={}, llm_router=None, prisma_client=None - ) + await pc._add_router_settings_from_db_config(config_data={}, llm_router=None, prisma_client=None) # Error-style: bad call signature raises. with pytest.raises(TypeError): await pc._add_router_settings_from_db_config() # type: ignore[call-arg] @@ -1568,9 +1807,7 @@ async def test_ProxyConfig__update_general_settings_updates_max_parallel(monkeyp snapshot = { "max_parallel_requests": ps.general_settings.get("max_parallel_requests"), - "global_max_parallel_requests": ps.general_settings.get( - "global_max_parallel_requests" - ), + "global_max_parallel_requests": ps.general_settings.get("global_max_parallel_requests"), "ui_access_mode": ps.general_settings.get("ui_access_mode"), } assert snapshot == { @@ -1712,9 +1949,7 @@ async def test_ProxyConfig__update_config_from_db_does_not_log_general_settings_ @pytest.mark.asyncio -async def test_ProxyConfig_load_config_redacts_secret_litellm_setting_keeps_plain( - tmp_path, monkeypatch -): +async def test_ProxyConfig_load_config_redacts_secret_litellm_setting_keeps_plain(tmp_path, monkeypatch): """Regression for LIT-4152 on the ``litellm_settings`` apply loop. ``load_config`` logged ``setting litellm.=`` verbatim at DEBUG, @@ -1735,11 +1970,7 @@ async def test_ProxyConfig_load_config_redacts_secret_litellm_setting_keeps_plai api_key_secret = "sk-lit4152-litellm-settings-secret-abcdef1234567890" f = tmp_path / "c.yaml" f.write_text( - "model_list: []\n" - "general_settings: {}\n" - "litellm_settings:\n" - f" api_key: {api_key_secret}\n" - " num_retries: 7\n" + f"model_list: []\ngeneral_settings: {{}}\nlitellm_settings:\n api_key: {api_key_secret}\n num_retries: 7\n" ) monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", None) monkeypatch.setattr("litellm.proxy.proxy_server.store_model_in_db", False) diff --git a/tests/test_litellm/proxy/spend_tracking/test_spend_management_endpoints.py b/tests/test_litellm/proxy/spend_tracking/test_spend_management_endpoints.py index 4acc94c737a..1e9818534c9 100644 --- a/tests/test_litellm/proxy/spend_tracking/test_spend_management_endpoints.py +++ b/tests/test_litellm/proxy/spend_tracking/test_spend_management_endpoints.py @@ -66,13 +66,14 @@ def _reconstruct_ui_where_from_sql(sql_query, params): Rebuild the Prisma-style ``where`` dict the filter_fns below expect from the raw SQL + params the endpoint emits. - ``ui_view_spend_logs`` folds the total into the page query via - ``COUNT(*) OVER ()`` and no longer issues a separate ``count(where=...)`` - call, so the mock derives the active filter from the one query it sees - instead of from the (now absent) count call. + ``ui_view_spend_logs`` computes the total with a bounded + ``SELECT COUNT(*) FROM (SELECT 1 ... LIMIT $cap+1)`` query and fetches the + page with a separate ``ORDER BY ... LIMIT/OFFSET`` query. Both carry the + same WHERE clause, so the terminator can be ``ORDER BY`` (page query) or + ``LIMIT`` (bounded count query). """ where: dict = {} - clause = re.search(r"WHERE (.*) ORDER BY", sql_query, re.DOTALL) + clause = re.search(r"WHERE (.*?)\s+(?:ORDER BY|LIMIT)", sql_query, re.DOTALL) if clause is None: return where @@ -163,13 +164,13 @@ def make_ui_spend_logs_mock_prisma(mock_spend_logs, filter_fn, team_lookup_fn=No async def query_raw(self, sql_query, *params): filtered = filter_fn(_reconstruct_ui_where_from_sql(sql_query, params)) + total = len(filtered) + if "COUNT(*)" in sql_query: + cap_plus_one = params[-1] + return [{"total_count": min(total, cap_plus_one)}] page_size = params[-2] if len(params) >= 2 else 50 skip = params[-1] if len(params) >= 1 else 0 - total = len(filtered) - return [ - {**row, "total_count": total} - for row in filtered[skip : skip + page_size] - ] + return [row for row in filtered[skip : skip + page_size]] class MockPrismaClient: def __init__(self): @@ -684,6 +685,8 @@ async def test_ui_view_spend_logs_sort_by_and_sort_order( return len(base_logs) async def mock_query_raw(sql_query, *params): + if "COUNT(*)" in sql_query: + return [{"total_count": len(base_logs)}] # Endpoint uses raw SQL with ORDER BY startTime DESC; mock returns sorted data order = ( {"startTime": "desc"} @@ -693,10 +696,7 @@ async def test_ui_view_spend_logs_sort_by_and_sort_order( sorted_logs = _sort_logs(base_logs, order) page_size = params[-2] if len(params) >= 2 else 50 skip = params[-1] if len(params) >= 1 else 0 - return [ - {**row, "total_count": len(base_logs)} - for row in sorted_logs[skip : skip + page_size] - ] + return [row for row in sorted_logs[skip : skip + page_size]] class MockPrismaClient: def __init__(self): @@ -830,16 +830,15 @@ async def test_ui_view_spend_logs_sort_by_request_duration_ms(client, monkeypatc return len(base_logs) async def mock_query_raw(sql_query, *params): + if "COUNT(*)" in sql_query: + return [{"total_count": len(base_logs)}] reverse = "DESC" in sql_query sorted_logs = sorted( base_logs, key=lambda x: x.get("request_duration_ms", 0), reverse=reverse ) page_size = params[-2] if len(params) >= 2 else 50 skip = params[-1] if len(params) >= 1 else 0 - return [ - {**row, "total_count": len(base_logs)} - for row in sorted_logs[skip : skip + page_size] - ] + return [row for row in sorted_logs[skip : skip + page_size]] class MockPrismaClient: def __init__(self): @@ -926,6 +925,8 @@ async def test_ui_view_spend_logs_sort_by_model( return len(base_logs) async def mock_query_raw(sql_query, *params): + if "COUNT(*)" in sql_query: + return [{"total_count": len(base_logs)}] assert "model" in sql_query # model is non-nullable in the schema, so NULLS LAST should NOT be # appended — only ttft_ms gets that clause. This guards against @@ -937,10 +938,7 @@ async def test_ui_view_spend_logs_sort_by_model( ) page_size = params[-2] if len(params) >= 2 else 50 skip = params[-1] if len(params) >= 1 else 0 - return [ - {**row, "total_count": len(base_logs)} - for row in sorted_logs[skip : skip + page_size] - ] + return [row for row in sorted_logs[skip : skip + page_size]] class MockPrismaClient: def __init__(self): @@ -1040,6 +1038,8 @@ async def test_ui_view_spend_logs_sort_by_ttft_ms(client, monkeypatch): return len(base_logs) async def mock_query_raw(sql_query, *params): + if "COUNT(*)" in sql_query: + return [{"total_count": len(base_logs)}] # Endpoint must compute TTFT inline and use NULLS LAST. assert "completionStartTime" in sql_query assert "NULLS LAST" in sql_query @@ -1051,10 +1051,7 @@ async def test_ui_view_spend_logs_sort_by_ttft_ms(client, monkeypatch): page_size = params[-2] if len(params) >= 2 else 50 skip = params[-1] if len(params) >= 1 else 0 return [ - { - **{k: v for k, v in row.items() if k != "_ttft_ms"}, - "total_count": len(base_logs), - } + {k: v for k, v in row.items() if k != "_ttft_ms"} for row in sorted_logs[skip : skip + page_size] ] diff --git a/tests/test_litellm/proxy/spend_tracking/test_spend_query_optimization.py b/tests/test_litellm/proxy/spend_tracking/test_spend_query_optimization.py index 5b793adbbb4..19083486974 100644 --- a/tests/test_litellm/proxy/spend_tracking/test_spend_query_optimization.py +++ b/tests/test_litellm/proxy/spend_tracking/test_spend_query_optimization.py @@ -16,6 +16,7 @@ import pytest sys.path.insert(0, os.path.abspath("../../../..")) from litellm.proxy.spend_tracking.spend_tracking_utils import ( + get_spend_by_team, get_spend_by_team_and_customer, ) @@ -60,31 +61,17 @@ async def test_spend_query_uses_timestamp_filtering(): params = call_args[1:] # 1) SQL should NOT cast the startTime column to DATE (prevents index usage) - assert ( - "::date" not in sql.lower() - ), "SQL should not use '::date' casting which prevents index usage" - assert ( - "date(" not in sql.lower() - ), "SQL should not use DATE() function which prevents index usage" + assert "::date" not in sql.lower(), "SQL should not use '::date' casting which prevents index usage" + assert "date(" not in sql.lower(), "SQL should not use DATE() function which prevents index usage" # 2) SQL should use timestamp-range filtering pattern for index optimization - assert ( - '"startTime" >=' in sql or '"startTime">=' in sql - ), "SQL should use >= operator for lower bound" - assert ( - '"startTime" <' in sql or '"startTime"<' in sql - ), "SQL should use < operator for upper bound" - assert ( - "interval '1 day'" in sql.lower() - ), "SQL should use INTERVAL for date arithmetic" + assert '"startTime" >=' in sql or '"startTime">=' in sql, "SQL should use >= operator for lower bound" + assert '"startTime" <' in sql or '"startTime"<' in sql, "SQL should use < operator for upper bound" + assert "interval '1 day'" in sql.lower(), "SQL should use INTERVAL for date arithmetic" # 3) Parameters should be datetime objects (not date objects) - assert isinstance( - params[0], datetime.datetime - ), "First parameter (start_date) should be datetime object" - assert isinstance( - params[1], datetime.datetime - ), "Second parameter (end_date) should be datetime object" + assert isinstance(params[0], datetime.datetime), "First parameter (start_date) should be datetime object" + assert isinstance(params[1], datetime.datetime), "Second parameter (end_date) should be datetime object" assert params[0].tzinfo is not None, "start_date should be timezone-aware" assert params[1].tzinfo is not None, "end_date should be timezone-aware" @@ -130,12 +117,8 @@ async def test_global_activity_wraps_params_in_at_time_zone_utc(monkeypatch): # 2) Params must still be tz-aware UTC datetimes (preserves existing contract). assert isinstance(params[0], datetime.datetime) assert isinstance(params[1], datetime.datetime) - assert params[0].tzinfo is not None and params[0].utcoffset() == datetime.timedelta( - 0 - ) - assert params[1].tzinfo is not None and params[1].utcoffset() == datetime.timedelta( - 0 - ) + assert params[0].tzinfo is not None and params[0].utcoffset() == datetime.timedelta(0) + assert params[1].tzinfo is not None and params[1].utcoffset() == datetime.timedelta(0) @pytest.mark.asyncio @@ -158,9 +141,7 @@ async def test_global_activity_internal_user_wraps_params_in_at_time_zone_utc( monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma) - auth = UserAPIKeyAuth( - user_role=LitellmUserRoles.INTERNAL_USER, user_id="internal_user_1" - ) + auth = UserAPIKeyAuth(user_role=LitellmUserRoles.INTERNAL_USER, user_id="internal_user_1") await get_global_activity( start_date="2026-02-16", @@ -171,8 +152,7 @@ async def test_global_activity_internal_user_wraps_params_in_at_time_zone_utc( assert mock_prisma.db.query_raw.called sql = mock_prisma.db.query_raw.call_args[0][0] assert sql.count("AT TIME ZONE 'UTC'") >= 2, ( - "Internal-user branch must also wrap date bounds with " - f"`AT TIME ZONE 'UTC'`. SQL was:\n{sql}" + f"Internal-user branch must also wrap date bounds with `AT TIME ZONE 'UTC'`. SQL was:\n{sql}" ) @@ -219,36 +199,46 @@ async def test_spend_logs_ui_wraps_params_in_at_time_zone_utc(monkeypatch): assert mock_prisma.db.query_raw.called, "query_raw should have been called" sql = mock_prisma.db.query_raw.call_args[0][0] assert sql.count("AT TIME ZONE 'UTC'") >= 2, ( - "/spend/logs/ui must wrap both `startTime` bounds with " - f"`AT TIME ZONE 'UTC'`. SQL was:\n{sql}" + f"/spend/logs/ui must wrap both `startTime` bounds with `AT TIME ZONE 'UTC'`. SQL was:\n{sql}" ) +def _make_ui_spend_logs_mock(count_total, page_rows): + """ + Build a prisma mock whose first `query_raw` (the bounded count) returns + `count_total` and whose second `query_raw` (the page data) returns + `page_rows`. + """ + mock_prisma = MagicMock() + mock_prisma.db = MagicMock() + mock_prisma.db.query_raw = AsyncMock( + side_effect=[[{"total_count": count_total}], page_rows] + ) + mock_prisma.db.litellm_spendlogs = MagicMock() + mock_prisma.db.litellm_spendlogs.count = AsyncMock(return_value=0) + return mock_prisma + + @pytest.mark.asyncio -async def test_spend_logs_ui_folds_count_into_window_function(monkeypatch): +async def test_spend_logs_ui_uses_bounded_count_not_full_scan(monkeypatch): """ - /spend/logs/ui must not issue a separate `COUNT(*)` round trip to compute - the total. On sharded engines like YugabyteDB a standalone `COUNT(*)` is a - distributed RPC that contacts every tablet and times out regardless of row - count, so the logs tab 500s (LIT-4027). The total is folded into the page - query via `COUNT(*) OVER ()` and read off the returned rows instead. + /spend/logs/ui must compute its pagination total with a bounded + `SELECT COUNT(*) FROM (SELECT 1 ... LIMIT $cap+1)` so it never scans the + whole time window of a huge LiteLLM_SpendLogs table (Aurora ACU spike, + LIT-4119). It must also avoid the unbounded prisma `.count()` / + `COUNT(*) OVER ()` full-window count that reads every matching row. """ from litellm.proxy._types import LitellmUserRoles, UserAPIKeyAuth from litellm.proxy.spend_tracking.spend_management_endpoints import ( + SPEND_LOGS_PAGINATION_COUNT_CAP, ui_view_spend_logs, ) - rows = [ - {"request_id": "req-1", "metadata": "{}", "session_id": None, "total_count": 137}, - {"request_id": "req-2", "metadata": "{}", "session_id": None, "total_count": 137}, + page_rows = [ + {"request_id": "req-1", "metadata": "{}", "session_id": None}, + {"request_id": "req-2", "metadata": "{}", "session_id": None}, ] - - mock_prisma = MagicMock() - mock_prisma.db = MagicMock() - mock_prisma.db.query_raw = AsyncMock(return_value=rows) - mock_prisma.db.litellm_spendlogs = MagicMock() - mock_prisma.db.litellm_spendlogs.count = AsyncMock(return_value=0) - + mock_prisma = _make_ui_spend_logs_mock(count_total=137, page_rows=page_rows) monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma) auth = UserAPIKeyAuth(user_role=LitellmUserRoles.PROXY_ADMIN, user_id="admin") @@ -271,36 +261,90 @@ async def test_spend_logs_ui_folds_count_into_window_function(monkeypatch): mock_prisma.db.litellm_spendlogs.count.assert_not_called() - sql = mock_prisma.db.query_raw.call_args[0][0] - assert "COUNT(*) OVER ()" in sql, ( - "the page query must carry a window-function count so a separate " - f"distributed COUNT(*) is avoided. SQL was:\n{sql}" + count_call = mock_prisma.db.query_raw.call_args_list[0] + count_sql = count_call[0][0] + assert "COUNT(*) OVER ()" not in count_sql + assert "LIMIT" in count_sql and "FROM (" in count_sql, ( + "the total must come from a bounded subquery count, not a full-window " + f"scan. SQL was:\n{count_sql}" + ) + assert count_call[0][-1] == SPEND_LOGS_PAGINATION_COUNT_CAP + 1, ( + "the bounded count must probe at most cap+1 rows" + ) + + page_sql = mock_prisma.db.query_raw.call_args_list[1][0][0] + assert "COUNT(*) OVER ()" not in page_sql, ( + "the page query must not carry a window count that forces a full-window " + f"scan. SQL was:\n{page_sql}" ) assert response["total"] == 137 + assert response["total_is_capped"] is False assert response["total_pages"] == (137 + 50 - 1) // 50 for row in response["data"]: - assert "total_count" not in row, ( - "the window-function helper column must be stripped before " - "serialising rows" - ) + assert "total_count" not in row, "the window-function helper column must be stripped before serialising rows" + + +@pytest.mark.asyncio +async def test_spend_logs_ui_caps_total_for_large_result_sets(monkeypatch): + """ + When more than the cap match, /spend/logs/ui reports the cap and flags + `total_is_capped` so the UI can render `+` instead of an exact total + that would require scanning the whole window (LIT-4119). + """ + from litellm.proxy._types import LitellmUserRoles, UserAPIKeyAuth + from litellm.proxy.spend_tracking.spend_management_endpoints import ( + SPEND_LOGS_PAGINATION_COUNT_CAP, + ui_view_spend_logs, + ) + + page_rows = [{"request_id": "req-1", "metadata": "{}", "session_id": None}] + mock_prisma = _make_ui_spend_logs_mock( + count_total=SPEND_LOGS_PAGINATION_COUNT_CAP + 1, page_rows=page_rows + ) + monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma) + + auth = UserAPIKeyAuth(user_role=LitellmUserRoles.PROXY_ADMIN, user_id="admin") + mock_request = MagicMock() + mock_request.url.path = "/spend/logs/ui" + + response = await ui_view_spend_logs( + request=mock_request, + api_key=None, + user_id=None, + request_id=None, + start_date="2026-02-16 00:00:00", + end_date="2026-02-16 23:59:59", + page=1, + page_size=50, + sort_by="startTime", + sort_order="desc", + user_api_key_dict=auth, + ) + + assert response["total"] == SPEND_LOGS_PAGINATION_COUNT_CAP + assert response["total_is_capped"] is True + assert response["total_pages"] == (SPEND_LOGS_PAGINATION_COUNT_CAP + 50 - 1) // 50 @pytest.mark.asyncio async def test_spend_logs_ui_empty_page_reports_zero_total(monkeypatch): """ - When a page matches no rows the window-function count row is absent, so the - total must fall back to zero without issuing a separate `COUNT(*)`. + When nothing matches, the bounded count query returns a single row with a + zero count (real `COUNT(*)` always returns one row) and the page query + returns no rows, so the total is zero without an unbounded prisma `.count()`. """ from litellm.proxy._types import LitellmUserRoles, UserAPIKeyAuth from litellm.proxy.spend_tracking.spend_management_endpoints import ( ui_view_spend_logs, ) + # First query_raw call is the bounded count (0 matches), second is the empty + # page. mock_prisma = MagicMock() mock_prisma.db = MagicMock() - mock_prisma.db.query_raw = AsyncMock(return_value=[]) + mock_prisma.db.query_raw = AsyncMock(side_effect=[[{"total_count": 0}], []]) mock_prisma.db.litellm_spendlogs = MagicMock() mock_prisma.db.litellm_spendlogs.count = AsyncMock(return_value=0) @@ -331,24 +375,25 @@ async def test_spend_logs_ui_empty_page_reports_zero_total(monkeypatch): @pytest.mark.asyncio -async def test_spend_logs_ui_out_of_range_page_falls_back_to_count(monkeypatch): +async def test_spend_logs_ui_out_of_range_page_keeps_total(monkeypatch): """ - An out-of-range page (offset past the last matching row) returns no rows, so - the window-function count is unavailable. The total must not collapse to zero - there; it falls back to a direct count so total/total_pages stay accurate. - This fallback only fires off the hot path (page > 1 with an empty result), so - the YugabyteDB timeout the fix removes from page 1 stays removed. + An out-of-range page (offset past the last matching row) returns no rows, + but the bounded count query runs independently of the page query, so the + total must not collapse to zero and no unbounded prisma `.count()` is + needed. total/total_pages stay accurate off the hot path too. """ from litellm.proxy._types import LitellmUserRoles, UserAPIKeyAuth from litellm.proxy.spend_tracking.spend_management_endpoints import ( ui_view_spend_logs, ) + # First query_raw call is the bounded count (7 matches), second is the + # out-of-range page (empty). mock_prisma = MagicMock() mock_prisma.db = MagicMock() - mock_prisma.db.query_raw = AsyncMock(return_value=[]) + mock_prisma.db.query_raw = AsyncMock(side_effect=[[{"total_count": 7}], []]) mock_prisma.db.litellm_spendlogs = MagicMock() - mock_prisma.db.litellm_spendlogs.count = AsyncMock(return_value=7) + mock_prisma.db.litellm_spendlogs.count = AsyncMock(return_value=0) monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma) @@ -370,6 +415,90 @@ async def test_spend_logs_ui_out_of_range_page_falls_back_to_count(monkeypatch): user_api_key_dict=auth, ) - mock_prisma.db.litellm_spendlogs.count.assert_called_once() + mock_prisma.db.litellm_spendlogs.count.assert_not_called() assert response["total"] == 7 assert response["total_pages"] == (7 + 2 - 1) // 2 + assert response["data"] == [] + + +@pytest.mark.asyncio +async def test_get_spend_by_team_binds_optional_team_filter(): + """ + get_spend_by_team must bind team_id as query parameter $3 behind an + `IS NULL OR` predicate: a provided team_id narrows the result to that team, + a None team_id short-circuits the filter and returns every team. Regression + guard for LIT-4125 (the team query previously carried no team_id predicate). + """ + mock_prisma = MagicMock() + mock_prisma.db = MagicMock() + mock_query_raw = AsyncMock(return_value=[]) + mock_prisma.db.query_raw = mock_query_raw + + start_date = datetime.datetime(2024, 1, 1, tzinfo=timezone.utc) + end_date = datetime.datetime(2024, 1, 31, tzinfo=timezone.utc) + + await get_spend_by_team( + start_date=start_date, + end_date=end_date, + team_id="test_team", + prisma_client=mock_prisma, + ) + + assert mock_query_raw.called, "query_raw should have been called" + sql = mock_query_raw.call_args[0][0] + params = mock_query_raw.call_args[0][1:] + + # team_id is bound as parameter $3 (not string-interpolated) and referenced in WHERE + assert "sl.team_id = $3" in sql, f"WHERE must filter on team_id. SQL was:\n{sql}" + assert "$3::text IS NULL OR" in sql, ( + f"the team filter must be optional via an IS NULL short-circuit. SQL was:\n{sql}" + ) + assert params[2] == "test_team", "team_id must be forwarded as the third query param" + + # None team_id still forwards param $3 (as None) so the predicate no-ops + mock_query_raw.reset_mock() + await get_spend_by_team( + start_date=start_date, + end_date=end_date, + team_id=None, + prisma_client=mock_prisma, + ) + assert mock_query_raw.call_args[0][1:][2] is None + + +@pytest.mark.asyncio +async def test_global_spend_report_team_group_forwards_team_id(monkeypatch): + """ + GET /global/spend/report?group_by=team&team_id=X must filter to team X. + + Before LIT-4125 the group_by=team branch ran a query with no team_id + predicate, so spend for every team in the range was returned regardless of + team_id (team_id was only honored when a customer_id was also supplied). + This asserts the endpoint forwards team_id into the DB query. + """ + from litellm.proxy.spend_tracking.spend_management_endpoints import ( + get_global_spend_report, + ) + + mock_prisma = MagicMock() + mock_prisma.db = MagicMock() + mock_prisma.db.query_raw = AsyncMock(return_value=[]) + + monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma) + monkeypatch.setattr("litellm.proxy.proxy_server.premium_user", True) + + await get_global_spend_report( + start_date="2026-07-01", + end_date="2026-07-03", + group_by="team", + api_key=None, + internal_user_id=None, + team_id="team_x", + customer_id=None, + ) + + assert mock_prisma.db.query_raw.called, "query_raw should have been called" + sql = mock_prisma.db.query_raw.call_args[0][0] + params = mock_prisma.db.query_raw.call_args[0][1:] + assert "team_x" in params, "team_id must be forwarded into the DB query params" + assert "sl.team_id = $3" in sql, f"team query must filter on team_id. SQL was:\n{sql}" diff --git a/tests/test_litellm/proxy/spend_tracking/test_spend_tracking_utils.py b/tests/test_litellm/proxy/spend_tracking/test_spend_tracking_utils.py index 874e0654a1f..39b5e120c48 100644 --- a/tests/test_litellm/proxy/spend_tracking/test_spend_tracking_utils.py +++ b/tests/test_litellm/proxy/spend_tracking/test_spend_tracking_utils.py @@ -29,6 +29,7 @@ from litellm.proxy.spend_tracking.spend_tracking_utils import ( _get_response_for_spend_logs_payload, _get_spend_logs_metadata, _get_vector_store_request_for_spend_logs_payload, + _hash_api_key_for_spend_log, _is_master_key, _redact_prompt_leaks_in_error_string, _sanitize_error_information_for_spend_logs, @@ -2229,3 +2230,86 @@ def test_get_logging_payload_cache_hit_keeps_raw_litellm_call_id(): assert json.loads(payload["metadata"])["litellm_call_id"] == trace_call_id assert "_cache_hit" in payload["request_id"] assert json.loads(payload["metadata"])["litellm_call_id"] != payload["request_id"] + + +class TestHashApiKeyForSpendLog: + """Regression: plaintext API keys with Bearer prefix were stored in + SpendLogs for failed requests (LIT-4121)""" + + def test_bearer_prefixed_sk_key_is_hashed(self): + raw = "Bearer sk-WLi4iRn4JmbVlTaYw12IOA" + result = _hash_api_key_for_spend_log(raw) + assert not result.startswith("Bearer") + assert not result.startswith("sk-") + assert len(result) == 64 + + def test_bare_sk_key_is_hashed(self): + raw = "sk-WLi4iRn4JmbVlTaYw12IOA" + result = _hash_api_key_for_spend_log(raw) + assert not result.startswith("sk-") + assert len(result) == 64 + + def test_bearer_lowercase_is_handled(self): + raw = "bearer sk-WLi4iRn4JmbVlTaYw12IOA" + result = _hash_api_key_for_spend_log(raw) + assert not result.startswith("bearer") + assert not result.startswith("sk-") + assert len(result) == 64 + + def test_already_hashed_key_unchanged(self): + hashed = "bcfe8173f5447f10be0e7fb37aaa8b97829d5c9e0498232152f9d123456789ab" + assert _hash_api_key_for_spend_log(hashed) == hashed + + def test_bearer_prefixed_non_sk_key_strips_prefix(self): + raw = "Bearer some-other-token-format" + result = _hash_api_key_for_spend_log(raw) + assert result == "some-other-token-format" + assert not result.startswith("Bearer") + + def test_bearer_and_bare_produce_same_hash(self): + bare = "sk-WLi4iRn4JmbVlTaYw12IOA" + bearer = "Bearer sk-WLi4iRn4JmbVlTaYw12IOA" + assert _hash_api_key_for_spend_log(bare) == _hash_api_key_for_spend_log(bearer) + + +@patch("litellm.proxy.proxy_server.master_key", None) +@patch("litellm.proxy.proxy_server.general_settings", {}) +def test_get_logging_payload_hashes_bearer_prefixed_api_key(): + """Regression for LIT-4121: failed-request spend logs stored plaintext + 'Bearer sk-...' in both the api_key column and metadata.user_api_key""" + raw_key = "Bearer sk-WLi4iRn4JmbVlTaYw12IOA" + + kwargs = { + "model": "openai/gpt-4.1", + "call_type": "acompletion", + "litellm_params": { + "metadata": { + "user_api_key": raw_key, + "user_api_key_user_id": "test_user", + "user_api_key_team_id": "test_team", + "status": "failure", + } + }, + } + + payload = get_logging_payload( + kwargs=kwargs, + response_obj=Exception("model error"), + start_time=datetime.datetime.now(timezone.utc), + end_time=datetime.datetime.now(timezone.utc), + ) + + assert not payload["api_key"].startswith("Bearer"), ( + f"api_key column contains plaintext Bearer key: {payload['api_key']}" + ) + assert not payload["api_key"].startswith("sk-"), ( + f"api_key column contains unhashed key: {payload['api_key']}" + ) + + metadata_dict = json.loads(payload["metadata"]) + assert not metadata_dict["user_api_key"].startswith("Bearer"), ( + f"metadata user_api_key contains plaintext Bearer key: {metadata_dict['user_api_key']}" + ) + assert not metadata_dict["user_api_key"].startswith("sk-"), ( + f"metadata user_api_key contains unhashed key: {metadata_dict['user_api_key']}" + ) diff --git a/tests/test_litellm/proxy/test_budget_reservation.py b/tests/test_litellm/proxy/test_budget_reservation.py index d940f592a83..75242af81f4 100644 --- a/tests/test_litellm/proxy/test_budget_reservation.py +++ b/tests/test_litellm/proxy/test_budget_reservation.py @@ -58,6 +58,101 @@ def _request_body() -> dict: } +async def _reserve(valid_token, cost, key_cache, proxy_logging_obj): + with patch( + "litellm.proxy.spend_tracking.budget_reservation.estimate_request_max_cost", + return_value=cost, + ): + return await reserve_budget_for_request( + request_body=_request_body(), + route="/chat/completions", + llm_router=None, + valid_token=valid_token, + team_object=None, + user_object=None, + prisma_client=None, + user_api_key_cache=key_cache, + proxy_logging_obj=proxy_logging_obj, + ) + + +@pytest.mark.asyncio +async def test_reservation_still_protects_under_budget_throttled_key( + spend_counter_state, monkeypatch +): + """An opted-in key that is still under budget keeps its reservation counter, + so concurrent requests can't collectively overshoot max_budget.""" + monkeypatch.setattr(litellm, "budget_exceeded_throttle_percentage", 0.1) + counter_cache, key_cache = spend_counter_state + proxy_logging_obj = ProxyLogging(user_api_key_cache=key_cache) + valid_token = UserAPIKeyAuth( + token="key-throttle-under", + spend=0.0, + max_budget=1.0, + metadata={"throttle_on_budget_exceeded": True}, + ) + + reservation = await _reserve(valid_token, 0.6, key_cache, proxy_logging_obj) + + assert reservation is not None + assert ( + counter_cache.in_memory_cache.get_cache(key="spend:key:key-throttle-under") + == 0.6 + ) + + +@pytest.mark.asyncio +async def test_reservation_does_not_block_over_budget_throttled_key( + spend_counter_state, monkeypatch +): + """Once an opted-in key is over budget the reservation path must not raise; + the rate limiter throttles it instead.""" + monkeypatch.setattr(litellm, "budget_exceeded_throttle_percentage", 0.1) + counter_cache, key_cache = spend_counter_state + proxy_logging_obj = ProxyLogging(user_api_key_cache=key_cache) + valid_token = UserAPIKeyAuth( + token="key-throttle-over", + spend=0.0, + max_budget=1.0, + tpm_limit=1000, + rpm_limit=100, + metadata={"throttle_on_budget_exceeded": True}, + ) + + # first reservation lands under budget (counter -> 0.6) + await _reserve(valid_token, 0.6, key_cache, proxy_logging_obj) + + # any further request is over budget (0.6 + 0.6 > 1.0): the opted-in key is + # released and allowed through (None), not blocked, and its over-budget + # increment is released so the counter is not permanently inflated + result = await _reserve(valid_token, 0.6, key_cache, proxy_logging_obj) + assert result is None + assert ( + counter_cache.in_memory_cache.get_cache(key="spend:key:key-throttle-over") + == 0.6 + ) + + +@pytest.mark.asyncio +async def test_reservation_blocks_over_budget_non_throttled_key( + spend_counter_state, monkeypatch +): + monkeypatch.setattr(litellm, "budget_exceeded_throttle_percentage", 0.1) + counter_cache, key_cache = spend_counter_state + proxy_logging_obj = ProxyLogging(user_api_key_cache=key_cache) + valid_token = UserAPIKeyAuth( + token="key-no-optin-over", + spend=0.0, + max_budget=1.0, + ) + + await _reserve(valid_token, 0.6, key_cache, proxy_logging_obj) + await _reserve(valid_token, 0.6, key_cache, proxy_logging_obj) # counter -> 1.0 + + with pytest.raises(litellm.BudgetExceededError): + await _reserve(valid_token, 0.6, key_cache, proxy_logging_obj) + + def test_should_not_serialize_budget_reservation_on_user_api_key_auth(): auth = UserAPIKeyAuth( token="key-budget-runtime-state", diff --git a/tests/test_litellm/proxy/test_common_request_processing.py b/tests/test_litellm/proxy/test_common_request_processing.py index 1d0dafed171..aa1911f80bc 100644 --- a/tests/test_litellm/proxy/test_common_request_processing.py +++ b/tests/test_litellm/proxy/test_common_request_processing.py @@ -3304,6 +3304,75 @@ class TestStreamingClientDisconnectLogging: assert recorded is False assert "client_disconnected" not in request_data["metadata"] + @pytest.mark.asyncio + async def test_record_streaming_client_disconnect_handles_none_metadata(self): + from litellm.proxy.common_request_processing import ( + _record_streaming_client_disconnect_if_needed, + ) + + mock_logging_obj = MagicMock() + mock_logging_obj.model_call_details = { + "litellm_params": {"metadata": None}, + "metadata": None, + } + mock_request = MagicMock(spec=Request) + mock_request.is_disconnected = AsyncMock(return_value=True) + request_data = { + "litellm_call_id": "test-call-id", + "litellm_logging_obj": mock_logging_obj, + "metadata": {}, + "litellm_params": {"metadata": {}}, + } + + recorded = await _record_streaming_client_disconnect_if_needed( + mock_request, request_data + ) + + assert recorded is True + assert request_data["metadata"]["client_disconnected"] is True + assert ( + mock_logging_obj.model_call_details["litellm_params"]["metadata"][ + "client_disconnected" + ] + is True + ) + assert ( + mock_logging_obj.model_call_details["metadata"]["client_disconnected"] + is True + ) + + @pytest.mark.asyncio + async def test_record_streaming_client_disconnect_handles_none_request_data_metadata(self): + from litellm.proxy.common_request_processing import ( + _record_streaming_client_disconnect_if_needed, + ) + + mock_request = MagicMock(spec=Request) + mock_request.is_disconnected = AsyncMock(return_value=True) + request_data = { + "litellm_call_id": "test-call-id", + "metadata": None, + "litellm_params": {"metadata": None}, + } + + recorded = await _record_streaming_client_disconnect_if_needed( + mock_request, request_data + ) + + assert recorded is True + assert request_data["metadata"]["client_disconnected"] is True + assert ( + request_data["litellm_params"]["metadata"]["client_disconnected"] is True + ) + + @pytest.mark.asyncio + async def test_apply_client_disconnect_metadata_none_returns_early(self): + from litellm.proxy.common_request_processing import ( + _apply_client_disconnect_metadata, + ) + + _apply_client_disconnect_metadata(None) + @pytest.mark.asyncio async def test_finalize_streaming_generator_cleanup_fires_deferred_logging( self, monkeypatch @@ -4090,7 +4159,7 @@ class TestResponseCostHeaderForTypedDictResponses: logging_obj._on_deferred_stream_complete = None return logging_obj - async def _drive_non_streaming(self, *, monkeypatch, response, logging_obj, route_type): + async def _drive_non_streaming(self, *, monkeypatch, response, logging_obj, route_type, return_result=False): import litellm.proxy.common_request_processing as crp from litellm.proxy._types import UserAPIKeyAuth as RealUserAPIKeyAuth @@ -4119,7 +4188,7 @@ class TestResponseCostHeaderForTypedDictResponses: "_has_post_call_guardrails", return_value=False, ): - await processing_obj.base_process_llm_request( + result = await processing_obj.base_process_llm_request( request=MagicMock(spec=Request, headers={}), fastapi_response=fastapi_response, user_api_key_dict=RealUserAPIKeyAuth(api_key="sk-test"), @@ -4131,6 +4200,8 @@ class TestResponseCostHeaderForTypedDictResponses: llm_router=None, skip_pre_call_logic=True, ) + if return_result: + return fastapi_response, result return fastapi_response @pytest.mark.asyncio @@ -4352,3 +4423,451 @@ class TestResponseCostHeaderForTypedDictResponses: assert "x-litellm-response-cost" not in fastapi_response.headers recompute.assert_not_called() + + @pytest.mark.asyncio + async def test_messages_typeddict_does_not_leak_hidden_params_into_response_body(self, monkeypatch): + """ + Router.set_response_headers now writes rate-limit headers onto dict-shaped + responses (e.g. Anthropic /v1/messages, whose AnthropicMessagesResponse is a + TypedDict) via response["_hidden_params"] = ... . Unlike a pydantic model's + private attribute, that key is indistinguishable from any other dict key and + would otherwise serialize verbatim into the client-facing JSON body, leaking + response_cost/model_id/api_base/fallback errors. base_process_llm_request + must strip it before returning the response to the endpoint layer. + """ + from litellm.types.utils import AnthropicMessagesResponse + + response = AnthropicMessagesResponse( + id="msg_1", + type="message", + role="assistant", + content=[{"type": "text", "text": "hi"}], + model="claude-haiku-4-5", + usage={"input_tokens": 10, "output_tokens": 5}, + ) + response["_hidden_params"] = { + "additional_headers": {"x-ratelimit-limit-input-tokens": "25"}, + "response_cost": 0.00123, + "model_id": "internal-deployment-id", + } + logging_obj = self._build_logging_obj( + model_call_details={"response_cost": 0.00123}, + response_cost_calculator=MagicMock(return_value=999.0), + ) + + fastapi_response, result = await self._drive_non_streaming( + monkeypatch=monkeypatch, + response=response, + logging_obj=logging_obj, + route_type="anthropic_messages", + return_result=True, + ) + + assert "_hidden_params" not in result + assert fastapi_response.headers["x-ratelimit-limit-input-tokens"] == "25" + assert fastapi_response.headers["x-litellm-response-cost"] == "0.00123" + + +class TestPreCallWithFallbacksOnLocalRateLimit: + + @pytest.mark.asyncio + async def test_fallback_triggered_on_local_rate_limit(self): + from litellm.proxy.common_utils.proxy_rate_limit_error import ProxyRateLimitError + from litellm.proxy.common_request_processing import ProxyBaseLLMRequestProcessing + + primary_model = "gpt-4" + fallback_model = "gpt-3.5-turbo" + + processor = ProxyBaseLLMRequestProcessing(data={"model": primary_model}) + + call_count = 0 + + async def mock_pre_call_logic(**kwargs): + nonlocal call_count + call_count += 1 + model_in_data = processor.data.get("model") + if model_in_data == primary_model: + raise ProxyRateLimitError( + detail="TPM limit exceeded for gpt-4", + headers={"retry-after": "30"}, + ) + logging_obj = MagicMock() + return processor.data, logging_obj + + mock_router = MagicMock() + mock_router.fallbacks = [{"gpt-4": ["gpt-3.5-turbo"]}] + + with patch.object( + processor, + "common_processing_pre_call_logic", + side_effect=mock_pre_call_logic, + ): + data, logging_obj = await processor._pre_call_with_fallbacks( + request=MagicMock(), + general_settings={}, + proxy_logging_obj=MagicMock(), + user_api_key_dict=MagicMock(router_settings=None), + version=None, + proxy_config=MagicMock(), + user_model=None, + user_temperature=None, + user_request_timeout=None, + user_max_tokens=None, + user_api_base=None, + model=primary_model, + route_type="acompletion", + llm_router=mock_router, + ) + + assert processor.data["model"] == fallback_model + assert call_count == 2 + + @pytest.mark.asyncio + async def test_raises_when_no_fallbacks_configured(self): + from litellm.proxy.common_utils.proxy_rate_limit_error import ProxyRateLimitError + from litellm.proxy.common_request_processing import ProxyBaseLLMRequestProcessing + + processor = ProxyBaseLLMRequestProcessing(data={"model": "gpt-4"}) + + async def mock_pre_call_logic(**kwargs): + raise ProxyRateLimitError( + detail="TPM limit exceeded", + headers={"retry-after": "30"}, + ) + + mock_router = MagicMock() + mock_router.fallbacks = None + + with patch.object( + processor, + "common_processing_pre_call_logic", + side_effect=mock_pre_call_logic, + ): + with pytest.raises(ProxyRateLimitError): + await processor._pre_call_with_fallbacks( + request=MagicMock(), + general_settings={}, + proxy_logging_obj=MagicMock(), + user_api_key_dict=MagicMock(router_settings=None), + version=None, + proxy_config=MagicMock(), + user_model=None, + user_temperature=None, + user_request_timeout=None, + user_max_tokens=None, + user_api_base=None, + model="gpt-4", + route_type="acompletion", + llm_router=mock_router, + ) + + @pytest.mark.asyncio + async def test_raises_when_all_fallbacks_also_rate_limited(self): + from litellm.proxy.common_utils.proxy_rate_limit_error import ProxyRateLimitError + from litellm.proxy.common_request_processing import ProxyBaseLLMRequestProcessing + + processor = ProxyBaseLLMRequestProcessing(data={"model": "gpt-4"}) + + async def mock_pre_call_logic(**kwargs): + raise ProxyRateLimitError( + detail=f"TPM limit exceeded for {processor.data.get('model')}", + headers={"retry-after": "30"}, + ) + + mock_router = MagicMock() + mock_router.fallbacks = [{"gpt-4": ["gpt-3.5-turbo", "claude-3-haiku"]}] + + with patch.object( + processor, + "common_processing_pre_call_logic", + side_effect=mock_pre_call_logic, + ): + with pytest.raises(ProxyRateLimitError, match="gpt-4"): + await processor._pre_call_with_fallbacks( + request=MagicMock(), + general_settings={}, + proxy_logging_obj=MagicMock(), + user_api_key_dict=MagicMock(router_settings=None), + version=None, + proxy_config=MagicMock(), + user_model=None, + user_temperature=None, + user_request_timeout=None, + user_max_tokens=None, + user_api_base=None, + model="gpt-4", + route_type="acompletion", + llm_router=mock_router, + ) + + assert processor.data["model"] == "gpt-4" + + @pytest.mark.asyncio + async def test_fallback_uses_key_level_router_settings(self): + from litellm.proxy.common_utils.proxy_rate_limit_error import ProxyRateLimitError + from litellm.proxy.common_request_processing import ProxyBaseLLMRequestProcessing + + processor = ProxyBaseLLMRequestProcessing(data={"model": "gpt-4"}) + + async def mock_pre_call_logic(**kwargs): + if processor.data.get("model") == "gpt-4": + raise ProxyRateLimitError( + detail="TPM limit exceeded", + headers={"retry-after": "30"}, + ) + return processor.data, MagicMock() + + mock_router = MagicMock() + mock_router.fallbacks = [{"gpt-4": ["gpt-3.5-turbo"]}] + + user_api_key_dict = MagicMock() + user_api_key_dict.router_settings = { + "fallbacks": [{"gpt-4": ["claude-3-haiku"]}] + } + + with patch.object( + processor, + "common_processing_pre_call_logic", + side_effect=mock_pre_call_logic, + ): + data, _ = await processor._pre_call_with_fallbacks( + request=MagicMock(), + general_settings={}, + proxy_logging_obj=MagicMock(), + user_api_key_dict=user_api_key_dict, + version=None, + proxy_config=MagicMock(), + user_model=None, + user_temperature=None, + user_request_timeout=None, + user_max_tokens=None, + user_api_base=None, + model="gpt-4", + route_type="acompletion", + llm_router=mock_router, + ) + + assert processor.data["model"] == "claude-3-haiku" + + @pytest.mark.asyncio + async def test_disable_fallbacks_flag_respected(self): + from litellm.proxy.common_utils.proxy_rate_limit_error import ProxyRateLimitError + from litellm.proxy.common_request_processing import ProxyBaseLLMRequestProcessing + + processor = ProxyBaseLLMRequestProcessing( + data={"model": "gpt-4", "disable_fallbacks": True} + ) + + async def mock_pre_call_logic(**kwargs): + raise ProxyRateLimitError( + detail="TPM limit exceeded", + headers={"retry-after": "30"}, + ) + + mock_router = MagicMock() + mock_router.fallbacks = [{"gpt-4": ["gpt-3.5-turbo"]}] + + with patch.object( + processor, + "common_processing_pre_call_logic", + side_effect=mock_pre_call_logic, + ): + with pytest.raises(ProxyRateLimitError): + await processor._pre_call_with_fallbacks( + request=MagicMock(), + general_settings={}, + proxy_logging_obj=MagicMock(), + user_api_key_dict=MagicMock(router_settings=None), + version=None, + proxy_config=MagicMock(), + user_model=None, + user_temperature=None, + user_request_timeout=None, + user_max_tokens=None, + user_api_base=None, + model="gpt-4", + route_type="acompletion", + llm_router=mock_router, + ) + + @pytest.mark.asyncio + async def test_model_restored_on_non_rate_limit_exception(self): + from litellm.proxy.common_utils.proxy_rate_limit_error import ProxyRateLimitError + from litellm.proxy.common_request_processing import ProxyBaseLLMRequestProcessing + + primary_model = "gpt-4" + + processor = ProxyBaseLLMRequestProcessing(data={"model": primary_model}) + + async def mock_pre_call_logic(**kwargs): + model_in_data = processor.data.get("model") + if model_in_data == primary_model: + raise ProxyRateLimitError( + detail="TPM limit exceeded for gpt-4", + headers={"retry-after": "30"}, + ) + raise ValueError("unexpected auth failure on fallback") + + mock_router = MagicMock() + mock_router.fallbacks = [{"gpt-4": ["gpt-3.5-turbo"]}] + + with patch.object( + processor, + "common_processing_pre_call_logic", + side_effect=mock_pre_call_logic, + ): + with pytest.raises(ValueError, match="unexpected auth failure"): + await processor._pre_call_with_fallbacks( + request=MagicMock(), + general_settings={}, + proxy_logging_obj=MagicMock(), + user_api_key_dict=MagicMock(router_settings=None), + version=None, + proxy_config=MagicMock(), + user_model=None, + user_temperature=None, + user_request_timeout=None, + user_max_tokens=None, + user_api_base=None, + model="gpt-4", + route_type="acompletion", + llm_router=mock_router, + ) + + assert processor.data["model"] == primary_model + + @pytest.mark.asyncio + async def test_real_parallel_request_limiter_model_tpm_limit_triggers_fallback(self): + """ + Customer-reported scenario from LIT-3890 / GH #8822. + + The prior tests in this class hand-build a ``ProxyRateLimitError``. The + customer's production setup is different: they set a *per-key per-model* + TPM cap on the key itself:: + + Model TPM Limits: {"gpt-4.1-20250414-test": 100} + + and configure a proxy-side fallback (gpt-4.1-...-test -> gpt-4.1-...). + When the per-model TPM cap trips, the real + ``parallel_request_limiter`` raises ``ProxyRateLimitError`` from inside + ``proxy_logging_obj.pre_call_hook`` — the seam ``_pre_call_with_fallbacks`` + wraps. This test drives that *real* limiter (not a mock error) end-to-end + to prove the customer's exact knob triggers the gateway fallback instead + of returning a 429 to the client. + """ + from litellm.caching.caching import DualCache + from litellm.proxy._types import UserAPIKeyAuth + from litellm.proxy.common_request_processing import ( + ProxyBaseLLMRequestProcessing, + ) + from litellm.proxy.common_utils.proxy_rate_limit_error import ( + ProxyRateLimitError, + ) + from litellm.proxy.hooks.parallel_request_limiter import ( + _PROXY_MaxParallelRequestsHandler, + ) + from litellm.proxy.utils import InternalUsageCache + + primary_model = "gpt-4" + fallback_model = "gpt-3.5-turbo" + + # Freeze the limiter's clock so the per-minute counter key is stable and + # the pre-seeded counter is guaranteed to be the one it reads. + class _FrozenClock(datetime.datetime): + @classmethod + def now(cls, tz=None): + return cls(2026, 1, 1, 12, 30, 0) + + precise_minute = "2026-01-01-12-30" + + # Real per-key per-model TPM limiter + a key carrying the customer's + # `model_tpm_limit` metadata (only the primary is capped). + limiter = _PROXY_MaxParallelRequestsHandler( + internal_usage_cache=InternalUsageCache(DualCache()) + ) + user_api_key_dict = UserAPIKeyAuth( + api_key="sk-lit3890", + metadata={"model_tpm_limit": {primary_model: 100}}, + ) + + # Pre-seed the primary's per-model token counter at the cap so the very + # next request trips it. The counter key uses the *hashed* api_key. + counter_key = ( + f"{user_api_key_dict.api_key}::{primary_model}" + f"::{precise_minute}::request_count" + ) + await limiter.internal_usage_cache.async_set_cache( + key=counter_key, + value={"current_requests": 0, "current_tpm": 100, "current_rpm": 0}, + litellm_parent_otel_span=None, + local_only=True, + ) + + processor = ProxyBaseLLMRequestProcessing(data={"model": primary_model}) + + # Stand in for common_processing_pre_call_logic's pre_call_hook step by + # invoking the real limiter for whatever model is currently selected. + limiter_calls = [] + + async def real_limiter_pre_call(**kwargs): + current_model = processor.data["model"] + limiter_calls.append(current_model) + await limiter.async_pre_call_hook( + user_api_key_dict=user_api_key_dict, + cache=DualCache(), + data={ + "model": current_model, + "messages": [{"role": "user", "content": "hi"}], + }, + call_type="acompletion", + ) + return processor.data, MagicMock() + + mock_router = MagicMock() + mock_router.fallbacks = [{primary_model: [fallback_model]}] + + with patch( + "litellm.proxy.hooks.parallel_request_limiter.datetime", _FrozenClock + ): + with patch.object( + processor, + "common_processing_pre_call_logic", + side_effect=real_limiter_pre_call, + ): + data, logging_obj = await processor._pre_call_with_fallbacks( + request=MagicMock(), + general_settings={}, + proxy_logging_obj=MagicMock(), + user_api_key_dict=user_api_key_dict, + version=None, + proxy_config=MagicMock(), + user_model=None, + user_temperature=None, + user_request_timeout=None, + user_max_tokens=None, + user_api_base=None, + model=primary_model, + route_type="acompletion", + llm_router=mock_router, + ) + + # The capped primary tripped the real limiter, and the fallback (which + # has no per-model cap) served the request — no 429 to the client. + assert processor.data["model"] == fallback_model + assert limiter_calls == [primary_model, fallback_model] + + # Sanity-check the premise: the limiter genuinely raises a + # ProxyRateLimitError for the capped primary under the frozen clock. + with patch( + "litellm.proxy.hooks.parallel_request_limiter.datetime", _FrozenClock + ): + with pytest.raises(ProxyRateLimitError): + await limiter.async_pre_call_hook( + user_api_key_dict=user_api_key_dict, + cache=DualCache(), + data={ + "model": primary_model, + "messages": [{"role": "user", "content": "hi"}], + }, + call_type="acompletion", + ) diff --git a/tests/test_litellm/proxy/test_component_allowlists.py b/tests/test_litellm/proxy/test_component_allowlists.py index 926ce3bee66..3dd8d8b28cd 100644 --- a/tests/test_litellm/proxy/test_component_allowlists.py +++ b/tests/test_litellm/proxy/test_component_allowlists.py @@ -11,10 +11,16 @@ clients hitting that path on the corresponding pod get a 404. This test guarantees that the union of the two trimmed route sets equals the full set of routes on the proxy app — i.e. no endpoint is dropped on the floor. -The test reproduces the same predicate that ``gateway/main.py`` and -``backend/main.py`` use, without importing them. The component modules wrap +The union-coverage test reproduces the same predicate that ``gateway/main.py`` +and ``backend/main.py`` use, without importing them. The component modules wrap the shared ``app.router.lifespan_context``; importing them in the test process -would chain wrappers and corrupt the snapshot. +would chain wrappers and corrupt the snapshot. The gateway Mount tests below +import the real ``gateway.main._is_gateway_route`` instead, undoing both of the +module's import-time side effects: the lifespan wrapper is restored right after +the import, and the DATABASE_* env vars are popped for its duration because +``gateway.main`` runs ``DatabaseURLSettings.from_env().apply_to_env()`` at +import (which raises on a non-postgres ``DATABASE_URL`` scheme and can mint an +RDS IAM token when ``IAM_TOKEN_DB_AUTH`` is set). """ import os @@ -36,6 +42,7 @@ for _key, _value in _THROWAWAY_ENV.items(): os.environ.setdefault(_key, _value) from fastapi.routing import Mount +from prometheus_client import make_asgi_app # gateway/ and backend/ live at the repo root, not inside litellm/. _REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..")) @@ -47,7 +54,11 @@ from backend.routes.allowlist import ( BACKEND_MOUNT_PATHS, BACKEND_PATH_PREFIXES, ) -from gateway.routes.allowlist import GATEWAY_EXACT_PATHS, GATEWAY_PATH_PREFIXES +from gateway.routes.allowlist import ( + GATEWAY_EXACT_PATHS, + GATEWAY_MOUNT_PATHS, + GATEWAY_PATH_PREFIXES, +) from litellm.proxy.proxy_server import app for _key, _previous in _PRE_EXISTING_ENV.items(): @@ -56,6 +67,24 @@ for _key, _previous in _PRE_EXISTING_ENV.items(): else: os.environ[_key] = _previous +_DB_ENV_KEYS = ( + "DATABASE_URL", + "DIRECT_URL", + "DATABASE_URL_READ_REPLICA", + "DATABASE_HOST", + "DATABASE_HOST_READ_REPLICA", + "DATABASE_PASSWORD", + "IAM_TOKEN_DB_AUTH", +) +_PRE_DB_ENV = {_key: os.environ.pop(_key, None) for _key in _DB_ENV_KEYS} +_PRE_COMPONENT_LIFESPAN = app.router.lifespan_context +from gateway.main import _is_gateway_route + +app.router.lifespan_context = _PRE_COMPONENT_LIFESPAN +for _key, _previous in _PRE_DB_ENV.items(): + if _previous is not None: + os.environ[_key] = _previous + def _component_paths(routes, exact_paths, path_prefixes) -> set[str]: """Reproduce ``gateway.main._is_gateway_route`` / ``backend.main._is_backend_route``.""" @@ -133,3 +162,61 @@ def test_backend_drops_non_allowlisted_mounts(): for mount_path in non_backend_mounts: assert mount_path not in BACKEND_MOUNT_PATHS, \ f"Mount {mount_path} should not be in BACKEND_MOUNT_PATHS" + + +def test_gateway_mount_paths_defined(): + """GATEWAY_MOUNT_PATHS constant must exist and expose /metrics.""" + assert isinstance(GATEWAY_MOUNT_PATHS, frozenset), \ + f"GATEWAY_MOUNT_PATHS must be a frozenset, got {type(GATEWAY_MOUNT_PATHS)}" + assert "/metrics" in GATEWAY_MOUNT_PATHS, \ + "/metrics Mount path must be in GATEWAY_MOUNT_PATHS" + + +def test_gateway_trim_keeps_metrics_mount(): + """The Prometheus /metrics Mount must survive the gateway route trim. + + Regression test for https://github.com/BerriAI/litellm/issues/30291: + ``_is_gateway_route`` used to reject every Mount before the allowlist + check, so the /metrics Mount registered by + ``PrometheusLogger._mount_metrics_endpoint()`` was dropped at startup and + the gateway returned 404 on /metrics. + """ + metrics_mount = Mount("/metrics", app=make_asgi_app()) + routes = [*app.router.routes, metrics_mount] + trimmed = [r for r in routes if _is_gateway_route(r)] + assert metrics_mount in trimmed, \ + "/metrics Mount must survive the gateway route trim" + + +def test_gateway_drops_ui_and_swagger_mounts(): + """UI static and swagger Mounts must still be trimmed from the gateway.""" + for path in ("/ui", "/_next", "/litellm-asset-prefix/_next", "/swagger"): + assert not _is_gateway_route(Mount(path, app=make_asgi_app())), \ + f"Mount {path} must not be served by the gateway" + + +def test_every_app_mount_is_assigned_to_a_component(): + """Every Mount on the proxy app must be consciously assigned to a component. + + A Mount must be kept by the gateway (GATEWAY_MOUNT_PATHS), kept by the + backend (BACKEND_MOUNT_PATHS), or be a static mount served by the + dedicated UI container. A Mount matching none of these is unreachable in + a componentized deployment, which is exactly how the /metrics Mount was + silently dropped. + """ + ui_served_prefixes = ("/ui", "/_next", "/litellm-asset-prefix") + mounts = [*app.router.routes, Mount("/metrics", app=make_asgi_app())] + unassigned = { + path + for r in mounts + if isinstance(r, Mount) + and (path := getattr(r, "path", None)) is not None + and path not in GATEWAY_MOUNT_PATHS + and path not in BACKEND_MOUNT_PATHS + and not path.startswith(ui_served_prefixes) + } + assert not unassigned, ( + f"{len(unassigned)} Mount(s) are not exposed on any component. " + f"Add them to GATEWAY_MOUNT_PATHS, BACKEND_MOUNT_PATHS, or serve them " + f"from the UI container:\n " + "\n ".join(sorted(unassigned)) + ) diff --git a/tests/test_litellm/proxy/test_proxy_server.py b/tests/test_litellm/proxy/test_proxy_server.py index 533c37e690e..d06a1c16ab9 100644 --- a/tests/test_litellm/proxy/test_proxy_server.py +++ b/tests/test_litellm/proxy/test_proxy_server.py @@ -2583,6 +2583,50 @@ async def test_load_config_max_budget_env_var_coerced_to_float(tmp_path, monkeyp litellm.max_budget = original_max_budget +@pytest.mark.asyncio +async def test_load_config_user_url_validation_handles_null_and_string_false(tmp_path, monkeypatch): + from litellm.proxy.proxy_server import ProxyConfig + + monkeypatch.setattr(litellm, "user_url_validation", True) + monkeypatch.setattr(litellm, "user_url_allowed_hosts", ["internal.example"]) + monkeypatch.setattr(litellm, "provider_url_destination_allowed_hosts", ["provider.example"]) + null_config_file = tmp_path / "null_config.yaml" + null_config_file.write_text( + yaml.dump( + { + "model_list": [], + "general_settings": { + "user_url_allowed_hosts": None, + "user_url_validation": None, + "provider_url_destination_allowed_hosts": None, + }, + } + ) + ) + + await ProxyConfig().load_config( + router=MagicMock(), config_file_path=str(null_config_file) + ) + assert litellm.user_url_validation is True + assert litellm.user_url_allowed_hosts is None + assert litellm.provider_url_destination_allowed_hosts is None + + false_config_file = tmp_path / "false_config.yaml" + false_config_file.write_text( + yaml.dump( + { + "model_list": [], + "general_settings": {"user_url_validation": "false"}, + } + ) + ) + + await ProxyConfig().load_config( + router=MagicMock(), config_file_path=str(false_config_file) + ) + assert litellm.user_url_validation is False + + @pytest.mark.asyncio async def test_load_environment_variables_direct_and_os_environ(): """ @@ -8600,6 +8644,149 @@ def test_get_config_list_includes_cancel_on_disconnect(monkeypatch): app.dependency_overrides.clear() +def test_get_config_list_includes_budget_exceeded_throttle_percentage(monkeypatch): + """The throttle fraction is a litellm_settings scalar surfaced on the General + Settings table as a Float field so it sits with the other global limits; it + must appear in /config/list reading its live litellm. value.""" + import types + from unittest.mock import AsyncMock, MagicMock + + from fastapi.testclient import TestClient + + import litellm.proxy.proxy_server as ps + from litellm.proxy._types import LitellmUserRoles, UserAPIKeyAuth + from litellm.proxy.proxy_server import app + + mock_prisma = MagicMock() + mock_config_table = MagicMock() + mock_config_table.find_first = AsyncMock(return_value=None) + mock_prisma.db = types.SimpleNamespace(litellm_config=mock_config_table) + monkeypatch.setattr(ps, "prisma_client", mock_prisma) + monkeypatch.setattr(litellm, "budget_exceeded_throttle_percentage", 0.15) + app.dependency_overrides[ps.user_api_key_auth] = lambda: UserAPIKeyAuth( + user_id="admin", user_role=LitellmUserRoles.PROXY_ADMIN + ) + try: + client = TestClient(app) + resp = client.get("/config/list", params={"config_type": "general_settings"}) + assert resp.status_code == 200, resp.text + fields = {item["field_name"]: item for item in resp.json()} + assert "budget_exceeded_throttle_percentage" in fields + assert fields["budget_exceeded_throttle_percentage"]["field_type"] == "Float" + assert fields["budget_exceeded_throttle_percentage"]["field_value"] == 0.15 + finally: + app.dependency_overrides.clear() + + +@pytest.mark.asyncio +async def test_update_config_field_throttle_persists_to_litellm_settings(monkeypatch): + """Editing the throttle Float row on the General Settings table routes to + litellm_settings (not general_settings): it sets litellm. live and + persists under litellm_settings so the runtime read is unchanged.""" + from unittest.mock import MagicMock + + import litellm.proxy.proxy_server as ps + from litellm.proxy._types import ( + ConfigFieldUpdate, + LitellmUserRoles, + UserAPIKeyAuth, + ) + from litellm.proxy.proxy_server import update_config_general_settings + + saved: dict = {} + + async def fake_get_config(): + return {"litellm_settings": {}} + + async def fake_save_config(new_config=None): + saved.update(new_config or {}) + + monkeypatch.setattr(ps.proxy_config, "get_config", fake_get_config) + monkeypatch.setattr(ps.proxy_config, "save_config", fake_save_config) + monkeypatch.setattr(ps, "prisma_client", MagicMock()) + monkeypatch.setattr(litellm, "store_audit_logs", False) + monkeypatch.setattr(litellm, "budget_exceeded_throttle_percentage", None) + + admin = UserAPIKeyAuth(api_key="k", user_id="a", user_role=LitellmUserRoles.PROXY_ADMIN) + await update_config_general_settings( + data=ConfigFieldUpdate( + field_name="budget_exceeded_throttle_percentage", + field_value=0.1, + config_type="general_settings", + ), + user_api_key_dict=admin, + ) + + assert litellm.budget_exceeded_throttle_percentage == 0.1 + assert saved["litellm_settings"]["budget_exceeded_throttle_percentage"] == 0.1 + + +@pytest.mark.parametrize("bad_value", [0, -0.1, 1.5, True]) +@pytest.mark.asyncio +async def test_update_config_field_throttle_rejects_invalid(monkeypatch, bad_value): + from unittest.mock import MagicMock + + from fastapi import HTTPException + + import litellm.proxy.proxy_server as ps + from litellm.proxy._types import ( + ConfigFieldUpdate, + LitellmUserRoles, + UserAPIKeyAuth, + ) + from litellm.proxy.proxy_server import update_config_general_settings + + async def fake_get_config(): + return {"litellm_settings": {}} + + monkeypatch.setattr(ps.proxy_config, "get_config", fake_get_config) + monkeypatch.setattr(ps, "prisma_client", MagicMock()) + monkeypatch.setattr(litellm, "budget_exceeded_throttle_percentage", None) + + admin = UserAPIKeyAuth(api_key="k", user_id="a", user_role=LitellmUserRoles.PROXY_ADMIN) + with pytest.raises(HTTPException) as exc: + await update_config_general_settings( + data=ConfigFieldUpdate( + field_name="budget_exceeded_throttle_percentage", + field_value=bad_value, + config_type="general_settings", + ), + user_api_key_dict=admin, + ) + assert exc.value.status_code == 400 + assert litellm.budget_exceeded_throttle_percentage is None + + +@pytest.mark.asyncio +async def test_update_config_field_throttle_rejected_for_non_admin(monkeypatch): + from unittest.mock import MagicMock + + from fastapi import HTTPException + + import litellm.proxy.proxy_server as ps + from litellm.proxy._types import ( + ConfigFieldUpdate, + LitellmUserRoles, + UserAPIKeyAuth, + ) + from litellm.proxy.proxy_server import update_config_general_settings + + monkeypatch.setattr(ps, "prisma_client", MagicMock()) + monkeypatch.setattr(litellm, "budget_exceeded_throttle_percentage", None) + + non_admin = UserAPIKeyAuth(api_key="k", user_id="u", user_role=LitellmUserRoles.INTERNAL_USER) + with pytest.raises(HTTPException): + await update_config_general_settings( + data=ConfigFieldUpdate( + field_name="budget_exceeded_throttle_percentage", + field_value=0.1, + config_type="general_settings", + ), + user_api_key_dict=non_admin, + ) + assert litellm.budget_exceeded_throttle_percentage is None + + def test_preserve_redacted_plugin_keys_keeps_stored_credential(): """A redacted or blank plugin_key on update must not overwrite the real key.""" from litellm.proxy.proxy_server import _preserve_redacted_plugin_keys @@ -8871,6 +9058,76 @@ async def test_update_config_general_settings_emits_audit_log(monkeypatch): assert before["some_api_key"] != "sk-stored-secret" +@pytest.mark.asyncio +async def test_update_config_general_settings_applies_ssrf_globals(monkeypatch): + import litellm.proxy.proxy_server as proxy_server_module + from litellm.proxy._types import ConfigFieldUpdate + from litellm.proxy.proxy_server import update_config_general_settings + + fake = _fake_prisma_with_config({}) + monkeypatch.setattr(proxy_server_module, "prisma_client", fake) + monkeypatch.setattr(litellm, "store_audit_logs", False) + monkeypatch.setattr(litellm, "user_url_validation", True) + monkeypatch.setattr(litellm, "user_url_allowed_hosts", []) + monkeypatch.setattr(litellm, "provider_url_destination_allowed_hosts", []) + + admin = UserAPIKeyAuth( + api_key="hashed-admin", + user_id="admin-1", + user_role=LitellmUserRoles.PROXY_ADMIN, + ) + await update_config_general_settings( + data=ConfigFieldUpdate( + field_name="user_url_validation", + field_value="false", + config_type="general_settings", + ), + user_api_key_dict=admin, + ) + await update_config_general_settings( + data=ConfigFieldUpdate( + field_name="user_url_allowed_hosts", + field_value=["internal.example"], + config_type="general_settings", + ), + user_api_key_dict=admin, + ) + await update_config_general_settings( + data=ConfigFieldUpdate( + field_name="provider_url_destination_allowed_hosts", + field_value=["provider.example"], + config_type="general_settings", + ), + user_api_key_dict=admin, + ) + await asyncio.sleep(0) + + assert litellm.user_url_validation is False + assert litellm.user_url_allowed_hosts == ["internal.example"] + assert litellm.provider_url_destination_allowed_hosts == ["provider.example"] + + await update_config_general_settings( + data=ConfigFieldUpdate( + field_name="user_url_allowed_hosts", + field_value=None, + config_type="general_settings", + ), + user_api_key_dict=admin, + ) + await update_config_general_settings( + data=ConfigFieldUpdate( + field_name="provider_url_destination_allowed_hosts", + field_value=None, + config_type="general_settings", + ), + user_api_key_dict=admin, + ) + await asyncio.sleep(0) + + assert litellm.user_url_allowed_hosts is None + assert litellm.provider_url_destination_allowed_hosts is None + + @pytest.mark.asyncio async def test_delete_config_general_settings_emits_deleted_audit_log(monkeypatch): import litellm.proxy.proxy_server as proxy_server_module diff --git a/tests/test_litellm/proxy/test_tools_allowlist_enforcement.py b/tests/test_litellm/proxy/test_tools_allowlist_enforcement.py index 8196cc97f50..31f5fbf606b 100644 --- a/tests/test_litellm/proxy/test_tools_allowlist_enforcement.py +++ b/tests/test_litellm/proxy/test_tools_allowlist_enforcement.py @@ -70,6 +70,22 @@ class TestExtractRequestToolNames: } assert extract_request_tool_names("/v1/responses", data) == ["dmcp"] + def test_openai_responses_custom_tools(self): + """Custom tools become callable function tools on the Chat Completions + bridge, so their names must be extracted for allowlist enforcement; + otherwise a restricted key could invoke a disallowed tool by declaring + it with type "custom" (VERIA finding on PR #32258).""" + data = { + "tools": [ + {"type": "custom", "name": "apply_patch", "description": "x"}, + {"type": "function", "name": "get_current_weather"}, + ] + } + assert extract_request_tool_names("/v1/responses", data) == [ + "apply_patch", + "get_current_weather", + ] + def test_anthropic_tools(self): data = {"tools": [{"name": "get_weather"}, {"name": "run_sql"}]} assert extract_request_tool_names("/v1/messages", data) == [ @@ -143,6 +159,20 @@ class TestCheckToolsAllowlist: assert exc_info.value.type == ProxyErrorTypes.tool_access_denied assert "get_weather" in str(exc_info.value.message) + @pytest.mark.asyncio + async def test_disallowed_custom_tool_raises_on_responses_route(self): + token = _token(metadata={"allowed_tools": ["other_tool"]}) + body = {"tools": [{"type": "custom", "name": "restricted_tool"}]} + with pytest.raises(ProxyException) as exc_info: + await check_tools_allowlist( + request_body=body, + valid_token=token, + team_object=None, + route="/v1/responses", + ) + assert exc_info.value.type == ProxyErrorTypes.tool_access_denied + assert "restricted_tool" in str(exc_info.value.message) + @pytest.mark.asyncio async def test_team_allowlist_used_when_key_empty(self): token = _token( diff --git a/tests/test_litellm/proxy/utils/prisma_and_spend/test_prisma_client_engine_watcher.py b/tests/test_litellm/proxy/utils/prisma_and_spend/test_prisma_client_engine_watcher.py index 2fedd6bb134..25c04caabba 100644 --- a/tests/test_litellm/proxy/utils/prisma_and_spend/test_prisma_client_engine_watcher.py +++ b/tests/test_litellm/proxy/utils/prisma_and_spend/test_prisma_client_engine_watcher.py @@ -42,6 +42,7 @@ def test_get_engine_pid_extracts_process_pid(prisma_client: PrismaClient) -> Non fake_engine.process = MagicMock() fake_engine.process.pid = 4242 prisma_client.db._original_prisma = MagicMock() + prisma_client.db._original_prisma.is_connected = MagicMock(return_value=True) prisma_client.db._original_prisma._engine = fake_engine actual = { "pid": prisma_client._get_engine_pid(), @@ -58,6 +59,15 @@ def test_get_engine_pid_returns_zero_when_engine_attr_missing( assert prisma_client._get_engine_pid() == 0 +def test_get_engine_pid_returns_zero_when_client_disconnected( + prisma_client: PrismaClient, disconnected_prisma +) -> None: + """The reconnect path calls this on an arbitrarily-broken client; it must + report "no engine" instead of re-raising ClientNotConnectedError.""" + prisma_client.db._original_prisma = disconnected_prisma + assert prisma_client._get_engine_pid() == 0 + + def test_is_engine_alive_true_when_pid_zero(prisma_client: PrismaClient) -> None: prisma_client._engine_pid = 0 pinned = { diff --git a/tests/test_litellm/responses/litellm_completion_transformation/test_litellm_completion_responses.py b/tests/test_litellm/responses/litellm_completion_transformation/test_litellm_completion_responses.py index 426c73645c1..9cf60ea5f91 100644 --- a/tests/test_litellm/responses/litellm_completion_transformation/test_litellm_completion_responses.py +++ b/tests/test_litellm/responses/litellm_completion_transformation/test_litellm_completion_responses.py @@ -1,6 +1,8 @@ import os import sys +import pytest + sys.path.insert( 0, os.path.abspath("../../..") ) # Adds the parent directory to the system path @@ -1066,7 +1068,13 @@ class TestToolTransformation: assert web_search_options is None def test_transform_computer_use_tools(self): - """Test that computer_use tools are passed through as-is""" + """Test that computer_use tools are dropped (no Chat Completions equivalent). + + This deliberately reverses the previous pass-through regression guard: + forwarding computer_use verbatim made Chat Completions providers reject + the whole request with "'function' is a required property", so the + bridge now drops such tools (with a warning log) instead. + """ computer_use_tool = { "type": "computer_use", "display_width_px": 1024, @@ -1083,11 +1091,129 @@ class TestToolTransformation: tools=tools ) + # Assert - computer_use has no Chat Completions equivalent, so it is dropped + assert len(result_tools) == 0 + assert web_search_options is None + + def test_transform_custom_tools_to_function_tools(self): + """Test that custom (freeform/grammar) tools are converted to function tools""" + custom_tool = { + "type": "custom", + "name": "apply_patch", + "description": "Apply a patch to files", + "format": { + "type": "grammar", + "syntax": "lark", + "definition": "start: begin_patch hunk+ end_patch", + }, + } + + tools = [custom_tool] + + # Execute + ( + result_tools, + web_search_options, + ) = LiteLLMCompletionResponsesConfig.transform_responses_api_tools_to_chat_completion_tools( + tools=tools + ) + + # Assert - custom tool is converted to a function tool + assert len(result_tools) == 1 + assert result_tools[0]["type"] == "function" + assert result_tools[0]["function"]["name"] == "apply_patch" + assert "content" in result_tools[0]["function"]["parameters"]["properties"] + assert result_tools[0]["function"]["parameters"]["required"] == ["content"] + assert "begin_patch" in result_tools[0]["function"]["description"] + assert web_search_options is None + + def test_transform_custom_tools_without_format(self): + """Test that custom tools without format info are still converted""" + custom_tool = { + "type": "custom", + "name": "exec", + "description": "Execute code", + } + + tools = [custom_tool] + + # Execute + ( + result_tools, + web_search_options, + ) = LiteLLMCompletionResponsesConfig.transform_responses_api_tools_to_chat_completion_tools( + tools=tools + ) + # Assert assert len(result_tools) == 1 - assert result_tools[0] == computer_use_tool - assert result_tools[0]["type"] == "computer_use" - assert web_search_options is None + assert result_tools[0]["type"] == "function" + assert result_tools[0]["function"]["name"] == "exec" + assert result_tools[0]["function"]["description"] == "Execute code" + + def test_transform_custom_tools_preserves_allowed_callers(self): + """allowed_callers on a custom tool gates direct model invocation in the + Anthropic adapter, so it must survive the custom->function conversion.""" + custom_tool = { + "type": "custom", + "name": "apply_patch", + "description": "Apply a patch to files", + "allowed_callers": ["exec"], + } + + tools = [custom_tool] + + # Execute + ( + result_tools, + _, + ) = LiteLLMCompletionResponsesConfig.transform_responses_api_tools_to_chat_completion_tools( + tools=tools + ) + + # Assert + assert len(result_tools) == 1 + assert result_tools[0]["type"] == "function" + assert result_tools[0]["allowed_callers"] == ["exec"] + + def test_transform_custom_tools_without_allowed_callers(self): + """A custom tool without allowed_callers must not synthesize the field.""" + custom_tool = { + "type": "custom", + "name": "apply_patch", + "description": "Apply a patch to files", + } + + tools = [custom_tool] + + # Execute + ( + result_tools, + _, + ) = LiteLLMCompletionResponsesConfig.transform_responses_api_tools_to_chat_completion_tools( + tools=tools + ) + + # Assert + assert len(result_tools) == 1 + assert "allowed_callers" not in result_tools[0] + + def test_transform_custom_tools_rejects_invalid_allowed_callers(self): + """Invalid allowed_callers must raise rather than silently dropping the + provider-side allowlist.""" + custom_tool = { + "type": "custom", + "name": "apply_patch", + "description": "Apply a patch to files", + "allowed_callers": "exec", + } + + tools = [custom_tool] + + with pytest.raises(ValueError, match="allowed_callers must be a list of strings"): + LiteLLMCompletionResponsesConfig.transform_responses_api_tools_to_chat_completion_tools( + tools=tools + ) def test_transform_web_search_tools_to_web_search_options(self): """Test that web_search tools are converted to web_search_options""" @@ -2185,6 +2311,155 @@ class TestStreamingIDConsistency: assert tool_calls is not None and len(tool_calls) == 1 +class TestCompletedResponseLatchedOnStreamEnd: + """Regression: LiteLLMCompletionStreamingIterator (the Chat Completions + bridge path) never set ``self.completed_response`` because it overrides + __anext__ and bypasses the base class's _process_chunk where that + attribute is normally latched. FallbackResponsesStreamWrapper reads + ``completed_response`` via getattr to record container ownership; when + it stays None the proxy logs a "Container ownership recording skipped" + warning and follow-up /v1/containers//files calls 403 for non-admin + keys. Codex CLI's apply_patch tool also surfaces as "aborted" because + the terminal response.completed event never propagates correctly.""" + + def _make_iterator_with_stop(self, model_response): + """Build a LiteLLMCompletionStreamingIterator whose underlying + CustomStreamWrapper raises StopAsyncIteration immediately (simulating + a stream that already delivered all content chunks).""" + from unittest.mock import Mock + + import litellm + from litellm.responses.litellm_completion_transformation.streaming_iterator import ( + LiteLLMCompletionStreamingIterator, + ) + + mock_wrapper = Mock(spec=litellm.CustomStreamWrapper) + mock_wrapper.logging_obj = Mock() + mock_wrapper.logging_obj._response_cost_calculator = Mock(return_value=0.0) + mock_wrapper.__aiter__ = Mock(return_value=mock_wrapper) + mock_wrapper.__anext__ = Mock(side_effect=StopAsyncIteration) + + iterator = LiteLLMCompletionStreamingIterator( + model="deepseek/deepseek-chat", + litellm_custom_stream_wrapper=mock_wrapper, + request_input="test", + responses_api_request={}, + ) + iterator.litellm_model_response = model_response + return iterator + + def test_completed_response_set_after_common_done_event_logic(self): + """common_done_event_logic builds a ResponseCompletedEvent and must + latch it onto self.completed_response so downstream wrappers can + read it. Before the fix the event was returned but + completed_response stayed None.""" + from litellm.types.utils import Choices, Message, ModelResponse + + complete_response = ModelResponse( + id="resp_test", + created=1234567890, + model="deepseek-chat", + object="chat.completion", + choices=[ + Choices( + finish_reason="stop", + index=0, + message=Message(content="hello", role="assistant"), + ) + ], + ) + iterator = self._make_iterator_with_stop(complete_response) + + import asyncio + + async def drain(): + results = [] + try: + async for chunk in iterator: + results.append(chunk) + except StopAsyncIteration: + pass + return results + + results = asyncio.run(drain()) + assert len(results) > 0 + assert iterator.completed_response is not None, ( + "LiteLLMCompletionStreamingIterator.completed_response is still None " + "after common_done_event_logic ran — downstream wrappers and the " + "proxy container-ownership hook will see no terminal event" + ) + assert iterator.completed_response.type == "response.completed" + + +class TestFallbackWrapperStopAsyncIterationFallback: + """Regression: FallbackResponsesStreamWrapper.__anext__ only sniffed + terminal events off forwarded chunks. When the inner generator raises + StopAsyncIteration without a sniffable chunk (the bridge path ends this + way), the wrapper re-raised without checking source_iterator for a + latched completed_response, leaving its own completed_response None.""" + + def test_falls_back_to_source_completed_response_on_stop(self): + """When the inner async generator raises StopAsyncIteration and the + wrapper never sniffed a terminal chunk, it must copy + source_iterator.completed_response so the proxy ownership hook + still sees the terminal event.""" + import asyncio + from types import SimpleNamespace + + from litellm.router import Router + + source = SimpleNamespace( + response=None, + model="deepseek/deepseek-chat", + logging_obj=None, + responses_api_provider_config=None, + start_time=None, + litellm_metadata=None, + custom_llm_provider="deepseek", + request_data={}, + call_type="aresponses", + _hidden_params={}, + completed_response=SimpleNamespace( + type="response.completed", + response=SimpleNamespace(id="resp_src", output=[], container=None), + ), + ) + + async def empty_gen(): + return + yield # pragma: no cover + + async def _drive(): + router = Router( + model_list=[ + { + "model_name": "deepseek/deepseek-chat", + "litellm_params": {"model": "deepseek/deepseek-chat", "api_key": "sk-test"}, + } + ] + ) + wrapper = await router._aresponses_streaming_iterator( + response=source, # type: ignore[arg-type] + initial_kwargs={}, + ) + wrapper._async_generator = empty_gen() + out = [] + try: + async for chunk in wrapper: + out.append(chunk) + except StopAsyncIteration: + pass + return wrapper, out + + wrapper, _ = asyncio.run(_drive()) + assert wrapper.completed_response is not None, ( + "FallbackResponsesStreamWrapper.completed_response is None after " + "StopAsyncIteration even though source_iterator had one — the " + "proxy ownership hook will log a spurious warning" + ) + assert wrapper.completed_response.type == "response.completed" + + class TestEnsureOutputItemContentPartAdded: """Test that _ensure_output_item_for_chunk emits content_part.added after output_item.added for message items.""" diff --git a/tests/test_litellm/responses/mcp/test_litellm_proxy_mcp_handler.py b/tests/test_litellm/responses/mcp/test_litellm_proxy_mcp_handler.py index 35bfa6ea9e4..6fdbb0741aa 100644 --- a/tests/test_litellm/responses/mcp/test_litellm_proxy_mcp_handler.py +++ b/tests/test_litellm/responses/mcp/test_litellm_proxy_mcp_handler.py @@ -1,4 +1,6 @@ +import subprocess import sys +import textwrap import types from unittest.mock import AsyncMock, MagicMock @@ -28,6 +30,7 @@ def _setup_mcp_call_environment(monkeypatch: pytest.MonkeyPatch) -> AsyncMock: call_tool=AsyncMock(return_value=_DummyMCPResult()), # Newer logging path calls this to enrich spend logs metadata _get_mcp_server_from_tool_name=MagicMock(return_value=None), + get_mcp_server_by_name=MagicMock(return_value=None), ) monkeypatch.setattr( "litellm.proxy._experimental.mcp_server.mcp_server_manager.global_mcp_server_manager", @@ -279,6 +282,87 @@ async def test_execute_tool_calls_keeps_tool_name_when_equal_to_server(monkeypat assert call_tool_mock.await_args.kwargs["name"] == tool_name +@pytest.mark.asyncio +async def test_execute_tool_calls_strips_prefix_when_alias_differs_from_server_name( + monkeypatch, +): + call_tool_mock = _setup_mcp_call_environment(monkeypatch) + fake_server = types.SimpleNamespace( + alias="my_deepwiki", + server_name="deepwiki_test", + server_id="test-server-id", + short_prefix=None, + mcp_info=None, + tool_name_to_display_name=None, + ) + from litellm.proxy._experimental.mcp_server import mcp_server_manager as _msm + + _msm.global_mcp_server_manager._get_mcp_server_from_tool_name = MagicMock( + return_value=fake_server + ) + + tool_name = "my_deepwiki-read_wiki_structure" + tool_calls = [ + { + "id": "call-4", + "function": {"name": tool_name, "arguments": "{}"}, + } + ] + + await LiteLLM_Proxy_MCP_Handler._execute_tool_calls( + tool_server_map={tool_name: "deepwiki_test"}, + tool_calls=tool_calls, + user_api_key_auth=None, + ) + + assert call_tool_mock.await_count == 1 + assert call_tool_mock.await_args is not None + assert call_tool_mock.await_args.kwargs["name"] == "read_wiki_structure" + + +@pytest.mark.asyncio +async def test_execute_tool_calls_reverse_maps_display_name(monkeypatch): + call_tool_mock = _setup_mcp_call_environment(monkeypatch) + colliding_server = types.SimpleNamespace( + alias=None, + server_name="other_mcp", + server_id="other-server-id", + short_prefix=None, + mcp_info=None, + tool_name_to_display_name={"search": "search_docs"}, + ) + fake_server = types.SimpleNamespace( + alias=None, + server_name="deepwiki_mcp", + server_id="test-server-id", + short_prefix=None, + mcp_info=None, + tool_name_to_display_name={"read_wiki_structure": "browse_repo_docs"}, + ) + from litellm.proxy._experimental.mcp_server import mcp_server_manager as _msm + + _msm.global_mcp_server_manager._get_mcp_server_from_tool_name = MagicMock(return_value=colliding_server) + _msm.global_mcp_server_manager.get_mcp_server_by_name = MagicMock(return_value=fake_server) + + tool_name = "browse_repo_docs" + tool_calls = [ + { + "id": "call-5", + "function": {"name": tool_name, "arguments": "{}"}, + } + ] + + await LiteLLM_Proxy_MCP_Handler._execute_tool_calls( + tool_server_map={tool_name: "deepwiki_mcp"}, + tool_calls=tool_calls, + user_api_key_auth=None, + ) + + assert call_tool_mock.await_count == 1 + assert call_tool_mock.await_args is not None + assert call_tool_mock.await_args.kwargs["name"] == "read_wiki_structure" + + @pytest.mark.asyncio async def test_execute_tool_calls_logs_failure_via_post_call_failure_hook(monkeypatch): """ @@ -475,3 +559,49 @@ async def test_execute_tool_calls_propagates_request_tags_to_function_setup(monk ) assert captured["metadata"]["tags"] == ["team-a", "prod"] + + +def test_completion_with_function_tools_works_without_fastapi_installed(): + script = textwrap.dedent( + """ + import sys + + class _FastapiBlocker: + def find_spec(self, fullname, path=None, target=None): + if fullname == "fastapi" or fullname.startswith("fastapi."): + raise ModuleNotFoundError("No module named 'fastapi'") + return None + + sys.meta_path.insert(0, _FastapiBlocker()) + + import litellm + + response = litellm.completion( + model="openai/gpt-5.5", + messages=[{"role": "user", "content": "What is the weather in SF?"}], + tools=[ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get the current weather for a location", + "parameters": { + "type": "object", + "properties": {"location": {"type": "string"}}, + "required": ["location"], + }, + }, + } + ], + mock_response="sunny", + ) + assert response.choices[0].message.content == "sunny" + """ + ) + result = subprocess.run( + [sys.executable, "-c", script], + capture_output=True, + text=True, + timeout=120, + ) + assert result.returncode == 0, result.stderr diff --git a/tests/test_litellm/responses/mcp/test_mcp_streaming_iterator.py b/tests/test_litellm/responses/mcp/test_mcp_streaming_iterator.py index ecf89ce18d4..cdace5f6327 100644 --- a/tests/test_litellm/responses/mcp/test_mcp_streaming_iterator.py +++ b/tests/test_litellm/responses/mcp/test_mcp_streaming_iterator.py @@ -66,6 +66,7 @@ def _mock_mcp_environment(monkeypatch: pytest.MonkeyPatch) -> AsyncMock: fake_manager = types.SimpleNamespace( call_tool=call_tool, _get_mcp_server_from_tool_name=MagicMock(return_value=None), + get_mcp_server_by_name=MagicMock(return_value=None), ) monkeypatch.setattr( "litellm.proxy._experimental.mcp_server.mcp_server_manager.global_mcp_server_manager", diff --git a/tests/test_litellm/responses/test_custom_tool_call.py b/tests/test_litellm/responses/test_custom_tool_call.py new file mode 100644 index 00000000000..c605ef24934 --- /dev/null +++ b/tests/test_litellm/responses/test_custom_tool_call.py @@ -0,0 +1,343 @@ +""" +Test custom_tool_call adaptation for apply_patch and other custom tools. + +This test verifies that when Codex sends custom tools (type="custom"), +LiteLLM bridge correctly: +1. Converts them to function tools for Chat Completions providers +2. Converts function_call responses back to custom_tool_call output items +3. Unwraps the JSON-wrapping arguments to extract the actual input content +""" + +import json +import pytest +from typing import Dict, Any, List + +from openai.types.responses import ResponseFunctionToolCall + +from litellm.responses.litellm_completion_transformation.transformation import ( + LiteLLMCompletionResponsesConfig, +) +from litellm.responses.litellm_completion_transformation.custom_tools import ( + extract_custom_tool_names, + is_custom_tool_call, + unwrap_custom_tool_arguments, + build_tool_call_item_kwargs, + convert_custom_tool_to_function_tool, + _MAX_ARGUMENTS_LEN, +) + +from litellm.types.responses.main import CustomToolCallOutputItem + + +class TestCustomToolUtilities: + """Test the custom_tools utility functions.""" + + def test_extract_custom_tool_names(self): + """Test extraction of custom tool names from tools list.""" + tools = [ + {"type": "function", "name": "regular_tool"}, + {"type": "custom", "name": "apply_patch"}, + {"type": "function", "name": "another_tool"}, + {"type": "custom", "name": "custom_format"}, + ] + + names = extract_custom_tool_names(tools) + assert names == {"apply_patch", "custom_format"} + + def test_extract_custom_tool_names_empty(self): + """Test extraction with no custom tools.""" + tools = [ + {"type": "function", "name": "tool1"}, + {"type": "function", "name": "tool2"}, + ] + + names = extract_custom_tool_names(tools) + assert names == set() + + def test_extract_custom_tool_names_none(self): + """Test extraction with None input.""" + names = extract_custom_tool_names(None) + assert names == set() + + def test_is_custom_tool_call_true(self): + """Test identification of custom tool call.""" + custom_names = {"apply_patch", "custom_format"} + assert is_custom_tool_call("apply_patch", custom_names) is True + assert is_custom_tool_call("custom_format", custom_names) is True + + def test_is_custom_tool_call_false(self): + """Test identification of non-custom tool call.""" + custom_names = {"apply_patch"} + assert is_custom_tool_call("regular_tool", custom_names) is False + assert is_custom_tool_call("unknown_tool", custom_names) is False + + def test_unwrap_custom_tool_arguments(self): + """Test unwrapping of JSON-wrapped arguments.""" + # Test with valid JSON + wrapped = json.dumps({"content": "*** Begin Patch\n*** Add File: test.py\n+hello\n*** End Patch"}) + unwrapped = unwrap_custom_tool_arguments(wrapped) + assert unwrapped == "*** Begin Patch\n*** Add File: test.py\n+hello\n*** End Patch" + + def test_unwrap_custom_tool_arguments_invalid_json(self): + """Test unwrapping with invalid JSON returns original.""" + raw = "*** Begin Patch\n*** Add File: test.py\n+hello\n*** End Patch" + unwrapped = unwrap_custom_tool_arguments(raw) + assert unwrapped == raw + + def test_unwrap_custom_tool_arguments_no_content_key(self): + """Test unwrapping with JSON but no content key.""" + wrapped = json.dumps({"other_key": "value"}) + unwrapped = unwrap_custom_tool_arguments(wrapped) + assert unwrapped == wrapped + + def test_build_tool_call_item_kwargs_custom_completed(self): + """A completed custom tool call unwraps the content into `input`.""" + wrapped = json.dumps({"content": "patch body"}) + kwargs = build_tool_call_item_kwargs( + call_id="c1", + name="apply_patch", + arguments_or_input=wrapped, + status="completed", + custom_tool_names={"apply_patch"}, + ) + assert kwargs["type"] == "custom_tool_call" + assert kwargs["input"] == "patch body" + assert "arguments" not in kwargs + + def test_build_tool_call_item_kwargs_custom_in_progress(self): + """An in-progress custom tool call seeds an empty input string.""" + kwargs = build_tool_call_item_kwargs( + call_id="c2", + name="apply_patch", + arguments_or_input="ignored-until-completed", + status="in_progress", + custom_tool_names={"apply_patch"}, + ) + assert kwargs["input"] == "" + + def test_build_tool_call_item_kwargs_regular_function(self): + """A regular function call keeps raw arguments and uses function_call type.""" + raw = json.dumps({"k": "v"}) + kwargs = build_tool_call_item_kwargs( + call_id="c3", + name="get_weather", + arguments_or_input=raw, + status="completed", + custom_tool_names=set(), + ) + assert kwargs["type"] == "function_call" + assert kwargs["arguments"] == raw + assert "input" not in kwargs + + def test_unwrap_custom_tool_arguments_oversized_returns_raw(self): + """Arguments larger than the safety cap are returned unchanged to avoid + OOM on JSON parsing a pathologically large string.""" + oversized = "x" * (_MAX_ARGUMENTS_LEN + 1) + assert unwrap_custom_tool_arguments(oversized) == oversized + + def test_unwrap_custom_tool_arguments_empty(self): + """Empty arguments unwrap to an empty string, not the raw input.""" + assert unwrap_custom_tool_arguments("") == "" + + def test_convert_custom_tool_to_function_tool_with_format(self): + """The grammar definition is embedded in the description so the model can + produce correctly-formatted output.""" + tool = { + "type": "custom", + "name": "apply_patch", + "description": "Apply a patch", + "format": { + "type": "grammar", + "syntax": "lark", + "definition": "start: begin_patch", + }, + } + result = convert_custom_tool_to_function_tool(tool) + assert result is not None + assert result["type"] == "function" + assert "begin_patch" in result["function"]["description"] + assert result["function"]["parameters"]["required"] == ["content"] + + def test_convert_custom_tool_to_function_tool_non_custom_returns_none(self): + """Non-custom tools are not convertible; the caller keeps them as-is.""" + assert convert_custom_tool_to_function_tool({"type": "function"}) is None + + +class TestTransformationCustomTools: + """Test custom tool handling in transformation logic.""" + + def test_transform_apply_patch_function_call_to_custom_tool_call(self): + """Test that apply_patch function_call is converted to custom_tool_call.""" + # Simulate a Chat Completion response with apply_patch function call + from litellm.types.utils import ModelResponse, Choices, Message, ChatCompletionMessageToolCall, Function + + tool_call = ChatCompletionMessageToolCall( + id="call_abc123", + type="function", + function=Function( + name="apply_patch", + arguments=json.dumps({"content": "*** Begin Patch\n*** Add File: test.py\n+hello\n*** End Patch"}), + ), + ) + + message = Message(role="assistant", content=None, tool_calls=[tool_call]) + + choices = [Choices(index=0, message=message, finish_reason="tool_calls")] + + response = ModelResponse( + id="test_response", choices=choices, created=1234567890, model="gpt-4", object="chat.completion" + ) + + # Transform with custom tool names + responses_api_request = { + "tools": [{"type": "custom", "name": "apply_patch"}, {"type": "function", "name": "regular_tool"}] + } + + result = LiteLLMCompletionResponsesConfig.transform_chat_completion_tools_to_responses_tools( + response, responses_api_request=responses_api_request + ) + + # Should return a CustomToolCallOutputItem object; ResponsesAPIResponse + # accepts it directly via its output item union. + assert len(result) == 1 + item = result[0] + assert isinstance(item, CustomToolCallOutputItem) + assert item.type == "custom_tool_call" + assert item.call_id == "call_abc123" + assert item.name == "apply_patch" + assert item.input == "*** Begin Patch\n*** Add File: test.py\n+hello\n*** End Patch" + assert item.status == "completed" + + def test_custom_tool_call_input_item_recovers_payload_from_input(self): + """A custom_tool_call input item stores its payload in `input`; the + assistant tool call must carry it as a JSON content envelope whether + `arguments` is missing or an empty string.""" + for arguments in (None, ""): + item = { + "type": "custom_tool_call", + "call_id": "call_1", + "name": "apply_patch", + "input": "*** Begin Patch\n+hello\n*** End Patch", + } + if arguments is not None: + item["arguments"] = arguments + messages = ( + LiteLLMCompletionResponsesConfig._transform_responses_api_function_call_to_chat_completion_message( + function_call=item + ) + ) + tool_call = messages[0]["tool_calls"][0] + assert tool_call["function"]["arguments"] == json.dumps( + {"content": "*** Begin Patch\n+hello\n*** End Patch"} + ) + + def test_function_call_input_item_with_empty_arguments_keeps_them_empty(self): + """A plain function_call input item with empty or missing `arguments` + must produce an empty arguments string, never a `{"content": ...}` + envelope (that recovery is reserved for custom_tool_call items) and + never the literal string "None".""" + for item in ( + { + "type": "function_call", + "call_id": "call_2", + "name": "get_weather", + "arguments": "", + "input": "stray value", + }, + { + "type": "function_call", + "call_id": "call_3", + "name": "get_weather", + }, + ): + messages = ( + LiteLLMCompletionResponsesConfig._transform_responses_api_function_call_to_chat_completion_message( + function_call=item + ) + ) + tool_call = messages[0]["tool_calls"][0] + assert tool_call["function"]["arguments"] == "" + + def test_transform_regular_function_call_unchanged(self): + """Test that regular function calls remain as ResponseFunctionToolCall.""" + from litellm.types.utils import ModelResponse, Choices, Message, ChatCompletionMessageToolCall, Function + + tool_call = ChatCompletionMessageToolCall( + id="call_xyz789", + type="function", + function=Function(name="regular_tool", arguments=json.dumps({"param": "value"})), + ) + + message = Message(role="assistant", content=None, tool_calls=[tool_call]) + + choices = [Choices(index=0, message=message, finish_reason="tool_calls")] + + response = ModelResponse( + id="test_response", choices=choices, created=1234567890, model="gpt-4", object="chat.completion" + ) + + # Transform with custom tool names (regular_tool is NOT custom) + responses_api_request = { + "tools": [{"type": "custom", "name": "apply_patch"}, {"type": "function", "name": "regular_tool"}] + } + + result = LiteLLMCompletionResponsesConfig.transform_chat_completion_tools_to_responses_tools( + response, responses_api_request=responses_api_request + ) + + # Should return ResponseFunctionToolCall + assert len(result) == 1 + item = result[0] + assert isinstance(item, ResponseFunctionToolCall) + assert item.type == "function_call" + assert item.name == "regular_tool" + assert item.arguments == json.dumps({"param": "value"}) + + def test_transform_mixed_tool_calls(self): + """Test transformation with both custom and regular tool calls.""" + from litellm.types.utils import ModelResponse, Choices, Message, ChatCompletionMessageToolCall, Function + + custom_call = ChatCompletionMessageToolCall( + id="call_001", + type="function", + function=Function(name="apply_patch", arguments=json.dumps({"content": "patch content"})), + ) + + regular_call = ChatCompletionMessageToolCall( + id="call_002", type="function", function=Function(name="get_weather", arguments=json.dumps({"city": "SF"})) + ) + + message = Message(role="assistant", content=None, tool_calls=[custom_call, regular_call]) + + choices = [Choices(index=0, message=message, finish_reason="tool_calls")] + + response = ModelResponse( + id="test_response", choices=choices, created=1234567890, model="gpt-4", object="chat.completion" + ) + + responses_api_request = { + "tools": [{"type": "custom", "name": "apply_patch"}, {"type": "function", "name": "get_weather"}] + } + + result = LiteLLMCompletionResponsesConfig.transform_chat_completion_tools_to_responses_tools( + response, responses_api_request=responses_api_request + ) + + assert len(result) == 2 + + # First should be custom_tool_call object + first = result[0] + assert isinstance(first, CustomToolCallOutputItem) + assert first.type == "custom_tool_call" + assert first.name == "apply_patch" + assert first.input == "patch content" + + # Second should be function_call + second = result[1] + assert isinstance(second, ResponseFunctionToolCall) + assert second.type == "function_call" + assert second.name == "get_weather" + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/test_litellm/responses/test_responses_streaming_iterator.py b/tests/test_litellm/responses/test_responses_streaming_iterator.py new file mode 100644 index 00000000000..9ba7dfcaa80 --- /dev/null +++ b/tests/test_litellm/responses/test_responses_streaming_iterator.py @@ -0,0 +1,158 @@ +""" +Regression tests for LIT-4210: the streaming iterators must never run the sync +success_handler on the thread-pool executor concurrently with +async_success_handler. Concurrent mutation of the shared response object / +model_call_details from two threads segfaults pydantic-core (customer pods +crashed with exit 139 whenever any CustomLogger was registered). +""" + +import asyncio +import time + +import httpx +import pytest + +import litellm +from litellm.integrations.custom_logger import CustomLogger +from litellm.litellm_core_utils import thread_pool_executor as thread_pool_executor_module +from litellm.responses import streaming_iterator as responses_streaming_iterator_module +from litellm.litellm_core_utils.litellm_logging import Logging as LitellmLogging +from litellm.responses.streaming_iterator import ResponsesAPIStreamingIterator +from litellm.types.llms.openai import ResponsesAPIResponse + + +class RecordingCustomLogger(CustomLogger): + def __init__(self): + super().__init__() + self.async_hook_started: float | None = None + self.async_hook_finished: float | None = None + + async def _record(self): + self.async_hook_started = time.monotonic() + await asyncio.sleep(0.2) + self.async_hook_finished = time.monotonic() + + async def async_log_success_event(self, kwargs, response_obj, start_time, end_time): + await self._record() + + async def async_log_stream_event(self, kwargs, response_obj, start_time, end_time): + await self._record() + + +class RecordingExecutor: + def __init__(self, inner): + self._inner = inner + self.submits: list = [] + + def submit(self, fn, *args, **kwargs): + self.submits.append((time.monotonic(), fn)) + return self._inner.submit(fn, *args, **kwargs) + + def submit_times_for(self, logging_obj) -> list: + return [t for t, fn in self.submits if getattr(fn, "__self__", None) is logging_obj] + + +@pytest.fixture(autouse=True) +def _isolate_callbacks(): + saved = ( + litellm.callbacks, + litellm.success_callback, + litellm._async_success_callback, + litellm.failure_callback, + litellm._async_failure_callback, + ) + yield + ( + litellm.callbacks, + litellm.success_callback, + litellm._async_success_callback, + litellm.failure_callback, + litellm._async_failure_callback, + ) = saved + + +@pytest.fixture +def recording_executor(monkeypatch): + recording = RecordingExecutor(thread_pool_executor_module.executor) + monkeypatch.setattr(thread_pool_executor_module, "executor", recording) + monkeypatch.setattr(responses_streaming_iterator_module, "executor", recording) + return recording + + +def _make_logging_obj() -> LitellmLogging: + logging_obj = LitellmLogging( + model="gpt-5.4-nano", + messages=[{"role": "user", "content": "hi"}], + stream=True, + call_type="aresponses", + start_time=time.time(), + litellm_call_id="lit-4210-test", + function_id="lit-4210-test", + ) + logging_obj.model_call_details["litellm_params"] = {"aresponses": True} + return logging_obj + + +def _make_iterator(logging_obj: LitellmLogging) -> ResponsesAPIStreamingIterator: + iterator = ResponsesAPIStreamingIterator( + response=httpx.Response(200), + model="gpt-5.4-nano", + responses_api_provider_config=None, + logging_obj=logging_obj, + ) + iterator.completed_response = ResponsesAPIResponse( + id="resp_lit4210", + created_at=1700000000.0, + model="gpt-5.4-nano", + object="response", + output=[], + parallel_tool_calls=True, + tool_choice="auto", + tools=[], + error=None, + incomplete_details=None, + instructions=None, + metadata={}, + temperature=1.0, + top_p=1.0, + ) + return iterator + + +@pytest.mark.asyncio +async def test_custom_logger_only_never_submits_sync_success_handler(recording_executor): + recorder = RecordingCustomLogger() + litellm.success_callback = [recorder] + litellm._async_success_callback = [recorder] + + logging_obj = _make_logging_obj() + iterator = _make_iterator(logging_obj) + + iterator._log_completed_response(is_async=True) + await asyncio.sleep(0.6) + + assert recorder.async_hook_started is not None + assert recording_executor.submit_times_for(logging_obj) == [] + + +@pytest.mark.asyncio +async def test_sync_callbacks_run_only_after_async_handler_completes(recording_executor): + recorder = RecordingCustomLogger() + sync_events: list = [] + + def sync_callback(kwargs, response_obj, start_time, end_time): + sync_events.append(time.monotonic()) + + litellm.success_callback = [recorder, sync_callback] + litellm._async_success_callback = [recorder] + + logging_obj = _make_logging_obj() + iterator = _make_iterator(logging_obj) + + iterator._log_completed_response(is_async=True) + await asyncio.sleep(0.8) + + assert recorder.async_hook_finished is not None + submit_times = recording_executor.submit_times_for(logging_obj) + assert len(submit_times) == 1 + assert submit_times[0] >= recorder.async_hook_finished diff --git a/tests/test_litellm/responses/test_responses_websocket_all_providers.py b/tests/test_litellm/responses/test_responses_websocket_all_providers.py index 0d13ff4fd05..4509abc7749 100644 --- a/tests/test_litellm/responses/test_responses_websocket_all_providers.py +++ b/tests/test_litellm/responses/test_responses_websocket_all_providers.py @@ -1122,7 +1122,7 @@ class TestNativeWebSocketGuardrails: client_ws = MagicMock() client_ws.send_text = AsyncMock() logging_obj = MagicMock() - logging_obj.async_success_handler = AsyncMock() + logging_obj.dispatch_success_handlers = AsyncMock() delta_event = json.dumps( {"type": "response.output_text.delta", "delta": "alice@example.com"} @@ -1196,7 +1196,7 @@ class TestNativeWebSocketGuardrails: client_ws = MagicMock() client_ws.send_text = AsyncMock() logging_obj = MagicMock() - logging_obj.async_success_handler = AsyncMock() + logging_obj.dispatch_success_handlers = AsyncMock() done_events = [ json.dumps( @@ -1895,7 +1895,7 @@ class TestNativeWebSocketGuardrailMasking: ] ) logging_obj = MagicMock() - logging_obj.async_success_handler = AsyncMock() + logging_obj.dispatch_success_handlers = AsyncMock() handler = _make_streaming( websocket=websocket, @@ -1951,7 +1951,7 @@ class TestNativeWebSocketGuardrailMasking: ] ) logging_obj = MagicMock() - logging_obj.async_success_handler = AsyncMock() + logging_obj.dispatch_success_handlers = AsyncMock() handler = _make_streaming( websocket=websocket, @@ -2014,7 +2014,7 @@ class TestNativeWebSocketGuardrailMasking: ] ) logging_obj = MagicMock() - logging_obj.async_success_handler = AsyncMock() + logging_obj.dispatch_success_handlers = AsyncMock() handler = _make_streaming( websocket=websocket, @@ -2077,7 +2077,7 @@ class TestNativeWebSocketGuardrailMasking: ] ) logging_obj = MagicMock() - logging_obj.async_success_handler = AsyncMock() + logging_obj.dispatch_success_handlers = AsyncMock() handler = _make_streaming( websocket=websocket, diff --git a/tests/test_litellm/responses/test_streaming_iterator.py b/tests/test_litellm/responses/test_streaming_iterator.py new file mode 100644 index 00000000000..3e4b07fce18 --- /dev/null +++ b/tests/test_litellm/responses/test_streaming_iterator.py @@ -0,0 +1,124 @@ +"""Regression tests for LIT-4185 — /v1/responses streaming must stamp +completion_start_time on the first chunk so downstream TTFT consumers +(Prometheus, OTEL, SpendLogs completionStartTime) do not fall back to +completion_start_time = end_time.""" + +import json +from datetime import datetime +from unittest.mock import Mock + +import pytest + +from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj +from litellm.llms.base_llm.responses.transformation import BaseResponsesAPIConfig +from litellm.responses.streaming_iterator import ResponsesAPIStreamingIterator +from litellm.types.llms.openai import ( + ResponseCompletedEvent, + ResponsesAPIResponse, + ResponsesAPIStreamEvents, +) + + +def _sse_event(payload: dict) -> bytes: + return f"data: {json.dumps(payload)}\n\n".encode("utf-8") + + +def _make_iterator( + *, + sse_events: list[bytes], + logging_obj: LiteLLMLoggingObj, +) -> ResponsesAPIStreamingIterator: + async def aiter_bytes(): + for evt in sse_events: + yield evt + + mock_response = Mock() + mock_response.headers = {} + mock_response.aiter_bytes = aiter_bytes + + mock_config = Mock(spec=BaseResponsesAPIConfig) + mock_responses_api_response = Mock(spec=ResponsesAPIResponse) + mock_responses_api_response.id = "resp_ttft" + + def _transform(model, parsed_chunk, logging_obj): + evt_type = parsed_chunk.get("type") + if evt_type == "response.completed": + completed = Mock(spec=ResponseCompletedEvent) + completed.type = ResponsesAPIStreamEvents.RESPONSE_COMPLETED + completed.response = mock_responses_api_response + return completed + stub = Mock() + stub.type = evt_type + return stub + + mock_config.transform_streaming_response.side_effect = _transform + + return ResponsesAPIStreamingIterator( + response=mock_response, + model="gpt-4o-mini", + responses_api_provider_config=mock_config, + logging_obj=logging_obj, + litellm_metadata={}, + custom_llm_provider="openai", + ) + + +@pytest.mark.asyncio +async def test_responses_streaming_stamps_completion_start_time_on_first_chunk(): + """Without the fix, `logging_obj.completion_start_time` stays None across the + entire stream and _success_handler_helper_fn falls back to end_time — collapsing + the reported TTFT to full generation time.""" + logging_obj = Mock(spec=LiteLLMLoggingObj) + logging_obj.completion_start_time = None + logging_obj.model_call_details = {"litellm_params": {}} + stamped: list[datetime] = [] + + def _update(*, completion_start_time): + stamped.append(completion_start_time) + logging_obj.completion_start_time = completion_start_time + logging_obj.model_call_details["completion_start_time"] = completion_start_time + + logging_obj._update_completion_start_time.side_effect = _update + + iterator = _make_iterator( + sse_events=[ + _sse_event({"type": "response.created"}), + _sse_event({"type": "response.output_text.delta", "delta": "hi"}), + _sse_event({"type": "response.completed"}), + ], + logging_obj=logging_obj, + ) + + async for _ in iterator: + pass + + assert len(stamped) == 1, ( + f"Expected exactly one first-chunk stamp; got {len(stamped)}. " + "Later chunks must not re-stamp completion_start_time." + ) + assert isinstance(stamped[0], datetime) + + +@pytest.mark.asyncio +async def test_responses_streaming_does_not_reset_prior_completion_start_time(): + """If `completion_start_time` is already set (e.g. by an outer wrapper), the + iterator must not overwrite it — otherwise TTFT would collapse to + time-to-last-chunk under contention.""" + prior = datetime(2020, 1, 1, 0, 0, 0) + logging_obj = Mock(spec=LiteLLMLoggingObj) + logging_obj.completion_start_time = prior + logging_obj.model_call_details = {"litellm_params": {}} + + iterator = _make_iterator( + sse_events=[ + _sse_event({"type": "response.created"}), + _sse_event({"type": "response.completed"}), + ], + logging_obj=logging_obj, + ) + + async for _ in iterator: + pass + + logging_obj._update_completion_start_time.assert_not_called() + assert logging_obj.completion_start_time == prior diff --git a/tests/test_litellm/router_strategy/test_complexity_router.py b/tests/test_litellm/router_strategy/test_complexity_router.py index e68ea863d82..d4a7da86734 100644 --- a/tests/test_litellm/router_strategy/test_complexity_router.py +++ b/tests/test_litellm/router_strategy/test_complexity_router.py @@ -22,6 +22,7 @@ from litellm.router_strategy.complexity_router.complexity_router import ( ) from litellm.router_strategy.complexity_router.config import ( DEFAULT_COMPLEXITY_CONFIG, + DEFAULT_TECHNICAL_KEYWORDS, ComplexityRouterConfig, ComplexityTier, ) @@ -468,6 +469,89 @@ class TestConfigOverrides: ), f"Expected 'long' signal, got {signals}" +class TestCustomTechnicalKeywords: + """Test the custom_technical_keywords config option.""" + + def test_custom_keywords_appended_to_defaults(self, mock_router_instance): + """Custom keywords should be appended to the default technical keywords.""" + router = ComplexityRouter( + model_name="test-router", + litellm_router_instance=mock_router_instance, + complexity_router_config={"custom_technical_keywords": ["udp", "kafka"]}, + ) + assert router.technical_keywords == DEFAULT_TECHNICAL_KEYWORDS + ["udp", "kafka"] + + def test_custom_keywords_appended_to_technical_keywords_override( + self, mock_router_instance + ): + """Custom keywords should be appended to a technical_keywords override.""" + router = ComplexityRouter( + model_name="test-router", + litellm_router_instance=mock_router_instance, + complexity_router_config={ + "technical_keywords": ["quantum", "photonics"], + "custom_technical_keywords": ["udp"], + }, + ) + assert router.technical_keywords == ["quantum", "photonics", "udp"] + + def test_custom_keywords_deduplicated_case_insensitively(self, mock_router_instance): + """Duplicates against the base list and within the custom list should be dropped.""" + router = ComplexityRouter( + model_name="test-router", + litellm_router_instance=mock_router_instance, + complexity_router_config={ + "custom_technical_keywords": ["TCP", "udp", "UDP", "kafka"] + }, + ) + lowered = [kw.lower() for kw in router.technical_keywords] + assert lowered == [kw.lower() for kw in DEFAULT_TECHNICAL_KEYWORDS] + [ + "udp", + "kafka", + ] + + def test_no_custom_keywords_leaves_defaults_unchanged(self, mock_router_instance): + """Absent or None custom_technical_keywords should leave the keyword list identical.""" + router_absent = ComplexityRouter( + model_name="test-router", + litellm_router_instance=mock_router_instance, + complexity_router_config={"tiers": {"MEDIUM": "gpt-4o"}}, + ) + router_none = ComplexityRouter( + model_name="test-router", + litellm_router_instance=mock_router_instance, + complexity_router_config={"custom_technical_keywords": None}, + ) + assert router_absent.technical_keywords == DEFAULT_TECHNICAL_KEYWORDS + assert router_none.technical_keywords == DEFAULT_TECHNICAL_KEYWORDS + + def test_prompt_with_only_custom_keywords_scores_technical( + self, mock_router_instance, basic_config + ): + """A prompt matching only custom keywords should score higher on technicalTerms.""" + prompt = "Configure udp multicast between kafka brokers" + baseline_router = ComplexityRouter( + model_name="test-router", + litellm_router_instance=mock_router_instance, + complexity_router_config=basic_config, + ) + custom_router = ComplexityRouter( + model_name="test-router", + litellm_router_instance=mock_router_instance, + complexity_router_config={ + **basic_config, + "custom_technical_keywords": ["UDP", "Kafka"], + }, + ) + _, baseline_score, baseline_signals = baseline_router.classify(prompt) + _, custom_score, custom_signals = custom_router.classify(prompt) + assert not any("technical" in s.lower() for s in baseline_signals) + assert any( + "technical" in s.lower() for s in custom_signals + ), f"Expected technical signal, got {custom_signals}" + assert custom_score > baseline_score + + class TestAsyncPreRoutingHookEdgeCases: """Test edge cases for async_pre_routing_hook method.""" diff --git a/tests/test_litellm/router_utils/test_add_retry_fallback_headers.py b/tests/test_litellm/router_utils/test_add_retry_fallback_headers.py index 2aee0f0a4ef..3a0deeb13d8 100644 --- a/tests/test_litellm/router_utils/test_add_retry_fallback_headers.py +++ b/tests/test_litellm/router_utils/test_add_retry_fallback_headers.py @@ -140,3 +140,25 @@ def test_get_fallback_errors_from_headers_invalid_json_returns_empty(): def test_get_fallback_errors_from_headers_missing_key_returns_empty(): result = get_fallback_errors_from_headers({}) assert result == [] + + +def test_get_hidden_params_dict_with_dict_response(): + response = {"id": "msg_1", "usage": {"input_tokens": 1, "output_tokens": 2}} + assert get_hidden_params_dict(response) == {} + + hidden_params = get_hidden_params_dict(response, create=True) + assert hidden_params == {} + assert response["_hidden_params"] == {} + + response["_hidden_params"] = {"additional_headers": {"x-test": "1"}} + assert get_hidden_params_dict(response) == { + "additional_headers": {"x-test": "1"}, + } + + +def test_add_fallback_headers_to_dict_response(): + response = {"id": "msg_1"} + result = add_fallback_headers_to_response(response=response, attempted_fallbacks=1) + + assert result is response + assert response["_hidden_params"]["additional_headers"]["x-litellm-attempted-fallbacks"] == 1 diff --git a/tests/test_litellm/test_check_type_discipline.py b/tests/test_litellm/test_check_type_discipline.py index 436904b017c..13edf6d1a95 100644 --- a/tests/test_litellm/test_check_type_discipline.py +++ b/tests/test_litellm/test_check_type_discipline.py @@ -62,12 +62,38 @@ def test_noqa_with_codes_and_reason_is_clean(tmp_path): assert "LIT003" not in _codes(tmp_path, "x = 1 # noqa: TID251 # legacy import, removed in #123\n") -def test_ignore_without_reason_is_flagged(tmp_path): - assert "LIT004" in _codes(tmp_path, "x = 1 # type: ignore[arg-type]\n") +def test_pyright_ignore_without_codes_is_flagged(tmp_path): + assert "LIT004" in _codes(tmp_path, "x = 1 # pyright: ignore\n") + + +def test_pyright_ignore_without_reason_is_flagged(tmp_path): + assert "LIT004" in _codes(tmp_path, "x = 1 # pyright: ignore[reportArgumentType]\n") def test_ignore_with_codes_and_reason_is_clean(tmp_path): - assert "LIT004" not in _codes(tmp_path, "x = 1 # pyright: ignore[reportArgumentType] # upstream stub is wrong\n") + codes = _codes(tmp_path, "x = 1 # pyright: ignore[reportArgumentType] # upstream stub is wrong\n") + assert "LIT004" not in codes + assert "LIT009" not in codes + + +def test_bare_type_ignore_is_flagged(tmp_path): + assert "LIT009" in _codes(tmp_path, "x = 1 # type: ignore\n") + + +def test_type_ignore_with_codes_and_reason_is_still_flagged(tmp_path): + codes = _codes(tmp_path, "x = 1 # type: ignore[arg-type] # inertness is the point\n") + assert "LIT009" in codes + assert "LIT004" not in codes + + +def test_prose_mentioning_type_ignored_is_not_flagged(tmp_path): + assert "LIT009" not in _codes(tmp_path, "x = 1 # type: ignored by the stub refresh, revisit\n") + + +def test_mypy_ignore_shape_is_lit004_not_lit009(tmp_path): + codes = _codes(tmp_path, "x = 1 # mypy: ignore[assignment]\n") + assert "LIT004" in codes + assert "LIT009" not in codes def test_ok_suppression_without_reason_is_flagged(tmp_path): @@ -196,4 +222,4 @@ def test_typed_args_is_clean_but_kwargs_ok_suppresses(tmp_path): def test_budget_covers_exactly_the_checker_rules(): budget = json.loads((_REPO_ROOT / "type-discipline-budget.json").read_text()) - assert set(budget) == {f"LIT00{n}" for n in range(1, 9)} + assert set(budget) == {f"LIT00{n}" for n in range(1, 10)} diff --git a/tests/test_litellm/test_circleci_path_filter.py b/tests/test_litellm/test_circleci_path_filter.py new file mode 100644 index 00000000000..b8e763b4979 --- /dev/null +++ b/tests/test_litellm/test_circleci_path_filter.py @@ -0,0 +1,167 @@ +"""Regression tests for CircleCI change-based job gating. + +`.circleci/scripts/classify_changes.sh` is the pure decision function behind +`path_filter.sh`: given the list of files a PR changed (on stdin) and a job +category, it prints `run` or `skip`. The gating contract we lock in here: + + * docs-only changes (``*.md``, ``*.mdx``, ``docs/``) run nothing + * client-only changes (``ui/``) run client jobs but skip backend jobs + * any backend change runs both client and backend jobs + +If this logic silently regresses, real test jobs get skipped, so these cases +are the guardrail against that. +""" + +from __future__ import annotations + +import os +import shutil +import subprocess +from pathlib import Path + +import pytest + +SCRIPTS_DIR = Path(__file__).resolve().parents[2] / ".circleci" / "scripts" +SCRIPT = SCRIPTS_DIR / "classify_changes.sh" +PATH_FILTER = SCRIPTS_DIR / "path_filter.sh" + + +def classify(category: str, changed: list[str]) -> str: + result = subprocess.run( + ["bash", str(SCRIPT), category], + input="\n".join(changed), + capture_output=True, + text=True, + check=True, + ) + return result.stdout.strip() + + +DOCS = ["README.md", "docs/my_website/index.mdx", "litellm/anywhere.md"] +CLIENT = ["ui/litellm-dashboard/src/App.tsx"] +BACKEND = ["litellm/main.py"] + + +@pytest.mark.parametrize( + "category,changed,expected", + [ + # docs-only: skip everything + ("backend", DOCS, "skip"), + ("client", DOCS, "skip"), + ("backend", [], "skip"), + ("client", [], "skip"), + # client-only: backend skips, client runs + ("backend", CLIENT, "skip"), + ("client", CLIENT, "run"), + ("backend", CLIENT + DOCS, "skip"), + ("client", CLIENT + DOCS, "run"), + # any backend change: both run ("backend runs both") + ("backend", BACKEND, "run"), + ("client", BACKEND, "run"), + ("backend", BACKEND + DOCS, "run"), + ("client", BACKEND + DOCS, "run"), + ("backend", BACKEND + CLIENT, "run"), + ("client", BACKEND + CLIENT, "run"), + ], +) +def test_classify_decisions(category: str, changed: list[str], expected: str) -> None: + assert classify(category, changed) == expected + + +def test_markdown_under_ui_counts_as_client_not_docs() -> None: + assert classify("client", ["ui/litellm-dashboard/README.md"]) == "run" + assert classify("backend", ["ui/litellm-dashboard/README.md"]) == "skip" + + +def test_non_docs_directory_with_docs_in_name_is_backend() -> None: + assert classify("backend", ["documentation_tests/foo.py"]) == "run" + + +def test_unknown_category_fails_open_to_run() -> None: + assert classify("mystery", DOCS) == "run" + + +def _git(cwd: Path, *args: str) -> None: + subprocess.run(["git", *args], cwd=cwd, check=True, capture_output=True, text=True) + + +def _pr_repo(tmp_path: Path, feature_files: dict[str, str]) -> Path: + """A repo whose HEAD is a feature branch off `main` with `feature_files` changed.""" + remote = tmp_path / "remote.git" + subprocess.run(["git", "init", "-q", "--bare", str(remote)], check=True) + work = tmp_path / "work" + work.mkdir() + _git(work, "init", "-q", "-b", "main") + _git(work, "config", "user.email", "t@t") + _git(work, "config", "user.name", "t") + _git(work, "remote", "add", "origin", str(remote)) + (work / "litellm_core.py").write_text("x\n") + _git(work, "add", ".") + _git(work, "commit", "-qm", "base") + _git(work, "push", "-q", "origin", "main") + _git(work, "checkout", "-q", "-b", "litellm_feature") + for rel, content in feature_files.items(): + target = work / rel + target.parent.mkdir(parents=True, exist_ok=True) + target.write_text(content) + _git(work, "add", "-A") + _git(work, "commit", "-qm", "feature") + return work + + +def _run_path_filter(work: Path, tmp_path: Path, category: str, scripts_dir: Path, is_pr: bool = True): + bin_dir = tmp_path / "bin" + bin_dir.mkdir(exist_ok=True) + stub = bin_dir / "circleci-agent" + stub.write_text("#!/usr/bin/env bash\necho \"[stub] circleci-agent $*\"\nexit 0\n") + stub.chmod(0o755) + env = dict(os.environ) + env["PATH"] = f"{bin_dir}{os.pathsep}{env['PATH']}" + env.pop("CIRCLE_PULL_REQUEST", None) + if is_pr: + env["CIRCLE_PULL_REQUEST"] = "https://github.com/x/y/pull/1" + return subprocess.run( + ["bash", str(scripts_dir / "path_filter.sh"), category], + cwd=work, + capture_output=True, + text=True, + env=env, + ) + + +def test_path_filter_halts_docs_only_pr(tmp_path: Path) -> None: + work = _pr_repo(tmp_path, {"README.md": "# docs\n"}) + result = _run_path_filter(work, tmp_path, "backend", SCRIPTS_DIR) + assert result.returncode == 0 + assert "circleci-agent step halt" in result.stdout + + +def test_path_filter_runs_backend_pr(tmp_path: Path) -> None: + work = _pr_repo(tmp_path, {"litellm/new.py": "y\n"}) + result = _run_path_filter(work, tmp_path, "backend", SCRIPTS_DIR) + assert result.returncode == 0 + assert "running job" in result.stdout + assert "halt" not in result.stdout + + +def test_path_filter_fails_open_when_not_a_pr(tmp_path: Path) -> None: + work = _pr_repo(tmp_path, {"README.md": "# docs\n"}) + result = _run_path_filter(work, tmp_path, "backend", SCRIPTS_DIR, is_pr=False) + assert result.returncode == 0 + assert "not a pull request" in result.stdout + assert "halt" not in result.stdout + + +def test_path_filter_fails_open_when_classifier_errors(tmp_path: Path) -> None: + """Regression: a broken classifier must run the job, never silently halt it.""" + broken_scripts = tmp_path / "broken_scripts" + broken_scripts.mkdir() + shutil.copy(PATH_FILTER, broken_scripts / "path_filter.sh") + (broken_scripts / "classify_changes.sh").write_text("#!/usr/bin/env bash\nexit 1\n") + (broken_scripts / "classify_changes.sh").chmod(0o755) + + work = _pr_repo(tmp_path, {"README.md": "# docs\n"}) + result = _run_path_filter(work, tmp_path, "backend", broken_scripts) + assert result.returncode == 0 + assert "classify_changes.sh failed" in result.stdout + assert "halt" not in result.stdout diff --git a/tests/test_litellm/test_cost_calculator.py b/tests/test_litellm/test_cost_calculator.py index 0198a396b9b..751177014ce 100644 --- a/tests/test_litellm/test_cost_calculator.py +++ b/tests/test_litellm/test_cost_calculator.py @@ -347,6 +347,30 @@ def test_transcription_cost_falls_back_to_duration(): assert pytest.approx(cost, rel=1e-6) == expected_cost +def test_vertex_chirp_3_transcription_cost_from_duration(): + """Regression: the chirp_3 cost map entry shipped with output_cost_per_second 0.0, + and cost_per_second prefers output_cost_per_second whenever it is not None, so + every transcription priced to $0.00 instead of using input_cost_per_second.""" + from litellm import completion_cost + + os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" + litellm.model_cost = litellm.get_model_cost_map(url="") + + response = TranscriptionResponse(text="demo text") + response.duration = 18.0 + + cost = completion_cost( + completion_response=response, + model="vertex_ai/chirp_3", + custom_llm_provider="vertex_ai", + call_type="atranscription", + ) + + expected_cost = 18.0 * 0.00026667 + assert cost > 0 + assert pytest.approx(cost, rel=1e-6) == expected_cost + + def test_handle_realtime_stream_cost_calculation(): from litellm.cost_calculator import RealtimeAPITokenUsageProcessor @@ -3262,3 +3286,92 @@ def test_completion_cost_logs_reasoning_and_cache_breakdown(): assert logging_obj.cost_breakdown is not None assert logging_obj.cost_breakdown["reasoning_cost"] == pytest.approx(3114 * 2.5e-06) assert logging_obj.cost_breakdown["cache_read_cost"] == pytest.approx(100 * 3e-08) + + +def test_cost_per_token_per_second_pricing(monkeypatch): + """ + Models priced by duration (input/output_cost_per_second) with no per-token rates + must be billed as cost_per_second * response_time_ms / 1000 in cost_per_token. + """ + monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True") + monkeypatch.setattr(litellm, "model_cost", litellm.get_model_cost_map(url="")) + + model = "test-per-second-pricing-model" + litellm.register_model( + model_cost={ + model: { + "input_cost_per_second": 0.02, + "output_cost_per_second": 0.04, + "litellm_provider": "together_ai", + "mode": "chat", + } + } + ) + + prompt_cost, completion_cost_value = cost_per_token( + model=model, + custom_llm_provider="together_ai", + prompt_tokens=10, + completion_tokens=20, + response_time_ms=1500.0, + ) + + assert prompt_cost == pytest.approx(0.02 * 1.5) + assert completion_cost_value == pytest.approx(0.04 * 1.5) + + +def _batch_cache_usage() -> Usage: + return Usage( + prompt_tokens=11000, + completion_tokens=200, + total_tokens=11200, + prompt_tokens_details=PromptTokensDetailsWrapper( + cached_tokens=8000, + cache_creation_tokens=2000, + text_tokens=1000, + ), + cache_creation_input_tokens=2000, + cache_read_input_tokens=8000, + ) + + +def test_batch_cost_calculator_prices_cache_creation_tokens_at_cache_write_rate(): + """ + LIT-4008 regression: anthropic batch usage is dominated by cache tokens. + Cache creation tokens must be priced at cache_creation_input_token_cost / 2, + not folded into the base input rate, and must not also be billed as base + input tokens. + """ + from litellm.cost_calculator import batch_cost_calculator + + prompt_cost, completion_cost_value = batch_cost_calculator( + usage=_batch_cache_usage(), + model="claude-sonnet-4-5-20250929", + custom_llm_provider="anthropic", + model_info={ # type: ignore[arg-type] + "input_cost_per_token": 3e-6, + "output_cost_per_token": 15e-6, + "cache_read_input_token_cost": 3e-7, + "cache_creation_input_token_cost": 3.75e-6, + }, + ) + + assert prompt_cost == pytest.approx((1000 * 3e-6 + 8000 * 3e-7 + 2000 * 3.75e-6) / 2) + assert completion_cost_value == pytest.approx(200 * 15e-6 / 2) + + +def test_batch_cost_calculator_cache_creation_falls_back_to_input_rate(): + from litellm.cost_calculator import batch_cost_calculator + + prompt_cost, _ = batch_cost_calculator( + usage=_batch_cache_usage(), + model="claude-sonnet-4-5-20250929", + custom_llm_provider="anthropic", + model_info={ # type: ignore[arg-type] + "input_cost_per_token": 3e-6, + "output_cost_per_token": 15e-6, + "cache_read_input_token_cost": 3e-7, + }, + ) + + assert prompt_cost == pytest.approx((1000 * 3e-6 + 8000 * 3e-7 + 2000 * 3e-6) / 2) diff --git a/tests/test_litellm/test_main.py b/tests/test_litellm/test_main.py index 113e1bc0df8..28cf4fa0744 100644 --- a/tests/test_litellm/test_main.py +++ b/tests/test_litellm/test_main.py @@ -638,6 +638,93 @@ def test_bedrock_llama(): ) +def _mocked_openai_chat_response(model: str) -> httpx.Response: + return httpx.Response( + status_code=200, + json={ + "id": "chatcmpl-123", + "object": "chat.completion", + "created": 1677652288, + "model": model, + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "Hello from mocked response!", + }, + "finish_reason": "stop", + } + ], + "usage": { + "prompt_tokens": 9, + "completion_tokens": 12, + "total_tokens": 21, + }, + }, + ) + + +def test_completion_forwards_verbosity_in_raw_request(respx_mock: respx.MockRouter): + """Regression test: completion() must forward the verbosity param to the provider request body.""" + from litellm.types.utils import CallTypes + from litellm.utils import return_raw_request + + model = "gpt-5.2" + messages = [{"role": "user", "content": "hi"}] + respx_mock.post("https://api.openai.com/v1/chat/completions").mock( + return_value=_mocked_openai_chat_response(model) + ) + + request = return_raw_request( + endpoint=CallTypes.completion, + kwargs={ + "model": model, + "messages": messages, + "verbosity": "high", + }, + ) + + assert request["raw_request_body"]["verbosity"] == "high" + assert request["raw_request_body"]["model"] == model + assert request["raw_request_body"]["messages"] == messages + + +@pytest.mark.asyncio +async def test_acompletion_forwards_verbosity_to_provider_request( + respx_mock: respx.MockRouter, monkeypatch +): + """Regression test: acompletion() must forward the verbosity param to the provider request body.""" + original_disable_aiohttp = litellm.disable_aiohttp_transport + try: + litellm.disable_aiohttp_transport = True + monkeypatch.setenv("DISABLE_AIOHTTP_TRANSPORT", "True") + litellm.in_memory_llm_clients_cache.flush_cache() + + model = "gpt-5.2" + messages = [{"role": "user", "content": "hi"}] + mock_route = respx_mock.post("https://api.openai.com/v1/chat/completions").mock( + return_value=_mocked_openai_chat_response(model) + ) + + response = await litellm.acompletion( + model=model, + messages=messages, + verbosity="low", + api_key="fake-openai-api-key", + ) + + assert response.choices[0].message.content == "Hello from mocked response!" + assert mock_route.called + request_body = json.loads(respx_mock.calls[0].request.read()) + assert request_body["verbosity"] == "low" + assert request_body["model"] == model + assert request_body["messages"] == messages + finally: + litellm.disable_aiohttp_transport = original_disable_aiohttp + litellm.in_memory_llm_clients_cache.flush_cache() + + def test_responses_api_bridge_check_strips_responses_prefix(): """Test that responses_api_bridge_check strips 'responses/' prefix and sets mode.""" from litellm.main import responses_api_bridge_check @@ -1962,3 +2049,35 @@ class TestCallTypesOCR: call_type = CallTypes("aocr") assert call_type == CallTypes.aocr + + +def test_stream_chunk_builder_text_completion_combines_text_and_usage(): + from litellm.main import stream_chunk_builder_text_completion + from litellm.types.utils import TextCompletionResponse + + chunks = [ + TextCompletionResponse( + id="cmpl-1", + object="text_completion", + created=1, + model="gpt-3.5-turbo-instruct", + choices=[{"text": "Hello", "index": 0, "logprobs": None, "finish_reason": None}], + ), + TextCompletionResponse( + id="cmpl-1", + object="text_completion", + created=1, + model="gpt-3.5-turbo-instruct", + choices=[{"text": " world", "index": 0, "logprobs": None, "finish_reason": "stop"}], + ), + ] + + response = stream_chunk_builder_text_completion( + chunks=chunks, messages=[{"role": "user", "content": "say hello"}] + ) + + assert response.choices[0].text == "Hello world" + assert response.choices[0].finish_reason == "stop" + assert response.usage.prompt_tokens > 0 + assert response.usage.completion_tokens > 0 + assert response.usage.total_tokens == response.usage.prompt_tokens + response.usage.completion_tokens diff --git a/tests/test_litellm/test_register_model_custom_pricing.py b/tests/test_litellm/test_register_model_custom_pricing.py index e384d3e1161..8c3f690982b 100644 --- a/tests/test_litellm/test_register_model_custom_pricing.py +++ b/tests/test_litellm/test_register_model_custom_pricing.py @@ -9,15 +9,32 @@ mode, and supports_prompt_caching were dropped, causing incorrect cost calculations for DB-sourced models with prompt caching pricing. """ +import copy import os import sys +import pytest + sys.path.insert( 0, os.path.abspath("../..") ) # Adds the parent directory to the system path import litellm from litellm.main import _build_custom_pricing_entry +from litellm.utils import _invalidate_model_cost_lowercase_map + + +def _snapshot_model_cost_entries(keys): + return {key: copy.deepcopy(litellm.model_cost.get(key)) for key in keys} + + +def _restore_model_cost_entries(original_entries): + for key, value in original_entries.items(): + if value is None: + litellm.model_cost.pop(key, None) + else: + litellm.model_cost[key] = value + _invalidate_model_cost_lowercase_map() def test_build_custom_pricing_entry_includes_all_kwargs_fields(): @@ -471,3 +488,166 @@ def test_register_model_router_add_deployment_custom_pricing_applies(): litellm.model_cost.pop(model_key, None) litellm.model_cost.pop(deployment_model, None) del router + + +def test_embedding_router_zero_pricing_does_not_clobber_builtin_pricing(): + """LIT-3991: a router-originated embedding request that carries explicit + zero custom pricing (e.g. resolved through an ``openai/*`` wildcard + deployment with ``input_cost_per_token: 0``) must not overwrite the shared + ``openai/text-embedding-3-small`` entry in ``litellm.model_cost``. Before + the fix, one call through the wildcard poisoned the shared key and every + sibling deployment relying on built-in pricing logged $0 until restart. + """ + shared_key = "openai/text-embedding-3-small" + deployment_id = "lit3991-wildcard-embed-zero" + snapshot = _snapshot_model_cost_entries( + [shared_key, "text-embedding-3-small", deployment_id] + ) + builtin_input_cost = litellm.get_model_info(model=shared_key)[ + "input_cost_per_token" + ] + assert builtin_input_cost > 0 + + try: + litellm.embedding( + model=shared_key, + input=["hello"], + api_key="fake-key", + input_cost_per_token=0.0, + output_cost_per_token=0.0, + model_info={"id": deployment_id}, + metadata={"model_info": {"id": deployment_id}}, + mock_response=[0.1, 0.2], + ) + + assert ( + litellm.get_model_info(model=shared_key)["input_cost_per_token"] + == builtin_input_cost + ), "wildcard deployment's zero pricing leaked into the shared model_cost key" + assert litellm.model_cost[deployment_id]["input_cost_per_token"] == 0.0 + assert litellm.model_cost[deployment_id]["output_cost_per_token"] == 0.0 + + sibling_response = litellm.embedding( + model=shared_key, + input=["hello"], + api_key="fake-key", + mock_response=[0.1, 0.2], + ) + sibling_cost = litellm.completion_cost( + completion_response=sibling_response, call_type="embedding" + ) + assert sibling_cost == pytest.approx(10 * builtin_input_cost) + finally: + _restore_model_cost_entries(snapshot) + + +def test_embedding_router_custom_pricing_costs_request_via_deployment_id(): + """The request that carries custom pricing must still be costed with that + pricing (via its deployment id entry), while the shared backend key keeps + the built-in rate for siblings. + """ + shared_key = "openai/text-embedding-3-small" + deployment_id = "lit3991-wildcard-embed-custom" + override_input_cost = 5e-05 + snapshot = _snapshot_model_cost_entries( + [shared_key, "text-embedding-3-small", deployment_id] + ) + builtin_input_cost = litellm.get_model_info(model=shared_key)[ + "input_cost_per_token" + ] + assert builtin_input_cost != override_input_cost + + try: + response = litellm.embedding( + model=shared_key, + input=["hello"], + api_key="fake-key", + input_cost_per_token=override_input_cost, + output_cost_per_token=override_input_cost * 2, + model_info={"id": deployment_id}, + metadata={"model_info": {"id": deployment_id}}, + mock_response=[0.1, 0.2], + ) + + request_cost = litellm.completion_cost( + completion_response=response, + model=shared_key, + custom_llm_provider="openai", + call_type="embedding", + custom_pricing=True, + router_model_id=deployment_id, + ) + assert request_cost == pytest.approx(10 * override_input_cost) + assert ( + litellm.get_model_info(model=shared_key)["input_cost_per_token"] + == builtin_input_cost + ) + finally: + _restore_model_cost_entries(snapshot) + + +def test_completion_router_zero_pricing_does_not_clobber_builtin_pricing(): + """Same isolation as the embedding path, exercised through completion().""" + shared_key = "openai/gpt-4o-mini" + deployment_id = "lit3991-wildcard-chat-zero" + snapshot = _snapshot_model_cost_entries( + [shared_key, "gpt-4o-mini", deployment_id] + ) + builtin_input_cost = litellm.get_model_info(model=shared_key)[ + "input_cost_per_token" + ] + assert builtin_input_cost > 0 + + try: + litellm.completion( + model=shared_key, + messages=[{"role": "user", "content": "hello"}], + api_key="fake-key", + input_cost_per_token=0.0, + output_cost_per_token=0.0, + model_info={"id": deployment_id}, + metadata={"model_info": {"id": deployment_id}}, + mock_response="hello back", + ) + + assert ( + litellm.get_model_info(model=shared_key)["input_cost_per_token"] + == builtin_input_cost + ), "wildcard deployment's zero pricing leaked into the shared model_cost key" + assert litellm.model_cost[deployment_id]["input_cost_per_token"] == 0.0 + finally: + _restore_model_cost_entries(snapshot) + + +def test_embedding_direct_sdk_custom_pricing_still_registers_shared_key(): + """Direct SDK calls (no router deployment id in metadata) keep the legacy + behavior: custom pricing is registered under ``{provider}/{model}`` and the + request is costed with it. + """ + model_key = "openai/lit3991-direct-sdk-embed-model" + override_input_cost = 3e-05 + try: + response = litellm.embedding( + model=model_key, + input=["hello"], + api_key="fake-key", + input_cost_per_token=override_input_cost, + output_cost_per_token=override_input_cost * 2, + mock_response=[0.1, 0.2], + ) + + assert ( + litellm.model_cost[model_key]["input_cost_per_token"] + == override_input_cost + ) + cost = litellm.completion_cost( + completion_response=response, + model=model_key, + custom_llm_provider="openai", + call_type="embedding", + custom_pricing=True, + ) + assert cost == pytest.approx(10 * override_input_cost) + finally: + litellm.model_cost.pop(model_key, None) + _invalidate_model_cost_lowercase_map() diff --git a/tests/test_litellm/test_responses_id_security.py b/tests/test_litellm/test_responses_id_security.py index 0f75e9cab3a..17487030cc1 100644 --- a/tests/test_litellm/test_responses_id_security.py +++ b/tests/test_litellm/test_responses_id_security.py @@ -519,6 +519,62 @@ class TestAsyncPreCallHook: assert "team" in exc_info.value.detail.lower() + @pytest.mark.asyncio + async def test_async_pre_call_hook_alist_input_items_decrypts_response_id( + self, responses_id_security, mock_user_api_key_dict, mock_cache + ): + data = {"response_id": "resp_encrypted_789"} + + with patch.object( + responses_id_security, "_is_encrypted_response_id", return_value=True + ): + with patch.object( + responses_id_security, + "_decrypt_response_id", + return_value=("resp_original_789", "test-user-123", "test-team-123"), + ): + result = await responses_id_security.async_pre_call_hook( + user_api_key_dict=mock_user_api_key_dict, + cache=mock_cache, + data=data, + call_type="alist_input_items", + ) + + assert result is not None + assert result["response_id"] == "resp_original_789" + + @pytest.mark.asyncio + async def test_async_pre_call_hook_alist_input_items_team_security( + self, responses_id_security, mock_cache + ): + mock_auth_team_a = MagicMock() + mock_auth_team_a.user_id = None + mock_auth_team_a.team_id = "team-a" + mock_auth_team_a.user_role = None + + data = {"response_id": "resp_encrypted_team_b"} + + with patch.object( + responses_id_security, "_is_encrypted_response_id", return_value=True + ): + with patch.object( + responses_id_security, + "_decrypt_response_id", + return_value=("resp_original_team_b", None, "team-b"), + ): + with patch("litellm.proxy.proxy_server.general_settings", {}): + with pytest.raises(HTTPException) as exc_info: + await responses_id_security.async_pre_call_hook( + user_api_key_dict=mock_auth_team_a, + cache=mock_cache, + data=data, + call_type="alist_input_items", + ) + + assert exc_info.value.status_code == 403 + assert "team" in exc_info.value.detail.lower() + + class TestAsyncPostCallSuccessHook: """Test async_post_call_success_hook function""" diff --git a/tests/test_litellm/test_router/test_io_token_rate_limits.py b/tests/test_litellm/test_router/test_io_token_rate_limits.py new file mode 100644 index 00000000000..939e3189596 --- /dev/null +++ b/tests/test_litellm/test_router/test_io_token_rate_limits.py @@ -0,0 +1,979 @@ +""" +Tests for separate ITPM/OTPM deployment rate limits (enforce_model_rate_limits). +""" + +import asyncio + +import pytest + +import litellm +from litellm import Router +from litellm.caching.dual_cache import DualCache +from litellm.router_utils.pre_call_checks.io_token_rate_limit_check import ( + ITPM_CACHE_KEY, + ITPM_RESERVED_KEY, + OTPM_CACHE_KEY, + OTPM_RESERVED_KEY, + _reservation_value, + _resolve_max_tokens, + async_io_token_pre_call_check, + async_io_token_reconcile_success, + build_io_token_rate_limit_headers, + deployment_has_io_token_limits, + get_io_token_rate_limit_request_kwargs, + io_token_reconcile_success, + io_token_refund_failure, + refund_stale_reservation_before_retry, + set_io_token_rate_limit_request_kwargs, +) +from litellm.router_utils.pre_call_checks.model_rate_limit_check import ( + ModelRateLimitingCheck, +) +from litellm.types.utils import ModelResponse, Usage + + +class TestIOTokenRateLimitHelpers: + def test_deployment_has_io_token_limits(self): + assert deployment_has_io_token_limits({"litellm_params": {"itpm": 100, "otpm": 50}}) + assert not deployment_has_io_token_limits({"litellm_params": {"model": "x"}}) + + def test_reservation_value_minimal_when_estimate_fails(self): + # A failed/empty estimate (0) must reserve a minimal slot, not the + # entire limit - otherwise one request whose estimate failed fills + # the whole bucket and blocks every concurrent request until it + # completes and reconciles. + assert _reservation_value(0, 100) == 1 + assert _reservation_value(0, 1) == 1 + # A real non-zero estimate is reserved as-is. + assert _reservation_value(42, 100) == 42 + + def test_resolve_max_tokens_respects_explicit_zero(self): + deployment = {"litellm_params": {"model": "openai/gpt-4o-mini"}} + # An explicit max_tokens=0 is honored, not replaced by the model default. + assert _resolve_max_tokens({"max_tokens": 0}, deployment) == 0 + # max_completion_tokens is the fallback only when max_tokens is absent. + assert _resolve_max_tokens({"max_completion_tokens": 12}, deployment) == 12 + assert _resolve_max_tokens({"max_output_tokens": 9}, deployment) == 9 + + def test_build_io_token_rate_limit_headers(self): + headers = build_io_token_rate_limit_headers( + itpm_limit=200, + otpm_limit=40, + current_itpm=15, + current_otpm=4, + ) + assert headers["x-ratelimit-limit-input-tokens"] == 200 + assert headers["x-ratelimit-remaining-input-tokens"] == 185 + assert headers["x-ratelimit-limit-output-tokens"] == 40 + assert headers["x-ratelimit-remaining-output-tokens"] == 36 + + +class TestModelRateLimitingCheckIOTokens: + @pytest.mark.asyncio + async def test_itpm_reservation_and_reconcile(self): + from litellm.utils import get_utc_datetime + + dual_cache = DualCache() + check = ModelRateLimitingCheck(dual_cache=dual_cache) + deployment = { + "litellm_params": { + "model": "bedrock_mantle/anthropic.claude-opus-4-7", + "itpm": 100, + "otpm": 50, + }, + "model_info": {"id": "io-test-id"}, + "model_name": "opus", + } + + request_kwargs = { + "messages": [{"role": "user", "content": "hello"}], + "max_tokens": 10, + "metadata": {}, + } + set_io_token_rate_limit_request_kwargs(request_kwargs) + await check.async_pre_call_check(deployment) + + minute = get_utc_datetime().strftime("%H-%M") + itpm_key = f"global_router:io-test-id:bedrock_mantle/anthropic.claude-opus-4-7:itpm:{minute}" + otpm_key = f"global_router:io-test-id:bedrock_mantle/anthropic.claude-opus-4-7:otpm:{minute}" + + kwargs = { + "standard_logging_object": { + "model_id": "io-test-id", + "hidden_params": {"litellm_model_name": "bedrock_mantle/anthropic.claude-opus-4-7"}, + "metadata": dict(request_kwargs["metadata"]), + }, + "metadata": request_kwargs["metadata"], + } + response = ModelResponse( + choices=[ + { + "message": {"role": "assistant", "content": "hi"}, + "index": 0, + "finish_reason": "stop", + } + ], + usage=Usage(prompt_tokens=5, completion_tokens=3, total_tokens=8), + ) + await check.async_log_success_event(kwargs, response, None, None) + + current_itpm = await dual_cache.async_get_cache(key=itpm_key) + current_otpm = await dual_cache.async_get_cache(key=otpm_key) + # ITPM tracks input tokens only (billable prompt tokens), not output. + assert current_itpm == 5 + assert current_otpm == 3 + + @pytest.mark.asyncio + async def test_itpm_limit_raises_429(self): + dual_cache = DualCache() + check = ModelRateLimitingCheck(dual_cache=dual_cache) + deployment = { + "litellm_params": { + "model": "bedrock_mantle/anthropic.claude-opus-4-7", + "itpm": 5, + }, + "model_info": {"id": "io-limit-id"}, + "model_name": "opus", + } + + # ITPM enforces input tokens only; the prompt alone must exceed the limit, + # a large max_tokens must not contribute to the ITPM reservation. + set_io_token_rate_limit_request_kwargs( + { + "messages": [ + { + "role": "user", + "content": "hello world this is a longer prompt that exceeds the tiny itpm limit", + } + ], + "max_tokens": 10, + "metadata": {}, + } + ) + + with pytest.raises(litellm.RateLimitError) as exc_info: + await check.async_pre_call_check(deployment) + + assert "ITPM limit=5" in str(exc_info.value) + + @pytest.mark.asyncio + async def test_otpm_atomic_reservation_no_overshoot_under_concurrency(self): + from litellm.utils import get_utc_datetime + + dual_cache = DualCache() + check = ModelRateLimitingCheck(dual_cache=dual_cache) + otpm_limit = 10 + max_tokens = 4 + deployment = { + "litellm_params": { + "model": "bedrock_mantle/anthropic.claude-opus-4-7", + "otpm": otpm_limit, + }, + "model_info": {"id": "io-otpm-race-id"}, + "model_name": "opus", + } + + set_io_token_rate_limit_request_kwargs( + { + "messages": [{"role": "user", "content": "hi"}], + "max_tokens": max_tokens, + "metadata": {}, + } + ) + + async def _attempt(): + try: + await check.async_pre_call_check(deployment) + return True + except litellm.RateLimitError: + return False + + results = await asyncio.gather(*[_attempt() for _ in range(8)]) + successes = sum(1 for r in results if r) + + minute = get_utc_datetime().strftime("%H-%M") + otpm_key = f"global_router:io-otpm-race-id:bedrock_mantle/anthropic.claude-opus-4-7:otpm:{minute}" + current_otpm = await dual_cache.async_get_cache(key=otpm_key) + + # Atomic reservation must never let concurrent requests overshoot the limit. + assert current_otpm is not None + assert current_otpm <= otpm_limit + assert successes == otpm_limit // max_tokens + assert current_otpm == successes * max_tokens + + @pytest.mark.asyncio + async def test_itpm_estimate_failure_reserves_minimal_not_full_limit(self): + """ + When input-token estimation yields 0 (no messages/prompt/input field, + unsupported model, tokenizer error), the reservation must be a + minimal 1 token, not the entire itpm limit. Otherwise the first + request whose estimate fails fills the whole bucket and every + concurrent request is rejected until it completes - effectively + serializing traffic to the deployment. + """ + from litellm.utils import get_utc_datetime + + dual_cache = DualCache() + check = ModelRateLimitingCheck(dual_cache=dual_cache) + itpm_limit = 5 + deployment = { + "litellm_params": { + "model": "bedrock_mantle/anthropic.claude-opus-4-7", + "itpm": itpm_limit, + }, + "model_info": {"id": "io-itpm-estimate-fail-id"}, + "model_name": "opus", + } + + # No messages/prompt/input field -> _estimate_input_tokens returns 0. + set_io_token_rate_limit_request_kwargs( + { + "max_tokens": 5, + "metadata": {}, + } + ) + + async def _attempt(): + try: + await check.async_pre_call_check(deployment) + return True + except litellm.RateLimitError: + return False + + results = await asyncio.gather(*[_attempt() for _ in range(8)]) + successes = sum(1 for r in results if r) + + minute = get_utc_datetime().strftime("%H-%M") + itpm_key = f"global_router:io-itpm-estimate-fail-id:bedrock_mantle/anthropic.claude-opus-4-7:itpm:{minute}" + current_itpm = await dual_cache.async_get_cache(key=itpm_key) + + # A minimal 1-token reservation per request lets itpm_limit concurrent + # requests through, instead of a single request starving the rest. + assert current_itpm is not None + assert current_itpm <= itpm_limit + assert successes == itpm_limit + + @pytest.mark.asyncio + async def test_reservation_read_prefers_top_level_metadata_over_litellm_params(self): + from litellm.utils import get_utc_datetime + + dual_cache = DualCache() + check = ModelRateLimitingCheck(dual_cache=dual_cache) + minute = get_utc_datetime().strftime("%H-%M") + itpm_key = f"global_router:io-lp-id:bedrock_mantle/test:itpm:{minute}" + await dual_cache.async_increment_cache(key=itpm_key, value=20, ttl=60) + + # Production kwargs commonly carry litellm_params.metadata; the stashed + # reservation lives in the top-level metadata and must still be found. + kwargs = { + "standard_logging_object": { + "model_id": "io-lp-id", + "hidden_params": {"litellm_model_name": "bedrock_mantle/test"}, + "metadata": {}, + }, + "metadata": {ITPM_RESERVED_KEY: 20, ITPM_CACHE_KEY: itpm_key}, + "litellm_params": {"metadata": {"user_api_key_hash": "abc123"}}, + } + await check.async_log_failure_event(kwargs, None, None, None) + + current = await dual_cache.async_get_cache(key=itpm_key) + assert current == 0 + + @pytest.mark.asyncio + async def test_reconcile_tracks_actual_usage_when_estimate_zero(self): + from litellm.utils import get_utc_datetime + + dual_cache = DualCache() + check = ModelRateLimitingCheck(dual_cache=dual_cache) + deployment = { + "litellm_params": { + "model": "bedrock_mantle/anthropic.claude-opus-4-7", + "itpm": 100, + }, + "model_info": {"id": "io-zero-est-id"}, + "model_name": "opus", + } + + request_kwargs = {"max_tokens": 5, "metadata": {}} + set_io_token_rate_limit_request_kwargs(request_kwargs) + await check.async_pre_call_check(deployment) + + minute = get_utc_datetime().strftime("%H-%M") + itpm_key = f"global_router:io-zero-est-id:bedrock_mantle/anthropic.claude-opus-4-7:itpm:{minute}" + # A failed/zero estimate reserves a minimal 1 token, not the full + # itpm limit, so it doesn't starve concurrent requests. + assert await dual_cache.async_get_cache(key=itpm_key) == 1 + + kwargs = { + "standard_logging_object": { + "model_id": "io-zero-est-id", + "hidden_params": {"litellm_model_name": "bedrock_mantle/anthropic.claude-opus-4-7"}, + "metadata": dict(request_kwargs["metadata"]), + }, + "metadata": request_kwargs["metadata"], + } + response = ModelResponse( + choices=[{"message": {"role": "assistant", "content": "hi"}, "index": 0, "finish_reason": "stop"}], + usage=Usage(prompt_tokens=7, completion_tokens=0, total_tokens=7), + ) + await check.async_log_success_event(kwargs, response, None, None) + + assert await dual_cache.async_get_cache(key=itpm_key) == 7 + + @pytest.mark.asyncio + async def test_zero_estimate_reserves_minimal_capacity_before_reconcile(self): + """ + A zero/failed estimate reserves a minimal 1 token rather than the + full itpm limit, so up to itpm_limit such calls are allowed + concurrently instead of the first one claiming the entire bucket. + """ + dual_cache = DualCache() + check = ModelRateLimitingCheck(dual_cache=dual_cache) + deployment = { + "litellm_params": { + "model": "bedrock_mantle/anthropic.claude-opus-4-7", + "itpm": 2, + }, + "model_info": {"id": "io-zero-cap-id"}, + "model_name": "opus", + } + request_kwargs = {"max_tokens": 5, "metadata": {}} + set_io_token_rate_limit_request_kwargs(request_kwargs) + await check.async_pre_call_check(deployment) + + # Second zero-estimate call still fits within the itpm=2 limit. + set_io_token_rate_limit_request_kwargs({"max_tokens": 5, "metadata": {}}) + await check.async_pre_call_check(deployment) + + # A third exceeds the limit and is rejected. + set_io_token_rate_limit_request_kwargs({"max_tokens": 5, "metadata": {}}) + with pytest.raises(litellm.RateLimitError): + await check.async_pre_call_check(deployment) + + @pytest.mark.asyncio + async def test_explicit_zero_max_tokens_does_not_reserve_otpm(self): + dual_cache = DualCache() + check = ModelRateLimitingCheck(dual_cache=dual_cache) + deployment = { + "litellm_params": { + "model": "bedrock_mantle/anthropic.claude-opus-4-7", + "otpm": 5, + }, + "model_info": {"id": "io-zero-output-id"}, + "model_name": "opus", + } + zero_output_kwargs = { + "messages": [{"role": "user", "content": "hello"}], + "max_tokens": 0, + "metadata": {}, + } + set_io_token_rate_limit_request_kwargs(zero_output_kwargs) + await check.async_pre_call_check(deployment) + + zero_output_otpm_key = zero_output_kwargs["metadata"][OTPM_CACHE_KEY] + assert zero_output_kwargs["metadata"][OTPM_RESERVED_KEY] == 0 + assert (await dual_cache.async_get_cache(key=zero_output_otpm_key) or 0) == 0 + + normal_output_kwargs = { + "messages": [{"role": "user", "content": "hello"}], + "max_tokens": 5, + "metadata": {}, + } + set_io_token_rate_limit_request_kwargs(normal_output_kwargs) + await check.async_pre_call_check(deployment) + + normal_output_otpm_key = normal_output_kwargs["metadata"][OTPM_CACHE_KEY] + assert await dual_cache.async_get_cache(key=normal_output_otpm_key) == 5 + + def test_sync_io_pre_call_reserves_and_reconciles(self): + from litellm.utils import get_utc_datetime + + dual_cache = DualCache() + check = ModelRateLimitingCheck(dual_cache=dual_cache) + deployment = { + "litellm_params": { + "model": "bedrock_mantle/anthropic.claude-opus-4-7", + "itpm": 100, + "otpm": 50, + }, + "model_info": {"id": "io-sync-id"}, + "model_name": "opus", + } + request_kwargs = { + "messages": [{"role": "user", "content": "hello"}], + "max_tokens": 10, + "metadata": {}, + } + set_io_token_rate_limit_request_kwargs(request_kwargs) + check.pre_call_check(deployment) + + minute = get_utc_datetime().strftime("%H-%M") + itpm_key = f"global_router:io-sync-id:bedrock_mantle/anthropic.claude-opus-4-7:itpm:{minute}" + otpm_key = f"global_router:io-sync-id:bedrock_mantle/anthropic.claude-opus-4-7:otpm:{minute}" + kwargs = { + "standard_logging_object": { + "model_id": "io-sync-id", + "hidden_params": {"litellm_model_name": "bedrock_mantle/anthropic.claude-opus-4-7"}, + "metadata": dict(request_kwargs["metadata"]), + }, + "metadata": request_kwargs["metadata"], + } + response = ModelResponse( + choices=[{"message": {"role": "assistant", "content": "hi"}, "index": 0, "finish_reason": "stop"}], + usage=Usage(prompt_tokens=5, completion_tokens=3, total_tokens=8), + ) + check.log_success_event(kwargs, response, None, None) + + assert dual_cache.get_cache(key=itpm_key) == 5 + assert dual_cache.get_cache(key=otpm_key) == 3 + + @pytest.mark.asyncio + async def test_reconcile_runs_via_success_event_without_model_id(self): + dual_cache = DualCache() + check = ModelRateLimitingCheck(dual_cache=dual_cache) + itpm_key = "global_router:io-noid:bedrock_mantle/test:itpm:12-34" + await dual_cache.async_increment_cache(key=itpm_key, value=10, ttl=60) + + # standard_logging_object has no model_id (only the TPM path needs it); + # IO reconciliation must still run off the stashed cache key. + kwargs = { + "standard_logging_object": { + "hidden_params": {"litellm_model_name": "bedrock_mantle/test"}, + "metadata": {}, + }, + "metadata": {ITPM_RESERVED_KEY: 10, ITPM_CACHE_KEY: itpm_key}, + } + response = ModelResponse( + choices=[{"message": {"role": "assistant", "content": "hi"}, "index": 0, "finish_reason": "stop"}], + usage=Usage(prompt_tokens=3, completion_tokens=0, total_tokens=3), + ) + await check.async_log_success_event(kwargs, response, None, None) + + assert await dual_cache.async_get_cache(key=itpm_key) == 3 + + @pytest.mark.asyncio + async def test_failure_clears_reservation_so_retry_is_not_poisoned(self): + from litellm.utils import get_utc_datetime + + dual_cache = DualCache() + check = ModelRateLimitingCheck(dual_cache=dual_cache) + minute = get_utc_datetime().strftime("%H-%M") + itpm_key = f"global_router:io-first:bedrock_mantle/test:itpm:{minute}" + await dual_cache.async_increment_cache(key=itpm_key, value=8, ttl=60) + + # Shared request metadata carrying the first (IO) deployment's reservation. + metadata = {ITPM_RESERVED_KEY: 8, ITPM_CACHE_KEY: itpm_key} + fail_kwargs = { + "metadata": metadata, + "standard_logging_object": { + "model_id": "io-first", + "hidden_params": {"litellm_model_name": "bedrock_mantle/test"}, + "metadata": {}, + }, + } + await check.async_log_failure_event(fail_kwargs, None, None, None) + + assert await dual_cache.async_get_cache(key=itpm_key) == 0 + assert ITPM_RESERVED_KEY not in metadata + assert ITPM_CACHE_KEY not in metadata + + # Retry succeeds on a non-IO fallback deployment reusing the same metadata. + retry_kwargs = { + "metadata": metadata, + "standard_logging_object": { + "model_id": "non-io-second", + "hidden_params": {"litellm_model_name": "openai/gpt-4o-mini"}, + "metadata": {}, + "total_tokens": 12, + }, + } + response = ModelResponse( + choices=[{"message": {"role": "assistant", "content": "ok"}, "index": 0, "finish_reason": "stop"}], + usage=Usage(prompt_tokens=6, completion_tokens=6, total_tokens=12), + ) + await check.async_log_success_event(retry_kwargs, response, None, None) + + # The first deployment's ITPM counter is not driven negative... + assert await dual_cache.async_get_cache(key=itpm_key) == 0 + # ...and the non-IO deployment's TPM usage is tracked normally. + tpm_key = f"non-io-second:openai/gpt-4o-mini:tpm:{minute}" + assert await dual_cache.async_get_cache(key=tpm_key) == 12 + + @pytest.mark.asyncio + async def test_stale_reservation_refunded_before_retry_overwrites_it(self): + """ + A retry reuses the same mutable kwargs dict for the next deployment. + If deployment A's failure event hasn't run yet (e.g. it was scheduled + as a background task) when the retry calls + set_io_token_rate_limit_request_kwargs for deployment B, the router + must first synchronously refund + clear A's reservation via + refund_stale_reservation_before_retry - otherwise A's counter stays + elevated by the reservation until its TTL expires, and the + now-orphaned sentinels must not leak into B's accounting either. + """ + from litellm.utils import get_utc_datetime + + dual_cache = DualCache() + minute = get_utc_datetime().strftime("%H-%M") + itpm_key_a = f"global_router:io-retry-a:bedrock_mantle/test-a:itpm:{minute}" + await dual_cache.async_increment_cache(key=itpm_key_a, value=9, ttl=60) + + # Deployment A's still-unreconciled reservation, stashed on the shared + # kwargs dict the retry loop reuses. + shared_kwargs = {"metadata": {ITPM_RESERVED_KEY: 9, ITPM_CACHE_KEY: itpm_key_a}} + + # Router calls this before overwriting kwargs for deployment B's attempt - + # simulating the fix landing ahead of set_io_token_rate_limit_request_kwargs. + refund_stale_reservation_before_retry(dual_cache, shared_kwargs) + + # A's reservation is refunded immediately, not left stranded for a + # background failure task that may run arbitrarily later (or never, + # if the sentinels get cleared out from under it first). + assert await dual_cache.async_get_cache(key=itpm_key_a) == 0 + assert ITPM_RESERVED_KEY not in shared_kwargs["metadata"] + assert ITPM_CACHE_KEY not in shared_kwargs["metadata"] + + # A's own (now-late) failure event finds nothing left to refund and + # is a safe no-op, since the sentinels were already cleared above. + io_token_refund_failure(dual_cache, shared_kwargs) + assert await dual_cache.async_get_cache(key=itpm_key_a) == 0 + + # The retry proceeds to stash deployment B's own reservation on the + # same dict; it starts clean, unaffected by A's cleared sentinels. + set_io_token_rate_limit_request_kwargs(shared_kwargs) + itpm_key_b = f"global_router:io-retry-b:bedrock_mantle/test-b:itpm:{minute}" + shared_kwargs["metadata"][ITPM_RESERVED_KEY] = 4 + shared_kwargs["metadata"][ITPM_CACHE_KEY] = itpm_key_b + await dual_cache.async_increment_cache(key=itpm_key_b, value=4, ttl=60) + assert await dual_cache.async_get_cache(key=itpm_key_b) == 4 + + @pytest.mark.asyncio + async def test_client_supplied_reservation_keys_are_stripped(self): + # metadata is caller-controlled; the server-only reservation sentinels + # must be removed before the router captures the request kwargs. + forged = { + "metadata": {ITPM_RESERVED_KEY: 999999, ITPM_CACHE_KEY: "attacker:key:itpm:00-00"}, + "litellm_metadata": {OTPM_RESERVED_KEY: 7}, + "litellm_params": {"metadata": {OTPM_CACHE_KEY: "attacker:key:otpm:00-00"}}, + } + set_io_token_rate_limit_request_kwargs(forged) + stored = get_io_token_rate_limit_request_kwargs() + + assert ITPM_RESERVED_KEY not in stored["metadata"] + assert ITPM_CACHE_KEY not in stored["metadata"] + assert OTPM_RESERVED_KEY not in stored["litellm_metadata"] + assert OTPM_CACHE_KEY not in stored["litellm_params"]["metadata"] + + @pytest.mark.asyncio + async def test_forged_reservation_cannot_decrement_counter(self): + dual_cache = DualCache() + check = ModelRateLimitingCheck(dual_cache=dual_cache) + victim_key = "global_router:victim:model:itpm:00-00" + await dual_cache.async_increment_cache(key=victim_key, value=100, ttl=60) + + # A caller forges a reservation pointing at another deployment's counter. + kwargs = { + "metadata": {ITPM_RESERVED_KEY: 100, ITPM_CACHE_KEY: victim_key}, + "standard_logging_object": { + "model_id": "m", + "hidden_params": {"litellm_model_name": "model"}, + "metadata": {}, + "total_tokens": 2, + }, + } + # The router sanitizes the request kwargs before the call runs. + set_io_token_rate_limit_request_kwargs(kwargs) + response = ModelResponse( + choices=[{"message": {"role": "assistant", "content": "ok"}, "index": 0, "finish_reason": "stop"}], + usage=Usage(prompt_tokens=1, completion_tokens=1, total_tokens=2), + ) + await check.async_log_success_event(kwargs, response, None, None) + + # The forged reservation was stripped, so the victim counter is untouched. + assert await dual_cache.async_get_cache(key=victim_key) == 100 + + @pytest.mark.asyncio + async def test_otpm_reservation_error_rolls_back_itpm(self): + from litellm.utils import get_utc_datetime + + class _OtpmFailCache(DualCache): + async def async_increment_cache(self, key, **kwargs): + if ":otpm:" in key: + raise RuntimeError("transient cache error") + return await super().async_increment_cache(key=key, **kwargs) + + dual_cache = _OtpmFailCache() + deployment = { + "litellm_params": { + "model": "bedrock_mantle/anthropic.claude-opus-4-7", + "itpm": 1000, + "otpm": 1000, + }, + "model_info": {"id": "io-rollback-id"}, + "model_name": "opus", + } + set_io_token_rate_limit_request_kwargs( + { + "messages": [{"role": "user", "content": "hello world"}], + "max_tokens": 5, + "metadata": {}, + } + ) + + with pytest.raises(RuntimeError): + await async_io_token_pre_call_check(dual_cache, deployment) + + minute = get_utc_datetime().strftime("%H-%M") + itpm_key = f"global_router:io-rollback-id:bedrock_mantle/anthropic.claude-opus-4-7:itpm:{minute}" + # A transient OTPM error must release the ITPM reservation, not leak it. + assert (await dual_cache.async_get_cache(key=itpm_key) or 0) == 0 + + @pytest.mark.asyncio + async def test_reconcile_clears_stash_even_when_increment_errors(self): + class _ItpmFailCache(DualCache): + async def async_increment_cache(self, key, **kwargs): + if ":itpm:" in key: + raise RuntimeError("transient cache error") + return await super().async_increment_cache(key=key, **kwargs) + + dual_cache = _ItpmFailCache() + metadata = {ITPM_RESERVED_KEY: 5, ITPM_CACHE_KEY: "global_router:x:model:itpm:00-00"} + kwargs = {"metadata": metadata} + response = ModelResponse( + choices=[{"message": {"role": "assistant", "content": "ok"}, "index": 0, "finish_reason": "stop"}], + usage=Usage(prompt_tokens=3, completion_tokens=0, total_tokens=3), + ) + + with pytest.raises(RuntimeError): + await async_io_token_reconcile_success(dual_cache, kwargs, response) + + # The stash is cleared even though reconciliation raised, so a duplicate + # success event can't re-process it. + assert ITPM_RESERVED_KEY not in metadata + assert ITPM_CACHE_KEY not in metadata + + @pytest.mark.asyncio + async def test_io_conflict_warning_not_collapsed_for_missing_model_id(self, caplog): + import logging + + check = ModelRateLimitingCheck(dual_cache=DualCache()) + deployment = { + "litellm_params": {"model": "openai/gpt-4o-mini", "itpm": 100, "tpm": 1000}, + "model_info": {}, + } + with caplog.at_level(logging.WARNING, logger="LiteLLM Router"): + check._warn_io_token_and_tpm_rpm_coexist_once(deployment) + check._warn_io_token_and_tpm_rpm_coexist_once(deployment) + + warnings = [r for r in caplog.records if "both limit types are enforced" in r.message] + # id-less deployments are not collapsed onto a single dedup key. + assert len(warnings) == 2 + + @pytest.mark.asyncio + async def test_missing_deployment_id_skips_io_reservation(self): + dual_cache = DualCache() + deployment = { + "litellm_params": {"model": "openai/gpt-4o-mini", "itpm": 100}, + "model_info": {}, # no id -> cannot build a per-deployment cache key + "model_name": "opus", + } + request_kwargs = { + "messages": [{"role": "user", "content": "hello world"}], + "max_tokens": 5, + "metadata": {}, + } + set_io_token_rate_limit_request_kwargs(request_kwargs) + + result = await async_io_token_pre_call_check(dual_cache, deployment) + + assert result is deployment + # No reservation is stashed, so nothing lands in a shared None:None bucket. + assert ITPM_RESERVED_KEY not in request_kwargs["metadata"] + + @pytest.mark.asyncio + async def test_reconcile_uses_reservation_minute_key(self): + dual_cache = DualCache() + # Reservation was made on a fixed minute key; a call that finishes in a + # later minute must reconcile against that same key, never a key built + # from the response-time minute. + itpm_key = "global_router:io-min-id:bedrock_mantle/test:itpm:99-99" + await dual_cache.async_increment_cache(key=itpm_key, value=10, ttl=60) + + kwargs = {"metadata": {ITPM_RESERVED_KEY: 10, ITPM_CACHE_KEY: itpm_key}} + response = ModelResponse( + choices=[{"message": {"role": "assistant", "content": "hi"}, "index": 0, "finish_reason": "stop"}], + usage=Usage(prompt_tokens=4, completion_tokens=0, total_tokens=4), + ) + await async_io_token_reconcile_success(dual_cache, kwargs, response) + + assert await dual_cache.async_get_cache(key=itpm_key) == 4 + + @pytest.mark.asyncio + async def test_reconcile_missing_usage_keeps_reservation(self): + dual_cache = DualCache() + itpm_key = "global_router:io-missing-usage:bedrock_mantle/test:itpm:00-00" + otpm_key = "global_router:io-missing-usage:bedrock_mantle/test:otpm:00-00" + await dual_cache.async_increment_cache(key=itpm_key, value=8, ttl=60) + await dual_cache.async_increment_cache(key=otpm_key, value=5, ttl=60) + + kwargs = { + "metadata": { + ITPM_RESERVED_KEY: 8, + OTPM_RESERVED_KEY: 5, + ITPM_CACHE_KEY: itpm_key, + OTPM_CACHE_KEY: otpm_key, + } + } + response = ModelResponse( + choices=[{"message": {"role": "assistant", "content": "ok"}, "index": 0, "finish_reason": "stop"}], + ) + + await async_io_token_reconcile_success(dual_cache, kwargs, response) + + assert await dual_cache.async_get_cache(key=itpm_key) == 8 + assert await dual_cache.async_get_cache(key=otpm_key) == 5 + assert ITPM_RESERVED_KEY not in kwargs["metadata"] + + @pytest.mark.asyncio + async def test_reconcile_total_tokens_only_keeps_reservation(self): + """ + A response usage object with only total_tokens (no prompt/completion + breakdown) can't be split into input/output, so it must be treated the + same as missing usage: keep the reservation instead of resolving to + (0, 0) and refunding it in full. + """ + dual_cache = DualCache() + itpm_key = "global_router:io-total-only:bedrock_mantle/test:itpm:00-00" + otpm_key = "global_router:io-total-only:bedrock_mantle/test:otpm:00-00" + await dual_cache.async_increment_cache(key=itpm_key, value=8, ttl=60) + await dual_cache.async_increment_cache(key=otpm_key, value=5, ttl=60) + + kwargs = { + "metadata": { + ITPM_RESERVED_KEY: 8, + OTPM_RESERVED_KEY: 5, + ITPM_CACHE_KEY: itpm_key, + OTPM_CACHE_KEY: otpm_key, + } + } + response = {"type": "message", "usage": {"total_tokens": 13}} + + await async_io_token_reconcile_success(dual_cache, kwargs, response) + + assert await dual_cache.async_get_cache(key=itpm_key) == 8 + assert await dual_cache.async_get_cache(key=otpm_key) == 5 + + def test_reconcile_standard_logging_total_tokens_only_keeps_reservation(self): + dual_cache = DualCache() + itpm_key = "global_router:io-slo-total-only:bedrock_mantle/test:itpm:00-00" + dual_cache.set_cache(key=itpm_key, value=10, ttl=60) + + kwargs = { + "metadata": {ITPM_RESERVED_KEY: 10, ITPM_CACHE_KEY: itpm_key}, + "standard_logging_object": {"total_tokens": 4}, + } + response = {"type": "message", "role": "assistant", "content": []} + + io_token_reconcile_success(dual_cache, kwargs, response) + + assert dual_cache.get_cache(key=itpm_key) == 10 + + @pytest.mark.asyncio + async def test_reconcile_falls_back_to_standard_logging_object(self): + dual_cache = DualCache() + itpm_key = "global_router:io-slo-fallback:bedrock_mantle/test:itpm:00-00" + await dual_cache.async_increment_cache(key=itpm_key, value=10, ttl=60) + + kwargs = { + "metadata": {ITPM_RESERVED_KEY: 10, ITPM_CACHE_KEY: itpm_key}, + "standard_logging_object": { + "prompt_tokens": 4, + "completion_tokens": 0, + "total_tokens": 4, + }, + } + response = {"type": "message", "role": "assistant", "content": []} + + await async_io_token_reconcile_success(dual_cache, kwargs, response) + + assert await dual_cache.async_get_cache(key=itpm_key) == 4 + + def test_sync_reconcile_anthropic_dict_usage(self): + dual_cache = DualCache() + itpm_key = "global_router:io-anthropic:bedrock_mantle/test:itpm:00-00" + otpm_key = "global_router:io-anthropic:bedrock_mantle/test:otpm:00-00" + dual_cache.set_cache(key=itpm_key, value=6, ttl=60) + dual_cache.set_cache(key=otpm_key, value=4, ttl=60) + + kwargs = { + "metadata": { + ITPM_RESERVED_KEY: 6, + OTPM_RESERVED_KEY: 4, + ITPM_CACHE_KEY: itpm_key, + OTPM_CACHE_KEY: otpm_key, + } + } + response = { + "type": "message", + "usage": {"input_tokens": 3, "output_tokens": 2, "cache_read_input_tokens": 1}, + } + + io_token_reconcile_success(dual_cache, kwargs, response) + + assert dual_cache.get_cache(key=itpm_key) == 2 + assert dual_cache.get_cache(key=otpm_key) == 2 + + @pytest.mark.asyncio + async def test_io_and_tpm_rpm_limits_both_enforced_with_warning(self, caplog): + import logging + + from litellm.utils import get_utc_datetime + + dual_cache = DualCache() + check = ModelRateLimitingCheck(dual_cache=dual_cache) + model_id = "io-mixed-id" + deployment_name = "bedrock_mantle/anthropic.claude-opus-4-7" + deployment = { + "litellm_params": { + "model": deployment_name, + "itpm": 100, + "rpm": 1, + }, + "model_info": {"id": model_id}, + "model_name": "opus", + } + + minute = get_utc_datetime().strftime("%H-%M") + rpm_key = f"{model_id}:{deployment_name}:rpm:{minute}" + itpm_key = f"global_router:{model_id}:{deployment_name}:itpm:{minute}" + await dual_cache.async_increment_cache(key=rpm_key, value=5, ttl=60) + + request_kwargs = { + "messages": [{"role": "user", "content": "hi"}], + "max_tokens": 5, + "metadata": {}, + } + set_io_token_rate_limit_request_kwargs(request_kwargs) + + with caplog.at_level(logging.WARNING, logger="LiteLLM Router"): + with pytest.raises(litellm.RateLimitError): + await check.async_pre_call_check(deployment) + + assert await dual_cache.async_get_cache(key=rpm_key) == 6 + assert (await dual_cache.async_get_cache(key=itpm_key) or 0) == 0 + assert ITPM_RESERVED_KEY not in request_kwargs["metadata"] + assert any("both limit types are enforced" in record.message for record in caplog.records) + + @pytest.mark.asyncio + async def test_io_success_still_tracks_tpm_for_mixed_deployment(self): + """ + A deployment with itpm/otpm AND tpm/rpm must have BOTH counters updated on + success, otherwise the tpm_key the pre-call check reads is never written + and the tpm_limit can never be enforced. + """ + from litellm.utils import get_utc_datetime + + dual_cache = DualCache() + check = ModelRateLimitingCheck(dual_cache=dual_cache) + model_id = "io-tpm-mixed-id" + deployment_name = "bedrock_mantle/anthropic.claude-opus-4-7" + minute = get_utc_datetime().strftime("%H-%M") + itpm_key = f"global_router:{model_id}:{deployment_name}:itpm:{minute}" + tpm_key = f"{model_id}:{deployment_name}:tpm:{minute}" + await dual_cache.async_increment_cache(key=itpm_key, value=5, ttl=60) + + kwargs = { + "metadata": {ITPM_RESERVED_KEY: 5, ITPM_CACHE_KEY: itpm_key}, + "standard_logging_object": { + "model_id": model_id, + "total_tokens": 7, + "hidden_params": {"litellm_model_name": deployment_name}, + }, + } + response = ModelResponse( + choices=[{"message": {"role": "assistant", "content": "hi"}, "index": 0, "finish_reason": "stop"}], + usage=Usage(prompt_tokens=3, completion_tokens=0, total_tokens=3), + ) + + await check.async_log_success_event(kwargs, response, None, None) + + # ITPM reconciled down from the 5-token reservation to actual usage (3). + assert await dual_cache.async_get_cache(key=itpm_key) == 3 + # TPM tracking must still run so the tpm/rpm pre-call path can enforce it. + assert await dual_cache.async_get_cache(key=tpm_key) == 7 + + def test_io_success_still_tracks_tpm_for_mixed_deployment_sync(self): + from litellm.utils import get_utc_datetime + + dual_cache = DualCache() + check = ModelRateLimitingCheck(dual_cache=dual_cache) + model_id = "io-tpm-mixed-sync-id" + deployment_name = "bedrock_mantle/anthropic.claude-opus-4-7" + minute = get_utc_datetime().strftime("%H-%M") + itpm_key = f"global_router:{model_id}:{deployment_name}:itpm:{minute}" + tpm_key = f"{model_id}:{deployment_name}:tpm:{minute}" + dual_cache.set_cache(key=itpm_key, value=5, ttl=60) + + kwargs = { + "metadata": {ITPM_RESERVED_KEY: 5, ITPM_CACHE_KEY: itpm_key}, + "standard_logging_object": { + "model_id": model_id, + "total_tokens": 7, + "hidden_params": {"litellm_model_name": deployment_name}, + }, + } + response = ModelResponse( + choices=[{"message": {"role": "assistant", "content": "hi"}, "index": 0, "finish_reason": "stop"}], + usage=Usage(prompt_tokens=3, completion_tokens=0, total_tokens=3), + ) + + check.log_success_event(kwargs, response, None, None) + + assert dual_cache.get_cache(key=itpm_key) == 3 + assert dual_cache.get_cache(key=tpm_key) == 7 + + @pytest.mark.asyncio + async def test_failure_refunds_itpm_reservation(self): + from litellm.utils import get_utc_datetime + + dual_cache = DualCache() + check = ModelRateLimitingCheck(dual_cache=dual_cache) + minute = get_utc_datetime().strftime("%H-%M") + itpm_key = f"global_router:io-refund-id:bedrock_mantle/test:itpm:{minute}" + await dual_cache.async_increment_cache(key=itpm_key, value=20, ttl=60) + + reservation = {ITPM_RESERVED_KEY: 20, ITPM_CACHE_KEY: itpm_key} + kwargs = { + "standard_logging_object": { + "model_id": "io-refund-id", + "hidden_params": {"litellm_model_name": "bedrock_mantle/test"}, + "metadata": dict(reservation), + }, + "metadata": dict(reservation), + } + await check.async_log_failure_event(kwargs, None, None, None) + + current = await dual_cache.async_get_cache(key=itpm_key) + assert current == 0 + + +class TestRouterIOTokenIntegration: + @pytest.mark.asyncio + async def test_model_group_info_aggregates_io_limits(self): + router = Router( + model_list=[ + { + "model_name": "opus", + "litellm_params": { + "model": "bedrock_mantle/anthropic.claude-opus-4-7", + "itpm": 100, + "otpm": 20, + }, + } + ], + optional_pre_call_checks=["enforce_model_rate_limits"], + ) + info = router.get_model_group_info("opus") + assert info is not None + assert info.itpm == 100 + assert info.otpm == 20 diff --git a/tests/test_litellm/test_router_model_cost_isolation.py b/tests/test_litellm/test_router_model_cost_isolation.py index d4ac9659f00..6db7b04b3b7 100644 --- a/tests/test_litellm/test_router_model_cost_isolation.py +++ b/tests/test_litellm/test_router_model_cost_isolation.py @@ -681,3 +681,76 @@ def test_custom_pricing_isolated_from_sibling_via_proxy_model_info_path(): assert resolved["gemini-2.5-flash"] != resolved["custom-priced-flash"] finally: _restore_model_cost_entries(model_keys) + + +def test_wildcard_zero_cost_request_does_not_poison_named_deployment_pricing(): + """LIT-3991 end to end: a proxy has a named text-embedding-3-small + deployment relying on built-in pricing plus an ``openai/*`` wildcard with + explicit zero pricing. One embedding call routed through the wildcard must + not clobber the shared ``openai/text-embedding-3-small`` pricing; requests + to the named deployment afterwards must still cost non-zero. + """ + shared_key = "openai/text-embedding-3-small" + model_keys = { + shared_key: copy.deepcopy(litellm.model_cost.get(shared_key)), + "text-embedding-3-small": copy.deepcopy( + litellm.model_cost.get("text-embedding-3-small") + ), + "openai/*": copy.deepcopy(litellm.model_cost.get("openai/*")), + "lit3991-named": litellm.model_cost.get("lit3991-named"), + "lit3991-wildcard": litellm.model_cost.get("lit3991-wildcard"), + } + builtin_input_cost = litellm.get_model_info(model=shared_key)[ + "input_cost_per_token" + ] + assert builtin_input_cost > 0 + + try: + router = Router( + model_list=[ + { + "model_name": "text-embedding-3-small", + "litellm_params": { + "model": "openai/text-embedding-3-small", + "api_key": "fake-key-named", + }, + "model_info": {"id": "lit3991-named"}, + }, + { + "model_name": "openai/*", + "litellm_params": { + "model": "openai/*", + "api_key": "fake-key-wildcard", + "input_cost_per_token": 0.0, + "output_cost_per_token": 0.0, + }, + "model_info": {"id": "lit3991-wildcard"}, + }, + ], + ) + + router.embedding( + model="openai/text-embedding-3-small", + input=["hello"], + mock_response=[0.1, 0.2], + ) + + assert ( + litellm.get_model_info(model=shared_key)["input_cost_per_token"] + == builtin_input_cost + ), ( + "one call through the zero-cost wildcard poisoned the shared " + f"{shared_key} pricing for the named deployment" + ) + + named_response = router.embedding( + model="text-embedding-3-small", + input=["hello"], + mock_response=[0.1, 0.2], + ) + named_cost = litellm.completion_cost( + completion_response=named_response, call_type="embedding" + ) + assert named_cost == pytest.approx(10 * builtin_input_cost) + finally: + _restore_model_cost_entries(model_keys) diff --git a/type-discipline-budget.json b/type-discipline-budget.json index aa16b30b215..8d12528d8ab 100644 --- a/type-discipline-budget.json +++ b/type-discipline-budget.json @@ -9,7 +9,7 @@ "limit": 422 }, "LIT004": { - "limit": 2565 + "limit": 44 }, "LIT005": { "limit": 0 @@ -22,5 +22,8 @@ }, "LIT008": { "limit": 1004 + }, + "LIT009": { + "limit": 2501 } } diff --git a/ui/litellm-dashboard/components.json b/ui/litellm-dashboard/components.json index c4d45aa0989..48f5c3d0f37 100644 --- a/ui/litellm-dashboard/components.json +++ b/ui/litellm-dashboard/components.json @@ -1,6 +1,6 @@ { "$schema": "https://ui.shadcn.com/schema.json", - "style": "new-york", + "style": "base-vega", "rsc": true, "tsx": true, "tailwind": { diff --git a/ui/litellm-dashboard/eslint-metrics.json b/ui/litellm-dashboard/eslint-metrics.json index 6d18a36f754..219cb0580e7 100644 --- a/ui/litellm-dashboard/eslint-metrics.json +++ b/ui/litellm-dashboard/eslint-metrics.json @@ -1,5 +1,5 @@ { - "@typescript-eslint/no-explicit-any": 1991, + "@typescript-eslint/no-explicit-any": 1990, "complexity": 128, "max-depth": 59, "no-console": 15 diff --git a/ui/litellm-dashboard/package-lock.json b/ui/litellm-dashboard/package-lock.json index 8b92683db67..ae3660f59e9 100644 --- a/ui/litellm-dashboard/package-lock.json +++ b/ui/litellm-dashboard/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@ant-design/cssinjs": "1.24.0", "@anthropic-ai/sdk": "0.92.0", + "@base-ui/react": "^1.6.0", "@headlessui/tailwindcss": "0.2.2", "@heroicons/react": "1.0.6", "@tanstack/react-pacer": "0.2.0", @@ -19,7 +20,7 @@ "@types/papaparse": "5.5.2", "antd": "5.29.3", "cva": "1.0.0-beta.4", - "date-fns": "3.6.0", + "date-fns": "^4.4.0", "dayjs": "1.11.19", "jwt-decode": "4.0.0", "lucide-react": "0.513.0", @@ -27,7 +28,6 @@ "next": "16.2.6", "openai": "4.104.0", "papaparse": "5.5.3", - "radix-ui": "1.6.1", "react": "18.3.1", "react-copy-to-clipboard": "5.1.1", "react-dom": "18.3.1", @@ -560,6 +560,79 @@ "node": ">=6.9.0" } }, + "node_modules/@base-ui/react": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@base-ui/react/-/react-1.6.0.tgz", + "integrity": "sha512-/jzjTWJYXhRFO45Bev9lc3cHbmjzCMpUqbMZ2AgKy/z25mY9B6shGSNcXcjQar9n5doM0KYW1W8fcFv2jZBuMw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2", + "@base-ui/utils": "0.3.1", + "@floating-ui/react-dom": "^2.1.8", + "@floating-ui/utils": "^0.2.11", + "use-sync-external-store": "^1.6.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@date-fns/tz": "^1.2.0", + "@types/react": "^17 || ^18 || ^19", + "date-fns": "^4.0.0", + "react": "^17 || ^18 || ^19", + "react-dom": "^17 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "@date-fns/tz": { + "optional": true + }, + "@types/react": { + "optional": true + }, + "date-fns": { + "optional": true + } + } + }, + "node_modules/@base-ui/react/node_modules/@floating-ui/react-dom": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", + "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.6" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@base-ui/utils": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@base-ui/utils/-/utils-0.3.1.tgz", + "integrity": "sha512-gFFiltORVmW/N6IILTGxizP3PBpVpysqML1ALY5Vk0mH+7faVkCknOU31goYHN5Aoek2dkjxva1XOD2Ce9WuIg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2", + "@floating-ui/utils": "^0.2.11", + "reselect": "^5.2.0", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "@types/react": "^17 || ^18 || ^19", + "react": "^17 || ^18 || ^19", + "react-dom": "^17 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@bcoe/v8-coverage": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", @@ -2100,14 +2173,14 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@tybys/wasm-util": "^0.10.1" + "@tybys/wasm-util": "^0.10.3" }, "funding": { "type": "github", @@ -2616,1512 +2689,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@radix-ui/number": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.2.tgz", - "integrity": "sha512-ceTwaxc4I5IOi97DgCotl3pqiyRGvffcc0oOsE2dQYaJOFIDsDt4VWG6xEbg1QePv9QWausCEIppud/tJ1wNig==", - "license": "MIT" - }, - "node_modules/@radix-ui/primitive": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.4.tgz", - "integrity": "sha512-7AdCK9PQyiljKoBDbN8OuctCbd/esdwZPQ8RtOE3SsyQtUpiPb+ND75q0jEhC1m1ecBI0MFNeLJvwIh9iKHRcQ==", - "license": "MIT" - }, - "node_modules/@radix-ui/react-accessible-icon": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-accessible-icon/-/react-accessible-icon-1.1.11.tgz", - "integrity": "sha512-HQDOFTKwSnmUij6l54wYJJtxTAnxI71+YJLOrjm2ladFB8HAV5Jt7hwaZPhWTGBkYoW4+ZAOfNZrLDh/qvxSYA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-visually-hidden": "1.2.7" - }, - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-accordion": { - "version": "1.2.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.15.tgz", - "integrity": "sha512-24Zz/0SYx8F2bSVThBnQrdJs2VbKelyuJordcFRRdA0fRAhrq/wSegGCqaQz34VQoiWqSMGYCYXEhynLSlyQlg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-collapsible": "1.1.15", - "@radix-ui/react-collection": "1.1.11", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-controllable-state": "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-alert-dialog": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.18.tgz", - "integrity": "sha512-6c2cXpNlAgHDhKguK24XcWHHayMpK+lk7/WwBXBco+ZJ4Dv7xP++GBM280KgTD/HCRu3jSdfe8WQiZssonYaIA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-dialog": "1.1.18", - "@radix-ui/react-primitive": "2.1.7" - }, - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-arrow": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.11.tgz", - "integrity": "sha512-Kdil9BB1rIFC/khmf4hC35bn8701AJcizTU7G7cUbEbk5XqqbjDuHW60uUfKqO5WojjZcbAW51Q7P0hRmMLw8A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.7" - }, - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-aspect-ratio": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.11.tgz", - "integrity": "sha512-IUAhIVpBUvP5NNICjlaB1OFmtRLGqQqTF3ZOSGPoq3XeLXRFtHiWTRxSVEULgOd9GQR2c7tsYqDnhUennapZnw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.7" - }, - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-avatar": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.2.1.tgz", - "integrity": "sha512-+8PWoLLZv3AVb5m0pvoiOca/bQGzc9vPVb+982HB2x3Un0DpYEPM3zLMl4oqRpBsocJuNqLkiv/HXTnTrlwr4g==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-is-hydrated": "0.1.1", - "@radix-ui/react-use-layout-effect": "1.1.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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-checkbox": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.6.tgz", - "integrity": "sha512-eUEUoGMDpfkgHWSE97ZZaUJtzR1M7EKnNIpD1Q16+8JR9NWghcaqMulx9PuCQ720w0UclfYn6FEbCdd5Hx087g==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-presence": "1.1.6", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-controllable-state": "1.2.3", - "@radix-ui/react-use-previous": "1.1.2", - "@radix-ui/react-use-size": "1.1.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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-collapsible": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.15.tgz", - "integrity": "sha512-8A1zibu5skAQ+UVbaeNH5hVMibiFCRJzgMuM14LTWGttnTZKQL9jwYnhAbHRuxrtCqPXa4JvvnVUq1pTNgyZYw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-presence": "1.1.6", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-controllable-state": "1.2.3", - "@radix-ui/react-use-layout-effect": "1.1.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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-collection": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.11.tgz", - "integrity": "sha512-djW9+zeg137KQdlPtmE8xnaD+K2rcXXMWFrSg0hsmYZ6HRbdTA7tDHFgpaW9+huWVEu0RCabL+985T4TA0BE7g==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-slot": "1.3.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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.3.tgz", - "integrity": "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-context": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.4.tgz", - "integrity": "sha512-QwH4PO5urrbO+FaGd5Aglg+YJgWTyyuZ3g/6mKvsqraLkglDdckw9JafgL5McL5VEJ6EPNduPaT3ZE9BttDAqg==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-context-menu": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.3.2.tgz", - "integrity": "sha512-qzsA/ZPhF6yMxBOTIk1nlCkoy2mswSbwYL+ErBa2iP0s4WWrlxmczArYqMcpVfEjmM7KJj/ADPXky0yZfbSxtQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-menu": "2.1.19", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-controllable-state": "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dialog": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.18.tgz", - "integrity": "sha512-apa28mldjMgORmE6g/w3sCcA0Y9UAVeeDVoozN4i7kOw12mLl9RBchfzK3Nn6qxOWjrZhK1Lfy7f07kyzxtnBw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-dismissable-layer": "1.1.14", - "@radix-ui/react-focus-guards": "1.1.4", - "@radix-ui/react-focus-scope": "1.1.11", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-portal": "1.1.13", - "@radix-ui/react-presence": "1.1.6", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-slot": "1.3.0", - "@radix-ui/react-use-controllable-state": "1.2.3", - "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.7.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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-direction": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.2.tgz", - "integrity": "sha512-C3vFhbyi4SW3PmbAi6Awpu4OzJtd0MxGurvSsYtr7p7nM8RNB3VAF3CUmnp2j50knpkrRcB7+ycVXzgLgF6yNA==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.14.tgz", - "integrity": "sha512-4lUhWTWAjbDIqFrAPWJ3WqBOpO5YchVZ88X3nh6H9Lu5AFi5nCUeTPj3D8FSDmabmFeRe9ME0BDA4MwKTha5GQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-effect-event": "0.0.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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dropdown-menu": { - "version": "2.1.19", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.19.tgz", - "integrity": "sha512-HZccBkbK0LOi8nYKIp5jll/zIRW0cCOmG6WWyqsSpmXCU+ZlcBbTqIwlBvPCu886C5RVu6c/kHV7xSP8IgYNHw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-menu": "2.1.19", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-controllable-state": "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-focus-guards": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.4.tgz", - "integrity": "sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-focus-scope": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.11.tgz", - "integrity": "sha512-Mn88Vg2whaRocGJNOH+DKFqYm6ySFPQaiwHNxZPyjn99B52KAEJWWY9NP83+nWdk2HM3rdov+STu9AG471Rt9w==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-form": { - "version": "0.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-form/-/react-form-0.1.11.tgz", - "integrity": "sha512-0mTMJHv1gQAuEQoq5VDpTD3MRgmfUFdXAVFhpqR7wBeUr+tyRsof0wv/4XdPHLwQrefhoH2FiGHCggrCJhalIw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-label": "2.1.11", - "@radix-ui/react-primitive": "2.1.7" - }, - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-hover-card": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.18.tgz", - "integrity": "sha512-rt+Fx4HoCeEwFL2IdoV2QaPltqDLlzxN77i9nwB3Y70scFlfAHh1QCdE2TXKuFJtA1TNygb0oivnFBZifgtZOw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-dismissable-layer": "1.1.14", - "@radix-ui/react-popper": "1.3.2", - "@radix-ui/react-portal": "1.1.13", - "@radix-ui/react-presence": "1.1.6", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-controllable-state": "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-id": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.2.tgz", - "integrity": "sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-label": { - "version": "2.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.11.tgz", - "integrity": "sha512-3PKvDDxOn62k0oV1n4QtNtD2vpu+zYjXR7ojLBPaO6SPvhy53yg0vAmgNeBQeJW5rV3dffoRG+HYfLBZuzw0CQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.7" - }, - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu": { - "version": "2.1.19", - "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.19.tgz", - "integrity": "sha512-Mht9BVd1AIsNFVQr4KG3bIK7XQn5IXF0TL/2ObsrzOdc1loaly/+kBDL5roSCYn8j8XZkvpOD0WYLz2FQtH1Eg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-collection": "1.1.11", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.14", - "@radix-ui/react-focus-guards": "1.1.4", - "@radix-ui/react-focus-scope": "1.1.11", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-popper": "1.3.2", - "@radix-ui/react-portal": "1.1.13", - "@radix-ui/react-presence": "1.1.6", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-roving-focus": "1.1.14", - "@radix-ui/react-slot": "1.3.0", - "@radix-ui/react-use-callback-ref": "1.1.2", - "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.7.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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menubar": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.19.tgz", - "integrity": "sha512-Glt6mebxcgQvLeVkH3HiqV5bgQubE+31ELxLs7q0GlYI5k0XYkOkeuPrhXoylxK8eufvIt9CJjzY1TfFMXK3qw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-collection": "1.1.11", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-menu": "2.1.19", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-roving-focus": "1.1.14", - "@radix-ui/react-use-controllable-state": "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-navigation-menu": { - "version": "1.2.17", - "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.17.tgz", - "integrity": "sha512-fYeYQvbeNn5AQk2RBbpO7koLm2YbS00UYxC/IL2sgLlninEH5UNIv+X3E0KJ1Vy4WIo+dhN9w8GNqSHhbHWCIg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-collection": "1.1.11", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.14", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-presence": "1.1.6", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-controllable-state": "1.2.3", - "@radix-ui/react-use-layout-effect": "1.1.2", - "@radix-ui/react-use-previous": "1.1.2", - "@radix-ui/react-visually-hidden": "1.2.7" - }, - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-one-time-password-field": { - "version": "0.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-one-time-password-field/-/react-one-time-password-field-0.1.11.tgz", - "integrity": "sha512-Rsgab65u73E5kPVh8OS6PgPwJgPyf08GFfJDGAbMdF4DL7CgDhFOaDnXuk/DiMEVF6kgQwl0oJmFklvipmiOLg==", - "license": "MIT", - "dependencies": { - "@radix-ui/number": "1.1.2", - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-collection": "1.1.11", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-roving-focus": "1.1.14", - "@radix-ui/react-use-controllable-state": "1.2.3", - "@radix-ui/react-use-effect-event": "0.0.3", - "@radix-ui/react-use-is-hydrated": "0.1.1", - "@radix-ui/react-use-layout-effect": "1.1.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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-password-toggle-field": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-password-toggle-field/-/react-password-toggle-field-0.1.6.tgz", - "integrity": "sha512-pQ3xGp/uemomASPH97Eb3shfXX8QlG11bBJyEvRBV+vwtO4HvQlS06Yj9f31Ao7XepvF98SFrRgVDQ7jv+2xjQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-controllable-state": "1.2.3", - "@radix-ui/react-use-effect-event": "0.0.3", - "@radix-ui/react-use-is-hydrated": "0.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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.18.tgz", - "integrity": "sha512-qdXDes+eHlnMUGlBAAAe5EG7oOQvqsXuq4mq585diMudg80iB+jHbsSeG3+Q4eWNsogNyhqU2p/3i+Y0iEepqg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-dismissable-layer": "1.1.14", - "@radix-ui/react-focus-guards": "1.1.4", - "@radix-ui/react-focus-scope": "1.1.11", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-popper": "1.3.2", - "@radix-ui/react-portal": "1.1.13", - "@radix-ui/react-presence": "1.1.6", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-slot": "1.3.0", - "@radix-ui/react-use-controllable-state": "1.2.3", - "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.7.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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popper": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.2.tgz", - "integrity": "sha512-3QXNeMkdshed1MR3LNoiCirBywRFPkD8ETJa/HlPuLwSajaQixf2ro+isoDNJlGABg9ug41XuZpINZJIle4XWg==", - "license": "MIT", - "dependencies": { - "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.11", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-layout-effect": "1.1.2", - "@radix-ui/react-use-rect": "1.1.2", - "@radix-ui/react-use-size": "1.1.2", - "@radix-ui/rect": "1.1.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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popper/node_modules/@floating-ui/react-dom": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", - "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", - "license": "MIT", - "dependencies": { - "@floating-ui/dom": "^1.7.6" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@radix-ui/react-portal": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.13.tgz", - "integrity": "sha512-z3oXfmaHLJTF1wktbjgD6cn9jiEbq3WSondB10LIuIt2m2Ym4iJlrW04/euMwENDdWDdE7z+OuY7Qyp1YpRSwA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-layout-effect": "1.1.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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-presence": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.6.tgz", - "integrity": "sha512-zdTk4PlUO0E18HnZ3wYbW0KkJJxWCdiNYp6g6X1PtONFhxVkg01vliTJAmwIszU6mHiyBOoW9P0rAugl5/hULQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-primitive": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.7.tgz", - "integrity": "sha512-bC3NiwsprbxKjuon9l7X6BUTw7FPVzEYaL92MPEY5SCd/9hUTPXVFtVwRix7778wtRsVao+zE062gL79FZleeQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-slot": "1.3.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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-progress": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.11.tgz", - "integrity": "sha512-KqiGJcFaZDc+BvveAgU3ZhACg2MvSUDrCBx4lRR/ZVRNal0bvt8lBpvnSkep9heeOuF8Qfw3fszLDX4OpQ2NVw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-primitive": "2.1.7" - }, - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-radio-group": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.4.2.tgz", - "integrity": "sha512-W8Uo9riHnlzLLWy+r2mVHUyuEWqD/+be4PZzbEvaGoFSBDHkm+GYWjtcE6u3AmPKNyfanWpnVfpZ2GqPCdzzsw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-presence": "1.1.6", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-roving-focus": "1.1.14", - "@radix-ui/react-use-controllable-state": "1.2.3", - "@radix-ui/react-use-previous": "1.1.2", - "@radix-ui/react-use-size": "1.1.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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-roving-focus": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.14.tgz", - "integrity": "sha512-8Qcnx9447tx/aCBgw6Jenfqg4Skq+vqab9mCBmuGNipIS5YXvL275wbKEu7+ICYHIlAPgCduUMJH1XOYewKF6Q==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-collection": "1.1.11", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-controllable-state": "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-scroll-area": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.13.tgz", - "integrity": "sha512-7tncSubo2G0UY1e8rk+72qe3XRzrGnOLtZQ1PL1KoBfRUNX0NrJT5akb+0kfwSCc3gVR4wdHqyhAQBDpDNOwDw==", - "license": "MIT", - "dependencies": { - "@radix-ui/number": "1.1.2", - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-presence": "1.1.6", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-layout-effect": "1.1.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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.3.2.tgz", - "integrity": "sha512-brXD6C/V0fVK0DDbscLVw6LsXrjQ+ay8jdOBaN+tLb4vsHsAMm6Gt6eT77wHX1Eq8GPtD5rJ+RxFtfDozsb4+Q==", - "license": "MIT", - "dependencies": { - "@radix-ui/number": "1.1.2", - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-collection": "1.1.11", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.14", - "@radix-ui/react-focus-guards": "1.1.4", - "@radix-ui/react-focus-scope": "1.1.11", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-popper": "1.3.2", - "@radix-ui/react-portal": "1.1.13", - "@radix-ui/react-presence": "1.1.6", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-slot": "1.3.0", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-controllable-state": "1.2.3", - "@radix-ui/react-use-layout-effect": "1.1.2", - "@radix-ui/react-use-previous": "1.1.2", - "@radix-ui/react-visually-hidden": "1.2.7", - "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.7.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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-separator": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.11.tgz", - "integrity": "sha512-jRhe86+8PF7VZ1u14eOWVOuh2BuAhALg/FT1VcMC4OHedMTRUazDnDlKTt+yxo5cRNKHMfmvZ4sSQtWDeMV4CQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.7" - }, - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-slider": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.4.2.tgz", - "integrity": "sha512-qt5C1ppJz66aUDrH1VccjPrq7aFchK0wBrn6xsxlCHNUyE57dRRQ7lp1QFpF7OscMexZF8MCGBTVBlENHPkNiA==", - "license": "MIT", - "dependencies": { - "@radix-ui/number": "1.1.2", - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-collection": "1.1.11", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-controllable-state": "1.2.3", - "@radix-ui/react-use-layout-effect": "1.1.2", - "@radix-ui/react-use-previous": "1.1.2", - "@radix-ui/react-use-size": "1.1.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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-slot": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.0.tgz", - "integrity": "sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.3" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-switch": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.3.2.tgz", - "integrity": "sha512-tgRBI3DdNwAJYE4BBZyZcz/HRRCvAsPkRvG1wvKc+41tBGMxPn/a87T/wikXAvyDypNQ9kaZwHbeZe+veHCGpA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-controllable-state": "1.2.3", - "@radix-ui/react-use-previous": "1.1.2", - "@radix-ui/react-use-size": "1.1.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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tabs": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.16.tgz", - "integrity": "sha512-v3Ab2l7z6U7tRB4xA0IyKdq0OsqaO1o9ZjsIEoKKnSZ/l96mZz8aCTX0NCXw+YVHJXr8Km4d+Mn6/Q8YjXa+gw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-presence": "1.1.6", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-roving-focus": "1.1.14", - "@radix-ui/react-use-controllable-state": "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-toast": { - "version": "1.2.18", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.18.tgz", - "integrity": "sha512-YNEnTHV47hPep+U0QvVM02OJNka9uygREc+k4Nh5VSZBg4MmE+myI442x3hCGfRpX7N2WSSYSJKws4gE+Z8lgg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-collection": "1.1.11", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-dismissable-layer": "1.1.14", - "@radix-ui/react-portal": "1.1.13", - "@radix-ui/react-presence": "1.1.6", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-controllable-state": "1.2.3", - "@radix-ui/react-use-layout-effect": "1.1.2", - "@radix-ui/react-visually-hidden": "1.2.7" - }, - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-toggle": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.13.tgz", - "integrity": "sha512-bI2ILJrzwgmAsH05TsJ9pVrzqQwAip7OM2/krqAdYn0R16bl86UPWbe5VPHsALat0EnqpV01cGtkleaUKPNdNg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-controllable-state": "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-toggle-group": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.14.tgz", - "integrity": "sha512-TK1vusNKb8IRhF23FTbRgUNZ9zfs5rGIyI7LfR3h26p9LrQ060i0uW9QWeD8baZMddaaP0DBGlIa6pbZG+mitg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-roving-focus": "1.1.14", - "@radix-ui/react-toggle": "1.1.13", - "@radix-ui/react-use-controllable-state": "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-toolbar": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.14.tgz", - "integrity": "sha512-L/EkWVqlnj3lL2toHh4C7PwH2jxfa7OCq6lGfXSCii99ve2S4Ux5rc9HnOa7LN9exHa/Nl9kmCAmP9BuDPy5UA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-roving-focus": "1.1.14", - "@radix-ui/react-separator": "1.1.11", - "@radix-ui/react-toggle-group": "1.1.14" - }, - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.11.tgz", - "integrity": "sha512-8XZ6Py3y3W2nEzAUGCN5cfVKaUi+CVApcz1d6lrNVVf2hvYEixMRkq8k9ggPKnQUpRRuOV5avt8uvxViH2jLwA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-dismissable-layer": "1.1.14", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-popper": "1.3.2", - "@radix-ui/react-portal": "1.1.13", - "@radix-ui/react-presence": "1.1.6", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-slot": "1.3.0", - "@radix-ui/react-use-controllable-state": "1.2.3", - "@radix-ui/react-visually-hidden": "1.2.7" - }, - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.2.tgz", - "integrity": "sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.3.tgz", - "integrity": "sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-effect-event": "0.0.3", - "@radix-ui/react-use-layout-effect": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-effect-event": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.3.tgz", - "integrity": "sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-escape-keydown": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.3.tgz", - "integrity": "sha512-3wEkMiPHXha/2VadZ68rYBcmYnPINVGl4Y3gtcM7fKRjANk0OscK+cdqBgUWdozb7YJxsh0vefM7vgAMHXOjqg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-is-hydrated": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.1.tgz", - "integrity": "sha512-qwOiz4Tjo8CNnrOLAYUMXeZwDzXgXpvK4TKQPmWLECM9XoWvA6+0Z2/7Ag3A4ivjS4ovbLJPbskkxioFyBhr8A==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.2.tgz", - "integrity": "sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-previous": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.2.tgz", - "integrity": "sha512-IGBQPtRFdhN6MQ8dbegVmBq1LVZluya3F1jWY+puIcQC3MHctRwTDSBWCkL/3ZcnMJLTMJ++Z+ktmvg0F89iCw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-rect": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.2.tgz", - "integrity": "sha512-d8a+bBY/FxikNPlgJJoaBHZX+zKVbWHYJGTLnLvveQgFSTntkGdEKv3JDtHrMS0DNYpllz2nRsTLGLKYttbpmw==", - "license": "MIT", - "dependencies": { - "@radix-ui/rect": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-size": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.2.tgz", - "integrity": "sha512-giWQp+4mxjBPt4KZ0MmyuykFNWfbDxKt4x+fPkRYmgRFJSbCZFzUglvMb/Kjn38tm10YP4ufiQZDx3zna4LU6w==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-visually-hidden": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.7.tgz", - "integrity": "sha512-1wNZBggTDK3GRuuQ6nP4k2yi7a6l7I5qbMPbZcRsrGsGVead/f/d5FhEzUvqFs0bcrDLx7n1zKQ3JvLR6whaaw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.7" - }, - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/rect": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.2.tgz", - "integrity": "sha512-xnXE7wG13PI+cxieVssYXlQJuYVRhH9NBoxt3KNwzghDIA69GMm7d4wXRouHIYjE+KvS6U/MsMO73NdS2MH9ZA==", - "license": "MIT" - }, "node_modules/@rc-component/async-validator": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.1.0.tgz", @@ -4718,9 +3285,9 @@ "license": "MIT" }, "node_modules/@swc/helpers": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.21.tgz", - "integrity": "sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==", + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.8.0" @@ -5248,9 +3815,9 @@ } }, "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", "dev": true, "license": "MIT", "optional": true, @@ -5474,7 +4041,7 @@ "version": "18.3.7", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", - "devOptional": true, + "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "^18.0.0" @@ -7325,9 +5892,9 @@ } }, "node_modules/date-fns": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", - "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.4.0.tgz", + "integrity": "sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==", "license": "MIT", "funding": { "type": "github", @@ -7464,12 +6031,6 @@ "node": ">=8" } }, - "node_modules/detect-node-es": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", - "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", - "license": "MIT" - }, "node_modules/devlop": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", @@ -8688,15 +7249,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-nonce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", - "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -10423,9 +8975,9 @@ } }, "node_modules/lru-cache": { - "version": "11.3.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.6.tgz", - "integrity": "sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==", + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -11593,15 +10145,6 @@ } } }, - "node_modules/next/node_modules/@swc/helpers": { - "version": "0.5.15", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", - "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.8.0" - } - }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -12144,9 +10687,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", "engines": { @@ -12317,9 +10860,9 @@ "license": "MIT" }, "node_modules/property-information": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", - "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", "license": "MIT", "funding": { "type": "github", @@ -12357,83 +10900,6 @@ ], "license": "MIT" }, - "node_modules/radix-ui": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/radix-ui/-/radix-ui-1.6.1.tgz", - "integrity": "sha512-QXDXJtB6sK83mLASONYUZCauatcWb+knFviFpN1EhtdbbmlsRmzCLrbZSKztnNiem2KOHIBbiDbauVB7SORXMw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-accessible-icon": "1.1.11", - "@radix-ui/react-accordion": "1.2.15", - "@radix-ui/react-alert-dialog": "1.1.18", - "@radix-ui/react-arrow": "1.1.11", - "@radix-ui/react-aspect-ratio": "1.1.11", - "@radix-ui/react-avatar": "1.2.1", - "@radix-ui/react-checkbox": "1.3.6", - "@radix-ui/react-collapsible": "1.1.15", - "@radix-ui/react-collection": "1.1.11", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-context-menu": "2.3.2", - "@radix-ui/react-dialog": "1.1.18", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.14", - "@radix-ui/react-dropdown-menu": "2.1.19", - "@radix-ui/react-focus-guards": "1.1.4", - "@radix-ui/react-focus-scope": "1.1.11", - "@radix-ui/react-form": "0.1.11", - "@radix-ui/react-hover-card": "1.1.18", - "@radix-ui/react-label": "2.1.11", - "@radix-ui/react-menu": "2.1.19", - "@radix-ui/react-menubar": "1.1.19", - "@radix-ui/react-navigation-menu": "1.2.17", - "@radix-ui/react-one-time-password-field": "0.1.11", - "@radix-ui/react-password-toggle-field": "0.1.6", - "@radix-ui/react-popover": "1.1.18", - "@radix-ui/react-popper": "1.3.2", - "@radix-ui/react-portal": "1.1.13", - "@radix-ui/react-presence": "1.1.6", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-progress": "1.1.11", - "@radix-ui/react-radio-group": "1.4.2", - "@radix-ui/react-roving-focus": "1.1.14", - "@radix-ui/react-scroll-area": "1.2.13", - "@radix-ui/react-select": "2.3.2", - "@radix-ui/react-separator": "1.1.11", - "@radix-ui/react-slider": "1.4.2", - "@radix-ui/react-slot": "1.3.0", - "@radix-ui/react-switch": "1.3.2", - "@radix-ui/react-tabs": "1.1.16", - "@radix-ui/react-toast": "1.2.18", - "@radix-ui/react-toggle": "1.1.13", - "@radix-ui/react-toggle-group": "1.1.14", - "@radix-ui/react-toolbar": "1.1.14", - "@radix-ui/react-tooltip": "1.2.11", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-controllable-state": "1.2.3", - "@radix-ui/react-use-effect-event": "0.0.3", - "@radix-ui/react-use-escape-keydown": "1.1.3", - "@radix-ui/react-use-is-hydrated": "0.1.1", - "@radix-ui/react-use-layout-effect": "1.1.2", - "@radix-ui/react-use-size": "1.1.2", - "@radix-ui/react-visually-hidden": "1.2.7" - }, - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, "node_modules/rc-cascader": { "version": "3.34.0", "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.34.0.tgz", @@ -13165,53 +11631,6 @@ "react": ">=18" } }, - "node_modules/react-remove-scroll": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", - "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", - "license": "MIT", - "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" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-remove-scroll-bar": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", - "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", - "license": "MIT", - "dependencies": { - "react-style-singleton": "^2.2.2", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/react-smooth": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", @@ -13244,28 +11663,6 @@ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, - "node_modules/react-style-singleton": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", - "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", - "license": "MIT", - "dependencies": { - "get-nonce": "^1.0.0", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/react-syntax-highlighter": { "version": "15.6.6", "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.6.6.tgz", @@ -13588,6 +11985,12 @@ "node": ">=0.10.0" } }, + "node_modules/reselect": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.2.0.tgz", + "integrity": "sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==", + "license": "MIT" + }, "node_modules/resize-observer-polyfill": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", @@ -13805,9 +12208,9 @@ } }, "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "devOptional": true, "license": "ISC", "bin": { @@ -14975,49 +13378,6 @@ "dev": true, "license": "MIT" }, - "node_modules/use-callback-ref": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", - "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/use-sidecar": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", - "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", - "license": "MIT", - "dependencies": { - "detect-node-es": "^1.1.0", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/use-sync-external-store": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", diff --git a/ui/litellm-dashboard/package.json b/ui/litellm-dashboard/package.json index cf2c234938d..1b0ce315e4d 100644 --- a/ui/litellm-dashboard/package.json +++ b/ui/litellm-dashboard/package.json @@ -26,6 +26,7 @@ "dependencies": { "@ant-design/cssinjs": "1.24.0", "@anthropic-ai/sdk": "0.92.0", + "@base-ui/react": "^1.6.0", "@headlessui/tailwindcss": "0.2.2", "@heroicons/react": "1.0.6", "@tanstack/react-pacer": "0.2.0", @@ -35,7 +36,7 @@ "@types/papaparse": "5.5.2", "antd": "5.29.3", "cva": "1.0.0-beta.4", - "date-fns": "3.6.0", + "date-fns": "^4.4.0", "dayjs": "1.11.19", "jwt-decode": "4.0.0", "lucide-react": "0.513.0", @@ -43,7 +44,6 @@ "next": "16.2.6", "openai": "4.104.0", "papaparse": "5.5.3", - "radix-ui": "1.6.1", "react": "18.3.1", "react-copy-to-clipboard": "5.1.1", "react-dom": "18.3.1", @@ -96,7 +96,8 @@ "braces": "3.0.3", "axios": "1.13.6", "postcss": "8.5.13", - "esbuild": "0.28.1" + "esbuild": "0.28.1", + "date-fns": "^4.4.0" }, "engines": { "node": ">=20.9.0", diff --git a/ui/litellm-dashboard/src/app/(dashboard)/api-reference/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/api-reference/page.tsx index a4a4d3d0f43..42cf094f0bb 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/api-reference/page.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/api-reference/page.tsx @@ -1,6 +1,7 @@ "use client"; import APIReferenceView from "@/app/(dashboard)/api-reference/APIReferenceView"; +import { DeprecationBanner } from "@/components/DeprecationBanner"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import useProxySettings from "@/app/(dashboard)/hooks/proxySettings/useProxySettings"; @@ -8,7 +9,12 @@ const APIReferencePage = () => { const { accessToken } = useAuthorized(); const proxySettings = useProxySettings(accessToken); - return ; + return ( + <> + + + + ); }; export default APIReferencePage; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/memory/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/memory/page.tsx index 6eb0fb57143..031a027d518 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/memory/page.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/memory/page.tsx @@ -1,9 +1,15 @@ "use client"; import { MemoryView } from "./components/MemoryView"; +import { DeprecationBanner } from "@/components/DeprecationBanner"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; export default function Memory() { const { accessToken, userRole, userId } = useAuthorized(); - return ; + return ( + <> + + + + ); } diff --git a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.test.tsx index b4f95efade7..ce6dd0879fa 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.test.tsx @@ -126,7 +126,7 @@ describe("ModelsAndEndpointsView", () => { expect(await findByText("Model Management", {}, { timeout: 10000 })).toBeInTheDocument(); }); - it("should show Missing provider banner by default", async () => { + it("should show Cost Optimization feedback banner by default", async () => { localStorageMock.clear(); const queryClient = createQueryClient(); const { findByText } = render( @@ -134,10 +134,10 @@ describe("ModelsAndEndpointsView", () => { , ); - expect(await findByText("Missing a provider?", {}, { timeout: 10000 })).toBeInTheDocument(); + expect(await findByText("Help shape cost optimization", {}, { timeout: 10000 })).toBeInTheDocument(); }); - it("should hide Missing provider banner when dismiss button is clicked and persist to localStorage", async () => { + it("should hide Cost Optimization feedback banner when dismiss button is clicked and persist to localStorage", async () => { localStorageMock.clear(); const queryClient = createQueryClient(); const { findByText, queryByText, container } = render( @@ -147,7 +147,7 @@ describe("ModelsAndEndpointsView", () => { ); // Wait for banner to appear - expect(await findByText("Missing a provider?", {}, { timeout: 10000 })).toBeInTheDocument(); + expect(await findByText("Help shape cost optimization", {}, { timeout: 10000 })).toBeInTheDocument(); // Find and click dismiss button (X button) const dismissButton = container.querySelector('button[aria-label="Dismiss banner"]'); @@ -155,15 +155,15 @@ describe("ModelsAndEndpointsView", () => { fireEvent.click(dismissButton!); // Banner should be hidden - expect(queryByText("Missing a provider?")).not.toBeInTheDocument(); + expect(queryByText("Help shape cost optimization")).not.toBeInTheDocument(); // LocalStorage should be updated - expect(localStorageMock.getItem("hideMissingProviderBanner")).toBe("true"); + expect(localStorageMock.getItem("hideCostOptimizationFeedbackBanner")).toBe("true"); }); - it("should show compact Request Provider button when banner is dismissed", async () => { + it("should keep Cost Optimization feedback banner hidden across remounts once dismissed", async () => { // Set localStorage to hide banner - localStorageMock.setItem("hideMissingProviderBanner", "true"); + localStorageMock.setItem("hideCostOptimizationFeedbackBanner", "true"); const queryClient = createQueryClient(); const { findByText, queryByText } = render( @@ -175,12 +175,7 @@ describe("ModelsAndEndpointsView", () => { await findByText("Model Management", {}, { timeout: 10000 }); // Banner should not be visible - expect(queryByText("Missing a provider?")).not.toBeInTheDocument(); - - // Compact Request Provider button should be visible in header - const requestProviderLinks = document.querySelectorAll('a[href="https://models.litellm.ai/?request=true"]'); - // There should be a compact button when banner is hidden - expect(requestProviderLinks.length).toBeGreaterThan(0); + expect(queryByText("Help shape cost optimization")).not.toBeInTheDocument(); }); it("should pass model IDs (not model names) to HealthCheckComponent as all_models_on_proxy", async () => { diff --git a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx index b24c376c9e0..560a4ec15d0 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx @@ -4,6 +4,7 @@ import { useModelsInfo } from "@/app/(dashboard)/hooks/models/useModels"; import { useUISettings } from "@/app/(dashboard)/hooks/uiSettings/useUISettings"; import { useUpdateRetryPolicy } from "@/app/(dashboard)/hooks/routerSettings/useUpdateRetryPolicy"; import AllModelsTab from "@/app/(dashboard)/models-and-endpoints/components/AllModelsTab"; +import CostOptimizationFeedbackBanner from "@/components/molecules/cost_optimization_feedback_banner"; import ModelRetrySettingsTab from "@/app/(dashboard)/models-and-endpoints/components/ModelRetrySettingsTab"; import PriceDataManagementTab from "@/app/(dashboard)/models-and-endpoints/components/PriceDataManagementTab"; import { handleAddModelSubmit } from "@/components/add_model/handle_add_model_submit"; @@ -19,7 +20,6 @@ import { useQueryClient } from "@tanstack/react-query"; import { Col, Grid, Icon, Tab, TabGroup, TabList, TabPanel, TabPanels } from "@tremor/react"; import type { UploadProps } from "antd"; import { Form } from "antd"; -import { PlusCircleOutlined } from "@ant-design/icons"; import React, { useCallback, useEffect, useMemo, useState } from "react"; import AddModelTab from "../../../components/add_model/add_model_tab"; import HealthCheckComponent from "../../../components/model_dashboard/HealthCheckComponent"; @@ -70,12 +70,6 @@ const ModelsAndEndpointsView: React.FC = ({ premiumUser, te const [selectedTeamId, setSelectedTeamId] = useState(null); const [selectedTabIndex, setSelectedTabIndex] = useState(0); const [healthCurrentPage, setHealthCurrentPage] = useState(1); - const [showMissingProviderBanner, setShowMissingProviderBanner] = useState(() => { - if (typeof window !== "undefined") { - return localStorage.getItem("hideMissingProviderBanner") !== "true"; - } - return true; - }); const queryClient = useQueryClient(); const { data: modelDataResponse, isLoading: isLoadingModels, refetch: refetchModels } = useModelsInfo(); @@ -315,75 +309,10 @@ const ModelsAndEndpointsView: React.FC = ({ premiumUser, te

Add and manage models for the proxy

)} - {!showMissingProviderBanner && ( - - - Request Provider - - )} - {/* Missing Provider Banner */} - {showMissingProviderBanner && ( -
-
- -
-
-

Missing a provider?

-

- The LiteLLM engineering team is constantly adding support for new LLM models, providers, endpoints. If - you don't see the one you need, let us know and we'll prioritize it. -

-
- - Request Provider - - - - - -
- )} + {/* Cost Optimization Feedback Banner */} + {selectedModelId && !isLoading ? ( + <> + + + ); } diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/page.tsx index c9bca86dc23..bd3c0e31456 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/page.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/page.tsx @@ -6,6 +6,7 @@ import ChatUI from "@/app/(dashboard)/playground/components/chat_ui/ChatUI"; import CompareUI from "@/app/(dashboard)/playground/components/compareUI/CompareUI"; import ComplianceUI from "@/app/(dashboard)/playground/components/complianceUI/ComplianceUI"; import { TabGroup, TabList, Tab, TabPanels, TabPanel } from "@tremor/react"; +import { DeprecationBanner } from "@/components/DeprecationBanner"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import { fetchProxySettings } from "@/utils/proxyUtils"; @@ -61,6 +62,7 @@ export default function PlaygroundPage() { + ; + return ( + <> + + + + ); } diff --git a/ui/litellm-dashboard/src/app/(dashboard)/workflows/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/workflows/page.tsx index 4b3d88f44fd..89dd30f7392 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/workflows/page.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/workflows/page.tsx @@ -1,9 +1,15 @@ "use client"; import WorkflowRuns from "./WorkflowRuns"; +import { DeprecationBanner } from "@/components/DeprecationBanner"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; export default function Workflows() { const { accessToken } = useAuthorized(); - return ; + return ( + <> + + + + ); } diff --git a/ui/litellm-dashboard/src/app/chat/page.tsx b/ui/litellm-dashboard/src/app/chat/page.tsx index 57bb740fbde..ae86ca6e926 100644 --- a/ui/litellm-dashboard/src/app/chat/page.tsx +++ b/ui/litellm-dashboard/src/app/chat/page.tsx @@ -403,32 +403,34 @@ export default function ChatConversationPage() { if (!open) setModelSearchText(""); }} > - - - + + {selectedModel ? ( + <> + {(() => { + const provider = getProviderFromModelName(selectedModel); + const { logo } = provider ? getProviderLogoAndName(provider) : { logo: "" }; + return logo ? ( + { + (e.currentTarget as HTMLImageElement).style.display = "none"; + }} + /> + ) : null; + })()} + {selectedModel} + + ) : ( + Select model + )} + + + } + /> {modelSelectorContent} @@ -456,14 +458,16 @@ export default function ChatConversationPage() {
{modelSelectorTrigger} - - - + + + {selectedMCPServers.length > 0 && ( + {selectedMCPServers.length} + )} + + } + /> = ({ featureName }) => ( + + {`${featureName} is one of several experimental features we're considering removing, potentially as early as ${DEPRECATION_TARGET_DATE}. This list is a draft and is not final. If you rely on this feature, please share feedback on the `} + + deprecation discussion + + . + + } + type="info" + showIcon + closable + style={{ marginBottom: 16 }} + /> +); diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/LoggingSettings/LoggingSettings.test.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/LoggingSettings/LoggingSettings.test.tsx index 1adf8039e6b..d90720df3c2 100644 --- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/LoggingSettings/LoggingSettings.test.tsx +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/LoggingSettings/LoggingSettings.test.tsx @@ -215,19 +215,6 @@ describe("LoggingSettings", () => { expect(saveButton.className).toContain("ant-btn-loading"); }); - it("should disable save button while config is loading", () => { - mockUseProxyConfig.mockReturnValue({ - data: undefined, - isLoading: true, - refetch: mockRefetch, - } as any); - - renderWithProviders(); - - const saveButton = screen.getByRole("button", { name: "Save Settings" }); - expect(saveButton).toBeDisabled(); - }); - it("should render form with initial values from config data", () => { mockUseProxyConfig.mockReturnValue({ data: [ @@ -261,6 +248,48 @@ describe("LoggingSettings", () => { expect(retentionInput).toHaveValue("30d"); }); + it("should reflect persisted values that arrive after the initial loading render", async () => { + mockUseProxyConfig.mockReturnValue({ + data: undefined, + isLoading: true, + refetch: mockRefetch, + } as unknown as ReturnType); + + const { rerender } = renderWithProviders(); + + expect(screen.queryByRole("switch")).not.toBeInTheDocument(); + + mockUseProxyConfig.mockReturnValue({ + data: [ + { + field_name: "store_prompts_in_spend_logs", + field_type: "bool", + field_description: "Store prompts in spend logs", + field_value: true, + stored_in_db: true, + field_default_value: false, + }, + { + field_name: "maximum_spend_logs_retention_period", + field_type: "string", + field_description: "Maximum retention period", + field_value: "30d", + stored_in_db: true, + field_default_value: undefined, + }, + ], + isLoading: false, + refetch: mockRefetch, + } as unknown as ReturnType); + + rerender(); + + await waitFor(() => { + expect(screen.getByRole("switch")).toBeChecked(); + }); + expect(screen.getByPlaceholderText("e.g., 7d, 30d")).toHaveValue("30d"); + }); + it("should show skeleton loaders when config is loading", () => { mockUseProxyConfig.mockReturnValue({ data: undefined, @@ -272,6 +301,7 @@ describe("LoggingSettings", () => { expect(screen.queryByRole("switch")).not.toBeInTheDocument(); expect(screen.queryByPlaceholderText("e.g., 7d, 30d")).not.toBeInTheDocument(); + expect(screen.queryByRole("button", { name: "Save Settings" })).not.toBeInTheDocument(); const skeletons = document.querySelectorAll(".ant-skeleton"); expect(skeletons.length).toBeGreaterThan(0); diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/LoggingSettings/LoggingSettings.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/LoggingSettings/LoggingSettings.tsx index 1c49035d36b..0d156a8dc10 100644 --- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/LoggingSettings/LoggingSettings.tsx +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/LoggingSettings/LoggingSettings.tsx @@ -21,7 +21,6 @@ const LoggingSettings: React.FC = () => { const { mutate, isPending } = useStoreRequestInSpendLogs(); const { mutate: deleteField, isPending: isDeletingField } = useDeleteProxyConfigField(); const { data: proxyConfigData, isLoading: isLoadingConfig } = useProxyConfig(ConfigType.GENERAL_SETTINGS); - const storePromptsValue = Form.useWatch("store_prompts_in_spend_logs", form); const initialValues = useMemo(() => { if (!proxyConfigData) { @@ -82,53 +81,41 @@ const LoggingSettings: React.FC = () => { Proxy-wide settings that control how request and response data are written to spend logs. -
- f.field_name === "store_prompts_in_spend_logs")?.field_description || - "When enabled, prompts will be stored in spend logs for tracking and analysis purposes." - } - valuePropName="checked" - > - {isLoadingConfig ? ( - - ) : ( - form.setFieldValue("store_prompts_in_spend_logs", checked)} - /> - )} - + {isLoadingConfig ? ( + + ) : ( + + f.field_name === "store_prompts_in_spend_logs")?.field_description || + "When enabled, prompts will be stored in spend logs for tracking and analysis purposes." + } + valuePropName="checked" + > + + - f.field_name === "maximum_spend_logs_retention_period")?.field_description || - "Set the maximum retention period for spend logs (e.g., '7d' for 7 days, '30d' for 30 days). Leave empty for no limit." - } - > - {isLoadingConfig ? ( - - ) : ( + f.field_name === "maximum_spend_logs_retention_period") + ?.field_description || + "Set the maximum retention period for spend logs (e.g., '7d' for 7 days, '30d' for 30 days). Leave empty for no limit." + } + > } /> - )} - + - - - - + + + + + )} ); diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterTestPanel.test.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterTestPanel.test.tsx index 1302ac48376..280a28e4765 100644 --- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterTestPanel.test.tsx +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterTestPanel.test.tsx @@ -90,7 +90,7 @@ describe("MCPSemanticFilterTestPanel", () => { expect(screen.queryByText("Semantic filtering is disabled")).not.toBeInTheDocument(); }); - it("should display test results when testResult is provided", () => { + it("should display selected and filtered-out counts when testResult is provided", () => { const testResult: TestResult = { totalTools: 10, selectedTools: 3, @@ -98,11 +98,35 @@ describe("MCPSemanticFilterTestPanel", () => { }; render(); - expect(screen.getByText("3 tools selected")).toBeInTheDocument(); - expect(screen.getByText("Filtered from 10 available tools")).toBeInTheDocument(); + expect(screen.getByText("3 of 10 tools selected")).toBeInTheDocument(); + expect(screen.getByText("7 tools filtered out")).toBeInTheDocument(); expect(screen.getByText("wiki-fetch")).toBeInTheDocument(); expect(screen.getByText("github-search")).toBeInTheDocument(); expect(screen.getByText("slack-post")).toBeInTheDocument(); + expect(screen.queryByText(/more selected tools not shown/i)).not.toBeInTheDocument(); + }); + + it("should note how many selected tools are missing when the header list is incomplete", () => { + const testResult: TestResult = { + totalTools: 40, + selectedTools: 8, + tools: ["metrics_mcp-node_query_by_id", "metrics_mcp-latency_query_api", "inventory_mcp-site_lookup"], + }; + render(); + + expect(screen.getByText("+5 more selected tools not shown")).toBeInTheDocument(); + }); + + it("should surface a zero filtered-out count when the filter selected every tool", () => { + const testResult: TestResult = { + totalTools: 207, + selectedTools: 207, + tools: ["tool-a", "tool-b"], + }; + render(); + + expect(screen.getByText("207 of 207 tools selected")).toBeInTheDocument(); + expect(screen.getByText("0 tools filtered out")).toBeInTheDocument(); }); it("should not render the results section when testResult is null", () => { diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterTestPanel.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterTestPanel.tsx index d8dc675361a..74850020aa8 100644 --- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterTestPanel.tsx +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterTestPanel.tsx @@ -86,9 +86,9 @@ export default function MCPSemanticFilterTestPanel({
Results 0 ? "success" : "warning"} + message={`${testResult.selectedTools} of ${testResult.totalTools} tools selected`} + description={`${testResult.totalTools - testResult.selectedTools} tools filtered out`} showIcon style={{ marginBottom: 16 }} /> @@ -103,6 +103,11 @@ export default function MCPSemanticFilterTestPanel({ ))} + {testResult.selectedTools > testResult.tools.length && ( + + +{testResult.selectedTools - testResult.tools.length} more selected tools not shown + + )}
)} diff --git a/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.test.tsx b/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.test.tsx index 9fe39a0ad50..5de9ad17208 100644 --- a/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.test.tsx +++ b/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.test.tsx @@ -1,4 +1,5 @@ -import { renderWithProviders, screen } from "../../../tests/test-utils"; +import { renderWithProviders, screen, within } from "../../../tests/test-utils"; +import userEvent from "@testing-library/user-event"; import { vi } from "vitest"; import ComplexityRouterConfig from "./ComplexityRouterConfig"; @@ -49,4 +50,41 @@ describe("ComplexityRouterConfig", () => { expect(screen.getByText(/Score 0.35 - 0.60/)).toBeInTheDocument(); expect(screen.getByText(/Score > 0.60/)).toBeInTheDocument(); }); + + it("should render the custom technical keywords field", () => { + renderWithProviders(); + expect(screen.getByText("Custom Technical Keywords")).toBeInTheDocument(); + }); + + it("should display existing custom technical keywords as tags", () => { + renderWithProviders( + , + ); + expect(screen.getByText("udp")).toBeInTheDocument(); + expect(screen.getByText("kafka")).toBeInTheDocument(); + }); + + it("should call onCustomTechnicalKeywordsChange when a keyword is entered", async () => { + const user = userEvent.setup(); + const onCustomTechnicalKeywordsChange = vi.fn(); + renderWithProviders( + , + ); + const keywordsCard = screen.getByText("Custom Technical Keywords").closest(".ant-card") as HTMLElement; + const input = within(keywordsCard).getByRole("combobox"); + await user.type(input, "udp,"); + expect(onCustomTechnicalKeywordsChange).toHaveBeenCalledWith(["udp"]); + }); }); diff --git a/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx b/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx index d826f3df32c..5cf97114169 100644 --- a/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx +++ b/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx @@ -16,6 +16,8 @@ interface ComplexityRouterConfigProps { modelInfo: ModelGroup[]; value: ComplexityTiers; onChange: (tiers: ComplexityTiers) => void; + customTechnicalKeywords?: string[]; + onCustomTechnicalKeywordsChange?: (keywords: string[]) => void; } const TIER_DESCRIPTIONS: Record = { @@ -41,7 +43,13 @@ const TIER_DESCRIPTIONS: Record = ({ modelInfo, value, onChange }) => { +const ComplexityRouterConfig: React.FC = ({ + modelInfo, + value, + onChange, + customTechnicalKeywords, + onCustomTechnicalKeywordsChange, +}) => { // Prepare model options for dropdowns const modelOptions = modelInfo.map((model) => ({ value: model.model_group, @@ -105,6 +113,33 @@ const ComplexityRouterConfig: React.FC = ({ modelIn + +
+ + Custom Technical Keywords + + + + +
+ + Optional: add terms the built-in list misses (e.g., udp, kafka, terraform) + + onCustomTechnicalKeywordsChange?.(keywords)} + placeholder="Type a keyword and press Enter, or paste a comma-separated list" + tokenSeparators={[","]} + open={false} + suffixIcon={null} + style={{ width: "100%" }} + allowClear + /> +
+ + + How Classification Works diff --git a/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.tsx b/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.tsx index 5c44b260e84..1714cfb3907 100644 --- a/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.tsx +++ b/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.tsx @@ -55,6 +55,8 @@ const AddAutoRouterTab: React.FC = ({ form, handleOk, acc REASONING: "", }); + const [customTechnicalKeywords, setCustomTechnicalKeywords] = useState([]); + useEffect(() => { const fetchModelAccessGroups = async () => { const response = await modelAvailableCall(accessToken, "", "", false, null, true, true); @@ -127,6 +129,7 @@ const AddAutoRouterTab: React.FC = ({ form, handleOk, acc model_type: "complexity_router", complexity_router_config: { tiers: complexityTiers, + ...(customTechnicalKeywords.length > 0 && { custom_technical_keywords: customTechnicalKeywords }), }, model_access_group: currentFormValues.model_access_group, }; @@ -280,6 +283,8 @@ const AddAutoRouterTab: React.FC = ({ form, handleOk, acc onChange={(tiers) => { setComplexityTiers(tiers); }} + customTechnicalKeywords={customTechnicalKeywords} + onCustomTechnicalKeywordsChange={setCustomTechnicalKeywords} /> ) : ( diff --git a/ui/litellm-dashboard/src/components/chat/ChatMessages.tsx b/ui/litellm-dashboard/src/components/chat/ChatMessages.tsx index b9f4fb7c3bc..dc58973d4ca 100644 --- a/ui/litellm-dashboard/src/components/chat/ChatMessages.tsx +++ b/ui/litellm-dashboard/src/components/chat/ChatMessages.tsx @@ -150,21 +150,23 @@ function UserBubble({ message, onEdit, isStreaming }: UserBubbleProps) { >
{hovered && !isStreaming && onEdit && ( - + - - - + { + setEditValue(message.content); + setEditing(true); + }} + className="text-muted-foreground hover:text-foreground shrink-0" + > + + + } + />

Edit message

@@ -265,18 +267,20 @@ function CopyButton({ text }: { text: string }) { return (
- + - - - + + {copied ? : } + + } + />

{copied ? "Copied!" : "Copy"}

diff --git a/ui/litellm-dashboard/src/components/chat/ConversationList.tsx b/ui/litellm-dashboard/src/components/chat/ConversationList.tsx index 91c18c09935..da57b70d096 100644 --- a/ui/litellm-dashboard/src/components/chat/ConversationList.tsx +++ b/ui/litellm-dashboard/src/components/chat/ConversationList.tsx @@ -143,13 +143,15 @@ const ConversationRow: React.FC = ({ conv, isActive, onSel className="flex gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity shrink-0" onClick={(e) => e.stopPropagation()} > - + - - - + + + + } + />

Rename

@@ -157,15 +159,23 @@ const ConversationRow: React.FC = ({ conv, isActive, onSel
- + - - - - - + + + + } + /> + } + />

Delete

diff --git a/ui/litellm-dashboard/src/components/chat/MCPCredentialsTab.tsx b/ui/litellm-dashboard/src/components/chat/MCPCredentialsTab.tsx index f73d18081d8..4f80a941511 100644 --- a/ui/litellm-dashboard/src/components/chat/MCPCredentialsTab.tsx +++ b/ui/litellm-dashboard/src/components/chat/MCPCredentialsTab.tsx @@ -184,21 +184,23 @@ const MCPCredentialsTab: React.FC = ({ accessToken }) => { - - - + + {isRevoking ? ( + + ) : ( + + )} + + } + /> Revoke connection? diff --git a/ui/litellm-dashboard/src/components/general_settings.tsx b/ui/litellm-dashboard/src/components/general_settings.tsx index a7a8af2691e..5b8dec39505 100644 --- a/ui/litellm-dashboard/src/components/general_settings.tsx +++ b/ui/litellm-dashboard/src/components/general_settings.tsx @@ -161,6 +161,14 @@ const GeneralSettings: React.FC = ({ accessToken, user checked={value.field_value === true || value.field_value === "true"} onChange={(checked) => handleInputChange(value.field_name, checked)} /> + ) : value.field_type == "Float" ? ( + handleInputChange(value.field_name, newValue)} + /> ) : null} diff --git a/ui/litellm-dashboard/src/components/mcp_tools/MCPNetworkSettings.tsx b/ui/litellm-dashboard/src/components/mcp_tools/MCPNetworkSettings.tsx index 2fc9e91e0c1..00323465731 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/MCPNetworkSettings.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/MCPNetworkSettings.tsx @@ -1,6 +1,7 @@ import React, { useState, useEffect } from "react"; import { Select, Button, Card, Typography, Spin, Tag } from "antd"; import { SaveOutlined, PlusOutlined } from "@ant-design/icons"; +import { DeprecationBanner } from "../DeprecationBanner"; import { getGeneralSettingsCall, updateConfigFieldSetting, @@ -93,6 +94,7 @@ const MCPNetworkSettings: React.FC = ({ accessToken }) return (
+
Private IP Ranges

diff --git a/ui/litellm-dashboard/src/components/mcp_tools/MCPServerCard.test.tsx b/ui/litellm-dashboard/src/components/mcp_tools/MCPServerCard.test.tsx new file mode 100644 index 00000000000..8463a23cc16 --- /dev/null +++ b/ui/litellm-dashboard/src/components/mcp_tools/MCPServerCard.test.tsx @@ -0,0 +1,45 @@ +import React from "react"; +import { render, screen } from "@testing-library/react"; +import { describe, it, expect, vi } from "vitest"; +import MCPServerCard from "./MCPServerCard"; +import type { MCPServer } from "./types"; + +const baseServer: MCPServer = { + server_id: "srv-1", + server_name: "demo_server", + alias: "demo_server", + transport: "http", + url: "https://example.com/mcp", + auth_type: "oauth2", +} as MCPServer; + +function renderCard(overrides: Partial) { + render(); +} + +describe("MCPServerCard OAuth flow indicator", () => { + it("shows the 'OAuth flow not set' badge for an oauth2 server with no oauth2_flow", () => { + renderCard({ auth_type: "oauth2", oauth2_flow: null }); + expect(screen.getByText("OAuth flow not set")).toBeInTheDocument(); + }); + + it("does not show the badge once oauth2_flow is set (client_credentials)", () => { + renderCard({ auth_type: "oauth2", oauth2_flow: "client_credentials" }); + expect(screen.queryByText("OAuth flow not set")).not.toBeInTheDocument(); + }); + + it("does not show the badge once oauth2_flow is set (authorization_code)", () => { + renderCard({ auth_type: "oauth2", oauth2_flow: "authorization_code" }); + expect(screen.queryByText("OAuth flow not set")).not.toBeInTheDocument(); + }); + + it("does not show the badge for a non-oauth2 server", () => { + renderCard({ auth_type: "api_key", oauth2_flow: null }); + expect(screen.queryByText("OAuth flow not set")).not.toBeInTheDocument(); + }); + + it("does not show the badge for a delegate (PKCE passthrough) server", () => { + renderCard({ auth_type: "oauth2", oauth2_flow: null, delegate_auth_to_upstream: true }); + expect(screen.queryByText("OAuth flow not set")).not.toBeInTheDocument(); + }); +}); diff --git a/ui/litellm-dashboard/src/components/mcp_tools/MCPServerCard.tsx b/ui/litellm-dashboard/src/components/mcp_tools/MCPServerCard.tsx index 082024fc853..c16ad87980e 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/MCPServerCard.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/MCPServerCard.tsx @@ -8,7 +8,7 @@ import { MoreOutlined, ThunderboltOutlined, } from "@ant-design/icons"; -import type { MCPServer } from "./types"; +import { AUTH_TYPE, type MCPServer } from "./types"; import { getMaskedAndFullUrl } from "./utils"; const { Text } = Typography; @@ -57,6 +57,14 @@ const MCPServerCard: FC = ({ const transport = server.transport || "http"; const displayTransport = server.spec_path && transport !== "stdio" ? "openapi" : transport; const authType = server.auth_type || "none"; + // An oauth2 server with no persisted oauth2_flow was never classified as M2M vs + // interactive; flag it so an admin can set it from the edit page (see the OAuth + // Flow Type selector) instead of leaving LiteLLM to fall back to a default. + // Delegate (PKCE passthrough) servers authenticate upstream and route to + // passthrough regardless of oauth2_flow, so the classification does not apply to + // them and they are not flagged. + const oauthFlowUnset = + server.auth_type === AUTH_TYPE.OAUTH2 && !server.oauth2_flow && !server.delegate_auth_to_upstream; const status = server.status || "unknown"; const healthTone = HEALTH_TONE[status] ?? HEALTH_TONE.unknown; const isPublic = server.available_on_public_internet; @@ -203,6 +211,16 @@ const MCPServerCard: FC = ({ /> {displayTransport.toUpperCase()} {authType} + {oauthFlowUnset && ( + + + + + OAuth flow not set + + + + )} diff --git a/ui/litellm-dashboard/src/components/mcp_tools/TokenExchangeFormFields.tsx b/ui/litellm-dashboard/src/components/mcp_tools/TokenExchangeFormFields.tsx new file mode 100644 index 00000000000..9e1e1a85743 --- /dev/null +++ b/ui/litellm-dashboard/src/components/mcp_tools/TokenExchangeFormFields.tsx @@ -0,0 +1,149 @@ +import React from "react"; +import { Form, Input, Select, Tooltip } from "antd"; +import { InfoCircleOutlined } from "@ant-design/icons"; + +interface TokenExchangeFormFieldsProps { + isEditing?: boolean; +} + +const fieldClassName = "rounded-lg border-gray-300 focus:border-blue-500 focus:ring-blue-500"; + +const FieldLabel: React.FC<{ label: string; tooltip: string }> = ({ label, tooltip }) => ( + + {label} + + + + +); + +const TokenExchangeFormFields: React.FC = ({ isEditing = false }) => { + const placeholderSuffix = isEditing ? " (leave blank to keep existing)" : ""; + + return ( + <> + + } + name="token_exchange_profile" + {...(isEditing ? {} : { initialValue: "rfc8693" })} + > + + + + } + name="token_exchange_endpoint" + > + + + + } + name={["credentials", "client_id"]} + rules={[{ required: !isEditing, message: "Client ID is required for token exchange" }]} + > + + + + } + name={["credentials", "client_secret"]} + rules={[{ required: !isEditing, message: "Client Secret is required for token exchange" }]} + > + + + prev.token_exchange_profile !== cur.token_exchange_profile}> + {({ getFieldValue }) => { + const isEntraObo = getFieldValue("token_exchange_profile") === "entra_obo"; + return ( + <> + {!isEntraObo && ( + <> + + } + name="audience" + > + + + + } + name="subject_token_type" + > + + + + )} + /.default)." + : "Optional scopes to request during the token exchange." + } + /> + } + name={["credentials", "scopes"]} + rules={ + isEntraObo + ? [ + { + required: true, + message: "Microsoft Entra OBO requires a scope, e.g. api:///.default", + }, + ] + : [] + } + > + @@ -964,6 +988,8 @@ const CreateMCPServer: React.FC = ({ }} /> )} + + {isTokenExchangeAuthType && } ), }, diff --git a/ui/litellm-dashboard/src/components/mcp_tools/mcp_server_edit.test.tsx b/ui/litellm-dashboard/src/components/mcp_tools/mcp_server_edit.test.tsx index 8da77120c09..d07c0f44fd7 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/mcp_server_edit.test.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/mcp_server_edit.test.tsx @@ -65,12 +65,20 @@ vi.mock("./mcp_tool_configuration", () => ({ +

), })); @@ -299,6 +307,81 @@ describe("MCPServerEdit (delegate auth)", () => { }); }); +describe("MCPServerEdit (auth type switch)", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("clears stale oauth2 endpoint overrides when switching to token exchange", async () => { + vi.mocked(networking.updateMCPServer).mockResolvedValue({ + ...interactiveOAuthServer, + auth_type: "oauth2_token_exchange", + }); + + render( + , + ); + + await selectAntOption("Authentication", "OAuth Token Exchange (OBO)"); + + const saveButtons = screen.getAllByRole("button", { name: "Save Changes" }); + await act(async () => { + fireEvent.click(saveButtons[0]); + }); + + await waitFor(() => { + expect(networking.updateMCPServer).toHaveBeenCalledTimes(1); + }); + + const [, payload] = vi.mocked(networking.updateMCPServer).mock.calls[0]; + expect(payload.auth_type).toBe("oauth2_token_exchange"); + expect(payload.token_url).toBeNull(); + expect(payload.authorization_url).toBeNull(); + expect(payload.registration_url).toBeNull(); + }); + + it("keeps oauth2 endpoint overrides when the auth type is unchanged", async () => { + vi.mocked(networking.updateMCPServer).mockResolvedValue({ ...interactiveOAuthServer }); + + render( + , + ); + + const saveButtons = screen.getAllByRole("button", { name: "Save Changes" }); + await act(async () => { + fireEvent.click(saveButtons[0]); + }); + + await waitFor(() => { + expect(networking.updateMCPServer).toHaveBeenCalledTimes(1); + }); + + const [, payload] = vi.mocked(networking.updateMCPServer).mock.calls[0]; + expect(payload.auth_type).toBe("oauth2"); + expect(payload.token_url).toBe("https://idp.example.com/oauth/token"); + }); +}); + describe("MCPServerEdit (tool allowlist)", () => { beforeEach(() => { vi.clearAllMocks(); @@ -382,7 +465,7 @@ describe("MCPServerEdit (tool allowlist)", () => { it("saves tool overrides for legacy unrestricted servers", async () => { vi.mocked(networking.updateMCPServer).mockResolvedValue({ ...interactiveOAuthServer, - tool_name_to_display_name: { read_user: "Read User" }, + tool_name_to_display_name: { read_user: "ReadUser" }, tool_name_to_description: { read_user: "Reads users" }, }); @@ -416,9 +499,36 @@ describe("MCPServerEdit (tool allowlist)", () => { const [, payload] = vi.mocked(networking.updateMCPServer).mock.calls[0]; expect(payload.mcp_info.tool_allowlist_enforced).toBe(false); expect(payload.allowed_tools).toBeUndefined(); - expect(payload.tool_name_to_display_name).toEqual({ read_user: "Read User" }); + expect(payload.tool_name_to_display_name).toEqual({ read_user: "ReadUser" }); expect(payload.tool_name_to_description).toEqual({ read_user: "Reads users" }); }); + + it("blocks save and does not call the API when a tool display name contains a space", async () => { + render( + , + ); + + await act(async () => { + fireEvent.click(screen.getByRole("button", { name: "Set invalid tool override" })); + }); + + const saveButtons = screen.getAllByRole("button", { name: "Save Changes" }); + await act(async () => { + fireEvent.click(saveButtons[0]); + }); + + expect(networking.updateMCPServer).not.toHaveBeenCalled(); + }); }); describe("MCPServerEdit (interactive OAuth)", () => { @@ -1003,3 +1113,195 @@ describe("MCPServerEdit (OAuth token persistence on save)", () => { expect(mockSetToken).not.toHaveBeenCalled(); }); }); + +describe("MCPServerEdit oauth2_flow selector", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + async function saveAndGetPayload(server: Record) { + vi.mocked(networking.updateMCPServer).mockResolvedValue({ ...interactiveOAuthServer }); + + render( + , + ); + + const saveButtons = screen.getAllByRole("button", { name: "Save Changes" }); + await act(async () => { + fireEvent.click(saveButtons[0]); + }); + + await waitFor(() => { + expect(networking.updateMCPServer).toHaveBeenCalledTimes(1); + }); + + const [, payload] = vi.mocked(networking.updateMCPServer).mock.calls[0]; + return payload; + } + + it("never writes oauth2_flow for a legacy null-flow server with a token_url", async () => { + const payload = await saveAndGetPayload({ + token_url: "https://idp.example.com/oauth/token", + oauth2_flow: null, + }); + expect(payload).not.toHaveProperty("oauth2_flow"); + }); + + it("re-writes an explicit client_credentials row with its own prefilled value", async () => { + const payload = await saveAndGetPayload({ + oauth2_flow: "client_credentials", + token_url: "https://idp.example.com/oauth/token", + }); + expect(payload.oauth2_flow).toBe("client_credentials"); + }); + + it("re-writes the DCR authorization_code stamp with its own prefilled value", async () => { + const payload = await saveAndGetPayload({ + oauth2_flow: "authorization_code", + token_url: "https://idp.example.com/oauth/token", + }); + expect(payload.oauth2_flow).toBe("authorization_code"); + }); + + it("persists client_credentials when the admin selects M2M on a legacy null-flow row", async () => { + vi.mocked(networking.updateMCPServer).mockResolvedValue({ ...interactiveOAuthServer }); + + render( + , + ); + + await selectAntOption("OAuth Flow Type", "Machine-to-Machine (M2M)"); + + const saveButtons = screen.getAllByRole("button", { name: "Save Changes" }); + await act(async () => { + fireEvent.click(saveButtons[0]); + }); + + await waitFor(() => { + expect(networking.updateMCPServer).toHaveBeenCalledTimes(1); + }); + + const [, payload] = vi.mocked(networking.updateMCPServer).mock.calls[0]; + expect(payload.oauth2_flow).toBe("client_credentials"); + }); + + it("persists authorization_code when the admin selects Interactive on a legacy null-flow row", async () => { + vi.mocked(networking.updateMCPServer).mockResolvedValue({ ...interactiveOAuthServer }); + + render( + , + ); + + await selectAntOption("OAuth Flow Type", "Interactive (PKCE)"); + + const saveButtons = screen.getAllByRole("button", { name: "Save Changes" }); + await act(async () => { + fireEvent.click(saveButtons[0]); + }); + + await waitFor(() => { + expect(networking.updateMCPServer).toHaveBeenCalledTimes(1); + }); + + const [, payload] = vi.mocked(networking.updateMCPServer).mock.calls[0]; + expect(payload.oauth2_flow).toBe("authorization_code"); + }); +}); + +describe("MCPServerEdit OAuth flow prefill display", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + function renderEdit(server: Record) { + render( + , + ); + } + + it("shows the placeholder and preselects nothing for a null-flow server (prompts the user to define it)", () => { + renderEdit({ oauth2_flow: null, token_url: "https://idp.example.com/oauth/token" }); + + // The select renders its placeholder (undefined value), not a guessed option. + expect(screen.getByText("Select OAuth flow")).toBeInTheDocument(); + // Neither flow is preselected as the current value. + expect(screen.queryByText("Machine-to-Machine (M2M)")).not.toBeInTheDocument(); + expect(screen.queryByText("Interactive (PKCE)")).not.toBeInTheDocument(); + }); + + it("prefills Machine-to-Machine (M2M) for a stored client_credentials server", () => { + renderEdit({ oauth2_flow: "client_credentials" }); + + expect(screen.getByText("Machine-to-Machine (M2M)")).toBeInTheDocument(); + expect(screen.queryByText("Select OAuth flow")).not.toBeInTheDocument(); + }); + + it("prefills Interactive (PKCE) for a stored authorization_code server", () => { + renderEdit({ oauth2_flow: "authorization_code" }); + + expect(screen.getByText("Interactive (PKCE)")).toBeInTheDocument(); + expect(screen.queryByText("Select OAuth flow")).not.toBeInTheDocument(); + }); + + it("warns when a server has no OAuth flow set", () => { + renderEdit({ oauth2_flow: null, token_url: "https://idp.example.com/oauth/token" }); + + expect(screen.getByText("This server has no OAuth flow set")).toBeInTheDocument(); + }); + + it("does not warn when the flow is already set", () => { + renderEdit({ oauth2_flow: "client_credentials" }); + + expect(screen.queryByText("This server has no OAuth flow set")).not.toBeInTheDocument(); + }); + + it("does not warn for a delegate (PKCE passthrough) server even with no flow set", () => { + renderEdit({ oauth2_flow: null, delegate_auth_to_upstream: true }); + + expect(screen.queryByText("This server has no OAuth flow set")).not.toBeInTheDocument(); + }); + + it("clears the warning once the admin selects a flow", async () => { + renderEdit({ oauth2_flow: null, token_url: "https://idp.example.com/oauth/token" }); + + expect(screen.getByText("This server has no OAuth flow set")).toBeInTheDocument(); + + await selectAntOption("OAuth Flow Type", "Machine-to-Machine (M2M)"); + + await waitFor(() => { + expect(screen.queryByText("This server has no OAuth flow set")).not.toBeInTheDocument(); + }); + }); +}); diff --git a/ui/litellm-dashboard/src/components/mcp_tools/mcp_server_edit.tsx b/ui/litellm-dashboard/src/components/mcp_tools/mcp_server_edit.tsx index 86e898809e5..faf7737e995 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/mcp_server_edit.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/mcp_server_edit.tsx @@ -1,15 +1,17 @@ import React, { useState, useEffect } from "react"; -import { Form, Select, Button as AntdButton, Tooltip, Input, InputNumber } from "antd"; +import { Form, Select, Button as AntdButton, Tooltip, Input, InputNumber, Alert } from "antd"; import { InfoCircleOutlined } from "@ant-design/icons"; import { Button, TabGroup, TabList, Tab, TabPanels, TabPanel } from "@tremor/react"; import { AUTH_TYPE, OAUTH_FLOW, MCP_OAUTH2_FLOW_M2M, + MCP_OAUTH2_FLOW_INTERACTIVE, MCPServer, MCPServerCostInfo, TRANSPORT, getMcpOAuthMode, + oauth2FlowToFormValue, } from "./types"; import { updateMCPServer, listMCPTools, storeMCPOAuthUserCredential } from "../networking"; import { getToken, isTokenValid, setToken } from "@/utils/mcpTokenStore"; @@ -18,10 +20,17 @@ import MCPServerCostConfig from "./mcp_server_cost_config"; import MCPPermissionManagement from "./MCPPermissionManagement"; import MCPToolConfiguration from "./mcp_tool_configuration"; import StdioConfiguration from "./StdioConfiguration"; +import TokenExchangeFormFields from "./TokenExchangeFormFields"; import MCPLogoSelector from "./MCPLogoSelector"; import EnvVarsSection from "./EnvVarsSection"; import TokenEndpointAuthMethodField from "./TokenEndpointAuthMethodField"; -import { validateMCPServerUrl, validateMCPServerName, normalizeEnvVars } from "./utils"; +import { + validateMCPServerUrl, + validateMCPServerName, + normalizeEnvVars, + normalizeToolOverrideMap, + TOOL_DISPLAY_NAME_PATTERN, +} from "./utils"; import NotificationsManager from "../molecules/notifications_manager"; import { useMcpOAuthFlow } from "@/hooks/useMcpOAuthFlow"; import { getSecureItem, setSecureItem } from "@/utils/secureStorage"; @@ -36,7 +45,12 @@ interface MCPServerEditProps { } const AUTH_TYPES_REQUIRING_AUTH_VALUE = [AUTH_TYPE.API_KEY, AUTH_TYPE.BEARER_TOKEN, AUTH_TYPE.TOKEN, AUTH_TYPE.BASIC]; -const AUTH_TYPES_REQUIRING_CREDENTIALS = [...AUTH_TYPES_REQUIRING_AUTH_VALUE, AUTH_TYPE.OAUTH2, AUTH_TYPE.AWS_SIGV4]; +const AUTH_TYPES_REQUIRING_CREDENTIALS = [ + ...AUTH_TYPES_REQUIRING_AUTH_VALUE, + AUTH_TYPE.OAUTH2, + AUTH_TYPE.OAUTH2_TOKEN_EXCHANGE, + AUTH_TYPE.AWS_SIGV4, +]; export const EDIT_OAUTH_UI_STATE_KEY = "litellm-mcp-oauth-edit-state"; const MCPServerEdit: React.FC = ({ @@ -67,9 +81,15 @@ const MCPServerEdit: React.FC = ({ const isMCPTransport = !isStdioTransport && !isOpenAPITransport; const shouldShowAuthValueField = authType ? AUTH_TYPES_REQUIRING_AUTH_VALUE.includes(authType) : false; const isOAuthAuthType = authType === AUTH_TYPE.OAUTH2; + const isTokenExchangeAuthType = authType === AUTH_TYPE.OAUTH2_TOKEN_EXCHANGE; const isAwsSigV4AuthType = authType === AUTH_TYPE.AWS_SIGV4; const oauthFlowTypeValue = Form.useWatch("oauth_flow_type", form) as string | undefined; const isM2MFlow = isOAuthAuthType && oauthFlowTypeValue === OAUTH_FLOW.M2M; + // Watch reflects a live toggle when the delegate switch is mounted; fall back to + // the stored value otherwise (useWatch returns undefined for an unmounted field, + // the same trap the oauth_flow_type field originally hit). + const delegateAuthWatched = Form.useWatch("delegate_auth_to_upstream", form) as boolean | undefined; + const isDelegateAuth = delegateAuthWatched ?? Boolean(mcpServer.delegate_auth_to_upstream); // Watch form fields that affect tool fetching const currentUrl = Form.useWatch("url", form); @@ -219,7 +239,7 @@ const MCPServerEdit: React.FC = ({ static_headers: initialStaticHeaders, env_vars: initialEnvVars, extra_headers: mcpServer.extra_headers || [], - oauth_flow_type: mcpServer.token_url ? OAUTH_FLOW.M2M : OAUTH_FLOW.INTERACTIVE, + oauth_flow_type: oauth2FlowToFormValue(mcpServer.oauth2_flow), token_validation_json: mcpServer.token_validation ? JSON.stringify(mcpServer.token_validation, null, 2) : undefined, @@ -257,8 +277,8 @@ const MCPServerEdit: React.FC = ({ if (hasExistingToolAllowlist) { setAllowedTools(mcpServer.allowed_tools ?? []); } - setToolNameToDisplayName(mcpServer.tool_name_to_display_name ?? {}); - setToolNameToDescription(mcpServer.tool_name_to_description ?? {}); + setToolNameToDisplayName(normalizeToolOverrideMap(mcpServer.tool_name_to_display_name)); + setToolNameToDescription(normalizeToolOverrideMap(mcpServer.tool_name_to_description)); }, [mcpServer, hasExistingToolAllowlist]); useEffect(() => { @@ -449,6 +469,15 @@ const MCPServerEdit: React.FC = ({ const handleSave = async (values: Record) => { if (!accessToken) return; + const invalidDisplayName = Object.entries(toolNameToDisplayName).find( + ([, displayName]) => displayName && !TOOL_DISPLAY_NAME_PATTERN.test(displayName), + ); + if (invalidDisplayName) { + NotificationsManager.fromBackend( + `Tool display name "${invalidDisplayName[1]}" is invalid. Only letters, digits, underscores, and hyphens are allowed (no spaces).`, + ); + return; + } try { // Ensure access groups is always a string array const { @@ -617,6 +646,13 @@ const MCPServerEdit: React.FC = ({ // Remove UI-only fields stdio_config: undefined, env_json: undefined, + ...(mcpServer.auth_type === AUTH_TYPE.OAUTH2 && restValues.auth_type !== AUTH_TYPE.OAUTH2 + ? { authorization_url: null, token_url: null, registration_url: null } + : {}), + ...(mcpServer.auth_type === AUTH_TYPE.OAUTH2_TOKEN_EXCHANGE && + restValues.auth_type !== AUTH_TYPE.OAUTH2_TOKEN_EXCHANGE + ? { token_exchange_endpoint: null, audience: null, subject_token_type: null, token_exchange_profile: null } + : {}), server_id: mcpServer.server_id, mcp_info: { ...(mcpServer.mcp_info ?? {}), @@ -666,6 +702,12 @@ const MCPServerEdit: React.FC = ({ ? Boolean(oauthPassthroughRaw ?? mcpServer.oauth_passthrough) : false; })(), + ...(restValues.auth_type === AUTH_TYPE.OAUTH2 && restValues.oauth_flow_type + ? { + oauth2_flow: + restValues.oauth_flow_type === OAUTH_FLOW.M2M ? MCP_OAUTH2_FLOW_M2M : MCP_OAUTH2_FLOW_INTERACTIVE, + } + : {}), // Include token_validation when it is set (non-null) or when clearing an existing value ...(tokenValidation !== null || mcpServer.token_validation ? { token_validation: tokenValidation } : {}), }; @@ -689,7 +731,7 @@ const MCPServerEdit: React.FC = ({ delegate_auth_to_upstream: Boolean(delegateAuthToUpstreamRaw ?? mcpServer.delegate_auth_to_upstream), }); try { - if (oauthMode === "obo") { + if (oauthMode === "authorization_code") { const scope = oauthTokenResponse.scope; await storeMCPOAuthUserCredential(accessToken, mcpServer.server_id, { access_token: oauthTokenResponse.access_token, @@ -820,6 +862,7 @@ const MCPServerEdit: React.FC = ({ Token Basic Auth OAuth + OAuth Token Exchange (OBO) AWS SigV4 (Bedrock AgentCore MCPs) @@ -914,6 +957,31 @@ const MCPServerEdit: React.FC = ({ {!isStdioTransport && isOAuthAuthType && ( <> + + OAuth Flow Type + + + + + } + name="oauth_flow_type" + > + + + {!oauthFlowTypeValue && !isDelegateAuth && ( + + )} @@ -1087,6 +1155,8 @@ const MCPServerEdit: React.FC = ({ )} + {!isStdioTransport && isTokenExchangeAuthType && } + {!isStdioTransport && isAwsSigV4AuthType && ( <>

@@ -1246,7 +1316,8 @@ const MCPServerEdit: React.FC = ({ transport: transportType ?? mcpServer.transport, auth_type: currentAuthType ?? mcpServer.auth_type, mcp_info: mcpServer.mcp_info, - oauth_flow_type: currentTokenUrl ?? mcpServer.token_url ? OAUTH_FLOW.M2M : OAUTH_FLOW.INTERACTIVE, + oauth_flow_type: + oauthFlowTypeValue ?? oauth2FlowToFormValue(mcpServer.oauth2_flow) ?? OAUTH_FLOW.INTERACTIVE, static_headers: currentStaticHeaders ?? mcpServer.static_headers, credentials: currentCredentials, authorization_url: currentAuthorizationUrl ?? mcpServer.authorization_url, diff --git a/ui/litellm-dashboard/src/components/mcp_tools/mcp_tool_configuration.test.tsx b/ui/litellm-dashboard/src/components/mcp_tools/mcp_tool_configuration.test.tsx index 064e3dea614..cd8282d29ed 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/mcp_tool_configuration.test.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/mcp_tool_configuration.test.tsx @@ -109,4 +109,78 @@ describe("MCPToolConfiguration", () => { expect(screen.getAllByText("Disabled")).toHaveLength(2); }); }); + + it("shows a validation error for a display name containing a space", async () => { + const Wrapper = () => { + const [toolNameToDisplayName, setToolNameToDisplayName] = useState>({}); + + return ( + + ); + }; + + render(); + + fireEvent.click(screen.getByText("Flat List")); + fireEvent.click(screen.getAllByTitle("Edit display name and description")[0]); + + const input = screen.getByPlaceholderText("read_user"); + fireEvent.change(input, { target: { value: "Browse Repo Docs" } }); + + await waitFor(() => { + expect( + screen.getByText("Only letters, digits, underscores, and hyphens are allowed (no spaces)."), + ).toBeInTheDocument(); + }); + }); + + it("accepts a Bedrock-safe display name without showing a validation error", async () => { + const Wrapper = () => { + const [toolNameToDisplayName, setToolNameToDisplayName] = useState>({}); + + return ( + + ); + }; + + render(); + + fireEvent.click(screen.getByText("Flat List")); + fireEvent.click(screen.getAllByTitle("Edit display name and description")[0]); + + const input = screen.getByPlaceholderText("read_user"); + fireEvent.change(input, { target: { value: "browse_repo_docs" } }); + + await waitFor(() => { + expect( + screen.queryByText("Only letters, digits, underscores, and hyphens are allowed (no spaces)."), + ).not.toBeInTheDocument(); + }); + }); }); diff --git a/ui/litellm-dashboard/src/components/mcp_tools/mcp_tool_configuration.tsx b/ui/litellm-dashboard/src/components/mcp_tools/mcp_tool_configuration.tsx index a1061c10515..1ebc07eac86 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/mcp_tool_configuration.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/mcp_tool_configuration.tsx @@ -3,6 +3,7 @@ import { Card, Title, Text } from "@tremor/react"; import { ToolOutlined, CheckCircleOutlined, SearchOutlined, EditOutlined } from "@ant-design/icons"; import { Badge, Spin, Checkbox, Input, Radio } from "antd"; import McpCrudPermissionPanel from "./McpCrudPermissionPanel"; +import { TOOL_DISPLAY_NAME_PATTERN } from "./utils"; interface KeyTool { name: string; @@ -60,86 +61,98 @@ const ToolRow: React.FC = ({ onToggleExpand, onDisplayNameChange, onDescriptionChange, -}) => ( -

-
onToggle(tool.name)}> -
- onToggle(tool.name)} /> -
-
- {toolNameToDisplayName[tool.name] || tool.name} - - {isEnabled ? "Enabled" : "Disabled"} - - {toolNameToDisplayName[tool.name] && ( - - Custom name +}) => { + const displayNameValue = toolNameToDisplayName[tool.name] || ""; + const isDisplayNameInvalid = displayNameValue !== "" && !TOOL_DISPLAY_NAME_PATTERN.test(displayNameValue); + + return ( +
+
onToggle(tool.name)}> +
+ onToggle(tool.name)} /> +
+
+ {toolNameToDisplayName[tool.name] || tool.name} + + {isEnabled ? "Enabled" : "Disabled"} + {toolNameToDisplayName[tool.name] && ( + + Custom name + + )} +
+ {(toolNameToDescription[tool.name] || tool.description) && ( + + {toolNameToDescription[tool.name] || tool.description} + + )} + + {isEnabled ? "✓ Users can call this tool" : "✗ Users cannot call this tool"} + +
+ +
+
+ {isEditExpanded && ( +
e.stopPropagation()} + > +
+ Display Name + onDisplayNameChange(tool.name, e.target.value)} + status={isDisplayNameInvalid ? "error" : undefined} + /> + {isDisplayNameInvalid ? ( + + Only letters, digits, underscores, and hyphens are allowed (no spaces). + + ) : ( + + Override how this tool's name appears to users. Leave blank to use original. + )}
- {(toolNameToDescription[tool.name] || tool.description) && ( - - {toolNameToDescription[tool.name] || tool.description} +
+ Description + onDescriptionChange(tool.name, e.target.value)} + rows={2} + /> + + Override the tool description shown to users. Leave blank to use original. - )} - - {isEnabled ? "✓ Users can call this tool" : "✗ Users cannot call this tool"} - +
- -
+ )}
- {isEditExpanded && ( -
e.stopPropagation()} - > -
- Display Name - onDisplayNameChange(tool.name, e.target.value)} - /> - - Override how this tool's name appears to users. Leave blank to use original. - -
-
- Description - onDescriptionChange(tool.name, e.target.value)} - rows={2} - /> - - Override the tool description shown to users. Leave blank to use original. - -
-
- )} -
-); + ); +}; const MCPToolConfiguration: React.FC = ({ accessToken, diff --git a/ui/litellm-dashboard/src/components/mcp_tools/mcp_tools.tsx b/ui/litellm-dashboard/src/components/mcp_tools/mcp_tools.tsx index 4cd8acfc07e..e436732ba3b 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/mcp_tools.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/mcp_tools.tsx @@ -36,13 +36,13 @@ const MCPToolsViewer = ({ const [showHeaderInput, setShowHeaderInput] = useState(false); // PKCE passthrough holds a browser-side session token (sessionStorage) and - // gates tool listing behind it. OBO uses a backend-stored per-user token that - // the user must establish once via an interactive login; we gate on whether - // that DB credential exists. M2M uses the backend's own service token and - // needs no gate. + // gates tool listing behind it. authorization_code uses a backend-stored + // per-user token that the user must establish once via an interactive login; + // we gate on whether that DB credential exists. M2M uses the backend's own + // service token and needs no gate. const oauthMode = getMcpOAuthMode({ auth_type, oauth2_flow, delegate_auth_to_upstream }); const isPassthrough = oauthMode === "passthrough"; - const isObo = oauthMode === "obo"; + const isAuthorizationCode = oauthMode === "authorization_code"; const [oauthToken, setOauthToken] = useState(() => isPassthrough && isTokenValid(serverId, userID) ? getToken(serverId, userID)?.access_token ?? null : null, ); @@ -68,18 +68,18 @@ const MCPToolsViewer = ({ onSuccess: setOauthToken, }); - // OBO servers list tools using a per-user token the backend stores in the DB; + // authorization_code servers list tools using a per-user token the backend stores in the DB; // check whether the current user has a valid one so we can prompt them to // authorize when they don't (otherwise the backend silently returns no tools). const { - data: oboCredStatus, - isLoading: isLoadingOboCred, - isError: isOboCredError, - refetch: refetchOboCred, + data: authorizationCodeCredStatus, + isLoading: isLoadingAuthorizationCodeCred, + isError: isAuthorizationCodeCredError, + refetch: refetchAuthorizationCodeCred, } = useQuery({ queryKey: ["mcpOauthUserCredStatus", serverId, userID], queryFn: () => getMCPOAuthUserCredentialStatus(accessToken ?? "", serverId), - enabled: !!accessToken && isObo, + enabled: !!accessToken && isAuthorizationCode, staleTime: 30000, }); @@ -89,9 +89,12 @@ const MCPToolsViewer = ({ // the status check itself fails we can't confirm a credential, so surface the // Authorize gate rather than a silent empty tool list; re-authorizing only // overwrites the user's own row, so it is safe when a credential did exist. - const hasOboCred = !!oboCredStatus?.has_credential; - const oboNeedsAuth = isObo && !isLoadingOboCred && (isOboCredError || (!!oboCredStatus && !hasOboCred)); - const oboStatusLoading = isObo && isLoadingOboCred; + const hasAuthorizationCodeCred = !!authorizationCodeCredStatus?.has_credential; + const authorizationCodeNeedsAuth = + isAuthorizationCode && + !isLoadingAuthorizationCodeCred && + (isAuthorizationCodeCredError || (!!authorizationCodeCredStatus && !hasAuthorizationCodeCred)); + const authorizationCodeStatusLoading = isAuthorizationCode && isLoadingAuthorizationCodeCred; // Check if this server has extra headers configured const hasExtraHeaders = extraHeaders && extraHeaders.length > 0; @@ -105,7 +108,7 @@ const MCPToolsViewer = ({ // The backend's _get_mcp_server_auth_headers_from_headers() picks up the // x-mcp-{alias}-{header} pattern and forwards it to the upstream MCP server. // When no alias is available, fall back to x-mcp-auth (legacy but still supported). - // Passthrough only: OBO/M2M tokens are attached server-side, not from the browser. + // Passthrough only: authorization_code/token_exchange/M2M tokens are attached server-side, not from the browser. if (isPassthrough && oauthToken) { Object.assign(customHeaders, buildMcpPassthroughAuthHeader(serverAlias, oauthToken)); } @@ -158,9 +161,10 @@ const MCPToolsViewer = ({ } return result; }, - // Passthrough blocks until a browser session token exists; OBO blocks until + // Passthrough blocks until a browser session token exists; authorization_code blocks until // the user has a valid DB credential (else the backend returns no tools). - enabled: !!accessToken && (isPassthrough ? oauthToken !== null : isObo ? hasOboCred : true), + enabled: + !!accessToken && (isPassthrough ? oauthToken !== null : isAuthorizationCode ? hasAuthorizationCodeCred : true), staleTime: 30000, // Consider data fresh for 30 seconds retry: (failureCount, error: any) => { // Don't retry on 401 — token is invalid, user must re-authenticate @@ -169,12 +173,12 @@ const MCPToolsViewer = ({ }, }); - // OBO authorize: same redirect+exchange flow as the admin "Authorize & Fetch" + // authorization_code authorize: same redirect+exchange flow as the admin "Authorize & Fetch" // and the chat "Connect" button, but persists the token to the per-user DB. - const onOboAuthSuccess = useCallback(() => { - refetchOboCred(); + const onAuthorizationCodeAuthSuccess = useCallback(() => { + refetchAuthorizationCodeCred(); refetchTools(); - }, [refetchOboCred, refetchTools]); + }, [refetchAuthorizationCodeCred, refetchTools]); const { startOAuthFlow: startDbOAuthFlow, @@ -184,12 +188,12 @@ const MCPToolsViewer = ({ accessToken: accessToken ?? "", serverId, serverAlias, - onSuccess: onOboAuthSuccess, + onSuccess: onAuthorizationCodeAuthSuccess, }); // Stash which server started the redirect so the MCP Servers page can reopen // this Tools tab on return and let the flow resume to persist the credential. - const startOboAuthorize = useCallback(() => { + const startAuthorizationCodeAuthorize = useCallback(() => { try { setSecureItem(TOOLS_OAUTH_UI_STATE_KEY, JSON.stringify({ serverId })); } catch (_) {} @@ -238,17 +242,21 @@ const MCPToolsViewer = ({ const toolsData = mcpToolsResponse?.tools || []; - const oboToolsError = mcpToolsError as (Error & { status?: number; response?: { status?: number } }) | null; - const oboTokenRejected = isObo && (oboToolsError?.status ?? oboToolsError?.response?.status) === 401; + const toolsError = mcpToolsError as (Error & { status?: number; response?: { status?: number } }) | null; + // authorization_code only: a 401 from the list call means the stored credential is unusable and + // the backend's refresh could not mint a token, so the user must re-authorize (the browser flow). + // token_exchange has no gateway-side authorize step, so it is not gated here. + const authorizationCodeTokenRejected = + isAuthorizationCode && (toolsError?.status ?? toolsError?.response?.status) === 401; // An auth gate replaces the tool list when the user must authenticate first: - // passthrough needs a browser token; OBO needs a stored DB credential or a + // passthrough needs a browser token; authorization_code needs a stored DB credential or a // still-valid one — a 401 from the list call means the backend has none even // after attempting a refresh, so re-authorization is required. - const authGateActive = (isPassthrough && !oauthToken) || oboNeedsAuth || oboTokenRejected; - // Treat OBO credential-status loading as "tools loading" so the empty state + const authGateActive = (isPassthrough && !oauthToken) || authorizationCodeNeedsAuth || authorizationCodeTokenRejected; + // Treat authorization_code credential-status loading as "tools loading" so the empty state // doesn't flash before we know whether the user needs to authorize. - const toolsAreaLoading = isLoadingTools || oboStatusLoading; + const toolsAreaLoading = isLoadingTools || authorizationCodeStatusLoading; // Filter tools based on search term const filteredTools = toolsData.filter((tool: MCPTool) => { @@ -369,12 +377,12 @@ const MCPToolsViewer = ({
)} - {/* OBO auth gate — shown when there is no credential row for this - user, or when the list call returns 401 (no valid token and the + {/* Auth gate (authorization_code or token_exchange) — shown when there is no credential + row for this user, or when the list call returns 401 (no valid token and the server-side refresh could not mint one, e.g. an expired token with no usable refresh token). A refreshable token is refreshed on the list call and never trips this gate. */} - {(oboNeedsAuth || oboTokenRejected) && ( + {(authorizationCodeNeedsAuth || authorizationCodeTokenRejected) && (

Authentication required

@@ -385,7 +393,7 @@ const MCPToolsViewer = ({ size="small" type="primary" loading={dbOAuthStatus === "authorizing" || dbOAuthStatus === "exchanging"} - onClick={startOboAuthorize} + onClick={startAuthorizationCodeAuthorize} disabled={!accessToken} > Authorize diff --git a/ui/litellm-dashboard/src/components/mcp_tools/types.test.tsx b/ui/litellm-dashboard/src/components/mcp_tools/types.test.tsx index 6f050d22fb4..846bcfc9e0b 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/types.test.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/types.test.tsx @@ -7,6 +7,7 @@ import { handleTransport, handleAuth, getMcpOAuthMode, + oauth2FlowToFormValue, } from "./types"; describe("handleTransport", () => { @@ -81,6 +82,17 @@ describe("getMcpOAuthMode", () => { expect(getMcpOAuthMode({ auth_type: AUTH_TYPE.OAUTH2, oauth2_flow: MCP_OAUTH2_FLOW_M2M })).toBe("m2m"); }); + it("classifies oauth2_token_exchange as token_exchange regardless of the oauth2 secondary fields", () => { + expect(getMcpOAuthMode({ auth_type: AUTH_TYPE.OAUTH2_TOKEN_EXCHANGE })).toBe("token_exchange"); + expect( + getMcpOAuthMode({ + auth_type: AUTH_TYPE.OAUTH2_TOKEN_EXCHANGE, + oauth2_flow: MCP_OAUTH2_FLOW_M2M, + delegate_auth_to_upstream: true, + }), + ).toBe("token_exchange"); + }); + it("treats m2m as m2m even when delegate_auth_to_upstream is true", () => { expect( getMcpOAuthMode({ @@ -97,14 +109,14 @@ describe("getMcpOAuthMode", () => { ); }); - it("classifies an interactive server without delegation as obo", () => { + it("classifies an interactive server without delegation as authorization_code", () => { expect(getMcpOAuthMode({ auth_type: AUTH_TYPE.OAUTH2, oauth2_flow: null, delegate_auth_to_upstream: false })).toBe( - "obo", + "authorization_code", ); }); - it("defaults to obo when delegate_auth_to_upstream is undefined", () => { - expect(getMcpOAuthMode({ auth_type: AUTH_TYPE.OAUTH2 })).toBe("obo"); + it("defaults to authorization_code when delegate_auth_to_upstream is undefined", () => { + expect(getMcpOAuthMode({ auth_type: AUTH_TYPE.OAUTH2 })).toBe("authorization_code"); }); it("treats explicit authorization_code as interactive, not m2m", () => { @@ -114,7 +126,7 @@ describe("getMcpOAuthMode", () => { oauth2_flow: "authorization_code", delegate_auth_to_upstream: false, }), - ).toBe("obo"); + ).toBe("authorization_code"); }); // Regression: the old heuristic labeled any OAuth2 server with a token endpoint @@ -122,7 +134,22 @@ describe("getMcpOAuthMode", () => { // legitimately carries one is classified by oauth2_flow + delegate, never M2M. it("does not treat an interactive server with a token endpoint as m2m", () => { expect(getMcpOAuthMode({ auth_type: AUTH_TYPE.OAUTH2, oauth2_flow: null, delegate_auth_to_upstream: false })).toBe( - "obo", + "authorization_code", ); }); }); + +describe("oauth2FlowToFormValue", () => { + it("maps client_credentials to the M2M select value", () => { + expect(oauth2FlowToFormValue(MCP_OAUTH2_FLOW_M2M)).toBe(OAUTH_FLOW.M2M); + }); + + it("maps authorization_code to the Interactive select value", () => { + expect(oauth2FlowToFormValue("authorization_code")).toBe(OAUTH_FLOW.INTERACTIVE); + }); + + it("returns undefined for a null/unset flow so the select shows its placeholder", () => { + expect(oauth2FlowToFormValue(null)).toBeUndefined(); + expect(oauth2FlowToFormValue(undefined)).toBeUndefined(); + }); +}); diff --git a/ui/litellm-dashboard/src/components/mcp_tools/types.tsx b/ui/litellm-dashboard/src/components/mcp_tools/types.tsx index cd3ffcab5ec..7e02b8779f5 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/types.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/types.tsx @@ -39,6 +39,7 @@ export const AUTH_TYPE = { TOKEN: "token", BASIC: "basic", OAUTH2: "oauth2", + OAUTH2_TOKEN_EXCHANGE: "oauth2_token_exchange", AWS_SIGV4: "aws_sigv4", }; @@ -51,22 +52,40 @@ export const OAUTH_FLOW = { // from the UI-local OAUTH_FLOW.M2M ("m2m"); this is what the API actually returns. export const MCP_OAUTH2_FLOW_M2M = "client_credentials"; -export type McpOAuthMode = "m2m" | "passthrough" | "obo"; +export const MCP_OAUTH2_FLOW_INTERACTIVE = "authorization_code"; -// Classify an OAuth2 MCP server into the mode that decides how the tool list is -// authenticated: M2M (backend service token), PKCE passthrough (browser-held -// session token), or OBO (backend-stored per-user token). `token_url` is -// intentionally not consulted: every OAuth2 grant that exchanges for a token -// carries one (interactive PKCE and client_credentials alike), so it cannot -// distinguish the modes; `oauth2_flow` is the authoritative M2M signal. +export type McpOAuthMode = "m2m" | "passthrough" | "authorization_code" | "token_exchange"; + +// Classify an OAuth MCP server into the mode that decides how the tool list is +// authenticated. token_exchange (RFC 8693 / OBO) is its own auth_type +// (`oauth2_token_exchange`), so it is keyed off auth_type directly; the other +// three all share auth_type `oauth2` and are told apart by secondary fields: +// M2M (backend service token via the client_credentials grant), PKCE passthrough +// (browser-held session token), or authorization_code (per-user token obtained +// via the interactive authorization_code/PKCE grant and stored by the backend). +// `token_url` is intentionally not consulted for the oauth2 modes: every OAuth2 +// grant that exchanges for a token carries one (interactive PKCE and +// client_credentials alike), so it cannot distinguish the modes; `oauth2_flow` +// is the authoritative M2M signal. export function getMcpOAuthMode(s: { auth_type?: string | null; oauth2_flow?: string | null; delegate_auth_to_upstream?: boolean | null; }): McpOAuthMode | null { + if (s.auth_type === AUTH_TYPE.OAUTH2_TOKEN_EXCHANGE) return "token_exchange"; if (s.auth_type !== AUTH_TYPE.OAUTH2) return null; if (s.oauth2_flow === MCP_OAUTH2_FLOW_M2M) return "m2m"; - return s.delegate_auth_to_upstream ? "passthrough" : "obo"; + return s.delegate_auth_to_upstream ? "passthrough" : "authorization_code"; +} + +// Map a server's stored `oauth2_flow` (the API value: client_credentials / +// authorization_code / null) to the edit form's OAuth Flow Type select value. +// A null/unset flow returns undefined so the select shows its placeholder rather +// than a guessed default — an unstamped legacy row must be assigned explicitly. +export function oauth2FlowToFormValue(oauth2Flow?: string | null): string | undefined { + if (oauth2Flow === MCP_OAUTH2_FLOW_M2M) return OAUTH_FLOW.M2M; + if (oauth2Flow) return OAUTH_FLOW.INTERACTIVE; + return undefined; } export const TRANSPORT = { @@ -219,6 +238,10 @@ export interface MCPServer { authorization_url?: string | null; token_url?: string | null; registration_url?: string | null; + token_exchange_endpoint?: string | null; + audience?: string | null; + subject_token_type?: string | null; + token_exchange_profile?: string | null; mcp_info?: MCPInfo | null; created_at: string; created_by: string; diff --git a/ui/litellm-dashboard/src/components/mcp_tools/utils.test.tsx b/ui/litellm-dashboard/src/components/mcp_tools/utils.test.tsx index c4c8c52888c..3b4fda400c2 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/utils.test.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/utils.test.tsx @@ -1,5 +1,12 @@ import { describe, it, expect } from "vitest"; -import { extractMCPToken, maskUrl, getMaskedAndFullUrl, validateMCPServerUrl, validateMCPServerName } from "./utils"; +import { + extractMCPToken, + maskUrl, + getMaskedAndFullUrl, + validateMCPServerUrl, + validateMCPServerName, + normalizeToolOverrideMap, +} from "./utils"; describe("extractMCPToken", () => { it("should extract token after /mcp/", () => { @@ -67,3 +74,21 @@ describe("validateMCPServerName", () => { await expect(validateMCPServerName("my server")).rejects.toBeDefined(); }); }); + +describe("normalizeToolOverrideMap", () => { + it("returns empty object for nullish input", () => { + expect(normalizeToolOverrideMap(null)).toEqual({}); + expect(normalizeToolOverrideMap(undefined)).toEqual({}); + }); + + it("parses JSON string maps from legacy API responses", () => { + expect(normalizeToolOverrideMap('{"read_wiki_structure":"browse_docs"}')).toEqual({ + read_wiki_structure: "browse_docs", + }); + }); + + it("passes through object maps unchanged", () => { + const map = { read_user: "Read User" }; + expect(normalizeToolOverrideMap(map)).toBe(map); + }); +}); diff --git a/ui/litellm-dashboard/src/components/mcp_tools/utils.tsx b/ui/litellm-dashboard/src/components/mcp_tools/utils.tsx index 6d9479a13c3..7d6e24fc480 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/utils.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/utils.tsx @@ -54,6 +54,14 @@ export const validateMCPServerName = (value: string) => { : Promise.resolve(); }; +export const TOOL_DISPLAY_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/; + +export const validateToolDisplayName = (value: string) => { + return value && !TOOL_DISPLAY_NAME_PATTERN.test(value) + ? Promise.reject("Only letters, digits, underscores, and hyphens are allowed (no spaces).") + : Promise.resolve(); +}; + // Normalize the env_vars form list into the payload shape the backend expects. // Drops empty rows, invalid identifiers, and duplicate names; user-scoped entries never carry a value. export const normalizeEnvVars = (list: unknown): MCPEnvVar[] => { @@ -77,3 +85,22 @@ export const normalizeEnvVars = (list: unknown): MCPEnvVar[] => { } return out; }; + +/** Normalize tool override maps from API/DB (dict or JSON string) for form state. */ +export const normalizeToolOverrideMap = ( + value: Record | string | null | undefined, +): Record => { + if (!value) return {}; + if (typeof value === "string") { + try { + const parsed = JSON.parse(value) as unknown; + if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) { + return parsed as Record; + } + } catch { + return {}; + } + return {}; + } + return value; +}; diff --git a/ui/litellm-dashboard/src/components/molecules/cost_optimization_feedback_banner.test.tsx b/ui/litellm-dashboard/src/components/molecules/cost_optimization_feedback_banner.test.tsx new file mode 100644 index 00000000000..328c1441fbc --- /dev/null +++ b/ui/litellm-dashboard/src/components/molecules/cost_optimization_feedback_banner.test.tsx @@ -0,0 +1,33 @@ +import { fireEvent, render } from "@testing-library/react"; +import { beforeEach, describe, expect, it } from "vitest"; +import CostOptimizationFeedbackBanner from "./cost_optimization_feedback_banner"; + +const STORAGE_KEY = "hideCostOptimizationFeedbackBanner"; + +describe("CostOptimizationFeedbackBanner", () => { + beforeEach(() => { + localStorage.removeItem(STORAGE_KEY); + }); + + it("renders with a link to the feedback discussion", () => { + const { getByText } = render(); + const link = getByText("Share Feedback").closest("a"); + expect(link).toHaveAttribute("href", "https://github.com/BerriAI/litellm/discussions/32172"); + }); + + it("hides itself and persists the dismissal when the dismiss button is clicked", () => { + const { queryByText, getByLabelText } = render(); + expect(queryByText("Help shape cost optimization")).toBeInTheDocument(); + + fireEvent.click(getByLabelText("Dismiss banner")); + + expect(queryByText("Help shape cost optimization")).not.toBeInTheDocument(); + expect(localStorage.getItem(STORAGE_KEY)).toBe("true"); + }); + + it("stays dismissed on remount once persisted", () => { + localStorage.setItem(STORAGE_KEY, "true"); + const { queryByText } = render(); + expect(queryByText("Help shape cost optimization")).not.toBeInTheDocument(); + }); +}); diff --git a/ui/litellm-dashboard/src/components/molecules/cost_optimization_feedback_banner.tsx b/ui/litellm-dashboard/src/components/molecules/cost_optimization_feedback_banner.tsx new file mode 100644 index 00000000000..9c8a561844e --- /dev/null +++ b/ui/litellm-dashboard/src/components/molecules/cost_optimization_feedback_banner.tsx @@ -0,0 +1,76 @@ +import React, { useState } from "react"; +import { CommentOutlined } from "@ant-design/icons"; + +const STORAGE_KEY = "hideCostOptimizationFeedbackBanner"; +const DISCUSSION_URL = "https://github.com/BerriAI/litellm/discussions/32172"; + +const CostOptimizationFeedbackBanner: React.FC = () => { + const [dismissed, setDismissed] = useState(() => { + if (typeof window !== "undefined") { + return localStorage.getItem(STORAGE_KEY) === "true"; + } + return false; + }); + + if (dismissed) { + return null; + } + + return ( +
+
+ +
+
+

Help shape cost optimization

+

+ We're collecting suggestions for cost optimization improvements across routing, budgets, and more. Let us + know what you'd like to see. +

+
+ + Share Feedback + + + + + +
+ ); +}; + +export default CostOptimizationFeedbackBanner; diff --git a/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx b/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx index ca5766a3682..bf0f0cc3fae 100644 --- a/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx +++ b/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx @@ -1163,6 +1163,21 @@ const CreateKey: React.FC = ({ team, teams, data, addKey, autoOp form={form} showDetailedDescriptions={true} /> + + Throttle on budget exceeded{" "} + + + + + } + name="throttle_on_budget_exceeded" + valuePropName="checked" + > + + diff --git a/ui/litellm-dashboard/src/components/shared/usage_date_picker.test.tsx b/ui/litellm-dashboard/src/components/shared/usage_date_picker.test.tsx new file mode 100644 index 00000000000..46fd8181560 --- /dev/null +++ b/ui/litellm-dashboard/src/components/shared/usage_date_picker.test.tsx @@ -0,0 +1,50 @@ +import { render, screen, within } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { beforeAll, describe, expect, it, vi } from "vitest"; +import UsageDatePicker from "./usage_date_picker"; + +beforeAll(() => { + vi.stubGlobal("requestIdleCallback", (cb: IdleRequestCallback) => { + cb({ didTimeout: false, timeRemaining: () => 50 } as IdleDeadline); + return 0; + }); +}); + +describe("UsageDatePicker (tremor DateRangePicker on date-fns 4)", () => { + const value = { from: new Date(2026, 5, 1), to: new Date(2026, 5, 15) }; + + it("renders the formatted range label", () => { + render( {}} />); + + const triggerText = screen.getAllByRole("button")[0].textContent ?? ""; + expect(triggerText).toMatch(/Jun/); + expect(triggerText).toMatch(/2026/); + expect(triggerText).toMatch(/15/); + }); + + it("opens the calendar and renders a full month grid", async () => { + const user = userEvent.setup(); + render( {}} />); + + await user.click(screen.getAllByRole("button")[0]); + + const grid = await screen.findByRole("grid"); + const dayCells = within(grid).getAllByRole("gridcell"); + expect(dayCells.length).toBeGreaterThanOrEqual(28); + expect(within(grid).getByText("15")).toBeInTheDocument(); + }); + + it("fires onValueChange when a day is selected", async () => { + const user = userEvent.setup(); + const onValueChange = vi.fn(); + render(); + + await user.click(screen.getAllByRole("button")[0]); + const grid = await screen.findByRole("grid"); + await user.click(within(grid).getByText("10")); + + expect(onValueChange).toHaveBeenCalled(); + const newValue = onValueChange.mock.calls[0][0]; + expect(newValue.from).toBeInstanceOf(Date); + }); +}); diff --git a/ui/litellm-dashboard/src/components/templates/key_edit_view.test.tsx b/ui/litellm-dashboard/src/components/templates/key_edit_view.test.tsx index 40c82c51031..ba68124beee 100644 --- a/ui/litellm-dashboard/src/components/templates/key_edit_view.test.tsx +++ b/ui/litellm-dashboard/src/components/templates/key_edit_view.test.tsx @@ -335,6 +335,36 @@ describe("KeyEditView", () => { }); }); + it("should initialize and submit throttle_on_budget_exceeded from key metadata", async () => { + const onSubmitMock = vi.fn().mockResolvedValue(undefined); + const keyDataWithThrottle = { + ...MOCK_KEY_DATA, + metadata: { ...MOCK_KEY_DATA.metadata, throttle_on_budget_exceeded: true }, + }; + + renderWithProviders( + {}} + onSubmit={onSubmitMock} + accessToken={"test-token"} + userID={"test-user"} + userRole={"admin"} + premiumUser={false} + />, + ); + + await waitFor(() => { + expect(screen.getByText("Throttle on budget exceeded")).toBeInTheDocument(); + }); + + await userEvent.click(screen.getByRole("button", { name: /save changes/i })); + + await waitFor(() => { + expect(onSubmitMock).toHaveBeenCalledWith(expect.objectContaining({ throttle_on_budget_exceeded: true })); + }); + }); + it("should disable models field when management routes are selected", async () => { const keyDataWithManagementRoutes = { ...MOCK_KEY_DATA, diff --git a/ui/litellm-dashboard/src/components/templates/key_edit_view.tsx b/ui/litellm-dashboard/src/components/templates/key_edit_view.tsx index 7dda555daa4..4821ea86b87 100644 --- a/ui/litellm-dashboard/src/components/templates/key_edit_view.tsx +++ b/ui/litellm-dashboard/src/components/templates/key_edit_view.tsx @@ -181,6 +181,7 @@ export function KeyEditView({ metadata: formatMetadataForDisplay(stripTagsFromMetadata(keyData.metadata)), guardrails: keyData.metadata?.guardrails, disable_global_guardrails: keyData.metadata?.disable_global_guardrails || false, + throttle_on_budget_exceeded: keyData.metadata?.throttle_on_budget_exceeded || false, prompts: keyData.metadata?.prompts, tags: keyData.metadata?.tags, vector_stores: keyData.object_permission?.vector_stores || [], @@ -222,6 +223,7 @@ export function KeyEditView({ accessGroups: keyData.object_permission?.mcp_access_groups || [], }, mcp_tool_permissions: keyData.object_permission?.mcp_tool_permissions || {}, + throttle_on_budget_exceeded: keyData.metadata?.throttle_on_budget_exceeded || false, logging_settings: extractLoggingSettings(keyData.metadata), disabled_callbacks: Array.isArray(keyData.metadata?.litellm_disabled_callbacks) ? mapInternalToDisplayNames(keyData.metadata.litellm_disabled_callbacks) @@ -512,6 +514,21 @@ export function KeyEditView({ + + Throttle on budget exceeded{" "} + + + + + } + name="throttle_on_budget_exceeded" + valuePropName="checked" + > + + + diff --git a/ui/litellm-dashboard/src/components/templates/key_info_view.tsx b/ui/litellm-dashboard/src/components/templates/key_info_view.tsx index 197f87b6dfe..1c0d916f0f3 100644 --- a/ui/litellm-dashboard/src/components/templates/key_info_view.tsx +++ b/ui/litellm-dashboard/src/components/templates/key_info_view.tsx @@ -542,6 +542,9 @@ export default function KeyInfoView({
TPM: {currentKeyData.tpm_limit !== null ? currentKeyData.tpm_limit : "Unlimited"} RPM: {currentKeyData.rpm_limit !== null ? currentKeyData.rpm_limit : "Unlimited"} + {Boolean(currentKeyData.metadata?.throttle_on_budget_exceeded) && ( + Throttle on budget exceeded: Yes + )}
diff --git a/ui/litellm-dashboard/src/components/ui/alert-dialog.test.tsx b/ui/litellm-dashboard/src/components/ui/alert-dialog.test.tsx new file mode 100644 index 00000000000..4b0a197b129 --- /dev/null +++ b/ui/litellm-dashboard/src/components/ui/alert-dialog.test.tsx @@ -0,0 +1,61 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { describe, expect, it, vi } from "vitest"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "./alert-dialog"; +import { Button } from "./button"; + +function ConfirmDialog({ onConfirm }: { onConfirm: () => void }) { + return ( + + Open} /> + + + Delete this? + Cannot be undone + + + Cancel + Confirm + + + + ); +} + +describe("AlertDialog", () => { + it("fires the action handler and closes the dialog on confirm", async () => { + const user = userEvent.setup(); + const onConfirm = vi.fn(); + render(); + + await user.click(screen.getByRole("button", { name: "Open" })); + expect(screen.getByText("Delete this?")).toBeInTheDocument(); + + await user.click(screen.getByRole("button", { name: "Confirm" })); + + expect(onConfirm).toHaveBeenCalledOnce(); + expect(screen.queryByText("Delete this?")).not.toBeInTheDocument(); + }); + + it("closes without firing the action on cancel", async () => { + const user = userEvent.setup(); + const onConfirm = vi.fn(); + render(); + + await user.click(screen.getByRole("button", { name: "Open" })); + await user.click(screen.getByRole("button", { name: "Cancel" })); + + expect(onConfirm).not.toHaveBeenCalled(); + expect(screen.queryByText("Delete this?")).not.toBeInTheDocument(); + }); +}); diff --git a/ui/litellm-dashboard/src/components/ui/alert-dialog.tsx b/ui/litellm-dashboard/src/components/ui/alert-dialog.tsx index 1561cc76cb8..164dc310ae7 100644 --- a/ui/litellm-dashboard/src/components/ui/alert-dialog.tsx +++ b/ui/litellm-dashboard/src/components/ui/alert-dialog.tsx @@ -1,29 +1,29 @@ "use client"; import * as React from "react"; -import { AlertDialog as AlertDialogPrimitive } from "radix-ui"; +import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog"; import { cn } from "@/lib/cva.config"; import { Button } from "@/components/ui/button"; -function AlertDialog({ ...props }: React.ComponentProps) { +function AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) { return ; } -function AlertDialogTrigger({ ...props }: React.ComponentProps) { +function AlertDialogTrigger({ ...props }: AlertDialogPrimitive.Trigger.Props) { return ; } -function AlertDialogPortal({ ...props }: React.ComponentProps) { +function AlertDialogPortal({ ...props }: AlertDialogPrimitive.Portal.Props) { return ; } -function AlertDialogOverlay({ className, ...props }: React.ComponentProps) { +function AlertDialogOverlay({ className, ...props }: AlertDialogPrimitive.Backdrop.Props) { return ( - & { +}: AlertDialogPrimitive.Popup.Props & { size?: "default" | "sm"; }) { return ( - ) ); } +function AlertDialogMedia({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + function AlertDialogTitle({ className, ...props }: React.ComponentProps) { return ( - ); -} - -function AlertDialogMedia({ className, ...props }: React.ComponentProps<"div">) { - return ( -
& - Pick, "variant" | "size">) { +}: AlertDialogPrimitive.Close.Props & Pick, "variant" | "size">) { return ( - + } + {...props} + /> ); } @@ -138,12 +143,14 @@ function AlertDialogCancel({ variant = "outline", size = "default", ...props -}: React.ComponentProps & - Pick, "variant" | "size">) { +}: AlertDialogPrimitive.Close.Props & Pick, "variant" | "size">) { return ( - + } + {...props} + /> ); } diff --git a/ui/litellm-dashboard/src/components/ui/button.test.tsx b/ui/litellm-dashboard/src/components/ui/button.test.tsx index 16863d5c309..1c8483634b9 100644 --- a/ui/litellm-dashboard/src/components/ui/button.test.tsx +++ b/ui/litellm-dashboard/src/components/ui/button.test.tsx @@ -9,7 +9,6 @@ describe("Button", () => { const button = screen.getByRole("button", { name: "Save" }); expect(button).toHaveClass("bg-primary"); expect(button).toHaveAttribute("data-slot", "button"); - expect(button).toHaveAttribute("data-variant", "default"); }); it("applies variant and size props", () => { @@ -19,9 +18,8 @@ describe("Button", () => { , ); const button = screen.getByRole("button", { name: "Delete" }); - expect(button).toHaveClass("bg-destructive"); + expect(button).toHaveClass("bg-destructive/10"); expect(button).toHaveClass("h-8"); - expect(button).toHaveAttribute("data-variant", "destructive"); }); it("resolves conflicting classes through twMerge so className wins", () => { @@ -31,15 +29,12 @@ describe("Button", () => { expect(button).not.toHaveClass("bg-primary"); }); - it("renders the child element when asChild is set", () => { - render( - , - ); - const link = screen.getByRole("link", { name: "Docs" }); - expect(link).toHaveClass("bg-primary"); - expect(screen.queryByRole("button")).not.toBeInTheDocument(); + it("renders the element passed via the render prop with button semantics", () => { + render( - - )} + {showCloseButton && }>Close}
); } -function DialogTitle({ className, ...props }: React.ComponentProps) { +function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) { return ( - + ); } -function DialogDescription({ className, ...props }: React.ComponentProps) { +function DialogDescription({ className, ...props }: DialogPrimitive.Description.Props) { return ( ); diff --git a/ui/litellm-dashboard/src/components/ui/label.tsx b/ui/litellm-dashboard/src/components/ui/label.tsx index 77c2cc6254d..ded2dfc1a7b 100644 --- a/ui/litellm-dashboard/src/components/ui/label.tsx +++ b/ui/litellm-dashboard/src/components/ui/label.tsx @@ -1,13 +1,12 @@ "use client"; import * as React from "react"; -import { Label as LabelPrimitive } from "radix-ui"; import { cn } from "@/lib/cva.config"; -function Label({ className, ...props }: React.ComponentProps) { +function Label({ className, ...props }: React.ComponentProps<"label">) { return ( - ) { +function Popover({ ...props }: PopoverPrimitive.Root.Props) { return ; } -function PopoverTrigger({ ...props }: React.ComponentProps) { +function PopoverTrigger({ ...props }: PopoverPrimitive.Trigger.Props) { return ; } function PopoverContent({ className, align = "center", + alignOffset = 0, + side = "bottom", sideOffset = 4, ...props -}: React.ComponentProps) { +}: PopoverPrimitive.Popup.Props & + Pick) { return ( - + className="isolate z-50" + > + + ); } -function PopoverAnchor({ ...props }: React.ComponentProps) { - return ; -} - function PopoverHeader({ className, ...props }: React.ComponentProps<"div">) { return
; } -function PopoverTitle({ className, ...props }: React.ComponentProps<"h2">) { - return
; +function PopoverTitle({ className, ...props }: PopoverPrimitive.Title.Props) { + return ; } -function PopoverDescription({ className, ...props }: React.ComponentProps<"p">) { - return

; +function PopoverDescription({ className, ...props }: PopoverPrimitive.Description.Props) { + return ( + + ); } -export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor, PopoverHeader, PopoverTitle, PopoverDescription }; +export { Popover, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger }; diff --git a/ui/litellm-dashboard/src/components/ui/scroll-area.tsx b/ui/litellm-dashboard/src/components/ui/scroll-area.tsx index b23d2daebc3..74106ce80ae 100644 --- a/ui/litellm-dashboard/src/components/ui/scroll-area.tsx +++ b/ui/litellm-dashboard/src/components/ui/scroll-area.tsx @@ -1,11 +1,11 @@ "use client"; import * as React from "react"; -import { ScrollArea as ScrollAreaPrimitive } from "radix-ui"; +import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area"; import { cn } from "@/lib/cva.config"; -function ScrollArea({ className, children, ...props }: React.ComponentProps) { +function ScrollArea({ className, children, ...props }: ScrollAreaPrimitive.Root.Props) { return ( ) { +function ScrollBar({ className, orientation = "vertical", ...props }: ScrollAreaPrimitive.Scrollbar.Props) { return ( - - - + + ); } diff --git a/ui/litellm-dashboard/src/components/ui/select.tsx b/ui/litellm-dashboard/src/components/ui/select.tsx index be6bf72c744..7d009b53084 100644 --- a/ui/litellm-dashboard/src/components/ui/select.tsx +++ b/ui/litellm-dashboard/src/components/ui/select.tsx @@ -1,21 +1,21 @@ "use client"; import * as React from "react"; -import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"; -import { Select as SelectPrimitive } from "radix-ui"; +import { Select as SelectPrimitive } from "@base-ui/react/select"; import { cn } from "@/lib/cva.config"; +import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react"; -function Select({ ...props }: React.ComponentProps) { - return ; +const Select = SelectPrimitive.Root; + +function SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) { + return ; } -function SelectGroup({ ...props }: React.ComponentProps) { - return ; -} - -function SelectValue({ ...props }: React.ComponentProps) { - return ; +function SelectValue({ className, ...props }: SelectPrimitive.Value.Props) { + return ( + + ); } function SelectTrigger({ @@ -23,7 +23,7 @@ function SelectTrigger({ size = "default", children, ...props -}: React.ComponentProps & { +}: SelectPrimitive.Trigger.Props & { size?: "sm" | "default"; }) { return ( @@ -31,15 +31,13 @@ function SelectTrigger({ data-slot="select-trigger" data-size={size} className={cn( - "flex w-fit items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground", + "flex w-fit items-center justify-between gap-1.5 rounded-md border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className, )} {...props} > {children} - - - + } /> ); } @@ -47,43 +45,45 @@ function SelectTrigger({ function SelectContent({ className, children, - position = "item-aligned", + side = "bottom", + sideOffset = 4, align = "center", + alignOffset = 0, + alignItemWithTrigger = true, ...props -}: React.ComponentProps) { +}: SelectPrimitive.Popup.Props & + Pick) { return ( - - - - {children} - - - + + {children} + + + ); } -function SelectLabel({ className, ...props }: React.ComponentProps) { +function SelectLabel({ className, ...props }: SelectPrimitive.GroupLabel.Props) { return ( - ) { +function SelectItem({ className, children, ...props }: SelectPrimitive.Item.Props) { return ( - - - - - - {children} + + {children} + + } + > + + ); } -function SelectSeparator({ className, ...props }: React.ComponentProps) { +function SelectSeparator({ className, ...props }: SelectPrimitive.Separator.Props) { return ( ) { +function SelectScrollUpButton({ className, ...props }: React.ComponentProps) { return ( - - - + + ); } -function SelectScrollDownButton({ - className, - ...props -}: React.ComponentProps) { +function SelectScrollDownButton({ className, ...props }: React.ComponentProps) { return ( - - - + + ); } diff --git a/ui/litellm-dashboard/src/components/ui/separator.tsx b/ui/litellm-dashboard/src/components/ui/separator.tsx index 6a1a78d1022..443f8e905f9 100644 --- a/ui/litellm-dashboard/src/components/ui/separator.tsx +++ b/ui/litellm-dashboard/src/components/ui/separator.tsx @@ -1,23 +1,16 @@ "use client"; -import * as React from "react"; -import { Separator as SeparatorPrimitive } from "radix-ui"; +import { Separator as SeparatorPrimitive } from "@base-ui/react/separator"; import { cn } from "@/lib/cva.config"; -function Separator({ - className, - orientation = "horizontal", - decorative = true, - ...props -}: React.ComponentProps) { +function Separator({ className, orientation = "horizontal", ...props }: SeparatorPrimitive.Props) { return ( - & { +}: SwitchPrimitive.Root.Props & { size?: "sm" | "default"; }) { return ( @@ -17,16 +16,14 @@ function Switch({ data-slot="switch" data-size={size} className={cn( - "peer group/switch inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input dark:data-[state=unchecked]:bg-input/80", + "peer group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-[18.4px] data-[size=default]:w-[32px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px] dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:bg-primary data-unchecked:bg-input dark:data-unchecked:bg-input/80 data-disabled:cursor-not-allowed data-disabled:opacity-50", className, )} {...props} > ); diff --git a/ui/litellm-dashboard/src/components/ui/tabs.tsx b/ui/litellm-dashboard/src/components/ui/tabs.tsx index d4d4eed75b0..773b9e31081 100644 --- a/ui/litellm-dashboard/src/components/ui/tabs.tsx +++ b/ui/litellm-dashboard/src/components/ui/tabs.tsx @@ -1,25 +1,23 @@ "use client"; -import * as React from "react"; +import { Tabs as TabsPrimitive } from "@base-ui/react/tabs"; import { type VariantProps } from "cva"; -import { Tabs as TabsPrimitive } from "radix-ui"; import { cn, cva } from "@/lib/cva.config"; -function Tabs({ className, orientation = "horizontal", ...props }: React.ComponentProps) { +function Tabs({ className, orientation = "horizontal", ...props }: TabsPrimitive.Root.Props) { return ( ); } const tabsListVariants = cva({ - base: "group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-[orientation=horizontal]/tabs:h-9 group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col data-[variant=line]:rounded-none", + base: "group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-horizontal/tabs:h-9 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none", variants: { variant: { default: "bg-muted", @@ -35,7 +33,7 @@ function TabsList({ className, variant = "default", ...props -}: React.ComponentProps & VariantProps) { +}: TabsPrimitive.List.Props & VariantProps) { return ( ) { +function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) { return ( - ) { - return ; +function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) { + return ( + + ); } export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }; diff --git a/ui/litellm-dashboard/src/components/ui/tooltip.tsx b/ui/litellm-dashboard/src/components/ui/tooltip.tsx index 569ff709bc8..5e21eab67f5 100644 --- a/ui/litellm-dashboard/src/components/ui/tooltip.tsx +++ b/ui/litellm-dashboard/src/components/ui/tooltip.tsx @@ -1,42 +1,52 @@ "use client"; -import * as React from "react"; -import { Tooltip as TooltipPrimitive } from "radix-ui"; +import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip"; import { cn } from "@/lib/cva.config"; -function TooltipProvider({ delayDuration = 0, ...props }: React.ComponentProps) { - return ; +function TooltipProvider({ delay = 0, ...props }: TooltipPrimitive.Provider.Props) { + return ; } -function Tooltip({ ...props }: React.ComponentProps) { +function Tooltip({ ...props }: TooltipPrimitive.Root.Props) { return ; } -function TooltipTrigger({ ...props }: React.ComponentProps) { +function TooltipTrigger({ ...props }: TooltipPrimitive.Trigger.Props) { return ; } function TooltipContent({ className, - sideOffset = 0, + side = "top", + sideOffset = 4, + align = "center", + alignOffset = 0, children, ...props -}: React.ComponentProps) { +}: TooltipPrimitive.Popup.Props & + Pick) { return ( - - {children} - - + + {children} + + + ); } diff --git a/ui/litellm-dashboard/src/components/view_logs/LogsTableToolbar.tsx b/ui/litellm-dashboard/src/components/view_logs/LogsTableToolbar.tsx index b20281ed0da..f65ff2cc6ca 100644 --- a/ui/litellm-dashboard/src/components/view_logs/LogsTableToolbar.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/LogsTableToolbar.tsx @@ -193,15 +193,24 @@ export function LogsTableToolbar({

- + Showing {isLoading ? "..." : filteredLogs ? (currentPage - 1) * pageSize + 1 : 0} -{" "} {isLoading ? "..." : filteredLogs ? Math.min(currentPage * pageSize, filteredLogs.total) : 0} of{" "} - {isLoading ? "..." : filteredLogs ? filteredLogs.total : 0} results + {isLoading ? "..." : filteredLogs ? filteredLogs.total : 0} + {!isLoading && filteredLogs?.total_is_capped ? "+" : ""} results
Page {isLoading ? "..." : currentPage} of{" "} {isLoading ? "..." : filteredLogs ? filteredLogs.total_pages : 1} + {!isLoading && filteredLogs?.total_is_capped ? "+" : ""} + ) : null, +}; + describe("DataTable column sizing", () => { it("min-widths the table to the column total and sizes every cell when columns declare sizes", () => { render(); @@ -44,3 +59,143 @@ describe("DataTable column sizing", () => { } }); }); + +describe("DataTable states", () => { + it("shows the loading message instead of rows while loading", () => { + render(); + + expect(screen.getByText("Fetching things")).toBeInTheDocument(); + expect(screen.queryByText("alpha")).not.toBeInTheDocument(); + }); + + it("shows the no-data message when there are no rows", () => { + render(); + + expect(screen.getByText("Nothing here")).toBeInTheDocument(); + }); + + it("renders row data through plain TanStack column defs, including custom cell renderers", () => { + const columns: ColumnDef[] = [ + { header: "A", accessorKey: "a" }, + { header: "B", cell: ({ row }) => custom:{row.original.b} }, + ]; + render(); + + expect(screen.getByText("alpha")).toBeInTheDocument(); + expect(screen.getByText("custom:beta")).toBeInTheDocument(); + }); +}); + +describe("DataTable row interaction", () => { + it("fires onRowClick with the row's original data", async () => { + const user = userEvent.setup(); + const onRowClick = vi.fn(); + render(); + + await user.click(screen.getByText("alpha")); + + expect(onRowClick).toHaveBeenCalledExactlyOnceWith(data[0]); + }); +}); + +describe("DataTable expansion", () => { + const rows: Row[] = [ + { request_id: "r1", a: "alpha", b: "beta" }, + { request_id: "r2", a: "gamma", b: "delta" }, + ]; + + it("toggles the sub-component in a full-width cell (colspan path)", async () => { + const user = userEvent.setup(); + render( + true} + renderSubComponent={({ row }) =>
details for {row.original.request_id}
} + />, + ); + + expect(screen.queryByText("details for r1")).not.toBeInTheDocument(); + + await user.click(screen.getByRole("button", { name: "expand r1" })); + const details = screen.getByText("details for r1"); + expect(details).toBeInTheDocument(); + expect(screen.queryByText("details for r2")).not.toBeInTheDocument(); + + const detailCell = details.closest("td"); + expect(detailCell).toHaveAttribute("colspan", "3"); + + await user.click(screen.getByRole("button", { name: "collapse r1" })); + expect(screen.queryByText("details for r1")).not.toBeInTheDocument(); + }); + + it("renders child rows as sibling table rows (child-rows path)", async () => { + const user = userEvent.setup(); + render( + true} + renderChildRows={({ row }) => ( + + child of {row.original.request_id} + + )} + />, + ); + + expect(screen.queryByText("child of r2")).not.toBeInTheDocument(); + + await user.click(screen.getByRole("button", { name: "expand r2" })); + + const childCell = screen.getByText("child of r2"); + expect(childCell.closest("tr")).not.toBeNull(); + expect(within(screen.getByRole("table")).getByText("child of r2")).toBeInTheDocument(); + }); + + it("does not expand rows when getRowCanExpand is missing even if a renderer is provided", () => { + render( +
details for {row.original.request_id}
} + />, + ); + + expect(screen.queryByRole("button", { name: "expand r1" })).not.toBeInTheDocument(); + }); +}); + +describe("DataTable sorting", () => { + const rows: Row[] = [ + { request_id: "r1", a: "bravo", b: "2" }, + { request_id: "r2", a: "alpha", b: "1" }, + { request_id: "r3", a: "charlie", b: "3" }, + ]; + + const firstColumnValues = () => + screen + .getAllByRole("row") + .slice(1) + .map((row) => within(row).getAllByRole("cell")[0].textContent); + + it("leaves row order untouched when sorting is disabled", async () => { + const user = userEvent.setup(); + render(); + + await user.click(screen.getByText("A")); + + expect(firstColumnValues()).toEqual(["bravo", "alpha", "charlie"]); + }); + + it("sorts ascending then descending on header clicks when enabled", async () => { + const user = userEvent.setup(); + render(); + + await user.click(screen.getByText("A")); + expect(firstColumnValues()).toEqual(["alpha", "bravo", "charlie"]); + + await user.click(screen.getByText("A")); + expect(firstColumnValues()).toEqual(["charlie", "bravo", "alpha"]); + }); +}); diff --git a/ui/litellm-dashboard/src/lib/http/schema.d.ts b/ui/litellm-dashboard/src/lib/http/schema.d.ts index bfd1efc99f3..d9bba85bf4b 100644 --- a/ui/litellm-dashboard/src/lib/http/schema.d.ts +++ b/ui/litellm-dashboard/src/lib/http/schema.d.ts @@ -6502,6 +6502,7 @@ export interface paths { * - guardrails: Optional[List[str]] - List of active guardrails for the key * - policies: Optional[List[str]] - List of policy names to apply to the key. Policies define guardrails, conditions, and inheritance rules. * - disable_global_guardrails: Optional[bool] - Whether to disable global guardrails for the key. + * - throttle_on_budget_exceeded: Optional[bool] - When the key exceeds its max_budget, throttle its tpm/rpm to the global budget_exceeded_throttle_percentage instead of blocking the key entirely. * - permissions: Optional[dict] - key-specific permissions. Currently just used for turning off pii masking (if connected). Example - {"pii": false} * - model_max_budget: Optional[Dict[str, BudgetConfig]] - Model-specific budgets {"gpt-4": {"budget_limit": 0.0005, "time_period": "30d"}}}. IF null or {} then no model specific budget. * - budget_fallbacks: Optional[Dict[str, List[str]]] - Per-model fallback chain tried in order when that model's own `model_max_budget` is exceeded, e.g. {"gpt-4o": ["gpt-4o-mini"]}. @@ -6905,6 +6906,7 @@ export interface paths { * - guardrails: Optional[List[str]] - List of active guardrails for the key * - policies: Optional[List[str]] - List of policy names to apply to the key. Policies define guardrails, conditions, and inheritance rules. * - disable_global_guardrails: Optional[bool] - Whether to disable global guardrails for the key. + * - throttle_on_budget_exceeded: Optional[bool] - When the key exceeds its max_budget, throttle its tpm/rpm to the global budget_exceeded_throttle_percentage instead of blocking the key entirely. * - prompts: Optional[List[str]] - List of prompts that the key is allowed to use. * - blocked: Optional[bool] - Whether the key is blocked * - aliases: Optional[dict] - Model aliases for the key - [Docs](https://litellm.vercel.app/docs/proxy/virtual_keys#model-aliases) @@ -22508,6 +22510,11 @@ export interface components { * @description external services registered as embeddable UI plugins */ plugins?: components["schemas"]["PluginConfig"][] | null; + /** + * Provider Url Destination Allowed Hosts + * @description Allowlist of hosts a request may redirect a provider call's destination URL to. + */ + provider_url_destination_allowed_hosts?: string[] | null; /** * Reject Clientside Metadata Tags * @description When set to True, rejects requests that contain client-side 'metadata.tags' to prevent users from influencing budgets by sending different tags. Tags can only be inherited from the API key metadata. @@ -22566,6 +22573,16 @@ export interface components { * @description Controls how non-admin users interact with MCP servers in the dashboard. 'restricted' shows only accessible servers, 'view_all' lists every server in read-only mode. */ user_mcp_management_mode?: ("restricted" | "view_all") | null; + /** + * User Url Allowed Hosts + * @description SSRF allowlist for user-supplied URLs. Entries are `hostname` or `hostname:port` (bracketed for IPv6, e.g. `[::1]:8080`). Allowlisted hosts skip the blocked-network check in validate_url() but still resolve DNS. Use this to permit legitimate internal targets, e.g. an internal OpenAPI/MCP server. + */ + user_url_allowed_hosts?: string[] | null; + /** + * User Url Validation + * @description Master switch for the SSRF guard applied to user-supplied URLs (image_url, file_url, MCP/OpenAPI spec URLs, etc). Defaults to True. Set to False to disable DNS/IP validation entirely (not recommended). + */ + user_url_validation?: boolean | null; }; /** ConfigList */ ConfigList: { @@ -23692,6 +23709,8 @@ export interface components { tags?: string[] | null; /** Team Id */ team_id?: string | null; + /** Throttle On Budget Exceeded */ + throttle_on_budget_exceeded?: boolean | null; /** Tpm Limit */ tpm_limit?: number | null; /** Tpm Limit Type */ @@ -23832,6 +23851,8 @@ export interface components { tags?: string[] | null; /** Team Id */ team_id?: string | null; + /** Throttle On Budget Exceeded */ + throttle_on_budget_exceeded?: boolean | null; /** Token */ token?: string | null; /** Token Id */ @@ -25430,6 +25451,8 @@ export interface components { input_cost_per_video_per_second_above_15s_interval?: number | null; /** Input Cost Per Video Per Second Above 8S Interval */ input_cost_per_video_per_second_above_8s_interval?: number | null; + /** Itpm */ + itpm?: number | null; /** Litellm Credential Name */ litellm_credential_name?: string | null; /** Litellm Trace Id */ @@ -25465,6 +25488,8 @@ export interface components { ocr_cost_per_page?: number | null; /** Organization */ organization?: string | null; + /** Otpm */ + otpm?: number | null; /** Output Cost Per Audio Per Second */ output_cost_per_audio_per_second?: number | null; /** Output Cost Per Audio Token */ @@ -27261,6 +27286,8 @@ export interface components { * @default false */ is_public_model_group: boolean; + /** Itpm */ + itpm?: number | null; /** Max Input Tokens */ max_input_tokens?: number | null; /** Max Output Tokens */ @@ -27272,6 +27299,8 @@ export interface components { mode: string | ("chat" | "embedding" | "completion" | "image_generation" | "audio_transcription" | "rerank" | "moderations") | null; /** Model Group */ model_group: string; + /** Otpm */ + otpm?: number | null; /** Output Cost Per Token */ output_cost_per_token?: number | null; /** Providers */ @@ -28163,6 +28192,8 @@ export interface components { team_id?: string | null; /** Teams */ teams?: unknown[] | null; + /** Throttle On Budget Exceeded */ + throttle_on_budget_exceeded?: boolean | null; /** Token */ token?: string | null; /** Token Id */ @@ -29790,6 +29821,8 @@ export interface components { tags?: string[] | null; /** Team Id */ team_id?: string | null; + /** Throttle On Budget Exceeded */ + throttle_on_budget_exceeded?: boolean | null; /** Tpm Limit */ tpm_limit?: number | null; /** Tpm Limit Type */ @@ -31737,6 +31770,8 @@ export interface components { temp_budget_expiry?: string | null; /** Temp Budget Increase */ temp_budget_increase?: number | null; + /** Throttle On Budget Exceeded */ + throttle_on_budget_exceeded?: boolean | null; /** Tpm Limit */ tpm_limit?: number | null; /** Tpm Limit Type */ @@ -33216,6 +33251,8 @@ export interface components { input_cost_per_video_per_second_above_15s_interval?: number | null; /** Input Cost Per Video Per Second Above 8S Interval */ input_cost_per_video_per_second_above_8s_interval?: number | null; + /** Itpm */ + itpm?: number | null; /** Litellm Credential Name */ litellm_credential_name?: string | null; /** Litellm Trace Id */ @@ -33251,6 +33288,8 @@ export interface components { ocr_cost_per_page?: number | null; /** Organization */ organization?: string | null; + /** Otpm */ + otpm?: number | null; /** Output Cost Per Audio Per Second */ output_cost_per_audio_per_second?: number | null; /** Output Cost Per Audio Token */ diff --git a/ui/litellm-dashboard/tsconfig.tsbuildinfo b/ui/litellm-dashboard/tsconfig.tsbuildinfo index eb721b0875a..9524f2fbf6c 100644 --- a/ui/litellm-dashboard/tsconfig.tsbuildinfo +++ b/ui/litellm-dashboard/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"fileNames":["./node_modules/typescript/lib/lib.es5.d.ts","./node_modules/typescript/lib/lib.es2015.d.ts","./node_modules/typescript/lib/lib.es2016.d.ts","./node_modules/typescript/lib/lib.es2017.d.ts","./node_modules/typescript/lib/lib.es2018.d.ts","./node_modules/typescript/lib/lib.es2019.d.ts","./node_modules/typescript/lib/lib.es2020.d.ts","./node_modules/typescript/lib/lib.es2021.d.ts","./node_modules/typescript/lib/lib.es2022.d.ts","./node_modules/typescript/lib/lib.es2023.d.ts","./node_modules/typescript/lib/lib.es2024.d.ts","./node_modules/typescript/lib/lib.esnext.d.ts","./node_modules/typescript/lib/lib.dom.d.ts","./node_modules/typescript/lib/lib.dom.iterable.d.ts","./node_modules/typescript/lib/lib.es2015.core.d.ts","./node_modules/typescript/lib/lib.es2015.collection.d.ts","./node_modules/typescript/lib/lib.es2015.generator.d.ts","./node_modules/typescript/lib/lib.es2015.iterable.d.ts","./node_modules/typescript/lib/lib.es2015.promise.d.ts","./node_modules/typescript/lib/lib.es2015.proxy.d.ts","./node_modules/typescript/lib/lib.es2015.reflect.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2016.array.include.d.ts","./node_modules/typescript/lib/lib.es2016.intl.d.ts","./node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","./node_modules/typescript/lib/lib.es2017.date.d.ts","./node_modules/typescript/lib/lib.es2017.object.d.ts","./node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2017.string.d.ts","./node_modules/typescript/lib/lib.es2017.intl.d.ts","./node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","./node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","./node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","./node_modules/typescript/lib/lib.es2018.intl.d.ts","./node_modules/typescript/lib/lib.es2018.promise.d.ts","./node_modules/typescript/lib/lib.es2018.regexp.d.ts","./node_modules/typescript/lib/lib.es2019.array.d.ts","./node_modules/typescript/lib/lib.es2019.object.d.ts","./node_modules/typescript/lib/lib.es2019.string.d.ts","./node_modules/typescript/lib/lib.es2019.symbol.d.ts","./node_modules/typescript/lib/lib.es2019.intl.d.ts","./node_modules/typescript/lib/lib.es2020.bigint.d.ts","./node_modules/typescript/lib/lib.es2020.date.d.ts","./node_modules/typescript/lib/lib.es2020.promise.d.ts","./node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2020.string.d.ts","./node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2020.intl.d.ts","./node_modules/typescript/lib/lib.es2020.number.d.ts","./node_modules/typescript/lib/lib.es2021.promise.d.ts","./node_modules/typescript/lib/lib.es2021.string.d.ts","./node_modules/typescript/lib/lib.es2021.weakref.d.ts","./node_modules/typescript/lib/lib.es2021.intl.d.ts","./node_modules/typescript/lib/lib.es2022.array.d.ts","./node_modules/typescript/lib/lib.es2022.error.d.ts","./node_modules/typescript/lib/lib.es2022.intl.d.ts","./node_modules/typescript/lib/lib.es2022.object.d.ts","./node_modules/typescript/lib/lib.es2022.string.d.ts","./node_modules/typescript/lib/lib.es2022.regexp.d.ts","./node_modules/typescript/lib/lib.es2023.array.d.ts","./node_modules/typescript/lib/lib.es2023.collection.d.ts","./node_modules/typescript/lib/lib.es2023.intl.d.ts","./node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts","./node_modules/typescript/lib/lib.es2024.collection.d.ts","./node_modules/typescript/lib/lib.es2024.object.d.ts","./node_modules/typescript/lib/lib.es2024.promise.d.ts","./node_modules/typescript/lib/lib.es2024.regexp.d.ts","./node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2024.string.d.ts","./node_modules/typescript/lib/lib.esnext.array.d.ts","./node_modules/typescript/lib/lib.esnext.collection.d.ts","./node_modules/typescript/lib/lib.esnext.intl.d.ts","./node_modules/typescript/lib/lib.esnext.disposable.d.ts","./node_modules/typescript/lib/lib.esnext.promise.d.ts","./node_modules/typescript/lib/lib.esnext.decorators.d.ts","./node_modules/typescript/lib/lib.esnext.iterator.d.ts","./node_modules/typescript/lib/lib.esnext.float16.d.ts","./node_modules/typescript/lib/lib.esnext.error.d.ts","./node_modules/typescript/lib/lib.esnext.sharedmemory.d.ts","./node_modules/typescript/lib/lib.decorators.d.ts","./node_modules/typescript/lib/lib.decorators.legacy.d.ts","./node_modules/@types/react/global.d.ts","./node_modules/csstype/index.d.ts","./node_modules/@types/prop-types/index.d.ts","./node_modules/@types/react/index.d.ts","./node_modules/next/dist/styled-jsx/types/css.d.ts","./node_modules/next/dist/styled-jsx/types/macro.d.ts","./node_modules/next/dist/styled-jsx/types/style.d.ts","./node_modules/next/dist/styled-jsx/types/global.d.ts","./node_modules/next/dist/styled-jsx/types/index.d.ts","./node_modules/next/dist/server/get-page-files.d.ts","./node_modules/@types/node/compatibility/disposable.d.ts","./node_modules/@types/node/compatibility/indexable.d.ts","./node_modules/@types/node/compatibility/iterators.d.ts","./node_modules/@types/node/compatibility/index.d.ts","./node_modules/@types/node/globals.typedarray.d.ts","./node_modules/@types/node/buffer.buffer.d.ts","./node_modules/@types/node/globals.d.ts","./node_modules/@types/node/web-globals/abortcontroller.d.ts","./node_modules/@types/node/web-globals/domexception.d.ts","./node_modules/@types/node/web-globals/events.d.ts","./node_modules/undici-types/header.d.ts","./node_modules/undici-types/readable.d.ts","./node_modules/undici-types/file.d.ts","./node_modules/undici-types/fetch.d.ts","./node_modules/undici-types/formdata.d.ts","./node_modules/undici-types/connector.d.ts","./node_modules/undici-types/client.d.ts","./node_modules/undici-types/errors.d.ts","./node_modules/undici-types/dispatcher.d.ts","./node_modules/undici-types/global-dispatcher.d.ts","./node_modules/undici-types/global-origin.d.ts","./node_modules/undici-types/pool-stats.d.ts","./node_modules/undici-types/pool.d.ts","./node_modules/undici-types/handlers.d.ts","./node_modules/undici-types/balanced-pool.d.ts","./node_modules/undici-types/agent.d.ts","./node_modules/undici-types/mock-interceptor.d.ts","./node_modules/undici-types/mock-agent.d.ts","./node_modules/undici-types/mock-client.d.ts","./node_modules/undici-types/mock-pool.d.ts","./node_modules/undici-types/mock-errors.d.ts","./node_modules/undici-types/proxy-agent.d.ts","./node_modules/undici-types/env-http-proxy-agent.d.ts","./node_modules/undici-types/retry-handler.d.ts","./node_modules/undici-types/retry-agent.d.ts","./node_modules/undici-types/api.d.ts","./node_modules/undici-types/interceptors.d.ts","./node_modules/undici-types/util.d.ts","./node_modules/undici-types/cookies.d.ts","./node_modules/undici-types/patch.d.ts","./node_modules/undici-types/websocket.d.ts","./node_modules/undici-types/eventsource.d.ts","./node_modules/undici-types/filereader.d.ts","./node_modules/undici-types/diagnostics-channel.d.ts","./node_modules/undici-types/content-type.d.ts","./node_modules/undici-types/cache.d.ts","./node_modules/undici-types/index.d.ts","./node_modules/@types/node/web-globals/fetch.d.ts","./node_modules/@types/node/assert.d.ts","./node_modules/@types/node/assert/strict.d.ts","./node_modules/@types/node/async_hooks.d.ts","./node_modules/@types/node/buffer.d.ts","./node_modules/@types/node/child_process.d.ts","./node_modules/@types/node/cluster.d.ts","./node_modules/@types/node/console.d.ts","./node_modules/@types/node/constants.d.ts","./node_modules/@types/node/crypto.d.ts","./node_modules/@types/node/dgram.d.ts","./node_modules/@types/node/diagnostics_channel.d.ts","./node_modules/@types/node/dns.d.ts","./node_modules/@types/node/dns/promises.d.ts","./node_modules/@types/node/domain.d.ts","./node_modules/@types/node/events.d.ts","./node_modules/@types/node/fs.d.ts","./node_modules/@types/node/fs/promises.d.ts","./node_modules/@types/node/http.d.ts","./node_modules/@types/node/http2.d.ts","./node_modules/@types/node/https.d.ts","./node_modules/@types/node/inspector.generated.d.ts","./node_modules/@types/node/module.d.ts","./node_modules/@types/node/net.d.ts","./node_modules/@types/node/os.d.ts","./node_modules/@types/node/path.d.ts","./node_modules/@types/node/perf_hooks.d.ts","./node_modules/@types/node/process.d.ts","./node_modules/@types/node/punycode.d.ts","./node_modules/@types/node/querystring.d.ts","./node_modules/@types/node/readline.d.ts","./node_modules/@types/node/readline/promises.d.ts","./node_modules/@types/node/repl.d.ts","./node_modules/@types/node/sea.d.ts","./node_modules/@types/node/stream.d.ts","./node_modules/@types/node/stream/promises.d.ts","./node_modules/@types/node/stream/consumers.d.ts","./node_modules/@types/node/stream/web.d.ts","./node_modules/@types/node/string_decoder.d.ts","./node_modules/@types/node/test.d.ts","./node_modules/@types/node/timers.d.ts","./node_modules/@types/node/timers/promises.d.ts","./node_modules/@types/node/tls.d.ts","./node_modules/@types/node/trace_events.d.ts","./node_modules/@types/node/tty.d.ts","./node_modules/@types/node/url.d.ts","./node_modules/@types/node/util.d.ts","./node_modules/@types/node/v8.d.ts","./node_modules/@types/node/vm.d.ts","./node_modules/@types/node/wasi.d.ts","./node_modules/@types/node/worker_threads.d.ts","./node_modules/@types/node/zlib.d.ts","./node_modules/@types/node/index.d.ts","./node_modules/@types/react/canary.d.ts","./node_modules/@types/react/experimental.d.ts","./node_modules/@types/react-dom/index.d.ts","./node_modules/@types/react-dom/canary.d.ts","./node_modules/@types/react-dom/experimental.d.ts","./node_modules/next/dist/lib/fallback.d.ts","./node_modules/next/dist/compiled/webpack/webpack.d.ts","./node_modules/next/dist/shared/lib/modern-browserslist-target.d.ts","./node_modules/next/dist/shared/lib/entry-constants.d.ts","./node_modules/next/dist/shared/lib/constants.d.ts","./node_modules/next/dist/lib/bundler.d.ts","./node_modules/next/dist/server/config.d.ts","./node_modules/next/dist/lib/load-custom-routes.d.ts","./node_modules/next/dist/shared/lib/image-config.d.ts","./node_modules/next/dist/build/webpack/plugins/subresource-integrity-plugin.d.ts","./node_modules/next/dist/server/body-streams.d.ts","./node_modules/next/dist/server/request/search-params.d.ts","./node_modules/next/dist/shared/lib/segment-cache/vary-params-decoding.d.ts","./node_modules/next/dist/server/app-render/vary-params.d.ts","./node_modules/next/dist/server/request/params.d.ts","./node_modules/next/dist/server/route-kind.d.ts","./node_modules/next/dist/server/route-definitions/route-definition.d.ts","./node_modules/next/dist/server/route-matches/route-match.d.ts","./node_modules/next/dist/client/components/app-router-headers.d.ts","./node_modules/next/dist/server/lib/cache-control.d.ts","./node_modules/next/dist/shared/lib/app-router-types.d.ts","./node_modules/next/dist/server/lib/cache-handlers/types.d.ts","./node_modules/next/dist/server/use-cache/use-cache-wrapper.d.ts","./node_modules/next/dist/server/resume-data-cache/cache-store.d.ts","./node_modules/next/dist/server/resume-data-cache/resume-data-cache.d.ts","./node_modules/next/dist/lib/constants.d.ts","./node_modules/next/dist/server/render-result.d.ts","./node_modules/next/dist/server/response-cache/types.d.ts","./node_modules/next/dist/server/response-cache/index.d.ts","./node_modules/@types/react/jsx-runtime.d.ts","./node_modules/next/dist/next-devtools/userspace/pages/pages-dev-overlay-setup.d.ts","./node_modules/next/dist/build/static-paths/types.d.ts","./node_modules/next/dist/server/route-definitions/app-page-route-definition.d.ts","./node_modules/next/dist/build/adapter/setup-node-env.external.d.ts","./node_modules/next/dist/server/instrumentation/types.d.ts","./node_modules/next/dist/lib/setup-exception-listeners.d.ts","./node_modules/next/dist/lib/worker.d.ts","./node_modules/next/dist/server/lib/experimental/ppr.d.ts","./node_modules/next/dist/lib/page-types.d.ts","./node_modules/next/dist/build/segment-config/app/app-segment-config.d.ts","./node_modules/next/dist/build/segment-config/pages/pages-segment-config.d.ts","./node_modules/next/dist/build/analysis/get-page-static-info.d.ts","./node_modules/next/dist/build/webpack/loaders/get-module-build-info.d.ts","./node_modules/next/dist/build/webpack/plugins/middleware-plugin.d.ts","./node_modules/next/dist/server/require-hook.d.ts","./node_modules/next/dist/server/node-polyfill-crypto.d.ts","./node_modules/next/dist/server/node-environment-baseline.d.ts","./node_modules/next/dist/server/node-environment-extensions/error-inspect.d.ts","./node_modules/next/dist/server/node-environment-extensions/console-file.d.ts","./node_modules/next/dist/server/node-environment-extensions/console-exit.d.ts","./node_modules/next/dist/server/node-environment-extensions/console-dim.external.d.ts","./node_modules/next/dist/server/node-environment-extensions/unhandled-rejection.external.d.ts","./node_modules/next/dist/server/node-environment-extensions/random.d.ts","./node_modules/next/dist/server/node-environment-extensions/date.d.ts","./node_modules/next/dist/server/node-environment-extensions/web-crypto.d.ts","./node_modules/next/dist/server/node-environment-extensions/node-crypto.d.ts","./node_modules/next/dist/server/node-environment-extensions/fast-set-immediate.external.d.ts","./node_modules/next/dist/server/node-environment.d.ts","./node_modules/next/dist/build/page-extensions-type.d.ts","./node_modules/next/dist/server/route-modules/app-page/module.compiled.d.ts","./node_modules/next/dist/server/route-definitions/app-route-route-definition.d.ts","./node_modules/next/dist/server/lib/i18n-provider.d.ts","./node_modules/next/dist/server/web/next-url.d.ts","./node_modules/next/dist/compiled/@edge-runtime/cookies/index.d.ts","./node_modules/next/dist/server/web/spec-extension/cookies.d.ts","./node_modules/next/dist/server/web/spec-extension/request.d.ts","./node_modules/next/dist/shared/lib/deep-readonly.d.ts","./node_modules/next/dist/server/lib/incremental-cache/index.d.ts","./node_modules/next/dist/shared/lib/router/utils/middleware-route-matcher.d.ts","./node_modules/next/dist/build/webpack/plugins/flight-manifest-plugin.d.ts","./node_modules/next/dist/build/webpack/plugins/next-font-manifest-plugin.d.ts","./node_modules/next/dist/server/route-definitions/locale-route-definition.d.ts","./node_modules/next/dist/server/route-definitions/pages-route-definition.d.ts","./node_modules/next/dist/shared/lib/mitt.d.ts","./node_modules/next/dist/client/with-router.d.ts","./node_modules/next/dist/client/router.d.ts","./node_modules/next/dist/client/route-loader.d.ts","./node_modules/next/dist/client/page-loader.d.ts","./node_modules/next/dist/shared/lib/bloom-filter.d.ts","./node_modules/next/dist/shared/lib/router/router.d.ts","./node_modules/next/dist/shared/lib/router-context.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/loadable-context.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/loadable.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/image-config-context.shared-runtime.d.ts","./node_modules/next/dist/client/components/readonly-url-search-params.d.ts","./node_modules/next/dist/shared/lib/hooks-client-context.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/head-manager-context.shared-runtime.d.ts","./node_modules/next/dist/client/flight-data-helpers.d.ts","./node_modules/next/dist/client/components/segment-cache/cache-key.d.ts","./node_modules/next/dist/client/components/router-reducer/fetch-server-response.d.ts","./node_modules/next/dist/client/components/segment-cache/types.d.ts","./node_modules/next/dist/shared/lib/segment-cache/segment-value-encoding.d.ts","./node_modules/next/dist/client/components/segment-cache/scheduler.d.ts","./node_modules/next/dist/client/components/segment-cache/cache-map.d.ts","./node_modules/next/dist/client/components/segment-cache/vary-path.d.ts","./node_modules/next/dist/client/components/segment-cache/cache.d.ts","./node_modules/next/dist/client/components/router-reducer/ppr-navigations.d.ts","./node_modules/next/dist/client/components/segment-cache/navigation.d.ts","./node_modules/next/dist/client/components/router-reducer/router-reducer-types.d.ts","./node_modules/next/dist/shared/lib/app-router-context.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/server-inserted-html.shared-runtime.d.ts","./node_modules/next/dist/server/route-modules/pages/vendored/contexts/entrypoints.d.ts","./node_modules/next/dist/server/route-modules/pages/module.compiled.d.ts","./node_modules/next/dist/build/templates/pages.d.ts","./node_modules/next/dist/server/route-modules/pages/module.d.ts","./node_modules/next/dist/server/render.d.ts","./node_modules/next/dist/build/webpack/plugins/pages-manifest-plugin.d.ts","./node_modules/next/dist/server/route-definitions/pages-api-route-definition.d.ts","./node_modules/next/dist/server/route-matches/pages-api-route-match.d.ts","./node_modules/next/dist/server/route-matchers/route-matcher.d.ts","./node_modules/next/dist/server/route-matcher-providers/route-matcher-provider.d.ts","./node_modules/next/dist/server/route-matcher-managers/route-matcher-manager.d.ts","./node_modules/next/dist/server/normalizers/normalizer.d.ts","./node_modules/next/dist/server/normalizers/locale-route-normalizer.d.ts","./node_modules/next/dist/server/normalizers/request/pathname-normalizer.d.ts","./node_modules/next/dist/server/normalizers/request/suffix.d.ts","./node_modules/next/dist/server/normalizers/request/rsc.d.ts","./node_modules/next/dist/server/normalizers/request/next-data.d.ts","./node_modules/next/dist/server/after/builtin-request-context.d.ts","./node_modules/next/dist/server/normalizers/request/segment-prefix-rsc.d.ts","./node_modules/next/dist/server/route-modules/pages/builtin/_error.d.ts","./node_modules/next/dist/server/load-default-error-components.d.ts","./node_modules/next/dist/server/base-server.d.ts","./node_modules/next/dist/server/after/after.d.ts","./node_modules/next/dist/server/after/after-context.d.ts","./node_modules/next/dist/server/use-cache/cache-life.d.ts","./node_modules/next/dist/server/app-render/work-async-storage-instance.d.ts","./node_modules/next/dist/server/lib/lazy-result.d.ts","./node_modules/next/dist/server/app-render/create-error-handler.d.ts","./node_modules/next/dist/shared/lib/action-revalidation-kind.d.ts","./node_modules/next/dist/server/app-render/work-async-storage.external.d.ts","./node_modules/next/dist/server/async-storage/work-store.d.ts","./node_modules/next/dist/server/web/http.d.ts","./node_modules/next/dist/client/components/hooks-server-context.d.ts","./node_modules/next/dist/server/route-modules/app-route/shared-modules.d.ts","./node_modules/next/dist/client/components/redirect-status-code.d.ts","./node_modules/next/dist/client/components/redirect-error.d.ts","./node_modules/next/dist/server/web/spec-extension/adapters/request-cookies.d.ts","./node_modules/next/dist/server/async-storage/draft-mode-provider.d.ts","./node_modules/next/dist/server/web/spec-extension/adapters/headers.d.ts","./node_modules/next/dist/server/app-render/cache-signal.d.ts","./node_modules/next/dist/server/app-render/instant-validation/boundary-tracking.d.ts","./node_modules/next/dist/server/app-render/instant-validation/instant-validation-error.d.ts","./node_modules/next/dist/shared/lib/router/utils/parse-relative-url.d.ts","./node_modules/next/dist/server/app-render/instant-validation/instant-samples.d.ts","./node_modules/next/dist/server/app-render/dynamic-rendering.d.ts","./node_modules/next/dist/server/app-render/work-unit-async-storage-instance.d.ts","./node_modules/next/dist/server/lib/implicit-tags.d.ts","./node_modules/next/dist/server/app-render/staged-rendering.d.ts","./node_modules/next/dist/server/app-render/work-unit-async-storage.external.d.ts","./node_modules/next/dist/build/templates/app-route.d.ts","./node_modules/next/dist/server/app-render/action-async-storage-instance.d.ts","./node_modules/next/dist/server/app-render/action-async-storage.external.d.ts","./node_modules/next/dist/server/route-modules/app-route/module.d.ts","./node_modules/next/dist/server/route-modules/app-route/module.compiled.d.ts","./node_modules/next/dist/build/segment-config/app/app-segments.d.ts","./node_modules/next/dist/build/get-supported-browsers.d.ts","./node_modules/next/dist/build/utils.d.ts","./node_modules/next/dist/build/rendering-mode.d.ts","./node_modules/next/dist/server/lib/router-utils/build-prefetch-segment-data-route.d.ts","./node_modules/next/dist/server/lib/cpu-profile.d.ts","./node_modules/next/dist/build/turborepo-access-trace/types.d.ts","./node_modules/next/dist/build/turborepo-access-trace/result.d.ts","./node_modules/next/dist/build/turborepo-access-trace/helpers.d.ts","./node_modules/next/dist/build/turborepo-access-trace/index.d.ts","./node_modules/next/dist/export/routes/types.d.ts","./node_modules/next/dist/export/types.d.ts","./node_modules/next/dist/export/worker.d.ts","./node_modules/next/dist/build/worker.d.ts","./node_modules/next/dist/build/index.d.ts","./node_modules/next/dist/lib/coalesced-function.d.ts","./node_modules/next/dist/server/lib/router-utils/types.d.ts","./node_modules/next/dist/trace/types.d.ts","./node_modules/next/dist/trace/trace.d.ts","./node_modules/next/dist/trace/shared.d.ts","./node_modules/next/dist/trace/index.d.ts","./node_modules/next/dist/build/load-jsconfig.d.ts","./node_modules/@next/env/dist/index.d.ts","./node_modules/next/dist/build/webpack/plugins/telemetry-plugin/use-cache-tracker-utils.d.ts","./node_modules/next/dist/build/webpack/plugins/telemetry-plugin/telemetry-plugin.d.ts","./node_modules/next/dist/telemetry/storage.d.ts","./node_modules/next/dist/build/build-context.d.ts","./node_modules/next/dist/build/webpack-config.d.ts","./node_modules/next/dist/build/swc/generated-native.d.ts","./node_modules/next/dist/build/define-env.d.ts","./node_modules/next/dist/build/swc/index.d.ts","./node_modules/next/dist/build/swc/types.d.ts","./node_modules/next/dist/server/dev/parse-version-info.d.ts","./node_modules/next/dist/next-devtools/shared/types.d.ts","./node_modules/next/dist/server/dev/dev-indicator-server-state.d.ts","./node_modules/next/dist/next-devtools/dev-overlay/cache-indicator.d.ts","./node_modules/next/dist/server/lib/parse-stack.d.ts","./node_modules/next/dist/next-devtools/server/shared.d.ts","./node_modules/next/dist/next-devtools/shared/stack-frame.d.ts","./node_modules/next/dist/next-devtools/dev-overlay/utils/get-error-by-type.d.ts","./node_modules/next/dist/next-devtools/dev-overlay/container/runtime-error/render-error.d.ts","./node_modules/next/dist/next-devtools/dev-overlay/shared.d.ts","./node_modules/next/dist/server/dev/debug-channel.d.ts","./node_modules/next/dist/server/dev/hot-reloader-types.d.ts","./node_modules/next/dist/server/web/spec-extension/fetch-event.d.ts","./node_modules/next/dist/server/web/spec-extension/response.d.ts","./node_modules/next/dist/build/segment-config/middleware/middleware-config.d.ts","./node_modules/next/dist/server/web/types.d.ts","./node_modules/next/dist/shared/lib/router/utils/parse-url.d.ts","./node_modules/next/dist/server/base-http/node.d.ts","./node_modules/next/dist/server/lib/async-callback-set.d.ts","./node_modules/next/dist/shared/lib/router/utils/route-regex.d.ts","./node_modules/next/dist/shared/lib/router/utils/route-matcher.d.ts","./node_modules/sharp/lib/index.d.ts","./node_modules/next/dist/server/image-optimizer.d.ts","./node_modules/next/dist/server/next-server.d.ts","./node_modules/next/dist/server/lib/types.d.ts","./node_modules/next/dist/server/lib/lru-cache.d.ts","./node_modules/next/dist/server/lib/dev-bundler-service.d.ts","./node_modules/next/dist/server/dev/static-paths-worker.d.ts","./node_modules/next/dist/server/dev/next-dev-server.d.ts","./node_modules/next/dist/server/next.d.ts","./node_modules/next/dist/server/lib/render-server.d.ts","./node_modules/next/dist/server/lib/router-server.d.ts","./node_modules/next/dist/shared/lib/router/utils/path-match.d.ts","./node_modules/next/dist/server/lib/router-utils/filesystem.d.ts","./node_modules/next/dist/server/lib/router-utils/setup-dev-bundler.d.ts","./node_modules/next/dist/server/lib/router-utils/router-server-context.d.ts","./node_modules/next/dist/server/route-modules/route-module.d.ts","./node_modules/next/dist/server/load-components.d.ts","./node_modules/next/dist/server/web/adapter.d.ts","./node_modules/next/dist/server/app-render/types.d.ts","./node_modules/next/dist/build/webpack/loaders/metadata/types.d.ts","./node_modules/next/dist/build/webpack/loaders/next-app-loader/index.d.ts","./node_modules/next/dist/server/lib/app-dir-module.d.ts","./node_modules/next/dist/server/app-render/app-render.d.ts","./node_modules/next/dist/server/route-modules/app-page/vendored/contexts/entrypoints.d.ts","./node_modules/next/dist/client/components/error-boundary.d.ts","./node_modules/next/dist/client/components/layout-router.d.ts","./node_modules/next/dist/client/components/render-from-template-context.d.ts","./node_modules/next/dist/client/components/client-page.d.ts","./node_modules/next/dist/client/components/client-segment.d.ts","./node_modules/next/dist/client/components/http-access-fallback/error-boundary.d.ts","./node_modules/next/dist/lib/metadata/types/alternative-urls-types.d.ts","./node_modules/next/dist/lib/metadata/types/extra-types.d.ts","./node_modules/next/dist/lib/metadata/types/metadata-types.d.ts","./node_modules/next/dist/lib/metadata/types/manifest-types.d.ts","./node_modules/next/dist/lib/metadata/types/opengraph-types.d.ts","./node_modules/next/dist/lib/metadata/types/twitter-types.d.ts","./node_modules/next/dist/lib/metadata/types/metadata-interface.d.ts","./node_modules/next/dist/lib/metadata/types/resolvers.d.ts","./node_modules/next/dist/lib/metadata/types/icons.d.ts","./node_modules/next/dist/lib/metadata/resolve-metadata.d.ts","./node_modules/next/dist/lib/metadata/metadata.d.ts","./node_modules/next/dist/lib/framework/boundary-components.d.ts","./node_modules/next/dist/server/app-render/rsc/preloads.d.ts","./node_modules/next/dist/server/app-render/rsc/postpone.d.ts","./node_modules/next/dist/server/app-render/rsc/taint.d.ts","./node_modules/next/dist/server/app-render/collect-segment-data.d.ts","./node_modules/next/dist/server/app-render/instant-validation/instant-validation.d.ts","./node_modules/next/dist/next-devtools/userspace/app/segment-explorer-node.d.ts","./node_modules/next/dist/server/app-render/entry-base.d.ts","./node_modules/next/dist/build/templates/app-page.d.ts","./node_modules/next/dist/server/route-modules/app-page/helpers/prerender-manifest-matcher.d.ts","./node_modules/@types/react/jsx-dev-runtime.d.ts","./node_modules/next/dist/server/route-modules/app-page/vendored/rsc/entrypoints.d.ts","./node_modules/@types/react-dom/client.d.ts","./node_modules/@types/react-dom/server.d.ts","./node_modules/next/dist/server/route-modules/app-page/vendored/ssr/entrypoints.d.ts","./node_modules/next/dist/server/route-modules/app-page/module.d.ts","./node_modules/next/dist/server/request/fallback-params.d.ts","./node_modules/next/dist/server/web/spec-extension/image-response.d.ts","./node_modules/next/dist/server/web/spec-extension/user-agent.d.ts","./node_modules/next/dist/server/web/spec-extension/url-pattern.d.ts","./node_modules/next/dist/server/after/index.d.ts","./node_modules/next/dist/server/request/connection.d.ts","./node_modules/next/dist/server/web/exports/index.d.ts","./node_modules/next/dist/server/request-meta.d.ts","./node_modules/next/dist/cli/next-test.d.ts","./node_modules/next/dist/shared/lib/size-limit.d.ts","./node_modules/next/dist/server/config-shared.d.ts","./node_modules/next/dist/server/base-http/index.d.ts","./node_modules/next/dist/server/api-utils/index.d.ts","./node_modules/next/dist/build/adapter/build-complete.d.ts","./node_modules/next/dist/types.d.ts","./node_modules/next/dist/shared/lib/html-context.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/utils.d.ts","./node_modules/next/dist/pages/_app.d.ts","./node_modules/next/app.d.ts","./node_modules/next/dist/server/web/spec-extension/unstable-cache.d.ts","./node_modules/next/dist/server/web/spec-extension/revalidate.d.ts","./node_modules/next/dist/server/web/spec-extension/unstable-no-store.d.ts","./node_modules/next/dist/server/use-cache/cache-tag.d.ts","./node_modules/next/cache.d.ts","./node_modules/next/dist/pages/_document.d.ts","./node_modules/next/document.d.ts","./node_modules/next/dist/shared/lib/dynamic.d.ts","./node_modules/next/dynamic.d.ts","./node_modules/next/dist/pages/_error.d.ts","./node_modules/next/dist/client/components/catch-error.d.ts","./node_modules/next/dist/api/error.d.ts","./node_modules/next/error.d.ts","./node_modules/next/dist/shared/lib/head.d.ts","./node_modules/next/head.d.ts","./node_modules/next/dist/server/request/cookies.d.ts","./node_modules/next/dist/server/request/headers.d.ts","./node_modules/next/dist/server/request/draft-mode.d.ts","./node_modules/next/headers.d.ts","./node_modules/next/dist/shared/lib/get-img-props.d.ts","./node_modules/next/dist/client/image-component.d.ts","./node_modules/next/dist/shared/lib/image-external.d.ts","./node_modules/next/image.d.ts","./node_modules/next/dist/client/link.d.ts","./node_modules/next/link.d.ts","./node_modules/next/dist/client/components/unrecognized-action-error.d.ts","./node_modules/next/dist/client/components/redirect.d.ts","./node_modules/next/dist/client/components/not-found.d.ts","./node_modules/next/dist/client/components/forbidden.d.ts","./node_modules/next/dist/client/components/unauthorized.d.ts","./node_modules/next/dist/client/components/unstable-rethrow.server.d.ts","./node_modules/next/dist/client/components/unstable-rethrow.d.ts","./node_modules/next/dist/client/components/navigation.react-server.d.ts","./node_modules/next/dist/client/components/navigation.d.ts","./node_modules/next/navigation.d.ts","./node_modules/next/router.d.ts","./node_modules/next/dist/client/script.d.ts","./node_modules/next/script.d.ts","./node_modules/next/dist/compiled/@edge-runtime/primitives/url.d.ts","./node_modules/next/dist/compiled/@vercel/og/satori/index.d.ts","./node_modules/next/dist/compiled/@vercel/og/types.d.ts","./node_modules/next/server.d.ts","./node_modules/next/types/global.d.ts","./node_modules/next/types/compiled.d.ts","./node_modules/next/types.d.ts","./node_modules/next/index.d.ts","./node_modules/next/image-types/global.d.ts","./.next/dev/types/routes.d.ts","./next-env.d.ts","./tailwind.config.ts","./node_modules/@vitest/spy/dist/index.d.ts","./node_modules/@vitest/pretty-format/dist/index.d.ts","./node_modules/@vitest/utils/dist/types.d.ts","./node_modules/@vitest/utils/dist/helpers.d.ts","./node_modules/tinyrainbow/dist/index-8b61d5bc.d.ts","./node_modules/tinyrainbow/dist/node.d.ts","./node_modules/@vitest/utils/dist/index.d.ts","./node_modules/@vitest/utils/dist/types.d-bcelap-c.d.ts","./node_modules/@vitest/utils/dist/diff.d.ts","./node_modules/@vitest/expect/dist/index.d.ts","./node_modules/vite/types/hmrpayload.d.ts","./node_modules/vite/dist/node/chunks/modulerunnertransport.d.ts","./node_modules/vite/types/customevent.d.ts","./node_modules/@types/estree/index.d.ts","./node_modules/rollup/dist/rollup.d.ts","./node_modules/rollup/dist/parseast.d.ts","./node_modules/vite/types/hot.d.ts","./node_modules/vite/dist/node/module-runner.d.ts","./node_modules/esbuild/lib/main.d.ts","./node_modules/vite/types/internal/terseroptions.d.ts","./node_modules/source-map-js/source-map.d.ts","./node_modules/postcss/lib/previous-map.d.ts","./node_modules/postcss/lib/input.d.ts","./node_modules/postcss/lib/css-syntax-error.d.ts","./node_modules/postcss/lib/declaration.d.ts","./node_modules/postcss/lib/root.d.ts","./node_modules/postcss/lib/warning.d.ts","./node_modules/postcss/lib/lazy-result.d.ts","./node_modules/postcss/lib/no-work-result.d.ts","./node_modules/postcss/lib/processor.d.ts","./node_modules/postcss/lib/result.d.ts","./node_modules/postcss/lib/document.d.ts","./node_modules/postcss/lib/rule.d.ts","./node_modules/postcss/lib/node.d.ts","./node_modules/postcss/lib/comment.d.ts","./node_modules/postcss/lib/container.d.ts","./node_modules/postcss/lib/at-rule.d.ts","./node_modules/postcss/lib/list.d.ts","./node_modules/postcss/lib/postcss.d.ts","./node_modules/postcss/lib/postcss.d.mts","./node_modules/vite/types/internal/csspreprocessoroptions.d.ts","./node_modules/vite/types/internal/lightningcssoptions.d.ts","./node_modules/vite/types/importglob.d.ts","./node_modules/vite/types/metadata.d.ts","./node_modules/vite/dist/node/index.d.ts","./node_modules/@vitest/runner/dist/tasks.d-cksck4of.d.ts","./node_modules/@vitest/runner/dist/types.d.ts","./node_modules/@vitest/utils/dist/error.d.ts","./node_modules/@vitest/runner/dist/index.d.ts","./node_modules/vitest/optional-types.d.ts","./node_modules/vitest/dist/chunks/environment.d.cl3nlxbe.d.ts","./node_modules/@vitest/mocker/dist/registry.d-d765pazg.d.ts","./node_modules/@vitest/mocker/dist/types.d-d_arzrdy.d.ts","./node_modules/@vitest/mocker/dist/index.d.ts","./node_modules/@vitest/utils/dist/source-map.d.ts","./node_modules/vite-node/dist/trace-mapping.d-dlvdeqop.d.ts","./node_modules/vite-node/dist/index.d-dgmxd2u7.d.ts","./node_modules/vite-node/dist/index.d.ts","./node_modules/@vitest/snapshot/dist/environment.d-dhdq1csl.d.ts","./node_modules/@vitest/snapshot/dist/rawsnapshot.d-lfsmjfud.d.ts","./node_modules/@vitest/snapshot/dist/index.d.ts","./node_modules/@vitest/snapshot/dist/environment.d.ts","./node_modules/vitest/dist/chunks/config.d.d2roskhv.d.ts","./node_modules/vitest/dist/chunks/worker.d.1gmbbd7g.d.ts","./node_modules/@types/deep-eql/index.d.ts","./node_modules/assertion-error/index.d.ts","./node_modules/@types/chai/index.d.ts","./node_modules/@vitest/runner/dist/utils.d.ts","./node_modules/tinybench/dist/index.d.ts","./node_modules/vitest/dist/chunks/benchmark.d.bwvbvtda.d.ts","./node_modules/vite-node/dist/client.d.ts","./node_modules/vitest/dist/chunks/coverage.d.s9rmnxie.d.ts","./node_modules/@vitest/snapshot/dist/manager.d.ts","./node_modules/vitest/dist/chunks/reporters.d.bflkqcl6.d.ts","./node_modules/vitest/dist/chunks/vite.d.cmlllifp.d.ts","./node_modules/vitest/dist/config.d.ts","./node_modules/vitest/config.d.ts","./vitest.config.ts","./src/types.ts","./node_modules/antd/es/_util/responsiveobserver.d.ts","./node_modules/antd/es/_util/type.d.ts","./node_modules/antd/es/_util/throttlebyanimationframe.d.ts","./node_modules/antd/es/affix/index.d.ts","./node_modules/rc-util/lib/portal.d.ts","./node_modules/rc-util/lib/dom/scrolllocker.d.ts","./node_modules/rc-util/lib/portalwrapper.d.ts","./node_modules/rc-dialog/lib/idialogproptypes.d.ts","./node_modules/rc-dialog/lib/dialogwrap.d.ts","./node_modules/rc-dialog/lib/dialog/content/panel.d.ts","./node_modules/rc-dialog/lib/index.d.ts","./node_modules/antd/es/_util/aria-data-attrs.d.ts","./node_modules/antd/es/_util/hooks/useclosable.d.ts","./node_modules/antd/es/_util/hooks/useforceupdate.d.ts","./node_modules/antd/es/_util/hooks/usemergesemantic.d.ts","./node_modules/antd/es/_util/hooks/usemultipleselect.d.ts","./node_modules/antd/es/_util/hooks/usepatchelement.d.ts","./node_modules/antd/es/_util/hooks/useproxyimperativehandle.d.ts","./node_modules/antd/es/_util/hooks/usesyncstate.d.ts","./node_modules/antd/es/_util/hooks/usezindex.d.ts","./node_modules/antd/es/_util/hooks/index.d.ts","./node_modules/antd/es/alert/alert.d.ts","./node_modules/antd/es/alert/errorboundary.d.ts","./node_modules/antd/es/alert/index.d.ts","./node_modules/antd/es/anchor/anchorlink.d.ts","./node_modules/antd/es/anchor/anchor.d.ts","./node_modules/antd/es/anchor/index.d.ts","./node_modules/antd/es/message/interface.d.ts","./node_modules/antd/es/config-provider/sizecontext.d.ts","./node_modules/antd/es/button/button-group.d.ts","./node_modules/antd/es/button/buttonhelpers.d.ts","./node_modules/antd/es/button/button.d.ts","./node_modules/antd/es/_util/warning.d.ts","./node_modules/rc-field-form/lib/namepathtype.d.ts","./node_modules/rc-field-form/lib/useform.d.ts","./node_modules/rc-field-form/lib/interface.d.ts","./node_modules/rc-picker/lib/generate/index.d.ts","./node_modules/rc-motion/es/interface.d.ts","./node_modules/rc-motion/es/cssmotion.d.ts","./node_modules/rc-motion/es/util/diff.d.ts","./node_modules/rc-motion/es/cssmotionlist.d.ts","./node_modules/rc-motion/es/context.d.ts","./node_modules/rc-motion/es/index.d.ts","./node_modules/@rc-component/trigger/lib/interface.d.ts","./node_modules/@rc-component/trigger/lib/index.d.ts","./node_modules/rc-picker/lib/interface.d.ts","./node_modules/rc-picker/lib/pickerinput/selector/rangeselector.d.ts","./node_modules/rc-picker/lib/pickerinput/rangepicker.d.ts","./node_modules/rc-picker/lib/pickerinput/singlepicker.d.ts","./node_modules/rc-picker/lib/pickerpanel/index.d.ts","./node_modules/rc-picker/lib/index.d.ts","./node_modules/rc-field-form/lib/field.d.ts","./node_modules/rc-field-form/es/namepathtype.d.ts","./node_modules/rc-field-form/es/useform.d.ts","./node_modules/rc-field-form/es/interface.d.ts","./node_modules/rc-field-form/es/field.d.ts","./node_modules/rc-field-form/es/list.d.ts","./node_modules/rc-field-form/es/form.d.ts","./node_modules/rc-field-form/es/formcontext.d.ts","./node_modules/rc-field-form/es/fieldcontext.d.ts","./node_modules/rc-field-form/es/listcontext.d.ts","./node_modules/rc-field-form/es/usewatch.d.ts","./node_modules/rc-field-form/es/index.d.ts","./node_modules/rc-field-form/lib/form.d.ts","./node_modules/antd/es/grid/col.d.ts","./node_modules/compute-scroll-into-view/dist/index.d.ts","./node_modules/scroll-into-view-if-needed/dist/index.d.ts","./node_modules/antd/es/form/interface.d.ts","./node_modules/antd/es/form/hooks/useform.d.ts","./node_modules/antd/es/form/form.d.ts","./node_modules/antd/es/form/formiteminput.d.ts","./node_modules/rc-tooltip/lib/placements.d.ts","./node_modules/rc-tooltip/lib/tooltip.d.ts","./node_modules/@ant-design/cssinjs/lib/cache.d.ts","./node_modules/@ant-design/cssinjs/lib/hooks/useglobalcache.d.ts","./node_modules/@ant-design/cssinjs/lib/util/css-variables.d.ts","./node_modules/@ant-design/cssinjs/lib/extractstyle.d.ts","./node_modules/@ant-design/cssinjs/lib/theme/interface.d.ts","./node_modules/@ant-design/cssinjs/lib/theme/theme.d.ts","./node_modules/@ant-design/cssinjs/lib/hooks/usecachetoken.d.ts","./node_modules/@ant-design/cssinjs/lib/hooks/usecssvarregister.d.ts","./node_modules/@ant-design/cssinjs/lib/keyframes.d.ts","./node_modules/@ant-design/cssinjs/lib/linters/interface.d.ts","./node_modules/@ant-design/cssinjs/lib/linters/contentquoteslinter.d.ts","./node_modules/@ant-design/cssinjs/lib/linters/hashedanimationlinter.d.ts","./node_modules/@ant-design/cssinjs/lib/linters/legacynotselectorlinter.d.ts","./node_modules/@ant-design/cssinjs/lib/linters/logicalpropertieslinter.d.ts","./node_modules/@ant-design/cssinjs/lib/linters/nanlinter.d.ts","./node_modules/@ant-design/cssinjs/lib/linters/parentselectorlinter.d.ts","./node_modules/@ant-design/cssinjs/lib/linters/index.d.ts","./node_modules/@ant-design/cssinjs/lib/transformers/interface.d.ts","./node_modules/@ant-design/cssinjs/lib/stylecontext.d.ts","./node_modules/@ant-design/cssinjs/lib/hooks/usestyleregister.d.ts","./node_modules/@ant-design/cssinjs/lib/theme/calc/calculator.d.ts","./node_modules/@ant-design/cssinjs/lib/theme/calc/csscalculator.d.ts","./node_modules/@ant-design/cssinjs/lib/theme/calc/numcalculator.d.ts","./node_modules/@ant-design/cssinjs/lib/theme/calc/index.d.ts","./node_modules/@ant-design/cssinjs/lib/theme/createtheme.d.ts","./node_modules/@ant-design/cssinjs/lib/theme/themecache.d.ts","./node_modules/@ant-design/cssinjs/lib/theme/index.d.ts","./node_modules/@ant-design/cssinjs/lib/transformers/legacylogicalproperties.d.ts","./node_modules/@ant-design/cssinjs/lib/transformers/px2rem.d.ts","./node_modules/@ant-design/cssinjs/lib/util/index.d.ts","./node_modules/@ant-design/cssinjs/lib/index.d.ts","./node_modules/antd/es/theme/interface/presetcolors.d.ts","./node_modules/antd/es/theme/interface/seeds.d.ts","./node_modules/antd/es/theme/interface/maps/colors.d.ts","./node_modules/antd/es/theme/interface/maps/font.d.ts","./node_modules/antd/es/theme/interface/maps/size.d.ts","./node_modules/antd/es/theme/interface/maps/style.d.ts","./node_modules/antd/es/theme/interface/maps/index.d.ts","./node_modules/antd/es/theme/interface/alias.d.ts","./node_modules/@ant-design/cssinjs-utils/lib/interface/components.d.ts","./node_modules/@ant-design/cssinjs-utils/lib/interface/index.d.ts","./node_modules/@ant-design/cssinjs-utils/lib/util/calc/calculator.d.ts","./node_modules/@ant-design/cssinjs-utils/lib/hooks/usecsp.d.ts","./node_modules/@ant-design/cssinjs-utils/lib/hooks/useprefix.d.ts","./node_modules/@ant-design/cssinjs-utils/lib/hooks/usetoken.d.ts","./node_modules/@ant-design/cssinjs-utils/lib/util/genstyleutils.d.ts","./node_modules/@ant-design/cssinjs-utils/lib/util/calc/csscalculator.d.ts","./node_modules/@ant-design/cssinjs-utils/lib/util/calc/numcalculator.d.ts","./node_modules/@ant-design/cssinjs-utils/lib/util/calc/index.d.ts","./node_modules/@ant-design/cssinjs-utils/lib/util/statistic.d.ts","./node_modules/@ant-design/cssinjs-utils/lib/index.d.ts","./node_modules/antd/es/theme/themes/shared/genfontsizes.d.ts","./node_modules/antd/es/theme/themes/default/theme.d.ts","./node_modules/antd/es/theme/context.d.ts","./node_modules/antd/es/theme/usetoken.d.ts","./node_modules/antd/es/theme/util/genstyleutils.d.ts","./node_modules/antd/es/theme/util/genpresetcolor.d.ts","./node_modules/antd/es/theme/util/usereseticonstyle.d.ts","./node_modules/antd/es/theme/internal.d.ts","./node_modules/antd/es/_util/wave/style.d.ts","./node_modules/antd/es/affix/style/index.d.ts","./node_modules/antd/es/alert/style/index.d.ts","./node_modules/antd/es/anchor/style/index.d.ts","./node_modules/antd/es/app/style/index.d.ts","./node_modules/antd/es/avatar/style/index.d.ts","./node_modules/antd/es/back-top/style/index.d.ts","./node_modules/antd/es/badge/style/index.d.ts","./node_modules/antd/es/breadcrumb/style/index.d.ts","./node_modules/antd/es/button/style/token.d.ts","./node_modules/antd/es/button/style/index.d.ts","./node_modules/antd/es/input/style/token.d.ts","./node_modules/antd/es/select/style/token.d.ts","./node_modules/antd/es/style/roundedarrow.d.ts","./node_modules/antd/es/date-picker/style/token.d.ts","./node_modules/antd/es/date-picker/style/panel.d.ts","./node_modules/antd/es/date-picker/style/index.d.ts","./node_modules/antd/es/calendar/style/index.d.ts","./node_modules/antd/es/card/style/index.d.ts","./node_modules/antd/es/carousel/style/index.d.ts","./node_modules/antd/es/cascader/style/index.d.ts","./node_modules/antd/es/checkbox/style/index.d.ts","./node_modules/antd/es/collapse/style/index.d.ts","./node_modules/antd/es/color-picker/style/index.d.ts","./node_modules/antd/es/descriptions/style/index.d.ts","./node_modules/antd/es/divider/style/index.d.ts","./node_modules/antd/es/drawer/style/index.d.ts","./node_modules/antd/es/style/placementarrow.d.ts","./node_modules/antd/es/dropdown/style/index.d.ts","./node_modules/antd/es/empty/style/index.d.ts","./node_modules/antd/es/flex/style/index.d.ts","./node_modules/antd/es/float-button/style/index.d.ts","./node_modules/antd/es/form/style/index.d.ts","./node_modules/antd/es/grid/style/index.d.ts","./node_modules/antd/es/image/style/index.d.ts","./node_modules/antd/es/input-number/style/token.d.ts","./node_modules/antd/es/input-number/style/index.d.ts","./node_modules/antd/es/input/style/index.d.ts","./node_modules/antd/es/layout/style/index.d.ts","./node_modules/antd/es/list/style/index.d.ts","./node_modules/antd/es/mentions/style/index.d.ts","./node_modules/antd/es/menu/style/index.d.ts","./node_modules/antd/es/message/style/index.d.ts","./node_modules/antd/es/modal/style/index.d.ts","./node_modules/antd/es/notification/style/index.d.ts","./node_modules/antd/es/pagination/style/index.d.ts","./node_modules/antd/es/popconfirm/style/index.d.ts","./node_modules/antd/es/popover/style/index.d.ts","./node_modules/antd/es/progress/style/index.d.ts","./node_modules/antd/es/qr-code/style/index.d.ts","./node_modules/antd/es/radio/style/index.d.ts","./node_modules/antd/es/rate/style/index.d.ts","./node_modules/antd/es/result/style/index.d.ts","./node_modules/antd/es/segmented/style/index.d.ts","./node_modules/antd/es/select/style/index.d.ts","./node_modules/antd/es/skeleton/style/index.d.ts","./node_modules/antd/es/slider/style/index.d.ts","./node_modules/antd/es/space/style/index.d.ts","./node_modules/antd/es/spin/style/index.d.ts","./node_modules/antd/es/statistic/style/index.d.ts","./node_modules/antd/es/steps/style/index.d.ts","./node_modules/antd/es/switch/style/index.d.ts","./node_modules/antd/es/table/style/index.d.ts","./node_modules/antd/es/tabs/style/index.d.ts","./node_modules/antd/es/tag/style/index.d.ts","./node_modules/antd/es/timeline/style/index.d.ts","./node_modules/antd/es/tooltip/style/index.d.ts","./node_modules/antd/es/tour/style/index.d.ts","./node_modules/antd/es/transfer/style/index.d.ts","./node_modules/antd/es/tree/style/index.d.ts","./node_modules/antd/es/tree-select/style/index.d.ts","./node_modules/antd/es/typography/style/index.d.ts","./node_modules/antd/es/upload/style/index.d.ts","./node_modules/antd/es/splitter/style/index.d.ts","./node_modules/antd/es/theme/interface/components.d.ts","./node_modules/antd/es/theme/interface/cssinjs-utils.d.ts","./node_modules/antd/es/theme/interface/index.d.ts","./node_modules/antd/es/_util/colors.d.ts","./node_modules/antd/es/_util/getrenderpropvalue.d.ts","./node_modules/antd/es/_util/placements.d.ts","./node_modules/antd/es/tooltip/purepanel.d.ts","./node_modules/antd/es/tooltip/index.d.ts","./node_modules/antd/es/form/formitemlabel.d.ts","./node_modules/antd/es/form/hooks/useformitemstatus.d.ts","./node_modules/antd/es/form/formitem/index.d.ts","./node_modules/antd/es/_util/statusutils.d.ts","./node_modules/dayjs/locale/types.d.ts","./node_modules/dayjs/locale/index.d.ts","./node_modules/dayjs/index.d.ts","./node_modules/antd/es/time-picker/index.d.ts","./node_modules/antd/es/date-picker/generatepicker/interface.d.ts","./node_modules/antd/es/button/index.d.ts","./node_modules/antd/es/date-picker/generatepicker/index.d.ts","./node_modules/antd/es/empty/index.d.ts","./node_modules/rc-pagination/lib/options.d.ts","./node_modules/rc-pagination/lib/interface.d.ts","./node_modules/rc-pagination/lib/pagination.d.ts","./node_modules/rc-pagination/lib/index.d.ts","./node_modules/rc-virtual-list/lib/filler.d.ts","./node_modules/rc-virtual-list/lib/interface.d.ts","./node_modules/rc-virtual-list/lib/utils/cachemap.d.ts","./node_modules/rc-virtual-list/lib/hooks/usescrollto.d.ts","./node_modules/rc-virtual-list/lib/scrollbar.d.ts","./node_modules/rc-virtual-list/lib/list.d.ts","./node_modules/rc-select/lib/interface.d.ts","./node_modules/rc-select/lib/baseselect/index.d.ts","./node_modules/rc-select/lib/optgroup.d.ts","./node_modules/rc-select/lib/option.d.ts","./node_modules/rc-select/lib/select.d.ts","./node_modules/rc-select/lib/hooks/usebaseprops.d.ts","./node_modules/rc-select/lib/index.d.ts","./node_modules/antd/es/_util/motion.d.ts","./node_modules/antd/es/select/index.d.ts","./node_modules/antd/es/pagination/pagination.d.ts","./node_modules/antd/es/popconfirm/index.d.ts","./node_modules/antd/es/popconfirm/purepanel.d.ts","./node_modules/rc-table/lib/constant.d.ts","./node_modules/rc-table/lib/namepathtype.d.ts","./node_modules/rc-table/lib/interface.d.ts","./node_modules/rc-table/lib/footer/row.d.ts","./node_modules/rc-table/lib/footer/cell.d.ts","./node_modules/rc-table/lib/footer/summary.d.ts","./node_modules/rc-table/lib/footer/index.d.ts","./node_modules/rc-table/lib/sugar/column.d.ts","./node_modules/rc-table/lib/sugar/columngroup.d.ts","./node_modules/@rc-component/context/lib/immutable.d.ts","./node_modules/rc-table/lib/table.d.ts","./node_modules/rc-table/lib/utils/legacyutil.d.ts","./node_modules/rc-table/lib/virtualtable/index.d.ts","./node_modules/rc-table/lib/index.d.ts","./node_modules/rc-checkbox/es/index.d.ts","./node_modules/antd/es/checkbox/checkbox.d.ts","./node_modules/antd/es/checkbox/groupcontext.d.ts","./node_modules/antd/es/checkbox/group.d.ts","./node_modules/antd/es/checkbox/index.d.ts","./node_modules/rc-menu/lib/interface.d.ts","./node_modules/rc-menu/lib/menu.d.ts","./node_modules/rc-menu/lib/menuitem.d.ts","./node_modules/rc-menu/lib/submenu/index.d.ts","./node_modules/rc-menu/lib/menuitemgroup.d.ts","./node_modules/rc-menu/lib/context/pathcontext.d.ts","./node_modules/rc-menu/lib/divider.d.ts","./node_modules/rc-menu/lib/index.d.ts","./node_modules/antd/es/menu/interface.d.ts","./node_modules/antd/es/layout/sider.d.ts","./node_modules/antd/es/menu/menucontext.d.ts","./node_modules/antd/es/menu/menu.d.ts","./node_modules/antd/es/menu/menudivider.d.ts","./node_modules/antd/es/menu/menuitem.d.ts","./node_modules/antd/es/menu/submenu.d.ts","./node_modules/antd/es/menu/index.d.ts","./node_modules/antd/es/dropdown/dropdown.d.ts","./node_modules/antd/es/dropdown/dropdown-button.d.ts","./node_modules/antd/es/dropdown/index.d.ts","./node_modules/antd/es/pagination/index.d.ts","./node_modules/antd/es/table/hooks/useselection.d.ts","./node_modules/antd/es/spin/index.d.ts","./node_modules/antd/es/table/internaltable.d.ts","./node_modules/antd/es/table/interface.d.ts","./node_modules/@rc-component/tour/es/placements.d.ts","./node_modules/@rc-component/tour/es/hooks/usetarget.d.ts","./node_modules/@rc-component/tour/es/tourstep/defaultpanel.d.ts","./node_modules/@rc-component/tour/es/interface.d.ts","./node_modules/@rc-component/tour/es/tour.d.ts","./node_modules/@rc-component/tour/es/index.d.ts","./node_modules/antd/es/tour/interface.d.ts","./node_modules/antd/es/transfer/interface.d.ts","./node_modules/antd/es/transfer/listbody.d.ts","./node_modules/antd/es/transfer/list.d.ts","./node_modules/antd/es/transfer/operation.d.ts","./node_modules/antd/es/transfer/search.d.ts","./node_modules/antd/es/transfer/index.d.ts","./node_modules/rc-upload/lib/interface.d.ts","./node_modules/antd/es/progress/progress.d.ts","./node_modules/antd/es/progress/index.d.ts","./node_modules/antd/es/upload/interface.d.ts","./node_modules/antd/es/locale/uselocale.d.ts","./node_modules/antd/es/locale/index.d.ts","./node_modules/antd/es/_util/wave/interface.d.ts","./node_modules/antd/es/badge/ribbon.d.ts","./node_modules/antd/es/badge/scrollnumber.d.ts","./node_modules/antd/es/badge/index.d.ts","./node_modules/rc-tabs/lib/hooks/useindicator.d.ts","./node_modules/rc-tabs/lib/tabnavlist/index.d.ts","./node_modules/rc-tabs/lib/tabpanellist/tabpane.d.ts","./node_modules/rc-dropdown/lib/placements.d.ts","./node_modules/rc-dropdown/lib/dropdown.d.ts","./node_modules/rc-tabs/lib/interface.d.ts","./node_modules/rc-tabs/lib/tabs.d.ts","./node_modules/rc-tabs/lib/index.d.ts","./node_modules/antd/es/tabs/tabpane.d.ts","./node_modules/antd/es/tabs/index.d.ts","./node_modules/antd/es/card/card.d.ts","./node_modules/antd/es/card/grid.d.ts","./node_modules/antd/es/card/meta.d.ts","./node_modules/antd/es/card/index.d.ts","./node_modules/rc-cascader/lib/panel.d.ts","./node_modules/rc-cascader/lib/utils/commonutil.d.ts","./node_modules/rc-cascader/lib/cascader.d.ts","./node_modules/rc-cascader/lib/index.d.ts","./node_modules/antd/es/cascader/panel.d.ts","./node_modules/antd/es/cascader/index.d.ts","./node_modules/rc-collapse/es/interface.d.ts","./node_modules/rc-collapse/es/collapse.d.ts","./node_modules/rc-collapse/es/index.d.ts","./node_modules/antd/es/collapse/collapsepanel.d.ts","./node_modules/antd/es/collapse/collapse.d.ts","./node_modules/antd/es/collapse/index.d.ts","./node_modules/antd/es/date-picker/index.d.ts","./node_modules/antd/es/descriptions/descriptionscontext.d.ts","./node_modules/antd/es/descriptions/item.d.ts","./node_modules/antd/es/descriptions/index.d.ts","./node_modules/@rc-component/portal/es/portal.d.ts","./node_modules/@rc-component/portal/es/mock.d.ts","./node_modules/@rc-component/portal/es/index.d.ts","./node_modules/rc-drawer/lib/drawerpanel.d.ts","./node_modules/rc-drawer/lib/inter.d.ts","./node_modules/rc-drawer/lib/drawerpopup.d.ts","./node_modules/rc-drawer/lib/drawer.d.ts","./node_modules/rc-drawer/lib/index.d.ts","./node_modules/antd/es/drawer/drawerpanel.d.ts","./node_modules/antd/es/drawer/index.d.ts","./node_modules/antd/es/flex/interface.d.ts","./node_modules/antd/es/float-button/interface.d.ts","./node_modules/antd/es/input/group.d.ts","./node_modules/rc-input/lib/utils/commonutils.d.ts","./node_modules/rc-input/lib/utils/types.d.ts","./node_modules/rc-input/lib/interface.d.ts","./node_modules/rc-input/lib/baseinput.d.ts","./node_modules/rc-input/lib/input.d.ts","./node_modules/rc-input/lib/index.d.ts","./node_modules/antd/es/input/input.d.ts","./node_modules/antd/es/input/otp/index.d.ts","./node_modules/antd/es/input/password.d.ts","./node_modules/antd/es/input/search.d.ts","./node_modules/rc-textarea/lib/interface.d.ts","./node_modules/rc-textarea/lib/textarea.d.ts","./node_modules/rc-textarea/lib/resizabletextarea.d.ts","./node_modules/rc-textarea/lib/index.d.ts","./node_modules/antd/es/input/textarea.d.ts","./node_modules/antd/es/input/index.d.ts","./node_modules/@rc-component/mini-decimal/es/interface.d.ts","./node_modules/@rc-component/mini-decimal/es/bigintdecimal.d.ts","./node_modules/@rc-component/mini-decimal/es/numberdecimal.d.ts","./node_modules/@rc-component/mini-decimal/es/minidecimal.d.ts","./node_modules/@rc-component/mini-decimal/es/numberutil.d.ts","./node_modules/@rc-component/mini-decimal/es/index.d.ts","./node_modules/rc-input-number/es/inputnumber.d.ts","./node_modules/rc-input-number/es/index.d.ts","./node_modules/antd/es/input-number/index.d.ts","./node_modules/antd/es/grid/row.d.ts","./node_modules/antd/es/grid/index.d.ts","./node_modules/antd/es/list/item.d.ts","./node_modules/antd/es/list/context.d.ts","./node_modules/antd/es/list/index.d.ts","./node_modules/rc-mentions/lib/option.d.ts","./node_modules/rc-mentions/lib/util.d.ts","./node_modules/rc-mentions/lib/mentions.d.ts","./node_modules/antd/es/mentions/index.d.ts","./node_modules/antd/es/modal/modal.d.ts","./node_modules/antd/es/modal/purepanel.d.ts","./node_modules/antd/es/modal/index.d.ts","./node_modules/antd/es/notification/interface.d.ts","./node_modules/antd/es/popover/purepanel.d.ts","./node_modules/antd/es/popover/index.d.ts","./node_modules/rc-slider/lib/interface.d.ts","./node_modules/rc-slider/lib/handles/handle.d.ts","./node_modules/rc-slider/lib/handles/index.d.ts","./node_modules/rc-slider/lib/marks/index.d.ts","./node_modules/rc-slider/lib/slider.d.ts","./node_modules/rc-slider/lib/context.d.ts","./node_modules/rc-slider/lib/index.d.ts","./node_modules/antd/es/slider/index.d.ts","./node_modules/antd/es/space/compact.d.ts","./node_modules/antd/es/space/addon.d.ts","./node_modules/antd/es/space/context.d.ts","./node_modules/antd/es/space/index.d.ts","./node_modules/antd/es/table/column.d.ts","./node_modules/antd/es/table/columngroup.d.ts","./node_modules/antd/es/table/table.d.ts","./node_modules/antd/es/table/index.d.ts","./node_modules/antd/es/tag/checkabletag.d.ts","./node_modules/antd/es/tag/index.d.ts","./node_modules/rc-tree/lib/interface.d.ts","./node_modules/rc-tree/lib/contexttypes.d.ts","./node_modules/rc-tree/lib/dropindicator.d.ts","./node_modules/rc-tree/lib/nodelist.d.ts","./node_modules/rc-tree/lib/tree.d.ts","./node_modules/rc-tree-select/lib/interface.d.ts","./node_modules/rc-tree-select/lib/treenode.d.ts","./node_modules/rc-tree-select/lib/utils/strategyutil.d.ts","./node_modules/rc-tree-select/lib/treeselect.d.ts","./node_modules/rc-tree-select/lib/index.d.ts","./node_modules/rc-tree/lib/treenode.d.ts","./node_modules/rc-tree/lib/index.d.ts","./node_modules/antd/es/tree/tree.d.ts","./node_modules/antd/es/tree/directorytree.d.ts","./node_modules/antd/es/tree/index.d.ts","./node_modules/antd/es/tree-select/index.d.ts","./node_modules/rc-upload/lib/ajaxuploader.d.ts","./node_modules/rc-upload/lib/upload.d.ts","./node_modules/rc-upload/lib/index.d.ts","./node_modules/antd/es/upload/upload.d.ts","./node_modules/antd/es/upload/dragger.d.ts","./node_modules/antd/es/upload/index.d.ts","./node_modules/antd/es/config-provider/defaultrenderempty.d.ts","./node_modules/antd/es/config-provider/context.d.ts","./node_modules/antd/es/config-provider/hooks/useconfig.d.ts","./node_modules/antd/es/config-provider/index.d.ts","./node_modules/antd/es/modal/interface.d.ts","./node_modules/antd/es/modal/confirm.d.ts","./node_modules/antd/es/modal/usemodal/index.d.ts","./node_modules/antd/es/app/context.d.ts","./node_modules/antd/es/app/app.d.ts","./node_modules/antd/es/app/useapp.d.ts","./node_modules/antd/es/app/index.d.ts","./node_modules/antd/es/auto-complete/autocomplete.d.ts","./node_modules/antd/es/auto-complete/index.d.ts","./node_modules/antd/es/avatar/avatarcontext.d.ts","./node_modules/antd/es/avatar/avatar.d.ts","./node_modules/antd/es/avatar/avatargroup.d.ts","./node_modules/antd/es/avatar/index.d.ts","./node_modules/antd/es/back-top/index.d.ts","./node_modules/antd/es/breadcrumb/breadcrumbitem.d.ts","./node_modules/antd/es/breadcrumb/breadcrumb.d.ts","./node_modules/antd/es/breadcrumb/index.d.ts","./node_modules/antd/es/date-picker/locale/en_us.d.ts","./node_modules/antd/es/calendar/locale/en_us.d.ts","./node_modules/antd/es/calendar/generatecalendar.d.ts","./node_modules/antd/es/calendar/index.d.ts","./node_modules/@ant-design/react-slick/types.d.ts","./node_modules/antd/es/carousel/index.d.ts","./node_modules/antd/es/col/index.d.ts","./node_modules/@ant-design/fast-color/lib/types.d.ts","./node_modules/@ant-design/fast-color/lib/fastcolor.d.ts","./node_modules/@ant-design/fast-color/lib/index.d.ts","./node_modules/@rc-component/color-picker/lib/color.d.ts","./node_modules/@rc-component/color-picker/lib/interface.d.ts","./node_modules/@rc-component/color-picker/lib/components/slider.d.ts","./node_modules/@rc-component/color-picker/lib/hooks/usecomponent.d.ts","./node_modules/@rc-component/color-picker/lib/colorpicker.d.ts","./node_modules/@rc-component/color-picker/lib/components/colorblock.d.ts","./node_modules/@rc-component/color-picker/lib/index.d.ts","./node_modules/antd/es/color-picker/color.d.ts","./node_modules/antd/es/color-picker/interface.d.ts","./node_modules/antd/es/color-picker/colorpicker.d.ts","./node_modules/antd/es/color-picker/index.d.ts","./node_modules/antd/es/divider/index.d.ts","./node_modules/antd/es/flex/index.d.ts","./node_modules/antd/es/float-button/backtop.d.ts","./node_modules/antd/es/float-button/floatbuttongroup.d.ts","./node_modules/antd/es/float-button/purepanel.d.ts","./node_modules/antd/es/float-button/floatbutton.d.ts","./node_modules/antd/es/float-button/index.d.ts","./node_modules/rc-field-form/lib/formcontext.d.ts","./node_modules/antd/es/form/context.d.ts","./node_modules/antd/es/form/errorlist.d.ts","./node_modules/antd/es/form/formlist.d.ts","./node_modules/antd/es/form/hooks/useforminstance.d.ts","./node_modules/antd/es/form/index.d.ts","./node_modules/rc-image/lib/hooks/useimagetransform.d.ts","./node_modules/rc-image/lib/preview.d.ts","./node_modules/rc-image/lib/interface.d.ts","./node_modules/rc-image/lib/previewgroup.d.ts","./node_modules/rc-image/lib/image.d.ts","./node_modules/rc-image/lib/index.d.ts","./node_modules/antd/es/image/previewgroup.d.ts","./node_modules/antd/es/image/index.d.ts","./node_modules/antd/es/layout/layout.d.ts","./node_modules/antd/es/layout/index.d.ts","./node_modules/rc-notification/lib/interface.d.ts","./node_modules/rc-notification/lib/notice.d.ts","./node_modules/antd/es/message/purepanel.d.ts","./node_modules/antd/es/message/usemessage.d.ts","./node_modules/antd/es/message/index.d.ts","./node_modules/antd/es/notification/purepanel.d.ts","./node_modules/antd/es/notification/usenotification.d.ts","./node_modules/antd/es/notification/index.d.ts","./node_modules/@rc-component/qrcode/lib/libs/qrcodegen.d.ts","./node_modules/@rc-component/qrcode/lib/interface.d.ts","./node_modules/@rc-component/qrcode/lib/utils.d.ts","./node_modules/@rc-component/qrcode/lib/qrcodecanvas.d.ts","./node_modules/@rc-component/qrcode/lib/qrcodesvg.d.ts","./node_modules/@rc-component/qrcode/lib/index.d.ts","./node_modules/antd/es/qr-code/interface.d.ts","./node_modules/antd/es/qr-code/index.d.ts","./node_modules/antd/es/radio/interface.d.ts","./node_modules/antd/es/radio/group.d.ts","./node_modules/antd/es/radio/radio.d.ts","./node_modules/antd/es/radio/radiobutton.d.ts","./node_modules/antd/es/radio/index.d.ts","./node_modules/rc-rate/lib/star.d.ts","./node_modules/rc-rate/lib/rate.d.ts","./node_modules/antd/es/rate/index.d.ts","./node_modules/@ant-design/icons-svg/lib/types.d.ts","./node_modules/@ant-design/icons/lib/components/icon.d.ts","./node_modules/@ant-design/icons/lib/components/twotoneprimarycolor.d.ts","./node_modules/@ant-design/icons/lib/components/antdicon.d.ts","./node_modules/antd/es/result/index.d.ts","./node_modules/antd/es/row/index.d.ts","./node_modules/rc-segmented/es/index.d.ts","./node_modules/antd/es/segmented/index.d.ts","./node_modules/antd/es/skeleton/element.d.ts","./node_modules/antd/es/skeleton/avatar.d.ts","./node_modules/antd/es/skeleton/button.d.ts","./node_modules/antd/es/skeleton/image.d.ts","./node_modules/antd/es/skeleton/input.d.ts","./node_modules/antd/es/skeleton/node.d.ts","./node_modules/antd/es/skeleton/paragraph.d.ts","./node_modules/antd/es/skeleton/title.d.ts","./node_modules/antd/es/skeleton/skeleton.d.ts","./node_modules/antd/es/skeleton/index.d.ts","./node_modules/antd/es/splitter/splitbar.d.ts","./node_modules/antd/es/splitter/interface.d.ts","./node_modules/antd/es/splitter/panel.d.ts","./node_modules/antd/es/splitter/splitter.d.ts","./node_modules/antd/es/splitter/index.d.ts","./node_modules/antd/es/statistic/utils.d.ts","./node_modules/antd/es/statistic/statistic.d.ts","./node_modules/antd/es/statistic/countdown.d.ts","./node_modules/antd/es/statistic/timer.d.ts","./node_modules/antd/es/statistic/index.d.ts","./node_modules/rc-steps/lib/interface.d.ts","./node_modules/rc-steps/lib/step.d.ts","./node_modules/rc-steps/lib/steps.d.ts","./node_modules/rc-steps/lib/index.d.ts","./node_modules/antd/es/steps/index.d.ts","./node_modules/rc-switch/lib/index.d.ts","./node_modules/antd/es/switch/index.d.ts","./node_modules/antd/es/theme/themes/default/index.d.ts","./node_modules/antd/es/theme/index.d.ts","./node_modules/antd/es/timeline/timelineitem.d.ts","./node_modules/antd/es/timeline/timeline.d.ts","./node_modules/antd/es/timeline/index.d.ts","./node_modules/antd/es/tour/purepanel.d.ts","./node_modules/antd/es/tour/index.d.ts","./node_modules/antd/es/typography/typography.d.ts","./node_modules/antd/es/typography/base/index.d.ts","./node_modules/antd/es/typography/link.d.ts","./node_modules/antd/es/typography/paragraph.d.ts","./node_modules/antd/es/typography/text.d.ts","./node_modules/antd/es/typography/title.d.ts","./node_modules/antd/es/typography/index.d.ts","./node_modules/antd/es/version/version.d.ts","./node_modules/antd/es/version/index.d.ts","./node_modules/antd/es/watermark/index.d.ts","./node_modules/antd/es/config-provider/unstablecontext.d.ts","./node_modules/antd/es/index.d.ts","./src/components/molecules/message_manager.tsx","./src/utils/mcptokenstore.ts","./src/utils/cookieutils.ts","./src/components/tag_management/types.tsx","./src/components/key_team_helpers/key_list.tsx","./src/components/view_users/types.ts","./src/components/email_events/types.ts","./node_modules/recharts/types/component/defaulttooltipcontent.d.ts","./node_modules/date-fns/fp/types.d.ts","./node_modules/date-fns/types.d.ts","./node_modules/date-fns/locale/types.d.ts","./node_modules/date-fns/locale/af.d.ts","./node_modules/date-fns/locale/ar.d.ts","./node_modules/date-fns/locale/ar-dz.d.ts","./node_modules/date-fns/locale/ar-eg.d.ts","./node_modules/date-fns/locale/ar-ma.d.ts","./node_modules/date-fns/locale/ar-sa.d.ts","./node_modules/date-fns/locale/ar-tn.d.ts","./node_modules/date-fns/locale/az.d.ts","./node_modules/date-fns/locale/be.d.ts","./node_modules/date-fns/locale/be-tarask.d.ts","./node_modules/date-fns/locale/bg.d.ts","./node_modules/date-fns/locale/bn.d.ts","./node_modules/date-fns/locale/bs.d.ts","./node_modules/date-fns/locale/ca.d.ts","./node_modules/date-fns/locale/ckb.d.ts","./node_modules/date-fns/locale/cs.d.ts","./node_modules/date-fns/locale/cy.d.ts","./node_modules/date-fns/locale/da.d.ts","./node_modules/date-fns/locale/de.d.ts","./node_modules/date-fns/locale/de-at.d.ts","./node_modules/date-fns/locale/el.d.ts","./node_modules/date-fns/locale/en-au.d.ts","./node_modules/date-fns/locale/en-ca.d.ts","./node_modules/date-fns/locale/en-gb.d.ts","./node_modules/date-fns/locale/en-ie.d.ts","./node_modules/date-fns/locale/en-in.d.ts","./node_modules/date-fns/locale/en-nz.d.ts","./node_modules/date-fns/locale/en-us.d.ts","./node_modules/date-fns/locale/en-za.d.ts","./node_modules/date-fns/locale/eo.d.ts","./node_modules/date-fns/locale/es.d.ts","./node_modules/date-fns/locale/et.d.ts","./node_modules/date-fns/locale/eu.d.ts","./node_modules/date-fns/locale/fa-ir.d.ts","./node_modules/date-fns/locale/fi.d.ts","./node_modules/date-fns/locale/fr.d.ts","./node_modules/date-fns/locale/fr-ca.d.ts","./node_modules/date-fns/locale/fr-ch.d.ts","./node_modules/date-fns/locale/fy.d.ts","./node_modules/date-fns/locale/gd.d.ts","./node_modules/date-fns/locale/gl.d.ts","./node_modules/date-fns/locale/gu.d.ts","./node_modules/date-fns/locale/he.d.ts","./node_modules/date-fns/locale/hi.d.ts","./node_modules/date-fns/locale/hr.d.ts","./node_modules/date-fns/locale/ht.d.ts","./node_modules/date-fns/locale/hu.d.ts","./node_modules/date-fns/locale/hy.d.ts","./node_modules/date-fns/locale/id.d.ts","./node_modules/date-fns/locale/is.d.ts","./node_modules/date-fns/locale/it.d.ts","./node_modules/date-fns/locale/it-ch.d.ts","./node_modules/date-fns/locale/ja.d.ts","./node_modules/date-fns/locale/ja-hira.d.ts","./node_modules/date-fns/locale/ka.d.ts","./node_modules/date-fns/locale/kk.d.ts","./node_modules/date-fns/locale/km.d.ts","./node_modules/date-fns/locale/kn.d.ts","./node_modules/date-fns/locale/ko.d.ts","./node_modules/date-fns/locale/lb.d.ts","./node_modules/date-fns/locale/lt.d.ts","./node_modules/date-fns/locale/lv.d.ts","./node_modules/date-fns/locale/mk.d.ts","./node_modules/date-fns/locale/mn.d.ts","./node_modules/date-fns/locale/ms.d.ts","./node_modules/date-fns/locale/mt.d.ts","./node_modules/date-fns/locale/nb.d.ts","./node_modules/date-fns/locale/nl.d.ts","./node_modules/date-fns/locale/nl-be.d.ts","./node_modules/date-fns/locale/nn.d.ts","./node_modules/date-fns/locale/oc.d.ts","./node_modules/date-fns/locale/pl.d.ts","./node_modules/date-fns/locale/pt.d.ts","./node_modules/date-fns/locale/pt-br.d.ts","./node_modules/date-fns/locale/ro.d.ts","./node_modules/date-fns/locale/ru.d.ts","./node_modules/date-fns/locale/se.d.ts","./node_modules/date-fns/locale/sk.d.ts","./node_modules/date-fns/locale/sl.d.ts","./node_modules/date-fns/locale/sq.d.ts","./node_modules/date-fns/locale/sr.d.ts","./node_modules/date-fns/locale/sr-latn.d.ts","./node_modules/date-fns/locale/sv.d.ts","./node_modules/date-fns/locale/ta.d.ts","./node_modules/date-fns/locale/te.d.ts","./node_modules/date-fns/locale/th.d.ts","./node_modules/date-fns/locale/tr.d.ts","./node_modules/date-fns/locale/ug.d.ts","./node_modules/date-fns/locale/uk.d.ts","./node_modules/date-fns/locale/uz.d.ts","./node_modules/date-fns/locale/uz-cyrl.d.ts","./node_modules/date-fns/locale/vi.d.ts","./node_modules/date-fns/locale/zh-cn.d.ts","./node_modules/date-fns/locale/zh-hk.d.ts","./node_modules/date-fns/locale/zh-tw.d.ts","./node_modules/date-fns/locale.d.mts","./node_modules/@tremor/react/dist/index.d.ts","./node_modules/@ant-design/icons/lib/icons/accountbookfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/accountbookoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/accountbooktwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/aimoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/alertfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/alertoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/alerttwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/alibabaoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/aligncenteroutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/alignleftoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/alignrightoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/alipaycirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/alipaycircleoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/alipayoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/alipaysquarefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/aliwangwangfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/aliwangwangoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/aliyunoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/amazoncirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/amazonoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/amazonsquarefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/androidfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/androidoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/antcloudoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/antdesignoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/apartmentoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/apifilled.d.ts","./node_modules/@ant-design/icons/lib/icons/apioutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/apitwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/applefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/appleoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/appstoreaddoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/appstorefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/appstoreoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/appstoretwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/areachartoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/arrowdownoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/arrowleftoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/arrowrightoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/arrowupoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/arrowsaltoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/audiofilled.d.ts","./node_modules/@ant-design/icons/lib/icons/audiomutedoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/audiooutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/audiotwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/auditoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/backwardfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/backwardoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/baiduoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/bankfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/bankoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/banktwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/barchartoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/barcodeoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/barsoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/behancecirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/behanceoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/behancesquarefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/behancesquareoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/bellfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/belloutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/belltwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/bgcolorsoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/bilibilifilled.d.ts","./node_modules/@ant-design/icons/lib/icons/bilibilioutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/blockoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/boldoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/bookfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/bookoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/booktwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/borderbottomoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/borderhorizontaloutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/borderinneroutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/borderleftoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/borderouteroutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/borderoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/borderrightoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/bordertopoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/borderverticleoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/borderlesstableoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/boxplotfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/boxplotoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/boxplottwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/branchesoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/bugfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/bugoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/bugtwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/buildfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/buildoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/buildtwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/bulbfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/bulboutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/bulbtwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/calculatorfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/calculatoroutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/calculatortwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/calendarfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/calendaroutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/calendartwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/camerafilled.d.ts","./node_modules/@ant-design/icons/lib/icons/cameraoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/cameratwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/carfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/caroutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/cartwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/caretdownfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/caretdownoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/caretleftfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/caretleftoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/caretrightfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/caretrightoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/caretupfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/caretupoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/carryoutfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/carryoutoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/carryouttwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/checkcirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/checkcircleoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/checkcircletwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/checkoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/checksquarefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/checksquareoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/checksquaretwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/chromefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/chromeoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/cicirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/cicircleoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/cicircletwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/cioutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/citwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/clearoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/clockcirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/clockcircleoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/clockcircletwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/closecirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/closecircleoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/closecircletwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/closeoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/closesquarefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/closesquareoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/closesquaretwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/clouddownloadoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/cloudfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/cloudoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/cloudserveroutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/cloudsyncoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/cloudtwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/clouduploadoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/clusteroutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/codefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/codeoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/codesandboxcirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/codesandboxoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/codesandboxsquarefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/codetwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/codepencirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/codepencircleoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/codepenoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/codepensquarefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/coffeeoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/columnheightoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/columnwidthoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/commentoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/compassfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/compassoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/compasstwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/compressoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/consolesqloutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/contactsfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/contactsoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/contactstwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/containerfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/containeroutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/containertwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/controlfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/controloutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/controltwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/copyfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/copyoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/copytwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/copyrightcirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/copyrightcircleoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/copyrightcircletwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/copyrightoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/copyrighttwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/creditcardfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/creditcardoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/creditcardtwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/crownfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/crownoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/crowntwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/customerservicefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/customerserviceoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/customerservicetwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/dashoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/dashboardfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/dashboardoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/dashboardtwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/databasefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/databaseoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/databasetwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/deletecolumnoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/deletefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/deleteoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/deleterowoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/deletetwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/deliveredprocedureoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/deploymentunitoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/desktopoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/difffilled.d.ts","./node_modules/@ant-design/icons/lib/icons/diffoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/difftwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/dingdingoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/dingtalkcirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/dingtalkoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/dingtalksquarefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/disconnectoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/discordfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/discordoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/dislikefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/dislikeoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/disliketwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/dockeroutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/dollarcirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/dollarcircleoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/dollarcircletwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/dollaroutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/dollartwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/dotchartoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/dotnetoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/doubleleftoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/doublerightoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/downcirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/downcircleoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/downcircletwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/downoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/downsquarefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/downsquareoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/downsquaretwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/downloadoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/dragoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/dribbblecirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/dribbbleoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/dribbblesquarefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/dribbblesquareoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/dropboxcirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/dropboxoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/dropboxsquarefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/editfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/editoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/edittwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/ellipsisoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/enteroutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/environmentfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/environmentoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/environmenttwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/eurocirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/eurocircleoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/eurocircletwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/eurooutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/eurotwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/exceptionoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/exclamationcirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/exclamationcircleoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/exclamationcircletwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/exclamationoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/expandaltoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/expandoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/experimentfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/experimentoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/experimenttwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/exportoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/eyefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/eyeinvisiblefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/eyeinvisibleoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/eyeinvisibletwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/eyeoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/eyetwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/facebookfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/facebookoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/falloutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/fastbackwardfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/fastbackwardoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/fastforwardfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/fastforwardoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/fieldbinaryoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/fieldnumberoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/fieldstringoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/fieldtimeoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/fileaddfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/fileaddoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/fileaddtwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/filedoneoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/fileexcelfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/fileexceloutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/fileexceltwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/fileexclamationfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/fileexclamationoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/fileexclamationtwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/filefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/filegifoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/fileimagefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/fileimageoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/fileimagetwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/filejpgoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/filemarkdownfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/filemarkdownoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/filemarkdowntwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/fileoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/filepdffilled.d.ts","./node_modules/@ant-design/icons/lib/icons/filepdfoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/filepdftwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/filepptfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/filepptoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/fileppttwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/fileprotectoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/filesearchoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/filesyncoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/filetextfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/filetextoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/filetexttwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/filetwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/fileunknownfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/fileunknownoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/fileunknowntwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/filewordfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/filewordoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/filewordtwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/filezipfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/filezipoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/fileziptwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/filterfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/filteroutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/filtertwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/firefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/fireoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/firetwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/flagfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/flagoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/flagtwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/folderaddfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/folderaddoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/folderaddtwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/folderfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/folderopenfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/folderopenoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/folderopentwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/folderoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/foldertwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/folderviewoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/fontcolorsoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/fontsizeoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/forkoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/formoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/formatpainterfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/formatpainteroutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/forwardfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/forwardoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/frownfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/frownoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/frowntwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/fullscreenexitoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/fullscreenoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/functionoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/fundfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/fundoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/fundprojectionscreenoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/fundtwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/fundviewoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/funnelplotfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/funnelplotoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/funnelplottwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/gatewayoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/gifoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/giftfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/giftoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/gifttwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/githubfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/githuboutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/gitlabfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/gitlaboutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/globaloutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/goldfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/goldoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/goldtwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/goldenfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/googlecirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/googleoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/googlepluscirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/googleplusoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/googleplussquarefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/googlesquarefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/groupoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/harmonyosoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/hddfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/hddoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/hddtwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/heartfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/heartoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/hearttwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/heatmapoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/highlightfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/highlightoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/highlighttwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/historyoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/holderoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/homefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/homeoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/hometwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/hourglassfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/hourglassoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/hourglasstwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/html5filled.d.ts","./node_modules/@ant-design/icons/lib/icons/html5outlined.d.ts","./node_modules/@ant-design/icons/lib/icons/html5twotone.d.ts","./node_modules/@ant-design/icons/lib/icons/idcardfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/idcardoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/idcardtwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/iecirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/ieoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/iesquarefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/importoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/inboxoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/infocirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/infocircleoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/infocircletwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/infooutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/insertrowaboveoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/insertrowbelowoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/insertrowleftoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/insertrowrightoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/instagramfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/instagramoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/insurancefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/insuranceoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/insurancetwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/interactionfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/interactionoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/interactiontwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/issuescloseoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/italicoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/javaoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/javascriptoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/keyoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/kubernetesoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/laptopoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/layoutfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/layoutoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/layouttwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/leftcirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/leftcircleoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/leftcircletwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/leftoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/leftsquarefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/leftsquareoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/leftsquaretwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/likefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/likeoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/liketwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/linechartoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/lineheightoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/lineoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/linkoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/linkedinfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/linkedinoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/linuxoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/loading3quartersoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/loadingoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/lockfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/lockoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/locktwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/loginoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/logoutoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/maccommandfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/maccommandoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/mailfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/mailoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/mailtwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/manoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/medicineboxfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/medicineboxoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/medicineboxtwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/mediumcirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/mediumoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/mediumsquarefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/mediumworkmarkoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/mehfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/mehoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/mehtwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/menufoldoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/menuoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/menuunfoldoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/mergecellsoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/mergefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/mergeoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/messagefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/messageoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/messagetwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/minuscirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/minuscircleoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/minuscircletwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/minusoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/minussquarefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/minussquareoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/minussquaretwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/mobilefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/mobileoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/mobiletwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/moneycollectfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/moneycollectoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/moneycollecttwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/monitoroutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/moonfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/moonoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/moreoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/mutedfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/mutedoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/nodecollapseoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/nodeexpandoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/nodeindexoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/notificationfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/notificationoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/notificationtwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/numberoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/onetooneoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/openaifilled.d.ts","./node_modules/@ant-design/icons/lib/icons/openaioutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/orderedlistoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/paperclipoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/partitionoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/pausecirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/pausecircleoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/pausecircletwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/pauseoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/paycirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/paycircleoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/percentageoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/phonefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/phoneoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/phonetwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/piccenteroutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/picleftoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/picrightoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/picturefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/pictureoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/picturetwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/piechartfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/piechartoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/piecharttwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/pinterestfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/pinterestoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/playcirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/playcircleoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/playcircletwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/playsquarefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/playsquareoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/playsquaretwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/pluscirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/pluscircleoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/pluscircletwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/plusoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/plussquarefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/plussquareoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/plussquaretwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/poundcirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/poundcircleoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/poundcircletwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/poundoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/poweroffoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/printerfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/printeroutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/printertwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/productfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/productoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/profilefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/profileoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/profiletwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/projectfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/projectoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/projecttwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/propertysafetyfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/propertysafetyoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/propertysafetytwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/pullrequestoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/pushpinfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/pushpinoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/pushpintwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/pythonoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/qqcirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/qqoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/qqsquarefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/qrcodeoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/questioncirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/questioncircleoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/questioncircletwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/questionoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/radarchartoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/radiusbottomleftoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/radiusbottomrightoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/radiussettingoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/radiusupleftoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/radiusuprightoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/readfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/readoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/reconciliationfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/reconciliationoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/reconciliationtwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/redenvelopefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/redenvelopeoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/redenvelopetwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/redditcirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/redditoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/redditsquarefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/redooutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/reloadoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/restfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/restoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/resttwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/retweetoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/rightcirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/rightcircleoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/rightcircletwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/rightoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/rightsquarefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/rightsquareoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/rightsquaretwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/riseoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/robotfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/robotoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/rocketfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/rocketoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/rockettwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/rollbackoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/rotateleftoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/rotaterightoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/rubyoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/safetycertificatefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/safetycertificateoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/safetycertificatetwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/safetyoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/savefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/saveoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/savetwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/scanoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/schedulefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/scheduleoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/scheduletwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/scissoroutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/searchoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/securityscanfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/securityscanoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/securityscantwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/selectoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/sendoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/settingfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/settingoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/settingtwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/shakeoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/sharealtoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/shopfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/shopoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/shoptwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/shoppingcartoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/shoppingfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/shoppingoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/shoppingtwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/shrinkoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/signalfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/signaturefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/signatureoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/sisternodeoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/sketchcirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/sketchoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/sketchsquarefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/skinfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/skinoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/skintwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/skypefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/skypeoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/slackcirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/slackoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/slacksquarefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/slacksquareoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/slidersfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/slidersoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/sliderstwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/smalldashoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/smilefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/smileoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/smiletwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/snippetsfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/snippetsoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/snippetstwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/solutionoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/sortascendingoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/sortdescendingoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/soundfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/soundoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/soundtwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/splitcellsoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/spotifyfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/spotifyoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/starfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/staroutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/startwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/stepbackwardfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/stepbackwardoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/stepforwardfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/stepforwardoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/stockoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/stopfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/stopoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/stoptwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/strikethroughoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/subnodeoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/sunfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/sunoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/swapleftoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/swapoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/swaprightoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/switcherfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/switcheroutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/switchertwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/syncoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/tableoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/tabletfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/tabletoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/tablettwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/tagfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/tagoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/tagtwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/tagsfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/tagsoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/tagstwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/taobaocirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/taobaocircleoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/taobaooutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/taobaosquarefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/teamoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/thunderboltfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/thunderboltoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/thunderbolttwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/tiktokfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/tiktokoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/totopoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/toolfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/tooloutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/tooltwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/trademarkcirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/trademarkcircleoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/trademarkcircletwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/trademarkoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/transactionoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/translationoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/trophyfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/trophyoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/trophytwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/truckfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/truckoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/twitchfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/twitchoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/twittercirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/twitteroutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/twittersquarefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/underlineoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/undooutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/ungroupoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/unlockfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/unlockoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/unlocktwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/unorderedlistoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/upcirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/upcircleoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/upcircletwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/upoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/upsquarefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/upsquareoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/upsquaretwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/uploadoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/usbfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/usboutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/usbtwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/useraddoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/userdeleteoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/useroutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/userswitchoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/usergroupaddoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/usergroupdeleteoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/verifiedoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/verticalalignbottomoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/verticalalignmiddleoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/verticalaligntopoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/verticalleftoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/verticalrightoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/videocameraaddoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/videocamerafilled.d.ts","./node_modules/@ant-design/icons/lib/icons/videocameraoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/videocameratwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/walletfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/walletoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/wallettwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/warningfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/warningoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/warningtwotone.d.ts","./node_modules/@ant-design/icons/lib/icons/wechatfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/wechatoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/wechatworkfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/wechatworkoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/weibocirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/weibocircleoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/weibooutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/weibosquarefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/weibosquareoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/whatsappoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/wifioutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/windowsfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/windowsoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/womanoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/xfilled.d.ts","./node_modules/@ant-design/icons/lib/icons/xoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/yahoofilled.d.ts","./node_modules/@ant-design/icons/lib/icons/yahoooutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/youtubefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/youtubeoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/yuquefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/yuqueoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/zhihucirclefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/zhihuoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/zhihusquarefilled.d.ts","./node_modules/@ant-design/icons/lib/icons/zoominoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/zoomoutoutlined.d.ts","./node_modules/@ant-design/icons/lib/icons/index.d.ts","./node_modules/@ant-design/icons/lib/components/iconfont.d.ts","./node_modules/@ant-design/icons/lib/components/context.d.ts","./node_modules/@ant-design/icons/lib/index.d.ts","./src/utils/textutils.ts","./src/components/common_components/check_openapi_schema.tsx","./src/components/shared/errorutils.tsx","./src/components/molecules/notifications_manager.tsx","./src/components/mcp_tools/types.tsx","./src/lib/http/client.ts","./src/lib/http/resolveapibase.ts","./src/components/networking.tsx","./src/app/(dashboard)/networking.ts","./src/app/(dashboard)/access-groups/components/types.ts","./node_modules/@types/aria-query/index.d.ts","./node_modules/@testing-library/dom/types/matches.d.ts","./node_modules/@testing-library/dom/types/wait-for.d.ts","./node_modules/@testing-library/dom/types/query-helpers.d.ts","./node_modules/@testing-library/dom/types/queries.d.ts","./node_modules/@testing-library/dom/types/get-queries-for-element.d.ts","./node_modules/pretty-format/build/types.d.ts","./node_modules/pretty-format/build/index.d.ts","./node_modules/@testing-library/dom/types/screen.d.ts","./node_modules/@testing-library/dom/types/wait-for-element-to-be-removed.d.ts","./node_modules/@testing-library/dom/types/get-node-text.d.ts","./node_modules/@testing-library/dom/types/events.d.ts","./node_modules/@testing-library/dom/types/pretty-dom.d.ts","./node_modules/@testing-library/dom/types/role-helpers.d.ts","./node_modules/@testing-library/dom/types/config.d.ts","./node_modules/@testing-library/dom/types/suggestions.d.ts","./node_modules/@testing-library/dom/types/index.d.ts","./node_modules/@types/react-dom/test-utils/index.d.ts","./node_modules/@testing-library/react/types/index.d.ts","./node_modules/vitest/dist/chunks/worker.d.ckwwzbsj.d.ts","./node_modules/vitest/dist/chunks/global.d.mamajcmj.d.ts","./node_modules/vitest/dist/chunks/mocker.d.be_2ls6u.d.ts","./node_modules/vitest/dist/chunks/suite.d.fvehnv49.d.ts","./node_modules/expect-type/dist/utils.d.ts","./node_modules/expect-type/dist/overloads.d.ts","./node_modules/expect-type/dist/branding.d.ts","./node_modules/expect-type/dist/messages.d.ts","./node_modules/expect-type/dist/index.d.ts","./node_modules/vitest/dist/index.d.ts","./node_modules/@tanstack/query-core/build/modern/_tsup-dts-rollup.d.ts","./node_modules/@tanstack/query-core/build/modern/index.d.ts","./node_modules/@tanstack/react-query/build/modern/_tsup-dts-rollup.d.ts","./node_modules/@tanstack/react-query/build/modern/index.d.ts","./node_modules/jwt-decode/build/esm/index.d.ts","./src/utils/jwtutils.ts","./src/utils/returnurlutils.ts","./src/utils/roles.ts","./src/app/(dashboard)/hooks/common/querykeysfactory.ts","./src/app/(dashboard)/hooks/uiconfig/useuiconfig.ts","./src/app/(dashboard)/hooks/useauthorized.ts","./src/app/(dashboard)/hooks/useauthorized.test.ts","./src/utils/localstorageutils.ts","./src/app/(dashboard)/hooks/usedisableblogposts.ts","./src/app/(dashboard)/hooks/usedisablebouncingicon.ts","./src/app/(dashboard)/hooks/usedisableshownewbadge.ts","./src/app/(dashboard)/hooks/usedisableshownewbadge.test.ts","./src/app/(dashboard)/hooks/usedisableshowprompts.ts","./src/app/(dashboard)/hooks/usedisableshowprompts.test.ts","./src/app/(dashboard)/hooks/usedisableusageindicator.ts","./src/app/(dashboard)/hooks/usedisableusageindicator.test.ts","./src/app/(dashboard)/hooks/usehideagentplatformbanner.ts","./src/app/(dashboard)/hooks/accessgroups/useaccessgroups.ts","./src/app/(dashboard)/hooks/accessgroups/useaccessgroupdetails.ts","./src/app/(dashboard)/hooks/accessgroups/useaccessgroups.test.ts","./src/app/(dashboard)/hooks/accessgroups/usecreateaccessgroup.ts","./src/app/(dashboard)/hooks/accessgroups/usedeleteaccessgroup.ts","./src/app/(dashboard)/hooks/accessgroups/useeditaccessgroup.ts","./src/components/agents/types.ts","./src/app/(dashboard)/hooks/agents/useagents.ts","./src/app/(dashboard)/hooks/agents/useagents.test.ts","./src/app/(dashboard)/hooks/blogposts/useblogposts.ts","./node_modules/@types/react-syntax-highlighter/index.d.ts","./src/components/common_components/deleteresourcemodal.tsx","./node_modules/@heroicons/react/outline/academiccapicon.d.ts","./node_modules/@heroicons/react/outline/adjustmentsicon.d.ts","./node_modules/@heroicons/react/outline/annotationicon.d.ts","./node_modules/@heroicons/react/outline/archiveicon.d.ts","./node_modules/@heroicons/react/outline/arrowcircledownicon.d.ts","./node_modules/@heroicons/react/outline/arrowcirclelefticon.d.ts","./node_modules/@heroicons/react/outline/arrowcirclerighticon.d.ts","./node_modules/@heroicons/react/outline/arrowcircleupicon.d.ts","./node_modules/@heroicons/react/outline/arrowdownicon.d.ts","./node_modules/@heroicons/react/outline/arrowlefticon.d.ts","./node_modules/@heroicons/react/outline/arrownarrowdownicon.d.ts","./node_modules/@heroicons/react/outline/arrownarrowlefticon.d.ts","./node_modules/@heroicons/react/outline/arrownarrowrighticon.d.ts","./node_modules/@heroicons/react/outline/arrownarrowupicon.d.ts","./node_modules/@heroicons/react/outline/arrowrighticon.d.ts","./node_modules/@heroicons/react/outline/arrowsmdownicon.d.ts","./node_modules/@heroicons/react/outline/arrowsmlefticon.d.ts","./node_modules/@heroicons/react/outline/arrowsmrighticon.d.ts","./node_modules/@heroicons/react/outline/arrowsmupicon.d.ts","./node_modules/@heroicons/react/outline/arrowupicon.d.ts","./node_modules/@heroicons/react/outline/arrowsexpandicon.d.ts","./node_modules/@heroicons/react/outline/atsymbolicon.d.ts","./node_modules/@heroicons/react/outline/backspaceicon.d.ts","./node_modules/@heroicons/react/outline/badgecheckicon.d.ts","./node_modules/@heroicons/react/outline/banicon.d.ts","./node_modules/@heroicons/react/outline/beakericon.d.ts","./node_modules/@heroicons/react/outline/bellicon.d.ts","./node_modules/@heroicons/react/outline/bookopenicon.d.ts","./node_modules/@heroicons/react/outline/bookmarkalticon.d.ts","./node_modules/@heroicons/react/outline/bookmarkicon.d.ts","./node_modules/@heroicons/react/outline/briefcaseicon.d.ts","./node_modules/@heroicons/react/outline/cakeicon.d.ts","./node_modules/@heroicons/react/outline/calculatoricon.d.ts","./node_modules/@heroicons/react/outline/calendaricon.d.ts","./node_modules/@heroicons/react/outline/cameraicon.d.ts","./node_modules/@heroicons/react/outline/cashicon.d.ts","./node_modules/@heroicons/react/outline/chartbaricon.d.ts","./node_modules/@heroicons/react/outline/chartpieicon.d.ts","./node_modules/@heroicons/react/outline/chartsquarebaricon.d.ts","./node_modules/@heroicons/react/outline/chatalt2icon.d.ts","./node_modules/@heroicons/react/outline/chatalticon.d.ts","./node_modules/@heroicons/react/outline/chaticon.d.ts","./node_modules/@heroicons/react/outline/checkcircleicon.d.ts","./node_modules/@heroicons/react/outline/checkicon.d.ts","./node_modules/@heroicons/react/outline/chevrondoubledownicon.d.ts","./node_modules/@heroicons/react/outline/chevrondoublelefticon.d.ts","./node_modules/@heroicons/react/outline/chevrondoublerighticon.d.ts","./node_modules/@heroicons/react/outline/chevrondoubleupicon.d.ts","./node_modules/@heroicons/react/outline/chevrondownicon.d.ts","./node_modules/@heroicons/react/outline/chevronlefticon.d.ts","./node_modules/@heroicons/react/outline/chevronrighticon.d.ts","./node_modules/@heroicons/react/outline/chevronupicon.d.ts","./node_modules/@heroicons/react/outline/chipicon.d.ts","./node_modules/@heroicons/react/outline/clipboardcheckicon.d.ts","./node_modules/@heroicons/react/outline/clipboardcopyicon.d.ts","./node_modules/@heroicons/react/outline/clipboardlisticon.d.ts","./node_modules/@heroicons/react/outline/clipboardicon.d.ts","./node_modules/@heroicons/react/outline/clockicon.d.ts","./node_modules/@heroicons/react/outline/clouddownloadicon.d.ts","./node_modules/@heroicons/react/outline/clouduploadicon.d.ts","./node_modules/@heroicons/react/outline/cloudicon.d.ts","./node_modules/@heroicons/react/outline/codeicon.d.ts","./node_modules/@heroicons/react/outline/cogicon.d.ts","./node_modules/@heroicons/react/outline/collectionicon.d.ts","./node_modules/@heroicons/react/outline/colorswatchicon.d.ts","./node_modules/@heroicons/react/outline/creditcardicon.d.ts","./node_modules/@heroicons/react/outline/cubetransparenticon.d.ts","./node_modules/@heroicons/react/outline/cubeicon.d.ts","./node_modules/@heroicons/react/outline/currencybangladeshiicon.d.ts","./node_modules/@heroicons/react/outline/currencydollaricon.d.ts","./node_modules/@heroicons/react/outline/currencyeuroicon.d.ts","./node_modules/@heroicons/react/outline/currencypoundicon.d.ts","./node_modules/@heroicons/react/outline/currencyrupeeicon.d.ts","./node_modules/@heroicons/react/outline/currencyyenicon.d.ts","./node_modules/@heroicons/react/outline/cursorclickicon.d.ts","./node_modules/@heroicons/react/outline/databaseicon.d.ts","./node_modules/@heroicons/react/outline/desktopcomputericon.d.ts","./node_modules/@heroicons/react/outline/devicemobileicon.d.ts","./node_modules/@heroicons/react/outline/devicetableticon.d.ts","./node_modules/@heroicons/react/outline/documentaddicon.d.ts","./node_modules/@heroicons/react/outline/documentdownloadicon.d.ts","./node_modules/@heroicons/react/outline/documentduplicateicon.d.ts","./node_modules/@heroicons/react/outline/documentremoveicon.d.ts","./node_modules/@heroicons/react/outline/documentreporticon.d.ts","./node_modules/@heroicons/react/outline/documentsearchicon.d.ts","./node_modules/@heroicons/react/outline/documenttexticon.d.ts","./node_modules/@heroicons/react/outline/documenticon.d.ts","./node_modules/@heroicons/react/outline/dotscirclehorizontalicon.d.ts","./node_modules/@heroicons/react/outline/dotshorizontalicon.d.ts","./node_modules/@heroicons/react/outline/dotsverticalicon.d.ts","./node_modules/@heroicons/react/outline/downloadicon.d.ts","./node_modules/@heroicons/react/outline/duplicateicon.d.ts","./node_modules/@heroicons/react/outline/emojihappyicon.d.ts","./node_modules/@heroicons/react/outline/emojisadicon.d.ts","./node_modules/@heroicons/react/outline/exclamationcircleicon.d.ts","./node_modules/@heroicons/react/outline/exclamationicon.d.ts","./node_modules/@heroicons/react/outline/externallinkicon.d.ts","./node_modules/@heroicons/react/outline/eyeofficon.d.ts","./node_modules/@heroicons/react/outline/eyeicon.d.ts","./node_modules/@heroicons/react/outline/fastforwardicon.d.ts","./node_modules/@heroicons/react/outline/filmicon.d.ts","./node_modules/@heroicons/react/outline/filtericon.d.ts","./node_modules/@heroicons/react/outline/fingerprinticon.d.ts","./node_modules/@heroicons/react/outline/fireicon.d.ts","./node_modules/@heroicons/react/outline/flagicon.d.ts","./node_modules/@heroicons/react/outline/folderaddicon.d.ts","./node_modules/@heroicons/react/outline/folderdownloadicon.d.ts","./node_modules/@heroicons/react/outline/folderopenicon.d.ts","./node_modules/@heroicons/react/outline/folderremoveicon.d.ts","./node_modules/@heroicons/react/outline/foldericon.d.ts","./node_modules/@heroicons/react/outline/gifticon.d.ts","./node_modules/@heroicons/react/outline/globealticon.d.ts","./node_modules/@heroicons/react/outline/globeicon.d.ts","./node_modules/@heroicons/react/outline/handicon.d.ts","./node_modules/@heroicons/react/outline/hashtagicon.d.ts","./node_modules/@heroicons/react/outline/hearticon.d.ts","./node_modules/@heroicons/react/outline/homeicon.d.ts","./node_modules/@heroicons/react/outline/identificationicon.d.ts","./node_modules/@heroicons/react/outline/inboxinicon.d.ts","./node_modules/@heroicons/react/outline/inboxicon.d.ts","./node_modules/@heroicons/react/outline/informationcircleicon.d.ts","./node_modules/@heroicons/react/outline/keyicon.d.ts","./node_modules/@heroicons/react/outline/libraryicon.d.ts","./node_modules/@heroicons/react/outline/lightbulbicon.d.ts","./node_modules/@heroicons/react/outline/lightningbolticon.d.ts","./node_modules/@heroicons/react/outline/linkicon.d.ts","./node_modules/@heroicons/react/outline/locationmarkericon.d.ts","./node_modules/@heroicons/react/outline/lockclosedicon.d.ts","./node_modules/@heroicons/react/outline/lockopenicon.d.ts","./node_modules/@heroicons/react/outline/loginicon.d.ts","./node_modules/@heroicons/react/outline/logouticon.d.ts","./node_modules/@heroicons/react/outline/mailopenicon.d.ts","./node_modules/@heroicons/react/outline/mailicon.d.ts","./node_modules/@heroicons/react/outline/mapicon.d.ts","./node_modules/@heroicons/react/outline/menualt1icon.d.ts","./node_modules/@heroicons/react/outline/menualt2icon.d.ts","./node_modules/@heroicons/react/outline/menualt3icon.d.ts","./node_modules/@heroicons/react/outline/menualt4icon.d.ts","./node_modules/@heroicons/react/outline/menuicon.d.ts","./node_modules/@heroicons/react/outline/microphoneicon.d.ts","./node_modules/@heroicons/react/outline/minuscircleicon.d.ts","./node_modules/@heroicons/react/outline/minussmicon.d.ts","./node_modules/@heroicons/react/outline/minusicon.d.ts","./node_modules/@heroicons/react/outline/moonicon.d.ts","./node_modules/@heroicons/react/outline/musicnoteicon.d.ts","./node_modules/@heroicons/react/outline/newspapericon.d.ts","./node_modules/@heroicons/react/outline/officebuildingicon.d.ts","./node_modules/@heroicons/react/outline/paperairplaneicon.d.ts","./node_modules/@heroicons/react/outline/paperclipicon.d.ts","./node_modules/@heroicons/react/outline/pauseicon.d.ts","./node_modules/@heroicons/react/outline/pencilalticon.d.ts","./node_modules/@heroicons/react/outline/pencilicon.d.ts","./node_modules/@heroicons/react/outline/phoneincomingicon.d.ts","./node_modules/@heroicons/react/outline/phonemissedcallicon.d.ts","./node_modules/@heroicons/react/outline/phoneoutgoingicon.d.ts","./node_modules/@heroicons/react/outline/phoneicon.d.ts","./node_modules/@heroicons/react/outline/photographicon.d.ts","./node_modules/@heroicons/react/outline/playicon.d.ts","./node_modules/@heroicons/react/outline/pluscircleicon.d.ts","./node_modules/@heroicons/react/outline/plussmicon.d.ts","./node_modules/@heroicons/react/outline/plusicon.d.ts","./node_modules/@heroicons/react/outline/presentationchartbaricon.d.ts","./node_modules/@heroicons/react/outline/presentationchartlineicon.d.ts","./node_modules/@heroicons/react/outline/printericon.d.ts","./node_modules/@heroicons/react/outline/puzzleicon.d.ts","./node_modules/@heroicons/react/outline/qrcodeicon.d.ts","./node_modules/@heroicons/react/outline/questionmarkcircleicon.d.ts","./node_modules/@heroicons/react/outline/receiptrefundicon.d.ts","./node_modules/@heroicons/react/outline/receipttaxicon.d.ts","./node_modules/@heroicons/react/outline/refreshicon.d.ts","./node_modules/@heroicons/react/outline/replyicon.d.ts","./node_modules/@heroicons/react/outline/rewindicon.d.ts","./node_modules/@heroicons/react/outline/rssicon.d.ts","./node_modules/@heroicons/react/outline/saveasicon.d.ts","./node_modules/@heroicons/react/outline/saveicon.d.ts","./node_modules/@heroicons/react/outline/scaleicon.d.ts","./node_modules/@heroicons/react/outline/scissorsicon.d.ts","./node_modules/@heroicons/react/outline/searchcircleicon.d.ts","./node_modules/@heroicons/react/outline/searchicon.d.ts","./node_modules/@heroicons/react/outline/selectoricon.d.ts","./node_modules/@heroicons/react/outline/servericon.d.ts","./node_modules/@heroicons/react/outline/shareicon.d.ts","./node_modules/@heroicons/react/outline/shieldcheckicon.d.ts","./node_modules/@heroicons/react/outline/shieldexclamationicon.d.ts","./node_modules/@heroicons/react/outline/shoppingbagicon.d.ts","./node_modules/@heroicons/react/outline/shoppingcarticon.d.ts","./node_modules/@heroicons/react/outline/sortascendingicon.d.ts","./node_modules/@heroicons/react/outline/sortdescendingicon.d.ts","./node_modules/@heroicons/react/outline/sparklesicon.d.ts","./node_modules/@heroicons/react/outline/speakerphoneicon.d.ts","./node_modules/@heroicons/react/outline/staricon.d.ts","./node_modules/@heroicons/react/outline/statusofflineicon.d.ts","./node_modules/@heroicons/react/outline/statusonlineicon.d.ts","./node_modules/@heroicons/react/outline/stopicon.d.ts","./node_modules/@heroicons/react/outline/sunicon.d.ts","./node_modules/@heroicons/react/outline/supporticon.d.ts","./node_modules/@heroicons/react/outline/switchhorizontalicon.d.ts","./node_modules/@heroicons/react/outline/switchverticalicon.d.ts","./node_modules/@heroicons/react/outline/tableicon.d.ts","./node_modules/@heroicons/react/outline/tagicon.d.ts","./node_modules/@heroicons/react/outline/templateicon.d.ts","./node_modules/@heroicons/react/outline/terminalicon.d.ts","./node_modules/@heroicons/react/outline/thumbdownicon.d.ts","./node_modules/@heroicons/react/outline/thumbupicon.d.ts","./node_modules/@heroicons/react/outline/ticketicon.d.ts","./node_modules/@heroicons/react/outline/translateicon.d.ts","./node_modules/@heroicons/react/outline/trashicon.d.ts","./node_modules/@heroicons/react/outline/trendingdownicon.d.ts","./node_modules/@heroicons/react/outline/trendingupicon.d.ts","./node_modules/@heroicons/react/outline/truckicon.d.ts","./node_modules/@heroicons/react/outline/uploadicon.d.ts","./node_modules/@heroicons/react/outline/useraddicon.d.ts","./node_modules/@heroicons/react/outline/usercircleicon.d.ts","./node_modules/@heroicons/react/outline/usergroupicon.d.ts","./node_modules/@heroicons/react/outline/userremoveicon.d.ts","./node_modules/@heroicons/react/outline/usericon.d.ts","./node_modules/@heroicons/react/outline/usersicon.d.ts","./node_modules/@heroicons/react/outline/variableicon.d.ts","./node_modules/@heroicons/react/outline/videocameraicon.d.ts","./node_modules/@heroicons/react/outline/viewboardsicon.d.ts","./node_modules/@heroicons/react/outline/viewgridaddicon.d.ts","./node_modules/@heroicons/react/outline/viewgridicon.d.ts","./node_modules/@heroicons/react/outline/viewlisticon.d.ts","./node_modules/@heroicons/react/outline/volumeofficon.d.ts","./node_modules/@heroicons/react/outline/volumeupicon.d.ts","./node_modules/@heroicons/react/outline/wifiicon.d.ts","./node_modules/@heroicons/react/outline/xcircleicon.d.ts","./node_modules/@heroicons/react/outline/xicon.d.ts","./node_modules/@heroicons/react/outline/zoominicon.d.ts","./node_modules/@heroicons/react/outline/zoomouticon.d.ts","./node_modules/@heroicons/react/outline/index.d.ts","./node_modules/cva/dist/index.d.ts","./node_modules/tailwind-merge/dist/types.d.ts","./src/lib/cva.config.ts","./src/components/common_components/iconactionbutton/baseactionbutton.tsx","./src/components/common_components/iconactionbutton/tableiconactionbuttons/tableiconactionbutton.tsx","./src/components/budgets/budget_modal.tsx","./src/components/budgets/edit_budget_modal.tsx","./src/components/budgets/constants.ts","./src/components/budgets/budget_panel.tsx","./src/app/(dashboard)/hooks/budgets/usebudgets.ts","./src/app/(dashboard)/hooks/cloudzero/usecloudzerocreate.ts","./src/app/(dashboard)/hooks/cloudzero/usecloudzerocreate.test.ts","./src/app/(dashboard)/hooks/cloudzero/usecloudzerodryrun.ts","./src/app/(dashboard)/hooks/cloudzero/usecloudzerodryrun.test.ts","./src/app/(dashboard)/hooks/cloudzero/usecloudzeroexport.ts","./src/app/(dashboard)/hooks/cloudzero/usecloudzeroexport.test.ts","./src/components/cloudzerocosttracking/types.ts","./src/app/(dashboard)/hooks/cloudzero/usecloudzerosettings.ts","./src/app/(dashboard)/hooks/cloudzero/usecloudzerosettings.test.ts","./src/app/(dashboard)/hooks/common/querykeysfactory.test.ts","./src/app/(dashboard)/hooks/configoverrides/hashicorpvaultapi.ts","./src/app/(dashboard)/hooks/configoverrides/usehashicorpvaultconfig.ts","./src/app/(dashboard)/hooks/configoverrides/usedeletehashicorpvaultconfig.ts","./src/app/(dashboard)/hooks/configoverrides/useupdatehashicorpvaultconfig.ts","./src/app/(dashboard)/hooks/credentials/usecredentials.ts","./src/app/(dashboard)/hooks/credentials/usecredentials.test.ts","./src/app/(dashboard)/hooks/customers/usecustomers.ts","./src/app/(dashboard)/hooks/customers/usecustomers.test.ts","./src/app/(dashboard)/hooks/guardrails/useguardrails.ts","./src/app/(dashboard)/hooks/guardrails/useguardrails.test.ts","./src/app/(dashboard)/hooks/guardrails/useregisterguardrail.ts","./src/app/(dashboard)/hooks/healthreadiness/usehealthreadinessdetails.ts","./src/app/(dashboard)/hooks/keys/usekeyaliases.ts","./src/app/(dashboard)/hooks/keys/usekeyaliases.test.ts","./src/app/(dashboard)/hooks/keys/usekeys.ts","./src/app/(dashboard)/hooks/keys/usekeys.test.ts","./src/app/(dashboard)/hooks/keys/useresetkeyspend.ts","./src/app/(dashboard)/hooks/logdetails/uselogdetails.ts","./src/app/(dashboard)/hooks/login/uselogin.ts","./src/app/(dashboard)/hooks/mcpsemanticfiltersettings/usemcpsemanticfiltersettings.ts","./src/app/(dashboard)/hooks/mcpsemanticfiltersettings/useupdatemcpsemanticfiltersettings.ts","./src/app/(dashboard)/hooks/mcpservers/usemcpaccessgroups.ts","./src/app/(dashboard)/hooks/mcpservers/usemcpaccessgroups.test.ts","./src/app/(dashboard)/hooks/mcpservers/usemcpserverhealth.ts","./src/app/(dashboard)/hooks/mcpservers/usemcpserverhealth.test.ts","./src/app/(dashboard)/hooks/mcpservers/usemcpservers.ts","./src/app/(dashboard)/hooks/mcpservers/usemcpservers.test.ts","./src/app/(dashboard)/hooks/mcpservers/usemcptoolsets.ts","./src/app/(dashboard)/hooks/models/usemodelcostmap.ts","./src/app/(dashboard)/hooks/models/usemodelcostmap.test.ts","./src/app/(dashboard)/hooks/models/usemodels.ts","./src/app/(dashboard)/hooks/models/usemodels.test.ts","./src/app/(dashboard)/hooks/onboarding/useonboarding.ts","./src/app/(dashboard)/hooks/onboarding/useonboarding.test.ts","./src/app/(dashboard)/hooks/organizations/useorganizations.ts","./src/app/(dashboard)/hooks/organizations/useorganizations.test.ts","./src/app/(dashboard)/hooks/projects/useprojects.ts","./src/app/(dashboard)/hooks/projects/usecreateproject.ts","./src/app/(dashboard)/hooks/projects/usecreateproject.test.ts","./src/app/(dashboard)/hooks/projects/usedeleteproject.ts","./src/app/(dashboard)/hooks/projects/usedeleteproject.test.ts","./src/app/(dashboard)/hooks/projects/useprojectdetails.ts","./src/app/(dashboard)/hooks/projects/useprojectdetails.test.ts","./src/app/(dashboard)/hooks/projects/useprojects.test.ts","./src/app/(dashboard)/hooks/projects/useupdateproject.ts","./src/app/(dashboard)/hooks/projects/useupdateproject.test.ts","./src/app/(dashboard)/hooks/providers/useproviderfields.ts","./src/app/(dashboard)/hooks/providers/useproviderfields.test.ts","./src/app/(dashboard)/hooks/proxyconfig/useproxyconfig.ts","./src/app/(dashboard)/hooks/proxyconfig/useproxyconfig.test.ts","./src/utils/proxyutils.ts","./src/app/(dashboard)/hooks/proxysettings/useproxysettings.ts","./src/app/(dashboard)/hooks/router/userouterfields.ts","./src/app/(dashboard)/hooks/router/userouterfields.test.ts","./src/components/routing_groups/types.ts","./src/app/(dashboard)/hooks/routinggroups/useroutinggroups.ts","./src/app/(dashboard)/hooks/sso/useeditssosettings.ts","./src/app/(dashboard)/hooks/sso/useeditssosettings.test.ts","./src/app/(dashboard)/hooks/sso/usessosettings.ts","./src/app/(dashboard)/hooks/sso/usessosettings.test.ts","./src/app/(dashboard)/hooks/storemodelindb/usestoremodelindb.ts","./src/app/(dashboard)/hooks/storemodelindb/usestoremodelindb.test.ts","./src/app/(dashboard)/hooks/storerequestinspendlogs/usestorerequestinspendlogs.ts","./src/app/(dashboard)/hooks/tags/usetags.ts","./src/app/(dashboard)/hooks/tags/usetags.test.ts","./src/app/(dashboard)/hooks/teams/useteams.ts","./src/app/(dashboard)/hooks/teams/useteams.test.ts","./src/app/(dashboard)/hooks/uiconfig/useuiconfig.test.ts","./src/app/(dashboard)/hooks/uisettings/useuisettings.ts","./src/app/(dashboard)/hooks/uisettings/useuisettings.test.ts","./src/app/(dashboard)/hooks/uisettings/useupdateuisettings.ts","./src/app/(dashboard)/hooks/uisettings/useupdateuisettings.test.ts","./src/app/(dashboard)/hooks/users/usecurrentuser.ts","./src/app/(dashboard)/hooks/users/usecurrentuser.test.ts","./src/app/(dashboard)/hooks/users/useusers.ts","./src/app/(dashboard)/hooks/users/useusers.test.ts","./src/app/(dashboard)/models-and-endpoints/utils/modeldatatransformer.ts","./src/app/(dashboard)/models-and-endpoints/utils/modeldatatransformer.test.ts","./src/components/chat_ui/mode_endpoint_mapping.tsx","./src/app/(dashboard)/playground/components/chat_ui/chatconstants.ts","./src/app/(dashboard)/playground/llm_calls/fetch_agents.tsx","./src/app/(dashboard)/playground/components/compareui/endpoint_config.ts","./src/app/(dashboard)/playground/components/compareui/endpoint_config.test.ts","./src/components/chat_ui/types.ts","./src/components/chat_ui/responsemetrics.tsx","./src/app/(dashboard)/playground/hooks/usechathistory.ts","./src/app/(dashboard)/playground/hooks/usechathistory.test.ts","./src/components/llm_calls/code_interpreter_handler.ts","./src/app/(dashboard)/playground/hooks/usecodeinterpreter.ts","./src/utils/datautils.ts","./node_modules/@types/lodash/common/common.d.ts","./node_modules/@types/lodash/common/array.d.ts","./node_modules/@types/lodash/common/collection.d.ts","./node_modules/@types/lodash/common/date.d.ts","./node_modules/@types/lodash/common/function.d.ts","./node_modules/@types/lodash/common/lang.d.ts","./node_modules/@types/lodash/common/math.d.ts","./node_modules/@types/lodash/common/number.d.ts","./node_modules/@types/lodash/common/object.d.ts","./node_modules/@types/lodash/common/seq.d.ts","./node_modules/@types/lodash/common/string.d.ts","./node_modules/@types/lodash/common/util.d.ts","./node_modules/@types/lodash/index.d.ts","./node_modules/@types/lodash/debounce.d.ts","./src/components/agent_management/agentselector.tsx","./src/components/callback_info_helpers.tsx","./src/components/common_components/accessgroupselector.tsx","./src/components/common_components/budget_duration_dropdown.tsx","./src/components/common_components/keylifecyclesettings.tsx","./src/components/llm_calls/fetch_models.tsx","./src/components/common_components/modelselector.tsx","./src/components/common_components/modelaliasmanager.tsx","./src/components/common_components/passthroughroutesselector.tsx","./src/components/shared/numerical_input.tsx","./src/components/team/loggingsettings.tsx","./src/components/common_components/premiumloggingsettings.tsx","./src/components/common_components/ratelimittypeformitem.tsx","./src/components/router_settings/latencybasedconfiguration.tsx","./src/components/router_settings/reliabilityretriessection.tsx","./src/components/router_settings/routingstrategyselector.tsx","./src/components/router_settings/tagfilteringtoggle.tsx","./src/components/router_settings/routersettingsform.tsx","./node_modules/lucide-react/dist/lucide-react.d.ts","./src/components/settings/routersettings/fallbacks/addfallbacksmodal.tsx","./src/components/settings/routersettings/fallbacks/fallbackgroupconfig.tsx","./src/components/settings/routersettings/fallbacks/fallbackselectionform.tsx","./src/components/settings/routersettings/fallbacks/addfallbacks.tsx","./src/components/common_components/routersettingsaccordion.tsx","./node_modules/@tanstack/pacer/dist/esm/types.d.ts","./node_modules/@tanstack/pacer/dist/esm/debouncer.d.ts","./node_modules/@tanstack/react-pacer/dist/esm/debouncer/usedebouncedcallback.d.ts","./node_modules/@tanstack/react-pacer/dist/esm/debouncer/usedebouncedstate.d.ts","./node_modules/@tanstack/react-pacer/dist/esm/debouncer/usedebouncedvalue.d.ts","./node_modules/@tanstack/react-pacer/dist/esm/debouncer/usedebouncer.d.ts","./node_modules/@tanstack/react-pacer/dist/esm/debouncer/index.d.ts","./src/components/common_components/team_dropdown.tsx","./src/components/common_components/organizationdropdown.tsx","./src/components/common_components/projectdropdown.tsx","./node_modules/@types/papaparse/index.d.ts","./node_modules/@types/react-copy-to-clipboard/index.d.ts","./src/components/bulk_create_users_button.tsx","./src/components/key_team_helpers/fetch_available_models_team_key.tsx","./src/components/onboarding_link.tsx","./src/components/createuserbutton.tsx","./src/components/key_team_helpers/budgetwindowseditor.tsx","./src/components/mcp_server_management/mcpserverselector.tsx","./src/utils/mcptoolcrudclassification.ts","./src/components/mcp_tools/mcpcrudpermissionpanel.tsx","./src/components/mcp_server_management/mcptoolpermissions.tsx","./src/components/shared/createdkeydisplay.tsx","./src/components/vector_store_management/types.tsx","./src/components/vector_store_management/vectorstoreselector.tsx","./src/components/organisms/utils.ts","./src/components/organisms/create_key_button.tsx","./src/app/(dashboard)/projects/components/projectmodals/projectbaseform.tsx","./src/app/(dashboard)/projects/components/projectmodals/projectformutils.ts","./src/app/(dashboard)/projects/components/projectmodals/projectformutils.test.ts","./src/utils/migratedpages.ts","./src/components/networking.test.ts","./src/components/page_metadata.ts","./src/components/common_components/newbadge.tsx","./src/components/usageindicator.tsx","./src/components/leftnav.tsx","./src/components/page_utils.ts","./src/components/page_utils.test.ts","./src/components/costtrackingsettings/types.ts","./src/components/common_components/simple_table.tsx","./src/components/provider_info_helpers.tsx","./src/components/costtrackingsettings/provider_display_helpers.ts","./src/components/costtrackingsettings/provider_discount_table.tsx","./src/components/costtrackingsettings/add_provider_form.tsx","./src/components/costtrackingsettings/provider_margin_table.tsx","./src/components/costtrackingsettings/add_margin_form.tsx","./src/components/costtrackingsettings/pricing_calculator/types.ts","./src/components/costtrackingsettings/pricing_calculator/multi_export_utils.ts","./src/components/costtrackingsettings/pricing_calculator/multi_export_dropdown.tsx","./src/components/costtrackingsettings/pricing_calculator/multi_cost_results.tsx","./src/components/costtrackingsettings/pricing_calculator/use_multi_cost_estimate.ts","./src/components/costtrackingsettings/pricing_calculator/index.tsx","./src/components/helplink.tsx","./src/app/(dashboard)/api-reference/components/codeblock.tsx","./src/components/costtrackingsettings/how_it_works.tsx","./src/components/costtrackingsettings/use_discount_config.ts","./src/components/costtrackingsettings/use_margin_config.ts","./src/components/costtrackingsettings/cost_tracking_settings.tsx","./src/components/costtrackingsettings/index.ts","./src/components/costtrackingsettings/provider_display_helpers.test.ts","./src/components/costtrackingsettings/use_discount_config.test.ts","./src/components/costtrackingsettings/use_margin_config.test.ts","./src/components/costtrackingsettings/pricing_calculator/multi_export_utils.test.ts","./src/components/costtrackingsettings/pricing_calculator/use_multi_cost_estimate.test.ts","./src/utils/teamutils.ts","./src/components/entityusageexport/types.ts","./src/components/entityusageexport/exportformatselector.tsx","./src/components/entityusageexport/exportsummary.tsx","./src/components/entityusageexport/exporttypeselector.tsx","./src/components/entityusageexport/utils.ts","./src/components/entityusageexport/entityusageexportmodal.tsx","./src/components/entityusageexport/usageexportheader.tsx","./src/components/entityusageexport/index.ts","./src/components/entityusageexport/utils.test.ts","./src/components/guardrailsmonitor/mockdata.ts","./src/components/modelselect/modelutils.ts","./src/components/modelselect/modelutils.test.ts","./src/components/navbar/navdisplayname.ts","./src/components/navbar/navdisplayname.test.ts","./src/components/navbar/navproductlinkclass.ts","./src/components/settings/adminsettings/hashicorpvault/constants.ts","./src/components/settings/adminsettings/mcpsemanticfiltersettings/semanticfiltertestutils.ts","./src/components/settings/adminsettings/mcpsemanticfiltersettings/semanticfiltertestutils.test.ts","./src/components/settings/adminsettings/ssosettings/constants.ts","./src/components/settings/adminsettings/ssosettings/utils.ts","./src/components/settings/adminsettings/ssosettings/utils.test.ts","./src/components/settings/loggingandalerts/loggingcallbacks/types.ts","./src/components/usagepage/types.ts","./src/components/usagepage/hooks/usepaginateddailyactivity.ts","./src/components/usagepage/utils/value_formatters.tsx","./src/components/usagepage/utils/value_formatters.test.ts","./src/components/agents/agent_config.ts","./src/components/agents/agent_discovery_utils.ts","./src/components/agents/agent_discovery_utils.test.ts","./src/components/agents/agent_type_utils.ts","./src/components/atoms/tooltip.tsx","./src/components/atoms/index.ts","./src/components/cache_settings/cachesettingsutils.ts","./src/components/chat/types.ts","./src/components/chat/usechathistory.ts","./src/components/claude_code_plugins/types.ts","./src/components/claude_code_plugins/helpers.ts","./src/components/claude_code_plugins/helpers.test.ts","./src/components/email_events/email_event_settings.tsx","./src/components/email_events/index.ts","./src/components/guardrails/guardrail_garden_configs.ts","./src/components/guardrails/guardrail_garden_data.ts","./src/components/guardrails/types.ts","./src/components/guardrails/custom_code/customcodemodal.tsx","./src/components/guardrails/custom_code/index.ts","./src/components/key_team_helpers/filter_helpers.ts","./src/components/key_team_helpers/filter_helpers.test.ts","./src/components/key_team_helpers/transform_key_info.tsx","./src/components/key_team_helpers/transform_key_info.test.ts","./src/components/model_dashboard/types.ts","./src/components/molecules/message_manager.test.ts","./src/components/organisms/utils.test.ts","./src/components/policies/types.ts","./src/components/policies/build_attachment_data.ts","./src/components/policies/build_attachment_data.test.ts","./src/components/prompts/prompt_editor_view/types.ts","./src/components/prompts/prompt_editor_view/utils.ts","./src/components/prompts/prompt_editor_view/utils.test.ts","./src/components/prompts/prompt_editor_view/conversation_panel/types.ts","./src/components/prompts/prompt_editor_view/conversation_panel/useconversation.ts","./src/components/team/tabvisibilityutils.ts","./src/components/team/tabvisibilityutils.test.ts","./src/components/team/usemyteammember.ts","./src/components/view_logs/constants.ts","./src/components/molecules/filter.tsx","./src/components/common_components/filterteamdropdown.tsx","./src/components/keyaliasselect/paginatedkeyaliasselect/paginatedkeyaliasselect.tsx","./src/components/modelselect/paginatedmodelselect/paginatedmodelselect.tsx","./node_modules/moment/ts3.1-typings/moment.d.ts","./src/components/constants.tsx","./node_modules/@tanstack/table-core/build/lib/utils.d.ts","./node_modules/@tanstack/table-core/build/lib/core/table.d.ts","./node_modules/@tanstack/table-core/build/lib/features/columnvisibility.d.ts","./node_modules/@tanstack/table-core/build/lib/features/columnordering.d.ts","./node_modules/@tanstack/table-core/build/lib/features/columnpinning.d.ts","./node_modules/@tanstack/table-core/build/lib/features/rowpinning.d.ts","./node_modules/@tanstack/table-core/build/lib/core/headers.d.ts","./node_modules/@tanstack/table-core/build/lib/features/columnfaceting.d.ts","./node_modules/@tanstack/table-core/build/lib/features/globalfaceting.d.ts","./node_modules/@tanstack/table-core/build/lib/filterfns.d.ts","./node_modules/@tanstack/table-core/build/lib/features/columnfiltering.d.ts","./node_modules/@tanstack/table-core/build/lib/features/globalfiltering.d.ts","./node_modules/@tanstack/table-core/build/lib/sortingfns.d.ts","./node_modules/@tanstack/table-core/build/lib/features/rowsorting.d.ts","./node_modules/@tanstack/table-core/build/lib/aggregationfns.d.ts","./node_modules/@tanstack/table-core/build/lib/features/columngrouping.d.ts","./node_modules/@tanstack/table-core/build/lib/features/rowexpanding.d.ts","./node_modules/@tanstack/table-core/build/lib/features/columnsizing.d.ts","./node_modules/@tanstack/table-core/build/lib/features/rowpagination.d.ts","./node_modules/@tanstack/table-core/build/lib/features/rowselection.d.ts","./node_modules/@tanstack/table-core/build/lib/core/row.d.ts","./node_modules/@tanstack/table-core/build/lib/core/cell.d.ts","./node_modules/@tanstack/table-core/build/lib/core/column.d.ts","./node_modules/@tanstack/table-core/build/lib/types.d.ts","./node_modules/@tanstack/table-core/build/lib/columnhelper.d.ts","./node_modules/@tanstack/table-core/build/lib/utils/getcorerowmodel.d.ts","./node_modules/@tanstack/table-core/build/lib/utils/getexpandedrowmodel.d.ts","./node_modules/@tanstack/table-core/build/lib/utils/getfacetedminmaxvalues.d.ts","./node_modules/@tanstack/table-core/build/lib/utils/getfacetedrowmodel.d.ts","./node_modules/@tanstack/table-core/build/lib/utils/getfaceteduniquevalues.d.ts","./node_modules/@tanstack/table-core/build/lib/utils/getfilteredrowmodel.d.ts","./node_modules/@tanstack/table-core/build/lib/utils/getgroupedrowmodel.d.ts","./node_modules/@tanstack/table-core/build/lib/utils/getpaginationrowmodel.d.ts","./node_modules/@tanstack/table-core/build/lib/utils/getsortedrowmodel.d.ts","./node_modules/@tanstack/table-core/build/lib/index.d.ts","./node_modules/@tanstack/react-table/build/lib/index.d.ts","./src/components/common_components/tableheadersortdropdown/tableheadersortdropdown.tsx","./src/components/view_logs/time_cell.tsx","./src/components/view_logs/typebadges.tsx","./src/components/view_logs/columns.tsx","./src/components/view_logs/log_filter_logic.tsx","./src/components/view_logs/filter_options.ts","./src/components/view_logs/utils.ts","./src/components/view_logs/guardrailviewer/bedrockguardraildetails.tsx","./src/components/view_logs/guardrailviewer/__tests__/fixtures.ts","./src/components/view_logs/logdetailsdrawer/constants.ts","./src/components/view_logs/logdetailsdrawer/drawerheader.tsx","./src/components/view_logs/logdetailsdrawer/usekeyboardnavigation.ts","./src/components/view_logs/guardrailviewer/presidiodetectedentities.tsx","./src/components/view_logs/guardrailviewer/contentfilterdetails.tsx","./src/components/view_logs/guardrailviewer/compliancepanel.tsx","./src/components/view_logs/guardrailviewer/guardrailviewer.tsx","./src/components/view_logs/evalviewer/evalviewer.tsx","./src/components/view_logs/costbreakdownviewer.tsx","./src/components/view_logs/configinfomessage.tsx","./src/components/view_logs/vectorstoreviewer.tsx","./src/components/view_logs/logdetailsdrawer/truncatedvalue.tsx","./src/components/view_logs/logdetailsdrawer/tokenflow.tsx","./node_modules/react-json-view-lite/dist/datarenderer.d.ts","./node_modules/react-json-view-lite/dist/index.d.ts","./src/components/view_logs/logdetailsdrawer/jsonviewer.tsx","./src/components/view_logs/logdetailsdrawer/utils.ts","./src/components/view_logs/toolssection/types.ts","./src/components/view_logs/toolssection/utils.ts","./src/components/view_logs/toolssection/formattedtoolview.tsx","./src/components/view_logs/toolssection/jsontoolview.tsx","./src/components/view_logs/toolssection/toolexpandedcontent.tsx","./src/components/view_logs/toolssection/toolitem.tsx","./src/components/view_logs/toolssection/toolssection.tsx","./src/components/view_logs/toolssection/index.ts","./src/components/view_logs/logdetailsdrawer/prettymessagestypes.ts","./src/components/view_logs/logdetailsdrawer/prettymessagesutils.ts","./src/components/view_logs/logdetailsdrawer/sectionheader.tsx","./src/components/view_logs/logdetailsdrawer/collapsiblemessage.tsx","./src/components/view_logs/logdetailsdrawer/simpletoolcallblock.tsx","./src/components/view_logs/logdetailsdrawer/simplemessageblock.tsx","./src/components/view_logs/logdetailsdrawer/historytree.tsx","./src/components/view_logs/logdetailsdrawer/inputcard.tsx","./src/components/view_logs/logdetailsdrawer/outputcard.tsx","./src/components/view_logs/logdetailsdrawer/realtimeprettyview.tsx","./src/components/view_logs/logdetailsdrawer/prettymessagesview.tsx","./src/components/view_logs/logdetailsdrawer/logdetailcontent.tsx","./src/components/view_logs/logdetailsdrawer/logdetailsdrawer.tsx","./src/components/view_logs/logdetailsdrawer/index.ts","./src/components/view_logs/toolssection/utils.test.ts","./src/data/insultscomplianceprompts.ts","./src/data/financialcomplianceprompts.ts","./src/data/codeexecutioncomplianceprompts.ts","./src/data/claimscomplianceprompts.ts","./src/data/complianceprompts.ts","./src/data/canadianpiicomplianceprompts.ts","./src/hooks/mcpoauthutils.ts","./src/hooks/use-safe-layout-effect.ts","./src/hooks/useworker.ts","./src/hooks/policies/usedeletepolicyattachment.ts","./src/lib/http/client.test.ts","./src/lib/http/resolveapibase.test.ts","./src/lib/http/schema.d.ts","./src/utils/budgetutils.ts","./src/utils/cookieutils.test.ts","./src/utils/datautils.test.ts","./src/utils/errorpatterns.ts","./src/utils/errorutils.ts","./src/utils/errorutils.test.ts","./src/utils/jwtutils.test.ts","./node_modules/date-fns/add.d.ts","./node_modules/date-fns/addbusinessdays.d.ts","./node_modules/date-fns/adddays.d.ts","./node_modules/date-fns/addhours.d.ts","./node_modules/date-fns/addisoweekyears.d.ts","./node_modules/date-fns/addmilliseconds.d.ts","./node_modules/date-fns/addminutes.d.ts","./node_modules/date-fns/addmonths.d.ts","./node_modules/date-fns/addquarters.d.ts","./node_modules/date-fns/addseconds.d.ts","./node_modules/date-fns/addweeks.d.ts","./node_modules/date-fns/addyears.d.ts","./node_modules/date-fns/areintervalsoverlapping.d.ts","./node_modules/date-fns/clamp.d.ts","./node_modules/date-fns/closestindexto.d.ts","./node_modules/date-fns/closestto.d.ts","./node_modules/date-fns/compareasc.d.ts","./node_modules/date-fns/comparedesc.d.ts","./node_modules/date-fns/constructfrom.d.ts","./node_modules/date-fns/constructnow.d.ts","./node_modules/date-fns/daystoweeks.d.ts","./node_modules/date-fns/differenceinbusinessdays.d.ts","./node_modules/date-fns/differenceincalendardays.d.ts","./node_modules/date-fns/differenceincalendarisoweekyears.d.ts","./node_modules/date-fns/differenceincalendarisoweeks.d.ts","./node_modules/date-fns/differenceincalendarmonths.d.ts","./node_modules/date-fns/differenceincalendarquarters.d.ts","./node_modules/date-fns/differenceincalendarweeks.d.ts","./node_modules/date-fns/differenceincalendaryears.d.ts","./node_modules/date-fns/differenceindays.d.ts","./node_modules/date-fns/differenceinhours.d.ts","./node_modules/date-fns/differenceinisoweekyears.d.ts","./node_modules/date-fns/differenceinmilliseconds.d.ts","./node_modules/date-fns/differenceinminutes.d.ts","./node_modules/date-fns/differenceinmonths.d.ts","./node_modules/date-fns/differenceinquarters.d.ts","./node_modules/date-fns/differenceinseconds.d.ts","./node_modules/date-fns/differenceinweeks.d.ts","./node_modules/date-fns/differenceinyears.d.ts","./node_modules/date-fns/eachdayofinterval.d.ts","./node_modules/date-fns/eachhourofinterval.d.ts","./node_modules/date-fns/eachminuteofinterval.d.ts","./node_modules/date-fns/eachmonthofinterval.d.ts","./node_modules/date-fns/eachquarterofinterval.d.ts","./node_modules/date-fns/eachweekofinterval.d.ts","./node_modules/date-fns/eachweekendofinterval.d.ts","./node_modules/date-fns/eachweekendofmonth.d.ts","./node_modules/date-fns/eachweekendofyear.d.ts","./node_modules/date-fns/eachyearofinterval.d.ts","./node_modules/date-fns/endofday.d.ts","./node_modules/date-fns/endofdecade.d.ts","./node_modules/date-fns/endofhour.d.ts","./node_modules/date-fns/endofisoweek.d.ts","./node_modules/date-fns/endofisoweekyear.d.ts","./node_modules/date-fns/endofminute.d.ts","./node_modules/date-fns/endofmonth.d.ts","./node_modules/date-fns/endofquarter.d.ts","./node_modules/date-fns/endofsecond.d.ts","./node_modules/date-fns/endoftoday.d.ts","./node_modules/date-fns/endoftomorrow.d.ts","./node_modules/date-fns/endofweek.d.ts","./node_modules/date-fns/endofyear.d.ts","./node_modules/date-fns/endofyesterday.d.ts","./node_modules/date-fns/_lib/format/formatters.d.ts","./node_modules/date-fns/_lib/format/longformatters.d.ts","./node_modules/date-fns/format.d.ts","./node_modules/date-fns/formatdistance.d.ts","./node_modules/date-fns/formatdistancestrict.d.ts","./node_modules/date-fns/formatdistancetonow.d.ts","./node_modules/date-fns/formatdistancetonowstrict.d.ts","./node_modules/date-fns/formatduration.d.ts","./node_modules/date-fns/formatiso.d.ts","./node_modules/date-fns/formatiso9075.d.ts","./node_modules/date-fns/formatisoduration.d.ts","./node_modules/date-fns/formatrfc3339.d.ts","./node_modules/date-fns/formatrfc7231.d.ts","./node_modules/date-fns/formatrelative.d.ts","./node_modules/date-fns/fromunixtime.d.ts","./node_modules/date-fns/getdate.d.ts","./node_modules/date-fns/getday.d.ts","./node_modules/date-fns/getdayofyear.d.ts","./node_modules/date-fns/getdaysinmonth.d.ts","./node_modules/date-fns/getdaysinyear.d.ts","./node_modules/date-fns/getdecade.d.ts","./node_modules/date-fns/_lib/defaultoptions.d.ts","./node_modules/date-fns/getdefaultoptions.d.ts","./node_modules/date-fns/gethours.d.ts","./node_modules/date-fns/getisoday.d.ts","./node_modules/date-fns/getisoweek.d.ts","./node_modules/date-fns/getisoweekyear.d.ts","./node_modules/date-fns/getisoweeksinyear.d.ts","./node_modules/date-fns/getmilliseconds.d.ts","./node_modules/date-fns/getminutes.d.ts","./node_modules/date-fns/getmonth.d.ts","./node_modules/date-fns/getoverlappingdaysinintervals.d.ts","./node_modules/date-fns/getquarter.d.ts","./node_modules/date-fns/getseconds.d.ts","./node_modules/date-fns/gettime.d.ts","./node_modules/date-fns/getunixtime.d.ts","./node_modules/date-fns/getweek.d.ts","./node_modules/date-fns/getweekofmonth.d.ts","./node_modules/date-fns/getweekyear.d.ts","./node_modules/date-fns/getweeksinmonth.d.ts","./node_modules/date-fns/getyear.d.ts","./node_modules/date-fns/hourstomilliseconds.d.ts","./node_modules/date-fns/hourstominutes.d.ts","./node_modules/date-fns/hourstoseconds.d.ts","./node_modules/date-fns/interval.d.ts","./node_modules/date-fns/intervaltoduration.d.ts","./node_modules/date-fns/intlformat.d.ts","./node_modules/date-fns/intlformatdistance.d.ts","./node_modules/date-fns/isafter.d.ts","./node_modules/date-fns/isbefore.d.ts","./node_modules/date-fns/isdate.d.ts","./node_modules/date-fns/isequal.d.ts","./node_modules/date-fns/isexists.d.ts","./node_modules/date-fns/isfirstdayofmonth.d.ts","./node_modules/date-fns/isfriday.d.ts","./node_modules/date-fns/isfuture.d.ts","./node_modules/date-fns/islastdayofmonth.d.ts","./node_modules/date-fns/isleapyear.d.ts","./node_modules/date-fns/ismatch.d.ts","./node_modules/date-fns/ismonday.d.ts","./node_modules/date-fns/ispast.d.ts","./node_modules/date-fns/issameday.d.ts","./node_modules/date-fns/issamehour.d.ts","./node_modules/date-fns/issameisoweek.d.ts","./node_modules/date-fns/issameisoweekyear.d.ts","./node_modules/date-fns/issameminute.d.ts","./node_modules/date-fns/issamemonth.d.ts","./node_modules/date-fns/issamequarter.d.ts","./node_modules/date-fns/issamesecond.d.ts","./node_modules/date-fns/issameweek.d.ts","./node_modules/date-fns/issameyear.d.ts","./node_modules/date-fns/issaturday.d.ts","./node_modules/date-fns/issunday.d.ts","./node_modules/date-fns/isthishour.d.ts","./node_modules/date-fns/isthisisoweek.d.ts","./node_modules/date-fns/isthisminute.d.ts","./node_modules/date-fns/isthismonth.d.ts","./node_modules/date-fns/isthisquarter.d.ts","./node_modules/date-fns/isthissecond.d.ts","./node_modules/date-fns/isthisweek.d.ts","./node_modules/date-fns/isthisyear.d.ts","./node_modules/date-fns/isthursday.d.ts","./node_modules/date-fns/istoday.d.ts","./node_modules/date-fns/istomorrow.d.ts","./node_modules/date-fns/istuesday.d.ts","./node_modules/date-fns/isvalid.d.ts","./node_modules/date-fns/iswednesday.d.ts","./node_modules/date-fns/isweekend.d.ts","./node_modules/date-fns/iswithininterval.d.ts","./node_modules/date-fns/isyesterday.d.ts","./node_modules/date-fns/lastdayofdecade.d.ts","./node_modules/date-fns/lastdayofisoweek.d.ts","./node_modules/date-fns/lastdayofisoweekyear.d.ts","./node_modules/date-fns/lastdayofmonth.d.ts","./node_modules/date-fns/lastdayofquarter.d.ts","./node_modules/date-fns/lastdayofweek.d.ts","./node_modules/date-fns/lastdayofyear.d.ts","./node_modules/date-fns/_lib/format/lightformatters.d.ts","./node_modules/date-fns/lightformat.d.ts","./node_modules/date-fns/max.d.ts","./node_modules/date-fns/milliseconds.d.ts","./node_modules/date-fns/millisecondstohours.d.ts","./node_modules/date-fns/millisecondstominutes.d.ts","./node_modules/date-fns/millisecondstoseconds.d.ts","./node_modules/date-fns/min.d.ts","./node_modules/date-fns/minutestohours.d.ts","./node_modules/date-fns/minutestomilliseconds.d.ts","./node_modules/date-fns/minutestoseconds.d.ts","./node_modules/date-fns/monthstoquarters.d.ts","./node_modules/date-fns/monthstoyears.d.ts","./node_modules/date-fns/nextday.d.ts","./node_modules/date-fns/nextfriday.d.ts","./node_modules/date-fns/nextmonday.d.ts","./node_modules/date-fns/nextsaturday.d.ts","./node_modules/date-fns/nextsunday.d.ts","./node_modules/date-fns/nextthursday.d.ts","./node_modules/date-fns/nexttuesday.d.ts","./node_modules/date-fns/nextwednesday.d.ts","./node_modules/date-fns/parse/_lib/types.d.ts","./node_modules/date-fns/parse/_lib/setter.d.ts","./node_modules/date-fns/parse/_lib/parser.d.ts","./node_modules/date-fns/parse/_lib/parsers.d.ts","./node_modules/date-fns/parse.d.ts","./node_modules/date-fns/parseiso.d.ts","./node_modules/date-fns/parsejson.d.ts","./node_modules/date-fns/previousday.d.ts","./node_modules/date-fns/previousfriday.d.ts","./node_modules/date-fns/previousmonday.d.ts","./node_modules/date-fns/previoussaturday.d.ts","./node_modules/date-fns/previoussunday.d.ts","./node_modules/date-fns/previousthursday.d.ts","./node_modules/date-fns/previoustuesday.d.ts","./node_modules/date-fns/previouswednesday.d.ts","./node_modules/date-fns/quarterstomonths.d.ts","./node_modules/date-fns/quarterstoyears.d.ts","./node_modules/date-fns/roundtonearesthours.d.ts","./node_modules/date-fns/roundtonearestminutes.d.ts","./node_modules/date-fns/secondstohours.d.ts","./node_modules/date-fns/secondstomilliseconds.d.ts","./node_modules/date-fns/secondstominutes.d.ts","./node_modules/date-fns/set.d.ts","./node_modules/date-fns/setdate.d.ts","./node_modules/date-fns/setday.d.ts","./node_modules/date-fns/setdayofyear.d.ts","./node_modules/date-fns/setdefaultoptions.d.ts","./node_modules/date-fns/sethours.d.ts","./node_modules/date-fns/setisoday.d.ts","./node_modules/date-fns/setisoweek.d.ts","./node_modules/date-fns/setisoweekyear.d.ts","./node_modules/date-fns/setmilliseconds.d.ts","./node_modules/date-fns/setminutes.d.ts","./node_modules/date-fns/setmonth.d.ts","./node_modules/date-fns/setquarter.d.ts","./node_modules/date-fns/setseconds.d.ts","./node_modules/date-fns/setweek.d.ts","./node_modules/date-fns/setweekyear.d.ts","./node_modules/date-fns/setyear.d.ts","./node_modules/date-fns/startofday.d.ts","./node_modules/date-fns/startofdecade.d.ts","./node_modules/date-fns/startofhour.d.ts","./node_modules/date-fns/startofisoweek.d.ts","./node_modules/date-fns/startofisoweekyear.d.ts","./node_modules/date-fns/startofminute.d.ts","./node_modules/date-fns/startofmonth.d.ts","./node_modules/date-fns/startofquarter.d.ts","./node_modules/date-fns/startofsecond.d.ts","./node_modules/date-fns/startoftoday.d.ts","./node_modules/date-fns/startoftomorrow.d.ts","./node_modules/date-fns/startofweek.d.ts","./node_modules/date-fns/startofweekyear.d.ts","./node_modules/date-fns/startofyear.d.ts","./node_modules/date-fns/startofyesterday.d.ts","./node_modules/date-fns/sub.d.ts","./node_modules/date-fns/subbusinessdays.d.ts","./node_modules/date-fns/subdays.d.ts","./node_modules/date-fns/subhours.d.ts","./node_modules/date-fns/subisoweekyears.d.ts","./node_modules/date-fns/submilliseconds.d.ts","./node_modules/date-fns/subminutes.d.ts","./node_modules/date-fns/submonths.d.ts","./node_modules/date-fns/subquarters.d.ts","./node_modules/date-fns/subseconds.d.ts","./node_modules/date-fns/subweeks.d.ts","./node_modules/date-fns/subyears.d.ts","./node_modules/date-fns/todate.d.ts","./node_modules/date-fns/transpose.d.ts","./node_modules/date-fns/weekstodays.d.ts","./node_modules/date-fns/yearstodays.d.ts","./node_modules/date-fns/yearstomonths.d.ts","./node_modules/date-fns/yearstoquarters.d.ts","./node_modules/date-fns/index.d.mts","./src/utils/keyexpiryutils.ts","./src/utils/keyexpiryutils.test.ts","./src/utils/keyupdateutils.ts","./src/utils/keyupdateutils.test.ts","./src/utils/localstorageutils.test.ts","./src/utils/mcpheaderutils.ts","./src/utils/mcpheaderutils.test.ts","./src/utils/mcptokenstore.test.ts","./src/utils/mcptoolcrudclassification.test.ts","./src/utils/migratedpages.test.ts","./src/utils/pkce.ts","./src/utils/proxyutils.test.ts","./src/utils/returnurlutils.test.ts","./src/utils/roles.test.ts","./src/utils/securestorage.ts","./src/utils/teamutils.test.ts","./src/utils/textutils.test.ts","./node_modules/@types/json-schema/index.d.ts","./node_modules/@eslint/core/dist/cjs/types.d.cts","./node_modules/eslint/lib/types/use-at-your-own-risk.d.ts","./node_modules/eslint/lib/types/index.d.ts","./tests/fetch-location-rule.test.ts","./node_modules/@testing-library/jest-dom/types/matchers.d.ts","./node_modules/@testing-library/jest-dom/types/jest.d.ts","./node_modules/@testing-library/jest-dom/types/index.d.ts","./tests/setuptests.ts","./node_modules/next/dist/compiled/@next/font/dist/types.d.ts","./node_modules/next/dist/compiled/@next/font/dist/google/index.d.ts","./node_modules/next/font/google/index.d.ts","./src/contexts/antdglobalprovider.tsx","./src/contexts/authcontext.tsx","./src/contexts/reactqueryprovider.tsx","./src/app/layout.tsx","./src/contexts/themecontext.tsx","./src/components/navbar/blogdropdown/blogdropdown.tsx","./src/components/navbar/communityengagementbuttons/communityengagementbuttons.tsx","./src/components/navbar/notificationsbell/notificationsbell.tsx","./src/components/navbar/userdropdown/userdropdown.tsx","./src/components/navbar/workerdropdown/workerdropdown.tsx","./src/components/navbar.tsx","./src/components/ui/ui-loading-spinner.tsx","./src/components/common_components/loadingscreen.tsx","./src/app/(dashboard)/components/sidebarprovider.tsx","./src/components/debugwarningbanner.tsx","./src/app/(dashboard)/layout.tsx","./src/components/model_dashboard/all_models_table.tsx","./src/components/molecules/models/providerlogo.tsx","./src/components/molecules/models/columns.tsx","./src/components/view_model/model_name_display.tsx","./src/components/model_dashboard/modelsettingsmodal/modelsettingsmodal.tsx","./src/app/(dashboard)/models-and-endpoints/components/allmodelstab.tsx","./src/app/(dashboard)/models-and-endpoints/components/modelretrysettingstab.tsx","./src/components/price_data_reload.tsx","./src/app/(dashboard)/models-and-endpoints/components/pricedatamanagementtab.tsx","./src/components/add_model/handle_add_model_submit.tsx","./src/components/add_model/provider_specific_fields.tsx","./src/components/model_add/addcredentialmodal.tsx","./src/components/model_add/editcredentialmodal.tsx","./src/components/model_add/credentials.tsx","./src/components/add_model/model_connection_test.tsx","./src/components/add_model/handle_add_auto_router_submit.tsx","./src/components/add_model/routerconfigbuilder.tsx","./src/components/add_model/complexityrouterconfig.tsx","./src/components/add_model/add_auto_router_tab.tsx","./src/components/add_model/cache_control_settings.tsx","./src/components/add_model/advanced_settings.tsx","./src/components/add_model/conditional_public_model_name.tsx","./src/components/add_model/litellm_model_name.tsx","./src/components/add_model/add_model_modes.tsx","./src/components/add_model/addmodelform.tsx","./src/components/add_model/add_model_tab.tsx","./src/components/model_dashboard/table.tsx","./src/components/model_dashboard/health_check_columns.tsx","./src/components/model_dashboard/healthcheckcomponent.tsx","./src/components/model_group_alias_settings.tsx","./src/components/edit_auto_router/edit_auto_router_modal.tsx","./src/components/model_add/reuse_credentials.tsx","./src/components/model_info_view.tsx","./src/components/key_value_input.tsx","./src/components/query_param_input.tsx","./src/components/route_preview.tsx","./src/components/common_components/passthroughsecuritysection.tsx","./src/components/guardrails/guardrailselector.tsx","./src/components/common_components/passthroughguardrailssection.tsx","./src/components/add_pass_through.tsx","./src/components/pass_through_info.tsx","./src/components/view_logs/table.tsx","./src/components/pass_through_settings.tsx","./src/components/common_components/user_search_modal.tsx","./src/components/common_components/durationselect.tsx","./src/components/guardrailsettingsview.tsx","./src/components/logging_settings_view.tsx","./src/components/modelselect/modelselect.tsx","./src/components/permissions/vectorstorepermissions.tsx","./src/components/permissions/mcpserverpermissions.tsx","./src/components/permissions/agentpermissions.tsx","./src/components/object_permissions_view.tsx","./src/components/searchtools/searchtoolselector.tsx","./src/components/team/editloggingsettings.tsx","./src/components/team/editmembership.tsx","./src/components/team/permission_definitions.tsx","./src/components/team/member_permissions.tsx","./src/components/team/myusertab.tsx","./src/components/common_components/membertable.tsx","./src/components/team/teammembertab.tsx","./src/components/common_components/defaultproxyadmintag.tsx","./src/app/(dashboard)/hooks/useteams.tsx","./src/components/common_components/labeledfield.tsx","./src/components/templates/keyinfoheader.tsx","./src/components/common_components/autorotationview.tsx","./src/components/key_info_utils.tsx","./src/components/organisms/regeneratekeymodal.tsx","./src/components/policies/policyselector.tsx","./src/components/templates/key_edit_view.tsx","./src/components/templates/key_info_view.tsx","./src/components/team/teamvirtualkeystable.tsx","./src/components/team/teaminfo.tsx","./src/app/(dashboard)/models-and-endpoints/modelsandendpointsview.tsx","./src/components/scim.tsx","./src/components/settings/adminsettings/loggingsettings/loggingsettings.tsx","./src/components/settings/adminsettings/ssosettings/modals/basessosettingsform.tsx","./src/components/settings/adminsettings/ssosettings/modals/addssosettingsmodal.tsx","./src/components/settings/adminsettings/ssosettings/modals/deletessosettingsmodal.tsx","./src/components/settings/adminsettings/ssosettings/modals/editssosettingsmodal.tsx","./src/components/settings/adminsettings/ssosettings/redactablefield.tsx","./src/components/settings/adminsettings/ssosettings/rolemappings.tsx","./src/components/settings/adminsettings/ssosettings/ssosettingsemptyplaceholder.tsx","./src/components/settings/adminsettings/ssosettings/ssosettingsloadingskeleton.tsx","./src/components/settings/adminsettings/ssosettings/ssosettings.tsx","./src/components/settings/adminsettings/uisettings/pagevisibilitysettings.tsx","./src/components/settings/adminsettings/uisettings/uisettings.tsx","./src/components/settings/adminsettings/hashicorpvault/edithashicorpvaultmodal.tsx","./src/components/settings/adminsettings/hashicorpvault/hashicorpvaultemptyplaceholder.tsx","./src/components/settings/adminsettings/hashicorpvault/hashicorpvault.tsx","./src/components/ssomodals.tsx","./src/components/uiaccesscontrolform.tsx","./src/components/adminpanel.tsx","./src/components/agents/cost_config_fields.tsx","./src/components/agents/agent_form_fields.tsx","./src/components/agents/agent_card_discovery.tsx","./src/components/agents/dynamic_agent_form_fields.tsx","./src/components/agents/add_agent_form.tsx","./src/components/agents/agent_cost_view.tsx","./src/components/agents/agent_info.tsx","./src/components/agents.tsx","./src/components/shared/usage_date_picker.tsx","./src/components/response_time_indicator.tsx","./src/components/cache_health.tsx","./src/components/cache_settings/redistypeselector.tsx","./src/components/cache_settings/cachefieldrenderer.tsx","./src/components/cache_settings/index.tsx","./src/components/cache_dashboard.tsx","./src/components/claude_code_plugins/add_plugin_form.tsx","./src/components/claude_code_plugins/plugin_table.tsx","./src/components/claude_code_plugins/skill_detail.tsx","./src/components/claude_code_plugins.tsx","./src/components/router_settings/index.tsx","./node_modules/openai/_shims/manual-types.d.ts","./node_modules/openai/_shims/auto/types.d.ts","./node_modules/openai/streaming.d.ts","./node_modules/openai/error.d.ts","./node_modules/openai/_shims/multipartbody.d.ts","./node_modules/openai/uploads.d.ts","./node_modules/openai/core.d.ts","./node_modules/openai/_shims/index.d.ts","./node_modules/openai/pagination.d.ts","./node_modules/openai/resources/shared.d.ts","./node_modules/openai/resources/batches.d.ts","./node_modules/openai/resources/chat/completions/messages.d.ts","./node_modules/openai/resources/chat/completions/completions.d.ts","./node_modules/openai/resources/completions.d.ts","./node_modules/openai/resources/embeddings.d.ts","./node_modules/openai/resources/files.d.ts","./node_modules/openai/resources/images.d.ts","./node_modules/openai/resources/models.d.ts","./node_modules/openai/resources/moderations.d.ts","./node_modules/openai/resources/audio/speech.d.ts","./node_modules/openai/resources/audio/transcriptions.d.ts","./node_modules/openai/resources/audio/translations.d.ts","./node_modules/openai/resources/audio/audio.d.ts","./node_modules/openai/resources/beta/threads/messages.d.ts","./node_modules/openai/resources/beta/threads/runs/steps.d.ts","./node_modules/openai/resources/beta/threads/runs/runs.d.ts","./node_modules/openai/lib/eventstream.d.ts","./node_modules/openai/lib/assistantstream.d.ts","./node_modules/openai/resources/beta/threads/threads.d.ts","./node_modules/openai/resources/beta/assistants.d.ts","./node_modules/openai/resources/chat/completions.d.ts","./node_modules/openai/lib/abstractchatcompletionrunner.d.ts","./node_modules/openai/lib/chatcompletionstream.d.ts","./node_modules/openai/lib/responsesparser.d.ts","./node_modules/openai/resources/responses/input-items.d.ts","./node_modules/openai/lib/responses/eventtypes.d.ts","./node_modules/openai/lib/responses/responsestream.d.ts","./node_modules/openai/resources/responses/responses.d.ts","./node_modules/openai/lib/parser.d.ts","./node_modules/openai/lib/chatcompletionstreamingrunner.d.ts","./node_modules/openai/lib/jsonschema.d.ts","./node_modules/openai/lib/runnablefunction.d.ts","./node_modules/openai/lib/chatcompletionrunner.d.ts","./node_modules/openai/resources/beta/chat/completions.d.ts","./node_modules/openai/resources/beta/chat/chat.d.ts","./node_modules/openai/resources/beta/realtime/sessions.d.ts","./node_modules/openai/resources/beta/realtime/transcription-sessions.d.ts","./node_modules/openai/resources/beta/realtime/realtime.d.ts","./node_modules/openai/resources/beta/beta.d.ts","./node_modules/openai/resources/containers/files/content.d.ts","./node_modules/openai/resources/containers/files/files.d.ts","./node_modules/openai/resources/containers/containers.d.ts","./node_modules/openai/resources/graders/grader-models.d.ts","./node_modules/openai/resources/evals/runs/output-items.d.ts","./node_modules/openai/resources/evals/runs/runs.d.ts","./node_modules/openai/resources/evals/evals.d.ts","./node_modules/openai/resources/fine-tuning/methods.d.ts","./node_modules/openai/resources/fine-tuning/alpha/graders.d.ts","./node_modules/openai/resources/fine-tuning/alpha/alpha.d.ts","./node_modules/openai/resources/fine-tuning/checkpoints/permissions.d.ts","./node_modules/openai/resources/fine-tuning/checkpoints/checkpoints.d.ts","./node_modules/openai/resources/fine-tuning/jobs/checkpoints.d.ts","./node_modules/openai/resources/fine-tuning/jobs/jobs.d.ts","./node_modules/openai/resources/fine-tuning/fine-tuning.d.ts","./node_modules/openai/resources/graders/graders.d.ts","./node_modules/openai/resources/uploads/parts.d.ts","./node_modules/openai/resources/uploads/uploads.d.ts","./node_modules/openai/resources/vector-stores/files.d.ts","./node_modules/openai/resources/vector-stores/file-batches.d.ts","./node_modules/openai/resources/vector-stores/vector-stores.d.ts","./node_modules/openai/index.d.ts","./node_modules/openai/resource.d.ts","./node_modules/openai/resources/chat/chat.d.ts","./node_modules/openai/resources/chat/completions/index.d.ts","./node_modules/openai/resources/chat/index.d.ts","./node_modules/openai/resources/index.d.ts","./node_modules/openai/index.d.mts","./src/components/settings/routersettings/fallbacks/fallbacks.tsx","./src/components/routing_groups/routinggroupstable.tsx","./src/components/routing_groups/routinggroupmodal.tsx","./src/components/routing_groups/index.tsx","./src/components/general_settings.tsx","./src/components/shared/advanced_date_picker.tsx","./src/components/guardrailsmonitor/evaluationsettingsmodal.tsx","./src/components/guardrailsmonitor/logviewer.tsx","./src/components/guardrailsmonitor/metriccard.tsx","./src/components/guardrailsmonitor/guardraildetail.tsx","./src/components/guardrailsmonitor/scorechart.tsx","./src/components/guardrailsmonitor/guardrailsoverview.tsx","./src/components/guardrailsmonitor/guardrailsmonitorview.tsx","./src/components/guardrails/content_filter/patternmodal.tsx","./src/components/guardrails/content_filter/custompatternmodal.tsx","./src/components/guardrails/content_filter/keywordmodal.tsx","./src/components/guardrails/content_filter/patterntable.tsx","./src/components/guardrails/content_filter/keywordtable.tsx","./src/components/guardrails/content_filter/contentcategoryconfiguration.tsx","./src/components/guardrails/content_filter/competitorintentconfiguration.tsx","./src/components/guardrails/content_filter/contentfilterconfiguration.tsx","./src/components/guardrails/guardrail_info_helpers.tsx","./src/components/guardrails/guardrail_optional_params.tsx","./src/components/guardrails/guardrail_provider_fields.tsx","./src/components/guardrails/llm_judge/llmjudgefields.tsx","./src/components/guardrails/pii_components.tsx","./src/components/guardrails/pii_configuration.tsx","./src/components/guardrails/tool_permission/toolpermissionruleseditor.tsx","./src/components/guardrails/add_guardrail_form.tsx","./src/components/guardrails/edit_guardrail_form.tsx","./src/components/guardrails/guardrail_table.tsx","./src/components/guardrails/content_filter/categorytable.tsx","./src/components/guardrails/content_filter/contentfilterdisplay.tsx","./src/components/guardrails/content_filter/contentfiltermanager.tsx","./src/components/guardrails/guardrail_info.tsx","./src/components/guardrails/guardrailtestresults.tsx","./src/components/guardrails/guardrailtestpanel.tsx","./src/components/guardrails/guardrailtestplayground.tsx","./src/components/guardrails/guardrail_garden_card.tsx","./src/components/guardrails/guardrail_garden_detail.tsx","./src/components/guardrails/guardrail_garden.tsx","./src/components/guardrails/teamguardrailstab.tsx","./src/components/guardrails.tsx","./src/components/policies/policy_table.tsx","./node_modules/@heroicons/react/solid/academiccapicon.d.ts","./node_modules/@heroicons/react/solid/adjustmentsicon.d.ts","./node_modules/@heroicons/react/solid/annotationicon.d.ts","./node_modules/@heroicons/react/solid/archiveicon.d.ts","./node_modules/@heroicons/react/solid/arrowcircledownicon.d.ts","./node_modules/@heroicons/react/solid/arrowcirclelefticon.d.ts","./node_modules/@heroicons/react/solid/arrowcirclerighticon.d.ts","./node_modules/@heroicons/react/solid/arrowcircleupicon.d.ts","./node_modules/@heroicons/react/solid/arrowdownicon.d.ts","./node_modules/@heroicons/react/solid/arrowlefticon.d.ts","./node_modules/@heroicons/react/solid/arrownarrowdownicon.d.ts","./node_modules/@heroicons/react/solid/arrownarrowlefticon.d.ts","./node_modules/@heroicons/react/solid/arrownarrowrighticon.d.ts","./node_modules/@heroicons/react/solid/arrownarrowupicon.d.ts","./node_modules/@heroicons/react/solid/arrowrighticon.d.ts","./node_modules/@heroicons/react/solid/arrowsmdownicon.d.ts","./node_modules/@heroicons/react/solid/arrowsmlefticon.d.ts","./node_modules/@heroicons/react/solid/arrowsmrighticon.d.ts","./node_modules/@heroicons/react/solid/arrowsmupicon.d.ts","./node_modules/@heroicons/react/solid/arrowupicon.d.ts","./node_modules/@heroicons/react/solid/arrowsexpandicon.d.ts","./node_modules/@heroicons/react/solid/atsymbolicon.d.ts","./node_modules/@heroicons/react/solid/backspaceicon.d.ts","./node_modules/@heroicons/react/solid/badgecheckicon.d.ts","./node_modules/@heroicons/react/solid/banicon.d.ts","./node_modules/@heroicons/react/solid/beakericon.d.ts","./node_modules/@heroicons/react/solid/bellicon.d.ts","./node_modules/@heroicons/react/solid/bookopenicon.d.ts","./node_modules/@heroicons/react/solid/bookmarkalticon.d.ts","./node_modules/@heroicons/react/solid/bookmarkicon.d.ts","./node_modules/@heroicons/react/solid/briefcaseicon.d.ts","./node_modules/@heroicons/react/solid/cakeicon.d.ts","./node_modules/@heroicons/react/solid/calculatoricon.d.ts","./node_modules/@heroicons/react/solid/calendaricon.d.ts","./node_modules/@heroicons/react/solid/cameraicon.d.ts","./node_modules/@heroicons/react/solid/cashicon.d.ts","./node_modules/@heroicons/react/solid/chartbaricon.d.ts","./node_modules/@heroicons/react/solid/chartpieicon.d.ts","./node_modules/@heroicons/react/solid/chartsquarebaricon.d.ts","./node_modules/@heroicons/react/solid/chatalt2icon.d.ts","./node_modules/@heroicons/react/solid/chatalticon.d.ts","./node_modules/@heroicons/react/solid/chaticon.d.ts","./node_modules/@heroicons/react/solid/checkcircleicon.d.ts","./node_modules/@heroicons/react/solid/checkicon.d.ts","./node_modules/@heroicons/react/solid/chevrondoubledownicon.d.ts","./node_modules/@heroicons/react/solid/chevrondoublelefticon.d.ts","./node_modules/@heroicons/react/solid/chevrondoublerighticon.d.ts","./node_modules/@heroicons/react/solid/chevrondoubleupicon.d.ts","./node_modules/@heroicons/react/solid/chevrondownicon.d.ts","./node_modules/@heroicons/react/solid/chevronlefticon.d.ts","./node_modules/@heroicons/react/solid/chevronrighticon.d.ts","./node_modules/@heroicons/react/solid/chevronupicon.d.ts","./node_modules/@heroicons/react/solid/chipicon.d.ts","./node_modules/@heroicons/react/solid/clipboardcheckicon.d.ts","./node_modules/@heroicons/react/solid/clipboardcopyicon.d.ts","./node_modules/@heroicons/react/solid/clipboardlisticon.d.ts","./node_modules/@heroicons/react/solid/clipboardicon.d.ts","./node_modules/@heroicons/react/solid/clockicon.d.ts","./node_modules/@heroicons/react/solid/clouddownloadicon.d.ts","./node_modules/@heroicons/react/solid/clouduploadicon.d.ts","./node_modules/@heroicons/react/solid/cloudicon.d.ts","./node_modules/@heroicons/react/solid/codeicon.d.ts","./node_modules/@heroicons/react/solid/cogicon.d.ts","./node_modules/@heroicons/react/solid/collectionicon.d.ts","./node_modules/@heroicons/react/solid/colorswatchicon.d.ts","./node_modules/@heroicons/react/solid/creditcardicon.d.ts","./node_modules/@heroicons/react/solid/cubetransparenticon.d.ts","./node_modules/@heroicons/react/solid/cubeicon.d.ts","./node_modules/@heroicons/react/solid/currencybangladeshiicon.d.ts","./node_modules/@heroicons/react/solid/currencydollaricon.d.ts","./node_modules/@heroicons/react/solid/currencyeuroicon.d.ts","./node_modules/@heroicons/react/solid/currencypoundicon.d.ts","./node_modules/@heroicons/react/solid/currencyrupeeicon.d.ts","./node_modules/@heroicons/react/solid/currencyyenicon.d.ts","./node_modules/@heroicons/react/solid/cursorclickicon.d.ts","./node_modules/@heroicons/react/solid/databaseicon.d.ts","./node_modules/@heroicons/react/solid/desktopcomputericon.d.ts","./node_modules/@heroicons/react/solid/devicemobileicon.d.ts","./node_modules/@heroicons/react/solid/devicetableticon.d.ts","./node_modules/@heroicons/react/solid/documentaddicon.d.ts","./node_modules/@heroicons/react/solid/documentdownloadicon.d.ts","./node_modules/@heroicons/react/solid/documentduplicateicon.d.ts","./node_modules/@heroicons/react/solid/documentremoveicon.d.ts","./node_modules/@heroicons/react/solid/documentreporticon.d.ts","./node_modules/@heroicons/react/solid/documentsearchicon.d.ts","./node_modules/@heroicons/react/solid/documenttexticon.d.ts","./node_modules/@heroicons/react/solid/documenticon.d.ts","./node_modules/@heroicons/react/solid/dotscirclehorizontalicon.d.ts","./node_modules/@heroicons/react/solid/dotshorizontalicon.d.ts","./node_modules/@heroicons/react/solid/dotsverticalicon.d.ts","./node_modules/@heroicons/react/solid/downloadicon.d.ts","./node_modules/@heroicons/react/solid/duplicateicon.d.ts","./node_modules/@heroicons/react/solid/emojihappyicon.d.ts","./node_modules/@heroicons/react/solid/emojisadicon.d.ts","./node_modules/@heroicons/react/solid/exclamationcircleicon.d.ts","./node_modules/@heroicons/react/solid/exclamationicon.d.ts","./node_modules/@heroicons/react/solid/externallinkicon.d.ts","./node_modules/@heroicons/react/solid/eyeofficon.d.ts","./node_modules/@heroicons/react/solid/eyeicon.d.ts","./node_modules/@heroicons/react/solid/fastforwardicon.d.ts","./node_modules/@heroicons/react/solid/filmicon.d.ts","./node_modules/@heroicons/react/solid/filtericon.d.ts","./node_modules/@heroicons/react/solid/fingerprinticon.d.ts","./node_modules/@heroicons/react/solid/fireicon.d.ts","./node_modules/@heroicons/react/solid/flagicon.d.ts","./node_modules/@heroicons/react/solid/folderaddicon.d.ts","./node_modules/@heroicons/react/solid/folderdownloadicon.d.ts","./node_modules/@heroicons/react/solid/folderopenicon.d.ts","./node_modules/@heroicons/react/solid/folderremoveicon.d.ts","./node_modules/@heroicons/react/solid/foldericon.d.ts","./node_modules/@heroicons/react/solid/gifticon.d.ts","./node_modules/@heroicons/react/solid/globealticon.d.ts","./node_modules/@heroicons/react/solid/globeicon.d.ts","./node_modules/@heroicons/react/solid/handicon.d.ts","./node_modules/@heroicons/react/solid/hashtagicon.d.ts","./node_modules/@heroicons/react/solid/hearticon.d.ts","./node_modules/@heroicons/react/solid/homeicon.d.ts","./node_modules/@heroicons/react/solid/identificationicon.d.ts","./node_modules/@heroicons/react/solid/inboxinicon.d.ts","./node_modules/@heroicons/react/solid/inboxicon.d.ts","./node_modules/@heroicons/react/solid/informationcircleicon.d.ts","./node_modules/@heroicons/react/solid/keyicon.d.ts","./node_modules/@heroicons/react/solid/libraryicon.d.ts","./node_modules/@heroicons/react/solid/lightbulbicon.d.ts","./node_modules/@heroicons/react/solid/lightningbolticon.d.ts","./node_modules/@heroicons/react/solid/linkicon.d.ts","./node_modules/@heroicons/react/solid/locationmarkericon.d.ts","./node_modules/@heroicons/react/solid/lockclosedicon.d.ts","./node_modules/@heroicons/react/solid/lockopenicon.d.ts","./node_modules/@heroicons/react/solid/loginicon.d.ts","./node_modules/@heroicons/react/solid/logouticon.d.ts","./node_modules/@heroicons/react/solid/mailopenicon.d.ts","./node_modules/@heroicons/react/solid/mailicon.d.ts","./node_modules/@heroicons/react/solid/mapicon.d.ts","./node_modules/@heroicons/react/solid/menualt1icon.d.ts","./node_modules/@heroicons/react/solid/menualt2icon.d.ts","./node_modules/@heroicons/react/solid/menualt3icon.d.ts","./node_modules/@heroicons/react/solid/menualt4icon.d.ts","./node_modules/@heroicons/react/solid/menuicon.d.ts","./node_modules/@heroicons/react/solid/microphoneicon.d.ts","./node_modules/@heroicons/react/solid/minuscircleicon.d.ts","./node_modules/@heroicons/react/solid/minussmicon.d.ts","./node_modules/@heroicons/react/solid/minusicon.d.ts","./node_modules/@heroicons/react/solid/moonicon.d.ts","./node_modules/@heroicons/react/solid/musicnoteicon.d.ts","./node_modules/@heroicons/react/solid/newspapericon.d.ts","./node_modules/@heroicons/react/solid/officebuildingicon.d.ts","./node_modules/@heroicons/react/solid/paperairplaneicon.d.ts","./node_modules/@heroicons/react/solid/paperclipicon.d.ts","./node_modules/@heroicons/react/solid/pauseicon.d.ts","./node_modules/@heroicons/react/solid/pencilalticon.d.ts","./node_modules/@heroicons/react/solid/pencilicon.d.ts","./node_modules/@heroicons/react/solid/phoneincomingicon.d.ts","./node_modules/@heroicons/react/solid/phonemissedcallicon.d.ts","./node_modules/@heroicons/react/solid/phoneoutgoingicon.d.ts","./node_modules/@heroicons/react/solid/phoneicon.d.ts","./node_modules/@heroicons/react/solid/photographicon.d.ts","./node_modules/@heroicons/react/solid/playicon.d.ts","./node_modules/@heroicons/react/solid/pluscircleicon.d.ts","./node_modules/@heroicons/react/solid/plussmicon.d.ts","./node_modules/@heroicons/react/solid/plusicon.d.ts","./node_modules/@heroicons/react/solid/presentationchartbaricon.d.ts","./node_modules/@heroicons/react/solid/presentationchartlineicon.d.ts","./node_modules/@heroicons/react/solid/printericon.d.ts","./node_modules/@heroicons/react/solid/puzzleicon.d.ts","./node_modules/@heroicons/react/solid/qrcodeicon.d.ts","./node_modules/@heroicons/react/solid/questionmarkcircleicon.d.ts","./node_modules/@heroicons/react/solid/receiptrefundicon.d.ts","./node_modules/@heroicons/react/solid/receipttaxicon.d.ts","./node_modules/@heroicons/react/solid/refreshicon.d.ts","./node_modules/@heroicons/react/solid/replyicon.d.ts","./node_modules/@heroicons/react/solid/rewindicon.d.ts","./node_modules/@heroicons/react/solid/rssicon.d.ts","./node_modules/@heroicons/react/solid/saveasicon.d.ts","./node_modules/@heroicons/react/solid/saveicon.d.ts","./node_modules/@heroicons/react/solid/scaleicon.d.ts","./node_modules/@heroicons/react/solid/scissorsicon.d.ts","./node_modules/@heroicons/react/solid/searchcircleicon.d.ts","./node_modules/@heroicons/react/solid/searchicon.d.ts","./node_modules/@heroicons/react/solid/selectoricon.d.ts","./node_modules/@heroicons/react/solid/servericon.d.ts","./node_modules/@heroicons/react/solid/shareicon.d.ts","./node_modules/@heroicons/react/solid/shieldcheckicon.d.ts","./node_modules/@heroicons/react/solid/shieldexclamationicon.d.ts","./node_modules/@heroicons/react/solid/shoppingbagicon.d.ts","./node_modules/@heroicons/react/solid/shoppingcarticon.d.ts","./node_modules/@heroicons/react/solid/sortascendingicon.d.ts","./node_modules/@heroicons/react/solid/sortdescendingicon.d.ts","./node_modules/@heroicons/react/solid/sparklesicon.d.ts","./node_modules/@heroicons/react/solid/speakerphoneicon.d.ts","./node_modules/@heroicons/react/solid/staricon.d.ts","./node_modules/@heroicons/react/solid/statusofflineicon.d.ts","./node_modules/@heroicons/react/solid/statusonlineicon.d.ts","./node_modules/@heroicons/react/solid/stopicon.d.ts","./node_modules/@heroicons/react/solid/sunicon.d.ts","./node_modules/@heroicons/react/solid/supporticon.d.ts","./node_modules/@heroicons/react/solid/switchhorizontalicon.d.ts","./node_modules/@heroicons/react/solid/switchverticalicon.d.ts","./node_modules/@heroicons/react/solid/tableicon.d.ts","./node_modules/@heroicons/react/solid/tagicon.d.ts","./node_modules/@heroicons/react/solid/templateicon.d.ts","./node_modules/@heroicons/react/solid/terminalicon.d.ts","./node_modules/@heroicons/react/solid/thumbdownicon.d.ts","./node_modules/@heroicons/react/solid/thumbupicon.d.ts","./node_modules/@heroicons/react/solid/ticketicon.d.ts","./node_modules/@heroicons/react/solid/translateicon.d.ts","./node_modules/@heroicons/react/solid/trashicon.d.ts","./node_modules/@heroicons/react/solid/trendingdownicon.d.ts","./node_modules/@heroicons/react/solid/trendingupicon.d.ts","./node_modules/@heroicons/react/solid/truckicon.d.ts","./node_modules/@heroicons/react/solid/uploadicon.d.ts","./node_modules/@heroicons/react/solid/useraddicon.d.ts","./node_modules/@heroicons/react/solid/usercircleicon.d.ts","./node_modules/@heroicons/react/solid/usergroupicon.d.ts","./node_modules/@heroicons/react/solid/userremoveicon.d.ts","./node_modules/@heroicons/react/solid/usericon.d.ts","./node_modules/@heroicons/react/solid/usersicon.d.ts","./node_modules/@heroicons/react/solid/variableicon.d.ts","./node_modules/@heroicons/react/solid/videocameraicon.d.ts","./node_modules/@heroicons/react/solid/viewboardsicon.d.ts","./node_modules/@heroicons/react/solid/viewgridaddicon.d.ts","./node_modules/@heroicons/react/solid/viewgridicon.d.ts","./node_modules/@heroicons/react/solid/viewlisticon.d.ts","./node_modules/@heroicons/react/solid/volumeofficon.d.ts","./node_modules/@heroicons/react/solid/volumeupicon.d.ts","./node_modules/@heroicons/react/solid/wifiicon.d.ts","./node_modules/@heroicons/react/solid/xcircleicon.d.ts","./node_modules/@heroicons/react/solid/xicon.d.ts","./node_modules/@heroicons/react/solid/zoominicon.d.ts","./node_modules/@heroicons/react/solid/zoomouticon.d.ts","./node_modules/@heroicons/react/solid/index.d.ts","./src/components/policies/pipeline_flow_builder.tsx","./src/components/policies/policy_info.tsx","./src/components/policies/add_policy_form.tsx","./src/components/policies/impact_popover.tsx","./src/components/policies/attachment_table.tsx","./src/components/policies/impact_preview_alert.tsx","./src/components/policies/add_attachment_form.tsx","./src/components/policies/policy_test_panel.tsx","./src/components/policies/policy_templates.tsx","./src/components/policies/guardrail_selection_modal.tsx","./src/components/policies/template_parameter_modal.tsx","./src/components/policies/ai_suggestion_modal.tsx","./src/components/policies/index.tsx","./src/components/mcp_tools/mcpstandardssettings.tsx","./src/components/mcp_tools/mcpsubmissionstab.tsx","./src/components/mcp_tools/mcptoolsetstab.tsx","./src/components/mcp_tools/oauthformfields.tsx","./src/components/mcp_tools/mcp_server_cost_config.tsx","./src/components/mcp_tools/mcp_connection_status.tsx","./src/components/mcp_tools/mcp_tool_configuration.tsx","./src/components/mcp_tools/stdioconfiguration.tsx","./src/components/mcp_tools/mcppermissionmanagement.tsx","./src/components/mcp_tools/openapiquickpicker.tsx","./src/components/mcp_tools/openapiformsection.tsx","./src/components/mcp_tools/mcplogoselector.tsx","./src/components/mcp_tools/envvarssection.tsx","./src/components/mcp_tools/utils.tsx","./src/hooks/usemcpoauthflow.tsx","./src/hooks/usetestmcpconnection.tsx","./src/components/mcp_tools/create_mcp_server.tsx","./src/components/mcp_tools/mcp_connect.tsx","./src/components/mcp_tools/mcpservercard.tsx","./src/components/mcp_tools/mcp_server_edit.tsx","./src/components/mcp_tools/mcp_server_cost_display.tsx","./src/components/mcp_tools/mcp_server_view.tsx","./src/components/settings/adminsettings/mcpsemanticfiltersettings/mcpsemanticfiltertestpanel.tsx","./src/components/settings/adminsettings/mcpsemanticfiltersettings/mcpsemanticfiltersettings.tsx","./src/components/mcp_tools/mcpnetworksettings.tsx","./src/components/mcp_tools/mcp_discovery.tsx","./src/components/mcp_tools/byokcredentialmodal.tsx","./src/components/mcp_tools/userenvvarsmodal.tsx","./src/components/mcp_tools/mcp_servers.tsx","./src/components/mcp_tools/tooltestpanel.tsx","./src/hooks/usetoolsoauthflow.tsx","./src/hooks/useusermcpoauthflow.tsx","./src/components/mcp_tools/mcp_tools.tsx","./src/components/mcp_tools/index.tsx","./src/components/aihub/agenthubtablecolumns.tsx","./src/components/aihub/forms/makeagentpublicform.tsx","./src/components/mcp_hub_table_columns.tsx","./src/components/aihub/forms/makemcppublicform.tsx","./src/components/model_filters.tsx","./src/components/aihub/forms/makemodelpublicform.tsx","./src/components/model_hub_table_columns.tsx","./src/components/aihub/usefullinksmanagement.tsx","./src/components/skill_hub_table_columns.tsx","./src/components/aihub/skillhubdashboard.tsx","./src/components/claude_code_plugins/makeskillpublicform.tsx","./src/components/chat_ui/codesnippets.tsx","./src/components/public_model_hub.tsx","./src/components/aihub/modelhubtable.tsx","./src/components/common_components/chartutils.tsx","./src/components/usagepage/components/keymodelusageview.tsx","./src/components/activity_metrics.tsx","./src/components/cloudzero_export_modal.tsx","./src/components/shared/chart_loader.tsx","./src/components/per_user_usage.tsx","./src/components/user_agent_activity.tsx","./src/components/view_user_spend.tsx","./src/components/usagepage/components/endpointusage/components/endpointusagebarchart.tsx","./src/components/usagepage/components/endpointusage/components/endpointusagelinechart.tsx","./src/components/usagepage/components/endpointusage/components/endpointusagetable.tsx","./src/components/usagepage/components/endpointusage/endpointusage.tsx","./src/components/common_components/team_multi_select.tsx","./src/components/usagepage/components/entityusage/topkeyview.tsx","./src/components/usagepage/components/entityusage/topmodelview.tsx","./src/components/usagepage/components/entityusage/entityusage.tsx","./src/components/usagepage/components/entityusage/spendbyprovider.tsx","./node_modules/@types/unist/index.d.ts","./node_modules/@types/hast/index.d.ts","./node_modules/vfile-message/lib/index.d.ts","./node_modules/vfile-message/index.d.ts","./node_modules/vfile/lib/index.d.ts","./node_modules/vfile/index.d.ts","./node_modules/unified/lib/callable-instance.d.ts","./node_modules/trough/lib/index.d.ts","./node_modules/trough/index.d.ts","./node_modules/unified/lib/index.d.ts","./node_modules/unified/index.d.ts","./node_modules/@types/mdast/index.d.ts","./node_modules/mdast-util-to-hast/lib/state.d.ts","./node_modules/mdast-util-to-hast/lib/footer.d.ts","./node_modules/mdast-util-to-hast/lib/handlers/blockquote.d.ts","./node_modules/mdast-util-to-hast/lib/handlers/break.d.ts","./node_modules/mdast-util-to-hast/lib/handlers/code.d.ts","./node_modules/mdast-util-to-hast/lib/handlers/delete.d.ts","./node_modules/mdast-util-to-hast/lib/handlers/emphasis.d.ts","./node_modules/mdast-util-to-hast/lib/handlers/footnote-reference.d.ts","./node_modules/mdast-util-to-hast/lib/handlers/heading.d.ts","./node_modules/mdast-util-to-hast/lib/handlers/html.d.ts","./node_modules/mdast-util-to-hast/lib/handlers/image-reference.d.ts","./node_modules/mdast-util-to-hast/lib/handlers/image.d.ts","./node_modules/mdast-util-to-hast/lib/handlers/inline-code.d.ts","./node_modules/mdast-util-to-hast/lib/handlers/link-reference.d.ts","./node_modules/mdast-util-to-hast/lib/handlers/link.d.ts","./node_modules/mdast-util-to-hast/lib/handlers/list-item.d.ts","./node_modules/mdast-util-to-hast/lib/handlers/list.d.ts","./node_modules/mdast-util-to-hast/lib/handlers/paragraph.d.ts","./node_modules/mdast-util-to-hast/lib/handlers/root.d.ts","./node_modules/mdast-util-to-hast/lib/handlers/strong.d.ts","./node_modules/mdast-util-to-hast/lib/handlers/table.d.ts","./node_modules/mdast-util-to-hast/lib/handlers/table-cell.d.ts","./node_modules/mdast-util-to-hast/lib/handlers/table-row.d.ts","./node_modules/mdast-util-to-hast/lib/handlers/text.d.ts","./node_modules/mdast-util-to-hast/lib/handlers/thematic-break.d.ts","./node_modules/mdast-util-to-hast/lib/handlers/index.d.ts","./node_modules/mdast-util-to-hast/lib/index.d.ts","./node_modules/mdast-util-to-hast/index.d.ts","./node_modules/remark-rehype/lib/index.d.ts","./node_modules/remark-rehype/index.d.ts","./node_modules/react-markdown/lib/index.d.ts","./node_modules/react-markdown/index.d.ts","./src/components/usagepage/components/usageaichatpanel.tsx","./src/components/usagepage/components/usageviewselect/usageviewselect.tsx","./src/components/usagepage/components/usagepageview.tsx","./src/components/team/available_teams.tsx","./src/components/teamssosettings.tsx","./src/components/ui/antdloadingspinner.tsx","./src/components/oldteams.tsx","./src/components/common_components/filters/filterinput.tsx","./src/components/common_components/filters/filtersbutton.tsx","./src/components/common_components/filters/resetfiltersbutton.tsx","./src/app/(dashboard)/organizations/organizationfilters.tsx","./src/components/organization/organization_view.tsx","./src/components/organizations.tsx","./src/components/prompts/prompt_utils.tsx","./src/components/prompts/prompt_table.tsx","./src/components/prompts/prompt_editor_view/promptcodesnippets.tsx","./src/components/prompts/prompt_info.tsx","./src/components/prompts/add_prompt_form.tsx","./src/components/prompts/tool_modal.tsx","./src/components/prompts/prompt_editor_view/prompteditorheader.tsx","./src/components/prompts/prompt_editor_view/modelconfigcard.tsx","./src/components/prompts/prompt_editor_view/toolscard.tsx","./src/components/prompts/variable_textarea.tsx","./src/components/prompts/prompt_editor_view/developermessagecard.tsx","./src/components/prompts/prompt_editor_view/promptmessagescard.tsx","./src/components/prompts/prompt_editor_view/conversation_panel/variableinput.tsx","./src/components/prompts/prompt_editor_view/conversation_panel/emptystate.tsx","./src/components/prompts/prompt_editor_view/conversation_panel/messagebubble.tsx","./src/components/prompts/prompt_editor_view/conversation_panel/messagelist.tsx","./src/components/prompts/prompt_editor_view/conversation_panel/variablewarning.tsx","./src/components/prompts/prompt_editor_view/conversation_panel/messageinput.tsx","./src/components/prompts/prompt_editor_view/conversation_panel/index.tsx","./src/components/prompts/prompt_editor_view/publishmodal.tsx","./src/components/prompts/prompt_editor_view/dotpromptviewtab.tsx","./src/components/prompts/prompt_editor_view/versionhistorysidepanel.tsx","./src/components/prompts/prompt_editor_view/index.tsx","./src/components/prompts/prompt_editor_view.tsx","./src/components/prompts.tsx","./src/components/searchtools/searchconnectiontest.tsx","./src/components/searchtools/types.tsx","./src/components/searchtools/createsearchtools.tsx","./src/components/searchtools/searchtoolcolumn.tsx","./src/components/searchtools/searchtooltester.tsx","./src/components/searchtools/searchtoolview.tsx","./src/components/searchtools/searchtools.tsx","./src/components/searchtools/index.tsx","./src/components/email_settings.tsx","./src/components/alerting/dynamic_form.tsx","./src/components/alerting/alerting_settings.tsx","./src/components/cloudzerocosttracking/cloudzeroemptyplaceholder.tsx","./src/components/cloudzerocosttracking/cloudzerocreatemodal.tsx","./src/components/cloudzerocosttracking/cloudzeroupdatemodal.tsx","./src/components/cloudzerocosttracking/cloudzerointegrationsettings.tsx","./src/components/cloudzerocosttracking/cloudzerocosttracking.tsx","./src/components/settings/loggingandalerts/loggingcallbacks/loggingcallbackstable.tsx","./src/components/settings.tsx","./src/components/survey/nudgeprompt.tsx","./src/components/survey/surveyprompt.tsx","./src/components/survey/surveymodal.tsx","./src/components/survey/claudecodeprompt.tsx","./src/components/survey/claudecodemodal.tsx","./src/components/survey/index.tsx","./src/components/tag_management/tag_info.tsx","./src/components/tag_management/tagtable.tsx","./src/components/tag_management/components/createtagmodal.tsx","./src/components/tag_management/index.tsx","./src/components/transform_request.tsx","./src/components/ui_theme_settings.tsx","./src/app/onboarding/onboardingloadingview.tsx","./src/app/onboarding/onboardingerrorview.tsx","./src/app/onboarding/onboardingformbody.tsx","./src/app/onboarding/onboardingform.tsx","./src/app/onboarding/page.tsx","./src/components/common_components/fetch_teams.tsx","./src/components/key_team_helpers/filter_logic.tsx","./src/components/virtualkeyspage/virtualkeystable.tsx","./src/components/user_dashboard.tsx","./src/components/usage.tsx","./src/components/vector_store_management/vectorstoretable.tsx","./src/components/vector_store_providers.tsx","./src/components/vector_store_management/vectorstoreform.tsx","./src/components/vector_store_management/vectorstoretester.tsx","./src/components/vector_store_management/vector_store_info.tsx","./src/components/vector_store_management/documentstable.tsx","./src/components/vector_store_management/s3vectorsconfig.tsx","./src/components/vector_store_management/createvectorstore.tsx","./src/components/vector_store_management/testvectorstoretab.tsx","./src/components/vector_store_management/index.tsx","./src/components/toolpolicies/policyselect.tsx","./src/components/tooldetail.tsx","./src/components/toolpolicies.tsx","./src/components/toolpoliciesview.tsx","./src/components/memoryview/memoryeditmodal.tsx","./src/components/memoryview/memoryview.tsx","./src/components/memoryview/index.tsx","./src/components/workflow_runs/index.tsx","./src/components/deletedkeyspage/deletedkeystable/deletedkeystable.tsx","./src/components/deletedkeyspage/deletedkeyspage.tsx","./src/components/deletedteamspage/deletedteamstable/deletedteamstable.tsx","./src/components/deletedteamspage/deletedteamspage.tsx","./src/components/view_logs/auditlogdrawer/auditlogdrawer.tsx","./src/components/view_logs/audit_logs.tsx","./src/components/view_logs/logs_utils.tsx","./src/components/view_logs/logstabletoolbar.tsx","./src/components/view_logs/index.tsx","./src/components/user_edit_view.tsx","./src/components/bulkeditusers.tsx","./src/components/edit_user.tsx","./src/components/defaultusersettings.tsx","./src/components/view_users/columns.tsx","./src/components/view_users/user_info_view.tsx","./src/components/view_users/table.tsx","./src/components/view_users.tsx","./src/app/(dashboard)/page.tsx","./src/app/(dashboard)/access-groups/components/accessgroupsmodal/accessgroupbaseform.tsx","./src/app/(dashboard)/access-groups/components/accessgroupsmodal/accessgroupeditmodal.tsx","./src/app/(dashboard)/access-groups/components/accessgroupsdetailspage.tsx","./src/app/(dashboard)/access-groups/components/accessgroupsmodal/accessgroupcreatemodal.tsx","./src/app/(dashboard)/access-groups/components/accessgroupspage.tsx","./src/app/(dashboard)/access-groups/page.tsx","./node_modules/@testing-library/user-event/dist/types/event/eventmap.d.ts","./node_modules/@testing-library/user-event/dist/types/event/types.d.ts","./node_modules/@testing-library/user-event/dist/types/event/dispatchevent.d.ts","./node_modules/@testing-library/user-event/dist/types/event/focus.d.ts","./node_modules/@testing-library/user-event/dist/types/event/input.d.ts","./node_modules/@testing-library/user-event/dist/types/utils/click/isclickableinput.d.ts","./node_modules/@testing-library/user-event/dist/types/utils/datatransfer/blob.d.ts","./node_modules/@testing-library/user-event/dist/types/utils/datatransfer/datatransfer.d.ts","./node_modules/@testing-library/user-event/dist/types/utils/datatransfer/filelist.d.ts","./node_modules/@testing-library/user-event/dist/types/utils/datatransfer/clipboard.d.ts","./node_modules/@testing-library/user-event/dist/types/utils/edit/timevalue.d.ts","./node_modules/@testing-library/user-event/dist/types/utils/edit/iscontenteditable.d.ts","./node_modules/@testing-library/user-event/dist/types/utils/edit/iseditable.d.ts","./node_modules/@testing-library/user-event/dist/types/utils/edit/maxlength.d.ts","./node_modules/@testing-library/user-event/dist/types/utils/edit/setfiles.d.ts","./node_modules/@testing-library/user-event/dist/types/utils/focus/cursor.d.ts","./node_modules/@testing-library/user-event/dist/types/utils/focus/getactiveelement.d.ts","./node_modules/@testing-library/user-event/dist/types/utils/focus/gettabdestination.d.ts","./node_modules/@testing-library/user-event/dist/types/utils/focus/isfocusable.d.ts","./node_modules/@testing-library/user-event/dist/types/utils/focus/selection.d.ts","./node_modules/@testing-library/user-event/dist/types/utils/focus/selector.d.ts","./node_modules/@testing-library/user-event/dist/types/utils/keydef/readnextdescriptor.d.ts","./node_modules/@testing-library/user-event/dist/types/utils/misc/cloneevent.d.ts","./node_modules/@testing-library/user-event/dist/types/utils/misc/findclosest.d.ts","./node_modules/@testing-library/user-event/dist/types/utils/misc/getdocumentfromnode.d.ts","./node_modules/@testing-library/user-event/dist/types/utils/misc/gettreediff.d.ts","./node_modules/@testing-library/user-event/dist/types/utils/misc/getwindow.d.ts","./node_modules/@testing-library/user-event/dist/types/utils/misc/isdescendantorself.d.ts","./node_modules/@testing-library/user-event/dist/types/utils/misc/iselementtype.d.ts","./node_modules/@testing-library/user-event/dist/types/utils/misc/isvisible.d.ts","./node_modules/@testing-library/user-event/dist/types/utils/misc/isdisabled.d.ts","./node_modules/@testing-library/user-event/dist/types/utils/misc/level.d.ts","./node_modules/@testing-library/user-event/dist/types/utils/misc/wait.d.ts","./node_modules/@testing-library/user-event/dist/types/utils/pointer/csspointerevents.d.ts","./node_modules/@testing-library/user-event/dist/types/utils/index.d.ts","./node_modules/@testing-library/user-event/dist/types/document/ui.d.ts","./node_modules/@testing-library/user-event/dist/types/document/getvalueortextcontent.d.ts","./node_modules/@testing-library/user-event/dist/types/document/copyselection.d.ts","./node_modules/@testing-library/user-event/dist/types/document/trackvalue.d.ts","./node_modules/@testing-library/user-event/dist/types/document/index.d.ts","./node_modules/@testing-library/user-event/dist/types/event/selection/getinputrange.d.ts","./node_modules/@testing-library/user-event/dist/types/event/selection/modifyselection.d.ts","./node_modules/@testing-library/user-event/dist/types/event/selection/moveselection.d.ts","./node_modules/@testing-library/user-event/dist/types/event/selection/setselectionpermouse.d.ts","./node_modules/@testing-library/user-event/dist/types/event/selection/modifyselectionpermouse.d.ts","./node_modules/@testing-library/user-event/dist/types/event/selection/selectall.d.ts","./node_modules/@testing-library/user-event/dist/types/event/selection/setselectionrange.d.ts","./node_modules/@testing-library/user-event/dist/types/event/selection/setselection.d.ts","./node_modules/@testing-library/user-event/dist/types/event/selection/updateselectiononfocus.d.ts","./node_modules/@testing-library/user-event/dist/types/event/selection/index.d.ts","./node_modules/@testing-library/user-event/dist/types/event/index.d.ts","./node_modules/@testing-library/user-event/dist/types/system/pointer/buttons.d.ts","./node_modules/@testing-library/user-event/dist/types/system/pointer/shared.d.ts","./node_modules/@testing-library/user-event/dist/types/system/pointer/index.d.ts","./node_modules/@testing-library/user-event/dist/types/system/index.d.ts","./node_modules/@testing-library/user-event/dist/types/system/keyboard.d.ts","./node_modules/@testing-library/user-event/dist/types/options.d.ts","./node_modules/@testing-library/user-event/dist/types/convenience/click.d.ts","./node_modules/@testing-library/user-event/dist/types/convenience/hover.d.ts","./node_modules/@testing-library/user-event/dist/types/convenience/tab.d.ts","./node_modules/@testing-library/user-event/dist/types/convenience/index.d.ts","./node_modules/@testing-library/user-event/dist/types/keyboard/index.d.ts","./node_modules/@testing-library/user-event/dist/types/clipboard/copy.d.ts","./node_modules/@testing-library/user-event/dist/types/clipboard/cut.d.ts","./node_modules/@testing-library/user-event/dist/types/clipboard/paste.d.ts","./node_modules/@testing-library/user-event/dist/types/clipboard/index.d.ts","./node_modules/@testing-library/user-event/dist/types/pointer/index.d.ts","./node_modules/@testing-library/user-event/dist/types/utility/clear.d.ts","./node_modules/@testing-library/user-event/dist/types/utility/selectoptions.d.ts","./node_modules/@testing-library/user-event/dist/types/utility/type.d.ts","./node_modules/@testing-library/user-event/dist/types/utility/upload.d.ts","./node_modules/@testing-library/user-event/dist/types/utility/index.d.ts","./node_modules/@testing-library/user-event/dist/types/setup/api.d.ts","./node_modules/@testing-library/user-event/dist/types/setup/directapi.d.ts","./node_modules/@testing-library/user-event/dist/types/setup/setup.d.ts","./node_modules/@testing-library/user-event/dist/types/setup/index.d.ts","./node_modules/@testing-library/user-event/dist/types/index.d.ts","./tests/test-utils.tsx","./src/app/(dashboard)/access-groups/components/accessgroupsdetailspage.test.tsx","./src/app/(dashboard)/access-groups/components/accessgroupspage.test.tsx","./src/app/(dashboard)/api-reference/components/doclink.tsx","./src/app/(dashboard)/api-reference/apireferenceview.tsx","./src/app/(dashboard)/api-reference/apireferenceview.test.tsx","./src/app/(dashboard)/api-reference/page.tsx","./src/app/(dashboard)/models-and-endpoints/modelsandendpointsview.test.tsx","./src/app/(dashboard)/models-and-endpoints/components/allmodelstab.test.tsx","./src/app/(dashboard)/models-and-endpoints/components/modelretrysettingstab.test.tsx","./src/app/(dashboard)/organizations/organizationfilters.test.tsx","./src/components/llm_calls/chat_completion.tsx","./src/app/(dashboard)/playground/components/complianceui/complianceui.tsx","./node_modules/uuid/dist/max.d.ts","./node_modules/uuid/dist/nil.d.ts","./node_modules/uuid/dist/types.d.ts","./node_modules/uuid/dist/parse.d.ts","./node_modules/uuid/dist/stringify.d.ts","./node_modules/uuid/dist/v1.d.ts","./node_modules/uuid/dist/v1tov6.d.ts","./node_modules/uuid/dist/v35.d.ts","./node_modules/uuid/dist/v3.d.ts","./node_modules/uuid/dist/v4.d.ts","./node_modules/uuid/dist/v5.d.ts","./node_modules/uuid/dist/v6.d.ts","./node_modules/uuid/dist/v6tov1.d.ts","./node_modules/uuid/dist/v7.d.ts","./node_modules/uuid/dist/validate.d.ts","./node_modules/uuid/dist/version.d.ts","./node_modules/uuid/dist/index.d.ts","./src/components/mcp_tools/mcptoolargumentsform.tsx","./src/components/tag_management/tagselector.tsx","./src/app/(dashboard)/playground/llm_calls/a2a_send_message.tsx","./node_modules/@anthropic-ai/sdk/internal/builtin-types.d.mts","./node_modules/form-data/index.d.ts","./node_modules/@types/node-fetch/externals.d.ts","./node_modules/@types/node-fetch/index.d.ts","./node_modules/@anthropic-ai/sdk/internal/types.d.mts","./node_modules/@anthropic-ai/sdk/internal/headers.d.mts","./node_modules/@anthropic-ai/sdk/internal/shim-types.d.mts","./node_modules/@anthropic-ai/sdk/core/streaming.d.mts","./node_modules/@anthropic-ai/sdk/internal/request-options.d.mts","./node_modules/@anthropic-ai/sdk/internal/utils/log.d.mts","./node_modules/@anthropic-ai/sdk/resources/shared.d.mts","./node_modules/@anthropic-ai/sdk/core/error.d.mts","./node_modules/@anthropic-ai/sdk/internal/parse.d.mts","./node_modules/@anthropic-ai/sdk/core/api-promise.d.mts","./node_modules/@anthropic-ai/sdk/core/pagination.d.mts","./node_modules/@anthropic-ai/sdk/internal/uploads.d.mts","./node_modules/@anthropic-ai/sdk/internal/to-file.d.mts","./node_modules/@anthropic-ai/sdk/core/uploads.d.mts","./node_modules/@anthropic-ai/sdk/core/resource.d.mts","./node_modules/@anthropic-ai/sdk/resources/beta/environments.d.mts","./node_modules/@anthropic-ai/sdk/resources/beta/files.d.mts","./node_modules/@anthropic-ai/sdk/resources/beta/models.d.mts","./node_modules/@anthropic-ai/sdk/resources/beta/user-profiles.d.mts","./node_modules/@anthropic-ai/sdk/resources/beta/agents/versions.d.mts","./node_modules/@anthropic-ai/sdk/resources/beta/agents/agents.d.mts","./node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memories.d.mts","./node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memory-versions.d.mts","./node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memory-stores.d.mts","./node_modules/@anthropic-ai/sdk/internal/decoders/line.d.mts","./node_modules/@anthropic-ai/sdk/internal/decoders/jsonl.d.mts","./node_modules/@anthropic-ai/sdk/error.d.mts","./node_modules/@anthropic-ai/sdk/resources/messages/batches.d.mts","./node_modules/@anthropic-ai/sdk/resources/messages/index.d.mts","./node_modules/@anthropic-ai/sdk/resources/messages.d.mts","./node_modules/@anthropic-ai/sdk/lib/parser.d.mts","./node_modules/@anthropic-ai/sdk/lib/messagestream.d.mts","./node_modules/@anthropic-ai/sdk/resources/messages/messages.d.mts","./node_modules/@anthropic-ai/sdk/resources/beta/messages/batches.d.mts","./node_modules/@anthropic-ai/sdk/lib/beta-parser.d.mts","./node_modules/@anthropic-ai/sdk/lib/betamessagestream.d.mts","./node_modules/@anthropic-ai/sdk/resources/beta/agents/index.d.mts","./node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/index.d.mts","./node_modules/@anthropic-ai/sdk/resources/beta/messages/index.d.mts","./node_modules/@anthropic-ai/sdk/resources/beta/sessions/events.d.mts","./node_modules/@anthropic-ai/sdk/resources/beta/sessions/sessions.d.mts","./node_modules/@anthropic-ai/sdk/resources/beta/sessions/resources.d.mts","./node_modules/@anthropic-ai/sdk/resources/beta/sessions/index.d.mts","./node_modules/@anthropic-ai/sdk/resources/beta/skills/versions.d.mts","./node_modules/@anthropic-ai/sdk/resources/beta/skills/skills.d.mts","./node_modules/@anthropic-ai/sdk/resources/beta/skills/index.d.mts","./node_modules/@anthropic-ai/sdk/resources/beta/vaults/credentials.d.mts","./node_modules/@anthropic-ai/sdk/resources/beta/vaults/vaults.d.mts","./node_modules/@anthropic-ai/sdk/resources/beta/vaults/index.d.mts","./node_modules/@anthropic-ai/sdk/resources/beta/index.d.mts","./node_modules/@anthropic-ai/sdk/resources/beta.d.mts","./node_modules/@anthropic-ai/sdk/lib/tools/betarunnabletool.d.mts","./node_modules/@anthropic-ai/sdk/resources.d.mts","./node_modules/@anthropic-ai/sdk/lib/tools/compactioncontrol.d.mts","./node_modules/@anthropic-ai/sdk/lib/tools/betatoolrunner.d.mts","./node_modules/@anthropic-ai/sdk/lib/tools/toolerror.d.mts","./node_modules/@anthropic-ai/sdk/resources/beta/messages/messages.d.mts","./node_modules/@anthropic-ai/sdk/resources/beta/beta.d.mts","./node_modules/@anthropic-ai/sdk/resources/completions.d.mts","./node_modules/@anthropic-ai/sdk/resources/models.d.mts","./node_modules/@anthropic-ai/sdk/resources/index.d.mts","./node_modules/@anthropic-ai/sdk/client.d.mts","./node_modules/@anthropic-ai/sdk/index.d.mts","./src/app/(dashboard)/playground/llm_calls/anthropic_messages.tsx","./src/app/(dashboard)/playground/llm_calls/audio_speech.tsx","./src/app/(dashboard)/playground/llm_calls/audio_transcriptions.tsx","./src/app/(dashboard)/playground/llm_calls/embeddings_api.tsx","./src/app/(dashboard)/playground/llm_calls/image_edits.tsx","./src/app/(dashboard)/playground/llm_calls/image_generation.tsx","./src/components/llm_calls/responses_api.tsx","./src/app/(dashboard)/playground/llm_calls/interactions_api.tsx","./src/app/(dashboard)/playground/components/chat_ui/a2ametrics.tsx","./src/app/(dashboard)/playground/components/chat_ui/additionalmodelsettings.tsx","./src/app/(dashboard)/playground/components/chat_ui/audiorenderer.tsx","./src/app/(dashboard)/playground/components/chat_ui/chatimageutils.tsx","./src/app/(dashboard)/playground/components/chat_ui/chatimagerenderer.tsx","./src/app/(dashboard)/playground/components/chat_ui/chatimageupload.tsx","./src/app/(dashboard)/playground/components/chat_ui/codeinterpreteroutput.tsx","./src/app/(dashboard)/playground/components/chat_ui/codeinterpretertool.tsx","./src/app/(dashboard)/playground/components/chat_ui/endpointselector.tsx","./src/app/(dashboard)/playground/components/chat_ui/filepreviewcard.tsx","./src/components/chat_ui/mcpeventsdisplay.tsx","./src/components/chat_ui/reasoningcontent.tsx","./src/app/(dashboard)/playground/components/chat_ui/responsesimageutils.tsx","./src/app/(dashboard)/playground/components/chat_ui/responsesimagerenderer.tsx","./src/app/(dashboard)/playground/components/chat_ui/searchresultsdisplay.tsx","./src/app/(dashboard)/playground/components/chat_ui/chatmessagebubble.tsx","./src/app/(dashboard)/playground/components/chat_ui/responsesimageupload.tsx","./src/app/(dashboard)/playground/components/chat_ui/sessionmanagement.tsx","./src/app/(dashboard)/playground/components/chat_ui/realtimeplayground.tsx","./src/app/(dashboard)/playground/components/chat_ui/chatui.tsx","./src/app/(dashboard)/playground/components/chat_ui/agentbuilderview.tsx","./src/app/(dashboard)/playground/components/compareui/components/messagedisplay.tsx","./src/app/(dashboard)/playground/components/compareui/components/unifiedselector.tsx","./src/app/(dashboard)/playground/components/compareui/components/comparisonpanel.tsx","./src/app/(dashboard)/playground/components/compareui/components/messageinput.tsx","./src/app/(dashboard)/playground/components/compareui/compareui.tsx","./src/app/(dashboard)/playground/page.tsx","./src/app/(dashboard)/playground/components/chat_ui/additionalmodelsettings.test.tsx","./src/app/(dashboard)/playground/components/chat_ui/audiorenderer.test.tsx","./src/app/(dashboard)/playground/components/chat_ui/chatimageutils.test.tsx","./src/app/(dashboard)/playground/components/chat_ui/chatmessagebubble.test.tsx","./src/app/(dashboard)/playground/components/chat_ui/chatui.test.tsx","./src/app/(dashboard)/playground/components/chat_ui/codeinterpreteroutput.test.tsx","./src/app/(dashboard)/playground/components/chat_ui/endpointselector.test.tsx","./src/app/(dashboard)/playground/components/chat_ui/endpointutils.tsx","./src/app/(dashboard)/playground/components/chat_ui/endpointutils.test.tsx","./src/app/(dashboard)/playground/components/chat_ui/filepreviewcard.test.tsx","./src/app/(dashboard)/playground/components/compareui/compareui.test.tsx","./src/app/(dashboard)/playground/components/compareui/components/comparisonpanel.test.tsx","./src/app/(dashboard)/playground/components/compareui/components/messagedisplay.test.tsx","./src/app/(dashboard)/playground/components/compareui/components/messageinput.test.tsx","./src/app/(dashboard)/playground/components/compareui/components/modelselector.tsx","./src/app/(dashboard)/playground/components/compareui/components/modelselector.test.tsx","./src/app/(dashboard)/playground/components/compareui/components/unifiedselector.test.tsx","./src/app/(dashboard)/playground/llm_calls/audio_speech.test.tsx","./src/app/(dashboard)/playground/llm_calls/audio_transcriptions.test.tsx","./src/app/(dashboard)/playground/llm_calls/embeddings_api.test.tsx","./src/app/(dashboard)/projects/components/projectmodals/createprojectmodal.tsx","./src/app/(dashboard)/projects/components/projectmodals/editprojectmodal.tsx","./src/app/(dashboard)/projects/components/projectdetailspage.tsx","./src/app/(dashboard)/projects/components/projectspage.tsx","./src/app/(dashboard)/projects/page.tsx","./src/app/(dashboard)/projects/components/projectdetailspage.test.tsx","./src/app/(dashboard)/projects/components/projectkeystable.tsx","./src/app/(dashboard)/projects/components/projectkeyssection.tsx","./src/app/(dashboard)/projects/components/projectkeyssection.test.tsx","./src/app/(dashboard)/projects/components/projectkeystable.test.tsx","./src/app/(dashboard)/projects/components/projectspage.test.tsx","./src/app/(dashboard)/projects/components/projectmodals/createprojectmodal.test.tsx","./src/app/(dashboard)/projects/components/projectmodals/editprojectmodal.test.tsx","./src/app/(dashboard)/projects/components/projectmodals/projectbaseform.test.tsx","./node_modules/micromark-util-types/index.d.ts","./node_modules/micromark-extension-gfm-footnote/lib/html.d.ts","./node_modules/micromark-extension-gfm-footnote/lib/syntax.d.ts","./node_modules/micromark-extension-gfm-footnote/index.d.ts","./node_modules/micromark-extension-gfm-strikethrough/lib/html.d.ts","./node_modules/micromark-extension-gfm-strikethrough/lib/syntax.d.ts","./node_modules/micromark-extension-gfm-strikethrough/index.d.ts","./node_modules/micromark-extension-gfm/index.d.ts","./node_modules/mdast-util-from-markdown/lib/types.d.ts","./node_modules/mdast-util-from-markdown/lib/index.d.ts","./node_modules/mdast-util-from-markdown/index.d.ts","./node_modules/mdast-util-to-markdown/lib/types.d.ts","./node_modules/mdast-util-to-markdown/lib/index.d.ts","./node_modules/mdast-util-to-markdown/lib/handle/blockquote.d.ts","./node_modules/mdast-util-to-markdown/lib/handle/break.d.ts","./node_modules/mdast-util-to-markdown/lib/handle/code.d.ts","./node_modules/mdast-util-to-markdown/lib/handle/definition.d.ts","./node_modules/mdast-util-to-markdown/lib/handle/emphasis.d.ts","./node_modules/mdast-util-to-markdown/lib/handle/heading.d.ts","./node_modules/mdast-util-to-markdown/lib/handle/html.d.ts","./node_modules/mdast-util-to-markdown/lib/handle/image.d.ts","./node_modules/mdast-util-to-markdown/lib/handle/image-reference.d.ts","./node_modules/mdast-util-to-markdown/lib/handle/inline-code.d.ts","./node_modules/mdast-util-to-markdown/lib/handle/link.d.ts","./node_modules/mdast-util-to-markdown/lib/handle/link-reference.d.ts","./node_modules/mdast-util-to-markdown/lib/handle/list.d.ts","./node_modules/mdast-util-to-markdown/lib/handle/list-item.d.ts","./node_modules/mdast-util-to-markdown/lib/handle/paragraph.d.ts","./node_modules/mdast-util-to-markdown/lib/handle/root.d.ts","./node_modules/mdast-util-to-markdown/lib/handle/strong.d.ts","./node_modules/mdast-util-to-markdown/lib/handle/text.d.ts","./node_modules/mdast-util-to-markdown/lib/handle/thematic-break.d.ts","./node_modules/mdast-util-to-markdown/lib/handle/index.d.ts","./node_modules/mdast-util-to-markdown/index.d.ts","./node_modules/mdast-util-gfm-footnote/lib/index.d.ts","./node_modules/mdast-util-gfm-footnote/index.d.ts","./node_modules/markdown-table/index.d.ts","./node_modules/mdast-util-gfm-table/lib/index.d.ts","./node_modules/mdast-util-gfm-table/index.d.ts","./node_modules/mdast-util-gfm/lib/index.d.ts","./node_modules/mdast-util-gfm/index.d.ts","./node_modules/remark-gfm/lib/index.d.ts","./node_modules/remark-gfm/index.d.ts","./src/components/chat/conversationlist.tsx","./src/components/chat/chatmessages.tsx","./src/components/chat/mcpconnectpicker.tsx","./src/components/chat/mcpappspanel.tsx","./src/components/chat/mcpcredentialstab.tsx","./src/components/chat/chatpage.tsx","./src/app/chat/page.tsx","./src/app/login/loginpage.tsx","./src/app/login/loginpage.test.tsx","./src/app/login/page.tsx","./src/app/mcp/oauth/callback/page.tsx","./src/app/model_hub/page.tsx","./src/app/model_hub_table/page.tsx","./src/app/onboarding/onboardingerrorview.test.tsx","./src/app/onboarding/onboardingform.test.tsx","./src/app/onboarding/onboardingformbody.test.tsx","./src/app/onboarding/onboardingloadingview.test.tsx","./src/components/adminpanel.test.tsx","./src/components/bulkeditusers.test.tsx","./src/components/createuserbutton.test.tsx","./src/components/debugwarningbanner.test.tsx","./src/components/defaultusersettings.test.tsx","./src/components/helplink.test.tsx","./src/components/oldteams.test.tsx","./src/components/ssomodals.test.tsx","./src/components/teamssosettings.test.tsx","./src/components/toolpoliciesview.test.tsx","./src/components/uiaccesscontrolform.unit.test.tsx","./src/components/usageindicator.test.tsx","./src/components/activity_metrics.test.tsx","./src/components/agents.test.tsx","./src/components/bulk_create_users_button.test.tsx","./src/components/guardrails.test.tsx","./src/components/key_info_utils.test.tsx","./src/components/leftnav.test.tsx","./src/components/model_info_view.test.tsx","./src/components/navbar.test.tsx","./src/components/organizations.test.tsx","./src/components/provider_info_helpers.test.tsx","./src/components/public_model_hub.test.tsx","./src/components/settings.test.tsx","./src/components/user_agent_activity.test.tsx","./src/components/user_dashboard.test.tsx","./src/components/user_edit_view.test.tsx","./src/components/view_users.test.tsx","./src/components/aihub/agenthubtablecolumns.test.tsx","./src/components/aihub/modelhubtable.test.tsx","./src/components/aihub/usefullinksmanagement.test.tsx","./src/components/aihub/forms/makeagentpublicform.test.tsx","./src/components/aihub/forms/makemcppublicform.test.tsx","./src/components/aihub/forms/makemodelpublicform.test.tsx","./src/components/cloudzerocosttracking/cloudzerocosttracking.test.tsx","./src/components/cloudzerocosttracking/cloudzerocreatemodal.test.tsx","./src/components/cloudzerocosttracking/cloudzeroemptyplaceholder.test.tsx","./src/components/cloudzerocosttracking/cloudzerointegrationsettings.test.tsx","./src/components/cloudzerocosttracking/cloudzeroupdatemodal.test.tsx","./src/components/costtrackingsettings/add_margin_form.test.tsx","./src/components/costtrackingsettings/add_provider_form.test.tsx","./src/components/costtrackingsettings/cost_tracking_settings.test.tsx","./src/components/costtrackingsettings/how_it_works.test.tsx","./src/components/costtrackingsettings/provider_discount_table.test.tsx","./src/components/costtrackingsettings/provider_margin_table.test.tsx","./src/components/costtrackingsettings/pricing_calculator/index.test.tsx","./src/components/costtrackingsettings/pricing_calculator/multi_cost_results.test.tsx","./src/components/costtrackingsettings/pricing_calculator/multi_export_dropdown.test.tsx","./src/components/deletedkeyspage/deletedkeyspage.test.tsx","./src/components/deletedkeyspage/deletedkeystable/deletedkeystable.test.tsx","./src/components/deletedteamspage/deletedteamspage.test.tsx","./src/components/deletedteamspage/deletedteamstable/deletedteamstable.test.tsx","./src/components/entityusageexport/entityusageexportmodal.test.tsx","./src/components/entityusageexport/exportformatselector.test.tsx","./src/components/entityusageexport/exportsummary.test.tsx","./src/components/entityusageexport/exporttypeselector.test.tsx","./src/components/entityusageexport/usageexportheader.test.tsx","./src/components/guardrailsmonitor/guardrailconfig.tsx","./src/components/guardrailsmonitor/guardrailconfig.test.tsx","./src/components/guardrailsmonitor/guardrailsmonitorview.test.tsx","./src/components/guardrailsmonitor/metriccard.test.tsx","./src/components/guardrailsmonitor/scorechart.test.tsx","./src/components/keyaliasselect/paginatedkeyaliasselect/paginatedkeyaliasselect.test.tsx","./src/components/modelselect/modelselect.test.tsx","./src/components/modelselect/paginatedmodelselect/paginatedmodelselect.test.tsx","./src/components/navbar/blogdropdown/blogdropdown.test.tsx","./src/components/navbar/communityengagementbuttons/communityengagementbuttons.test.tsx","./src/components/navbar/notificationsbell/notificationsbell.test.tsx","./src/components/navbar/userdropdown/userdropdown.test.tsx","./src/components/navbar/workerdropdown/workerdropdown.test.tsx","./src/components/searchtools/searchtooltester.test.tsx","./src/components/searchtools/searchtoolview.test.tsx","./src/components/searchtools/searchtools.test.tsx","./src/components/settings/adminsettings/hashicorpvault/hashicorpvaultemptyplaceholder.test.tsx","./src/components/settings/adminsettings/loggingsettings/loggingsettings.test.tsx","./src/components/settings/adminsettings/mcpsemanticfiltersettings/mcpsemanticfiltersettings.test.tsx","./src/components/settings/adminsettings/mcpsemanticfiltersettings/mcpsemanticfiltertestpanel.test.tsx","./src/components/settings/adminsettings/ssosettings/redactablefield.test.tsx","./src/components/settings/adminsettings/ssosettings/rolemappings.test.tsx","./src/components/settings/adminsettings/ssosettings/ssosettings.test.tsx","./src/components/settings/adminsettings/ssosettings/ssosettingsemptyplaceholder.test.tsx","./src/components/settings/adminsettings/ssosettings/ssosettingsloadingskeleton.test.tsx","./src/components/settings/adminsettings/ssosettings/modals/addssosettingsmodal.test.tsx","./src/components/settings/adminsettings/ssosettings/modals/basessosettingsform.test.tsx","./src/components/settings/adminsettings/ssosettings/modals/deletessosettingsmodal.test.tsx","./src/components/settings/adminsettings/ssosettings/modals/editssosettingsmodal.test.tsx","./src/components/settings/adminsettings/uisettings/pagevisibilitysettings.test.tsx","./src/components/settings/adminsettings/uisettings/uisettings.test.tsx","./src/components/settings/loggingandalerts/loggingcallbacks/loggingcallbackstable.test.tsx","./src/components/settings/routersettings/fallbacks/addfallbacks.test.tsx","./src/components/settings/routersettings/fallbacks/addfallbacksmodal.test.tsx","./src/components/settings/routersettings/fallbacks/fallbackselectionform.test.tsx","./src/components/settings/routersettings/fallbacks/fallbacks.test.tsx","./src/components/toolpolicies/policyselect.test.tsx","./src/components/usagepage/components/keymodelusageview.test.tsx","./src/components/usagepage/components/usageaichatpanel.test.tsx","./src/components/usagepage/components/usagepageview.test.tsx","./src/components/usagepage/components/endpointusage/endpointusage.test.tsx","./src/components/usagepage/components/endpointusage/components/endpointusagebarchart.test.tsx","./src/components/usagepage/components/endpointusage/components/endpointusagelinechart.test.tsx","./src/components/usagepage/components/endpointusage/components/endpointusagetable.test.tsx","./src/components/usagepage/components/entityusage/entityusage.test.tsx","./src/components/usagepage/components/entityusage/spendbyprovider.test.tsx","./src/components/usagepage/components/entityusage/topkeyview.test.tsx","./src/components/usagepage/components/entityusage/topmodelview.test.tsx","./src/components/usagepage/components/usageviewselect/usageviewselect.test.tsx","./src/components/virtualkeyspage/virtualkeystable.test.tsx","./src/components/add_model/addmodelform.test.tsx","./src/components/add_model/complexityrouterconfig.test.tsx","./src/components/add_model/routerconfigbuilder.test.tsx","./src/components/add_model/add_model_tab.test.tsx","./src/components/add_model/advanced_settings.test.tsx","./src/components/add_model/conditional_public_model_name.test.tsx","./src/components/add_model/handle_add_model_submit.test.tsx","./src/components/add_model/litellm_model_name.test.tsx","./src/components/add_model/provider_specific_fields.test.tsx","./src/components/agent_management/agentselector.test.tsx","./src/components/agents/agent_card.tsx","./src/components/agents/agent_card.test.tsx","./src/components/agents/agent_card_discovery.test.tsx","./src/components/agents/agent_card_grid.tsx","./src/components/agents/agent_card_grid.test.tsx","./src/components/atoms/tooltip.test.tsx","./src/components/budgets/budget_panel.test.tsx","./src/components/cache_settings/cachefieldgroup.tsx","./src/components/cache_settings/cachefieldgroup.test.tsx","./src/components/cache_settings/cachefieldrenderer.test.tsx","./src/components/cache_settings/redistypeselector.test.tsx","./src/components/chat_ui/codesnippets.test.tsx","./src/components/claude_code_plugins/add_plugin_form.test.tsx","./src/components/common_components/defaultproxyadmintag.test.tsx","./src/components/common_components/deleteresourcemodal.test.tsx","./src/components/common_components/durationselect.test.tsx","./src/components/common_components/keylifecyclesettings.test.tsx","./src/components/common_components/labeledfield.test.tsx","./src/components/common_components/loadingscreen.test.tsx","./src/components/common_components/newbadge.test.tsx","./src/components/common_components/organizationdropdown.test.tsx","./src/components/common_components/ratelimittypeformitem.test.tsx","./src/components/common_components/chartutils.test.tsx","./src/components/common_components/filters/filterinput.test.tsx","./src/components/common_components/filters/filtersbutton.test.tsx","./src/components/common_components/filters/resetfiltersbutton.test.tsx","./src/components/common_components/iconactionbutton/baseactionbutton.test.tsx","./src/components/common_components/iconactionbutton/tableiconactionbuttons/tableiconactionbutton.test.tsx","./src/components/common_components/tableheadersortdropdown/tableheadersortdropdown.test.tsx","./src/components/guardrails/guardrailselector.test.tsx","./src/components/guardrails/guardrailtestpanel.test.tsx","./src/components/guardrails/guardrailtestplayground.test.tsx","./src/components/guardrails/guardrailtestresults.test.tsx","./src/components/guardrails/add_guardrail_form.test.tsx","./src/components/guardrails/guardrail_garden_card.test.tsx","./src/components/guardrails/guardrail_info.test.tsx","./src/components/guardrails/guardrail_info_helpers.test.tsx","./src/components/guardrails/guardrail_table.test.tsx","./src/components/guardrails/pii_components.test.tsx","./src/components/guardrails/pii_configuration.test.tsx","./src/components/guardrails/content_filter/contentfiltermanager.test.tsx","./src/components/guardrails/content_filter/custompatternmodal.test.tsx","./src/components/guardrails/content_filter/patternmodal.test.tsx","./src/components/guardrails/tool_permission/toolpermissionruleseditor.test.tsx","./src/components/key_team_helpers/fetch_available_models_team_key.test.tsx","./src/components/key_team_helpers/filter_logic.test.tsx","./src/components/llm_calls/chat_completion.test.tsx","./src/components/llm_calls/responses_api.test.tsx","./src/components/mcp_server_management/mcptoolpermissions.test.tsx","./src/components/mcp_tools/mcplogoselector.test.tsx","./src/components/mcp_tools/mcppermissionmanagement.test.tsx","./src/components/mcp_tools/mcpstandardssettings.test.tsx","./src/components/mcp_tools/oauthformfields.test.tsx","./src/components/mcp_tools/tooltestpanel.test.tsx","./src/components/mcp_tools/create_mcp_server.test.tsx","./src/components/mcp_tools/mcp_connection_status.test.tsx","./src/components/mcp_tools/mcp_server_edit.test.tsx","./src/components/mcp_tools/mcp_servers.test.tsx","./src/components/mcp_tools/mcp_tool_configuration.test.tsx","./src/components/mcp_tools/mcp_tools.test.tsx","./src/components/mcp_tools/types.test.tsx","./src/components/mcp_tools/utils.test.tsx","./src/components/model_add/addcredentialmodal.test.tsx","./src/components/model_add/editcredentialmodal.test.tsx","./src/components/model_add/credentials.test.tsx","./src/components/model_dashboard/healthcheckcomponent.test.tsx","./src/components/model_dashboard/modelsettingsmodal/modelsettingsmodal.test.tsx","./src/components/molecules/filter.test.tsx","./src/components/molecules/notifications_manager.test.tsx","./src/components/molecules/models/providerlogo.test.tsx","./src/components/molecules/models/columns.test.tsx","./src/components/organisms/regeneratekeymodal.test.tsx","./src/components/organisms/create_key_button.test.tsx","./src/components/organization/organization_view.test.tsx","./src/components/permissions/mcpserverpermissions.test.tsx","./src/components/policies/policyselector.test.tsx","./src/components/policies/add_attachment_form.test.tsx","./src/components/policies/attachment_table.test.tsx","./src/components/policies/guardrail_selection_modal.test.tsx","./src/components/policies/impact_popover.test.tsx","./src/components/policies/impact_preview_alert.test.tsx","./src/components/policies/index.test.tsx","./src/components/policies/policy_info.test.tsx","./src/components/policies/policy_table.test.tsx","./src/components/policies/policy_templates.test.tsx","./src/components/prompts/prompt_editor_view/toolscard.test.tsx","./src/components/prompts/prompt_editor_view/versionhistorysidepanel.test.tsx","./src/components/router_settings/latencybasedconfiguration.test.tsx","./src/components/router_settings/reliabilityretriessection.test.tsx","./src/components/router_settings/routersettingsform.test.tsx","./src/components/router_settings/routingstrategyselector.test.tsx","./src/components/router_settings/tagfilteringtoggle.test.tsx","./src/components/router_settings/index.test.tsx","./src/components/shared/createdkeydisplay.test.tsx","./src/components/shared/advanced_date_picker.test.tsx","./src/components/shared/chart_loader.test.tsx","./src/components/survey/claudecodemodal.test.tsx","./src/components/survey/claudecodeprompt.test.tsx","./src/components/survey/nudgeprompt.test.tsx","./src/components/survey/surveymodal.test.tsx","./src/components/survey/surveyprompt.test.tsx","./src/components/tag_management/tagselector.test.tsx","./src/components/tag_management/tagtable.test.tsx","./src/components/tag_management/components/createtagmodal.test.tsx","./src/components/team/editmembership.test.tsx","./src/components/team/loggingsettings.test.tsx","./src/components/team/teaminfo.test.tsx","./src/components/team/teammembertab.test.tsx","./src/components/team/teamvirtualkeystable.test.tsx","./src/components/team/available_teams.test.tsx","./src/components/team/member_permissions.test.tsx","./src/components/team/permission_definitions.test.tsx","./src/components/templates/keyinfoheader.test.tsx","./src/components/templates/keyinfoview.handlekeyupdate.test.tsx","./src/components/templates/key_edit_view.test.tsx","./src/components/templates/key_info_view.budget_display.test.tsx","./src/components/templates/key_info_view.test.tsx","./src/components/ui/antdloadingspinner.test.tsx","./src/components/ui/ui-loading-spinner.test.tsx","./src/components/vector_store_management/createvectorstore.test.tsx","./src/components/vector_store_management/documentstable.test.tsx","./src/components/vector_store_management/s3vectorsconfig.test.tsx","./src/components/vector_store_management/testvectorstoretab.test.tsx","./src/components/vector_store_management/vectorstoreform.test.tsx","./src/components/vector_store_management/vectorstoreselector.test.tsx","./src/components/vector_store_management/vectorstoretable.test.tsx","./src/components/view_logs/configinfomessage.test.tsx","./src/components/view_logs/costbreakdownviewer.test.tsx","./src/components/view_logs/errorviewer.tsx","./src/components/view_logs/errorviewer.test.tsx","./src/components/view_logs/requestresponsepanel.tsx","./src/components/view_logs/requestresponsepanel.test.tsx","./src/components/view_logs/typebadges.test.tsx","./src/components/view_logs/index.test.tsx","./src/components/view_logs/log_filter_logic.test.tsx","./src/components/view_logs/logs_utils.test.tsx","./src/components/view_logs/time_cell.test.tsx","./src/components/view_logs/guardrailviewer/bedrockguardraildetails.test.tsx","./src/components/view_logs/guardrailviewer/guardrailviewer.test.tsx","./src/components/view_logs/guardrailviewer/presidiodetectedentities.test.tsx","./src/components/view_logs/logdetailsdrawer/collapsiblemessage.test.tsx","./src/components/view_logs/logdetailsdrawer/historytree.test.tsx","./src/components/view_logs/logdetailsdrawer/inputcard.test.tsx","./src/components/view_logs/logdetailsdrawer/logdetailcontent.test.tsx","./src/components/view_logs/logdetailsdrawer/outputcard.test.tsx","./src/components/view_logs/logdetailsdrawer/prettymessagesview.test.tsx","./src/components/view_logs/logdetailsdrawer/realtimeprettyview.test.tsx","./src/components/view_logs/logdetailsdrawer/simplemessageblock.test.tsx","./src/components/view_logs/logdetailsdrawer/simpletoolcallblock.test.tsx","./src/components/view_logs/logdetailsdrawer/truncatedvalue.test.tsx","./src/components/view_logs/toolssection/toolssection.test.tsx","./src/components/view_users/table.test.tsx","./src/components/view_users/user_info_view.test.tsx","./src/hooks/usemcpoauthflow.test.tsx","./src/hooks/policies/usedeletepolicyattachment.test.tsx","./tests/createkeypage.expiredtoken.test.tsx","./tests/top_key_view.test.tsx","./.next/dev/types/cache-life.d.ts","./.next/dev/types/validator.ts","./node_modules/@babel/types/lib/index.d.ts","./node_modules/@types/babel__traverse/index.d.ts","./node_modules/@types/d3-array/index.d.ts","./node_modules/@types/d3-color/index.d.ts","./node_modules/@types/d3-ease/index.d.ts","./node_modules/@types/d3-interpolate/index.d.ts","./node_modules/@types/d3-path/index.d.ts","./node_modules/@types/d3-time/index.d.ts","./node_modules/@types/d3-scale/index.d.ts","./node_modules/@types/d3-shape/index.d.ts","./node_modules/@types/d3-timer/index.d.ts","./node_modules/@types/ms/index.d.ts","./node_modules/@types/debug/index.d.ts","./node_modules/@types/estree-jsx/index.d.ts","./node_modules/@types/json5/index.d.ts","./node_modules/@types/scheduler/index.d.ts","./node_modules/@types/uuid/index.d.ts"],"fileIdsList":[[98,144,482,483,484,485],[98,144],[98,144,227,526,529,3111,3123,3782,3823,3913,4041,4125,4128,4129,4130,4131],[98,144,527,528,529],[98,144,714,724],[98,144,724,725,729,732,733],[98,144,714],[86,98,144,723],[98,144,725],[98,144,725,730,731],[86,98,144,714,724,725,726,727,728],[98,144,724],[98,144,684,685,686],[98,144,685,689],[98,144,685,686],[98,144,684],[84,86,98,144,685,692,700,702,714],[98,144,686,687,690,691,692,700,701,702,703,710,711,712,713],[98,144,703],[98,144,693],[98,144,693,694,695,696,697,698,699],[86,98,144,684,693,701],[98,144,704],[98,144,704,705,706],[98,144,688,689],[98,144,688,689,704,707,708,709],[98,144,688],[98,144,701],[98,144,1076],[98,144,1076,1077],[86,98,144,1137,1138,1139],[86,98,144],[86,98,144,1138],[86,98,144,1140],[98,144,1299,1300,1301,1302,1303,1304,1305,1306,1307,1308,1309,1310,1311,1312,1313,1314,1315,1316,1317,1318,1319,1320,1321,1322,1323,1324,1325,1326,1327,1328,1329,1330,1331,1332,1333,1334,1335,1336,1337,1338,1339,1340,1341,1342,1343,1344,1345,1346,1347,1348,1349,1350,1351,1352,1353,1354,1355,1356,1357,1358,1359,1360,1361,1362,1363,1364,1365,1366,1367,1368,1369,1370,1371,1372,1373,1374,1375,1376,1377,1378,1379,1380,1381,1382,1383,1384,1385,1386,1387,1388,1389,1390,1391,1392,1393,1394,1395,1396,1397,1398,1399,1400,1401,1402,1403,1404,1405,1406,1407,1408,1409,1410,1411,1412,1413,1414,1415,1416,1417,1418,1419,1420,1421,1422,1423,1424,1425,1426,1427,1428,1429,1430,1431,1432,1433,1434,1435,1436,1437,1438,1439,1440,1441,1442,1443,1444,1445,1446,1447,1448,1449,1450,1451,1452,1453,1454,1455,1456,1457,1458,1459,1460,1461,1462,1463,1464,1465,1466,1467,1468,1469,1470,1471,1472,1473,1474,1475,1476,1477,1478,1479,1480,1481,1482,1483,1484,1485,1486,1487,1488,1489,1490,1491,1492,1493,1494,1495,1496,1497,1498,1499,1500,1501,1502,1503,1504,1505,1506,1507,1508,1509,1510,1511,1512,1513,1514,1515,1516,1517,1518,1519,1520,1521,1522,1523,1524,1525,1526,1527,1528,1529,1530,1531,1532,1533,1534,1535,1536,1537,1538,1539,1540,1541,1542,1543,1544,1545,1546,1547,1548,1549,1550,1551,1552,1553,1554,1555,1556,1557,1558,1559,1560,1561,1562,1563,1564,1565,1566,1567,1568,1569,1570,1571,1572,1573,1574,1575,1576,1577,1578,1579,1580,1581,1582,1583,1584,1585,1586,1587,1588,1589,1590,1591,1592,1593,1594,1595,1596,1597,1598,1599,1600,1601,1602,1603,1604,1605,1606,1607,1608,1609,1610,1611,1612,1613,1614,1615,1616,1617,1618,1619,1620,1621,1622,1623,1624,1625,1626,1627,1628,1629,1630,1631,1632,1633,1634,1635,1636,1637,1638,1639,1640,1641,1642,1643,1644,1645,1646,1647,1648,1649,1650,1651,1652,1653,1654,1655,1656,1657,1658,1659,1660,1661,1662,1663,1664,1665,1666,1667,1668,1669,1670,1671,1672,1673,1674,1675,1676,1677,1678,1679,1680,1681,1682,1683,1684,1685,1686,1687,1688,1689,1690,1691,1692,1693,1694,1695,1696,1697,1698,1699,1700,1701,1702,1703,1704,1705,1706,1707,1708,1709,1710,1711,1712,1713,1714,1715,1716,1717,1718,1719,1720,1721,1722,1723,1724,1725,1726,1727,1728,1729,1730,1731,1732,1733,1734,1735,1736,1737,1738,1739,1740,1741,1742,1743,1744,1745,1746,1747,1748,1749,1750,1751,1752,1753,1754,1755,1756,1757,1758,1759,1760,1761,1762,1763,1764,1765,1766,1767,1768,1769,1770,1771,1772,1773,1774,1775,1776,1777,1778,1779,1780,1781,1782,1783,1784,1785,1786,1787,1788,1789,1790,1791,1792,1793,1794,1795,1796,1797,1798,1799,1800,1801,1802,1803,1804,1805,1806,1807,1808,1809,1810,1811,1812,1813,1814,1815,1816,1817,1818,1819,1820,1821,1822,1823,1824,1825,1826,1827,1828,1829,1830,1831,1832,1833,1834,1835,1836,1837,1838,1839,1840,1841,1842,1843,1844,1845,1846,1847,1848,1849,1850,1851,1852,1853,1854,1855,1856,1857,1858,1859,1860,1861,1862,1863,1864,1865,1866,1867,1868,1869,1870,1871,1872,1873,1874,1875,1876,1877,1878,1879,1880,1881,1882,1883,1884,1885,1886,1887,1888,1889,1890,1891,1892,1893,1894,1895,1896,1897,1898,1899,1900,1901,1902,1903,1904,1905,1906,1907,1908,1909,1910,1911,1912,1913,1914,1915,1916,1917,1918,1919,1920,1921,1922,1923,1924,1925,1926,1927,1928,1929,1930,1931,1932,1933,1934,1935,1936,1937,1938,1939,1940,1941,1942,1943,1944,1945,1946,1947,1948,1949,1950,1951,1952,1953,1954,1955,1956,1957,1958,1959,1960,1961,1962,1963,1964,1965,1966,1967,1968,1969,1970,1971,1972,1973,1974,1975,1976,1977,1978,1979,1980,1981,1982,1983,1984,1985,1986,1987,1988,1989,1990,1991,1992,1993,1994,1995,1996,1997,1998,1999,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022,2023,2024,2025,2026,2027,2028,2029,2030,2031,2032,2033,2034,2035,2036,2037,2038,2039,2040,2041,2042,2043,2044,2045,2046,2047,2048,2049,2050,2051,2052,2053,2054,2055,2056,2057,2058,2059,2060,2061,2062,2063,2064,2065,2066,2067,2068,2069,2070,2071,2072,2073,2074,2075,2076,2077,2078,2079,2080,2081,2082,2083,2084,2085,2086,2087,2088,2089,2090,2091,2092,2093,2094,2095,2096,2097,2098,2099,2100,2101,2102,2103,2104,2105,2106,2107,2108,2109,2110,2111,2112,2113,2114,2115,2116,2117,2118,2119,2120,2121,2122,2123,2124,2125,2126,2127,2128,2129],[86,98,144,1138,1139,2130,2131,2132],[98,144,3940,3944,3945,3948,3949,3951,3953,3954,3957,3976,4001,4002,4003,4004],[98,144,3944,3952,4005],[98,144,3950],[98,144,3948,3952,3953,4005],[98,144,4005],[98,144,3946,4005],[98,144,3955,3956],[98,144,3951],[98,144,3951,3953,3954,3957,3974,4005],[98,144,3968],[98,144,3948,3954,4005],[98,144,3940,3944,3945,3947],[98,144,177],[98,144,3940],[98,139,144,3943],[98,144,3940,3948,4005],[98,144,3948,4005],[98,144,4000,4005],[98,144,3948,3970,3978,4000,4005],[98,144,3948,3970,3973,3974,4005],[98,144,3976,4005],[98,144,3994],[98,144,3948,3979,3994,3995,3997,4006],[98,144,3996],[98,144,4004],[98,144,3993],[98,144,3948,3953,3954,3958,3963,4001],[98,144,3963,3964],[98,144,3948,3954,3958,3964,4001],[98,144,3958,3959,3960,3961,3962,3964,3967,3984,3988,3991,4000],[98,144,3948,3953,3954,3958,4001],[98,144,3948,3953,3954,3957,3958,4001],[98,144,3959,3960,3961,3962,3980,3981,3982,3986,3989,3992,4001],[98,144,3965,3966,3967],[98,144,3948,3953,3954,3958,3965,3966,4001],[98,144,3948,3953,3954,3958,3965,4001],[98,144,3948,3953,3954,3958,3969,3976,4000,4001],[98,144,3977,4000],[98,144,3947,3948,3953,3958,3976,3977,3978,3979,3998,3999,4000,4001],[98,144,3947,3948,3953,3954,3958,4001],[98,144,3983,3984,3985],[98,144,3948,3953,3954,3958,3984,4001],[98,144,3948,3953,3954,3958,3964,3983,3985,4001],[98,144,3987,3988],[98,144,3948,3953,3954,3957,3958,3987,4001],[98,144,3990,3991],[98,144,3948,3953,3954,3958,3990,4001],[98,144,3947,3948,3953,3958,3976,4001,4002],[98,144,3950,3976,4001,4002,4003],[98,144,3972],[98,144,3948,3950,3953,3954,3958,3969,3976],[98,144,3971,3976],[98,144,3947,3948,3953,3958,3971,3974,3975,3976],[98,144,3096],[98,144,2207,2208,2209,2210,2211,2212,2213,2214,2215,2216,2217,2218,2219,2220,2221,2222,2223,2224,2225,2226,2227,2228,2229,2230,2231,2232,2233,2234,2235,2236,2237,2238,2239,2240,2241,2242,2243,2244,2245,2246,2247,2248,2249,2250,2251,2252,2253,2254,2255,2256,2257,2258,2259,2260,2261,2262,2263,2264,2265,2266,2267,2268,2269,2270,2271,2272,2273,2274,2275,2276,2277,2278,2279,2280,2281,2282,2283,2284,2285,2286,2287,2288,2289,2290,2291,2292,2293,2294,2295,2296,2297,2298,2299,2300,2301,2302,2303,2304,2305,2306,2307,2308,2309,2310,2311,2312,2313,2314,2315,2316,2317,2318,2319,2320,2321,2322,2323,2324,2325,2326,2327,2328,2329,2330,2331,2332,2333,2334,2335,2336,2337,2338,2339,2340,2341,2342,2343,2344,2345,2346,2347,2348,2349,2350,2351,2352,2353,2354,2355,2356,2357,2358,2359,2360,2361,2362,2363,2364,2365,2366,2367,2368,2369,2370,2371,2372,2373,2374,2375,2376,2377,2378,2379,2380,2381,2382,2383,2384,2385,2386,2387,2388,2389,2390,2391,2392,2393,2394,2395,2396,2397,2398,2399,2400,2401,2402,2403,2404,2405,2406,2407,2408,2409,2410,2411,2412,2413,2414,2415,2416,2417,2418,2419,2420,2421,2422,2423,2424,2425,2426,2427,2428,2429,2430,2431,2432,2433,2434,2435,2436],[98,144,3357,3358,3359,3360,3361,3362,3363,3364,3365,3366,3367,3368,3369,3370,3371,3372,3373,3374,3375,3376,3377,3378,3379,3380,3381,3382,3383,3384,3385,3386,3387,3388,3389,3390,3391,3392,3393,3394,3395,3396,3397,3398,3399,3400,3401,3402,3403,3404,3405,3406,3407,3408,3409,3410,3411,3412,3413,3414,3415,3416,3417,3418,3419,3420,3421,3422,3423,3424,3425,3426,3427,3428,3429,3430,3431,3432,3433,3434,3435,3436,3437,3438,3439,3440,3441,3442,3443,3444,3445,3446,3447,3448,3449,3450,3451,3452,3453,3454,3455,3456,3457,3458,3459,3460,3461,3462,3463,3464,3465,3466,3467,3468,3469,3470,3471,3472,3473,3474,3475,3476,3477,3478,3479,3480,3481,3482,3483,3484,3485,3486,3487,3488,3489,3490,3491,3492,3493,3494,3495,3496,3497,3498,3499,3500,3501,3502,3503,3504,3505,3506,3507,3508,3509,3510,3511,3512,3513,3514,3515,3516,3517,3518,3519,3520,3521,3522,3523,3524,3525,3526,3527,3528,3529,3530,3531,3532,3533,3534,3535,3536,3537,3538,3539,3540,3541,3542,3543,3544,3545,3546,3547,3548,3549,3550,3551,3552,3553,3554,3555,3556,3557,3558,3559,3560,3561,3562,3563,3564,3565,3566,3567,3568,3569,3570,3571,3572,3573,3574,3575,3576,3577,3578,3579,3580,3581,3582,3583,3584,3585,3586],[98,144,1078,1080],[86,98,144,1080,1082],[86,98,144,1079,1080],[86,98,144,1081],[98,144,1079,1080,1081,1083,1084],[98,144,1079],[98,144,984],[98,144,987,988],[98,144,984,985,986],[98,144,955,956],[98,144,1122,1123,1124,1125],[86,98,144,1121],[86,98,144,1122],[98,144,1122],[98,144,907],[98,144,905,906],[86,98,144,655,902,903,904],[98,144,655],[86,98,144,905],[86,98,144,653,654],[86,98,144,653],[98,144,2586],[98,144,2173],[98,144,2587,2588,2589,2590,2591],[98,144,2586,2587],[98,144,2587],[86,98,144,227,2174],[98,144,2175],[86,98,144,2754],[98,144,2735],[98,144,2720,2743],[98,144,2743],[98,144,2743,2754],[98,144,2729,2743,2754],[98,144,2734,2743,2754],[98,144,2724,2743],[98,144,2732,2743,2754],[98,144,2730],[98,144,2720,2721,2722,2723,2724,2725,2726,2727,2728,2729,2730,2731,2732,2733,2734,2735,2736,2737,2738,2739,2740,2741,2742,2743,2744,2745,2746,2747,2748,2749,2750,2751,2752,2753],[98,144,2733],[98,144,2720,2721,2722,2723,2724,2725,2726,2727,2728,2730,2731,2733,2735,2736,2737,2738,2739,2740,2741,2742],[98,144,2148],[98,144,2145,2146,2147,2148,2149,2152,2153,2154,2155,2156,2157,2158,2159],[98,144,2144],[98,144,2151],[98,144,2145,2146,2147],[98,144,2145,2146],[98,144,2148,2149,2151],[98,144,2146],[98,144,3102],[98,144,3101],[86,98,144,196,459,2160,2161],[98,144,3905],[98,144,3892,3893,3894],[98,144,3887,3888,3889],[98,144,3865,3866,3867,3868],[98,144,3831,3905],[98,144,3831],[98,144,3831,3832,3833,3834,3879],[98,144,3869],[98,144,3864,3870,3871,3872,3873,3874,3875,3876,3877,3878],[98,144,3879],[98,144,3830],[98,144,3883,3885,3886,3904,3905],[98,144,3883,3885],[98,144,3880,3883,3905],[98,144,3890,3891,3895,3896,3901],[98,144,3884,3886,3896,3904],[98,144,3903,3904],[98,144,3880,3884,3886,3902,3903],[98,144,3884,3905],[98,144,3882],[98,144,3882,3884,3905],[98,144,3880,3881],[98,144,3897,3898,3899,3900],[98,144,3886,3905],[98,144,3841],[98,144,3835,3842],[98,144,3835,3836,3837,3838,3839,3840,3841,3842,3843,3844,3845,3846,3847,3848,3849,3850,3851,3852,3853,3854,3855,3856,3857,3858,3859,3860,3861,3862,3863],[98,144,3861,3905],[86,98,144,1198,1297],[98,144,4412],[98,144,596,597],[98,144,4415],[98,144,4419],[98,144,4418],[98,144,4423],[98,144,545,546,4425],[98,144,3666],[98,144,2548,2550,2551,2552,2553,2554,2555,2556,2557,2558,2559,2560],[98,144,2548,2549,2551,2552,2553,2554,2555,2556,2557,2558,2559,2560],[98,144,2549,2550,2551,2552,2553,2554,2555,2556,2557,2558,2559,2560],[98,144,2548,2549,2550,2552,2553,2554,2555,2556,2557,2558,2559,2560],[98,144,2548,2549,2550,2551,2553,2554,2555,2556,2557,2558,2559,2560],[98,144,2548,2549,2550,2551,2552,2554,2555,2556,2557,2558,2559,2560],[98,144,2548,2549,2550,2551,2552,2553,2555,2556,2557,2558,2559,2560],[98,144,2548,2549,2550,2551,2552,2553,2554,2556,2557,2558,2559,2560],[98,144,2548,2549,2550,2551,2552,2553,2554,2555,2557,2558,2559,2560],[98,144,2548,2549,2550,2551,2552,2553,2554,2555,2556,2558,2559,2560],[98,144,2548,2549,2550,2551,2552,2553,2554,2555,2556,2557,2559,2560],[98,144,2548,2549,2550,2551,2552,2553,2554,2555,2556,2557,2558,2560],[98,144,2560],[98,144,2548,2549,2550,2551,2552,2553,2554,2555,2556,2557,2558,2559],[98,144,158,185,192,3941,3942],[98,141,144],[98,143,144],[144],[98,144,149,177],[98,144,145,150,155,163,174,185],[98,144,145,146,155,163],[93,94,95,98,144],[98,144,147,186],[98,144,148,149,156,164],[98,144,149,174,182],[98,144,150,152,155,163],[98,143,144,151],[98,144,152,153],[98,144,154,155],[98,143,144,155],[98,144,155,156,157,174,185],[98,144,155,156,157,170,174,177],[98,144,152,155,158,163,174,185],[98,144,155,156,158,159,163,174,182,185],[98,144,158,160,174,182,185],[96,97,98,99,100,101,102,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191],[98,144,155,161],[98,144,162,185,190],[98,144,152,155,163,174],[98,144,164],[98,144,165],[98,143,144,166],[98,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191],[98,144,168],[98,144,169],[98,144,155,170,171],[98,144,170,172,186,188],[98,144,155,174,175,177],[98,144,176,177],[98,144,174,175],[98,144,178],[98,141,144,174,179],[98,144,155,180,181],[98,144,180,181],[98,144,149,163,174,182],[98,144,183],[98,144,163,184],[98,144,158,169,185],[98,144,149,186],[98,144,174,187],[98,144,162,188],[98,144,189],[98,139,144],[98,139,144,155,157,166,174,177,185,188,190],[98,144,174,191],[98,144,174,192],[86,98,144,195,196,197,459],[86,98,144,195,196],[86,98,144,196,459],[86,98,144,2161],[86,98,144,2205],[86,90,98,144,194,477,522],[86,90,98,144,193,477,522],[83,84,85,98,144],[98,144,532,537,538,540],[98,144,583,584],[98,144,538,540,577,578,579],[98,144,538],[98,144,538,540,577],[98,144,538,577],[98,144,590],[98,144,533,590,591],[98,144,533,590],[98,144,533,539],[98,144,534],[98,144,533,534,535,537],[98,144,533],[98,144,819],[98,144,623,624,625,626,627,628,629,630],[86,98,144,621,622],[98,144,612],[98,144,653],[98,144,655,770],[98,144,827],[98,144,742],[98,144,724,742],[86,98,144,613],[86,98,144,631],[98,144,632,633],[86,98,144,742],[86,98,144,614,635],[98,144,635,636],[86,98,144,612,1055],[86,98,144,638,1005,1054],[98,144,1056,1057],[98,144,1055],[86,98,144,828,853,855],[86,98,144,612,850,1059],[86,98,144,1061],[86,98,144,611],[86,98,144,1007,1061],[98,144,1062,1063],[86,98,144,612,742,820,922,923],[86,98,144,612,820],[86,98,144,612,896,1066],[86,98,144,894],[98,144,1066,1067],[86,98,144,639],[86,98,144,639,640,641],[86,98,144,642],[98,144,639,640,641,642],[98,144,752],[86,98,144,612,647,656,1070],[86,98,144,831,1071],[98,144,1069],[98,144,714,742,759],[86,98,144,930,934],[98,144,935,936,937],[86,98,144,1073],[86,98,144,612,639,828,854,942,943,1051],[86,98,144,939,944],[86,98,144,873],[86,98,144,874,875],[86,98,144,876],[98,144,873,874,876],[98,144,714,742],[98,144,994],[86,98,144,639,947,948],[98,144,948,949],[98,144,1078,1087],[86,98,144,612,1087],[98,144,1086,1087,1088],[86,98,144,639,824,1007,1085,1086],[86,98,144,634,643,680,819,824,832,834,836,855,857,893,897,899,908,914,920,921,924,934,938,944,950,951,954,964,965,966,983,992,997,1001,1004,1005,1007,1015,1019,1023,1025,1041,1047,1048],[98,144,639],[86,98,144,639,643,920,1048,1049,1050],[86,98,144,612,647,661,828,833,834,1051],[98,144,612,639,656,661,828,832,1051],[86,98,144,612,661,828,831,833,834,835,1051],[98,144,835],[98,144,757,758],[98,144,714,742,757],[98,144,742,754,755,756],[86,98,144,611,952,953],[86,98,144,631,962],[86,98,144,961,962,963],[86,98,144,640,834,894],[86,98,144,655,822,885,893],[98,144,894,895],[86,98,144,742,756,770],[86,98,144,612,965],[86,98,144,612,639],[86,98,144,966],[86,98,144,966,1092,1093,1094],[98,144,1095],[86,98,144,824,834,924],[86,98,144,646,675,678,680,827,1097],[86,98,144,827],[86,98,144,639,646,673,674,675,678,679,827,1051],[86,98,144,662,680,681,825,826],[86,98,144,675,827],[86,98,144,675,678,824],[86,98,144,646],[98,144,673,678],[98,144,679],[98,144,646,680,827,1098,1099,1100,1101],[98,144,646,677],[86,98,144,611,612],[98,144,675,993,1190],[86,98,144,1108,1109],[86,98,144,1106],[98,144,611,612,614,634,637,824,832,834,836,855,857,877,893,896,897,899,908,914,917,924,934,938,943,944,950,951,954,964,965,966,983,992,994,997,1001,1004,1007,1015,1019,1023,1025,1040,1041,1047,1051,1058,1060,1064,1065,1068,1072,1074,1075,1089,1090,1091,1096,1102,1110,1112,1117,1120,1127,1128,1133,1136,1141,1142,1144,1154,1159,1164,1169,1171,1173,1176,1178,1185,1187,1188,1189],[86,98,144,639,828,991,1051],[98,144,778],[98,144,742,754],[98,144,967,974,975,976,977,982],[86,98,144,639,828,968,973,1051],[86,98,144,639,828,1051],[86,98,144,974],[98,144,714,742,754],[86,98,144,639,828,974,981,1051],[98,144,887,1111],[86,98,144,997],[86,98,144,897,899,994,995,996],[86,98,144,646,835,836,856,858,901,908,914,918,919,1052],[98,144,920],[86,98,144,612,828,998,1000,1051],[86,98,144,885,886,888,889,890,891,892],[98,144,878],[86,98,144,885,886,887,888],[86,98,144,1051],[86,98,144,885],[86,98,144,886],[86,98,144,638,1115,1116],[86,98,144,638,1114],[86,98,144,638],[98,144,1052],[98,144,1002,1003,1052,1053,1054],[86,98,144,611,621,642,1051],[86,98,144,1052],[86,98,144,620,1052],[86,98,144,1053],[86,98,144,1005,1118,1119],[86,98,144,1005,1114],[86,98,144,1005],[98,144,856],[86,98,144,840,855],[86,98,144,642,821,824,858],[86,98,144,857],[86,98,144,821,824,1006],[86,98,144,1007],[98,144,742,756,770],[98,144,916],[86,98,144,1127],[86,98,144,920,1126],[86,98,144,1129],[98,144,1129,1130,1131,1132],[86,98,144,639,873,874,876],[86,98,144,874,1129],[86,98,144,1135],[86,98,144,639,1143],[86,98,144,612,639,828,850,851,853,854,1051],[98,144,755],[86,98,144,1145],[98,144,1153],[86,98,144,1146,1147,1148,1149,1150,1151,1152],[86,98,144,612,824,1012,1014],[86,98,144,639,1051],[86,98,144,639,1016,1017,1018],[98,144,1156,1157,1158],[98,144,1155],[86,98,144,1156],[86,98,144,1160,1161],[98,144,1161,1162,1163],[86,98,144,622,1160],[86,98,144,1167,1168],[98,144,714,742,756],[98,144,714,742,819],[86,98,144,1170],[98,144,612,901],[86,98,144,612,901,1020],[98,144,872,900,901,1020,1022],[86,98,144,611,612,824,861,872,877,896,897,898,900],[98,144,612,639,872,899,901],[98,144,872,898,901,1020,1021],[86,98,144,639,925,930,932,933],[86,98,144,927,934],[86,98,144,612,631,820,1024],[86,98,144,714,736,819],[86,98,144,714,737,819,1172,1190],[86,98,144,721],[98,144,743,744,745,746,747,748,749,750,751,753,759,760,761,762,763,764,765,766,767,768,769,771,772,773,774,775,776,777,779,780,781,782,783,784,785,786,787,788,789,790,791,792,793,794,795,796,797,798,799,800,801,802,803,804,805,806,807,808,809,810,811,812,813,814,815,816],[98,144,722,734,817],[98,144,612,714,715,716,721,722,817,818],[98,144,715,716,717,718,719,720],[98,144,715],[98,144,714,734,735,737,738,739,740,741,819],[98,144,714,737,819],[98,144,724,729,734,819],[98,144,1051],[86,98,144,612,661,828,831,833],[98,144,1174,1175],[86,98,144,1174],[86,98,144,612],[86,98,144,612,682,683,820,821,822,823],[86,98,144,824],[86,98,144,908,1177],[86,98,144,907],[86,98,144,908],[86,98,144,828,909,911,912,913],[86,98,144,909,910,914],[86,98,144,909,911,914],[86,98,144,612,639,828,853,854,1031,1035,1038,1040,1051],[98,144,742,812],[86,98,144,1026,1037,1038],[98,144,1026,1037,1038,1039],[86,98,144,1026,1037],[86,98,144,824,981,1179],[98,144,1179,1181,1182,1183,1184],[86,98,144,1180],[86,98,144,918,1045],[98,144,918,1045,1046],[86,98,144,915,917],[86,98,144,918,1044],[98,144,1186],[98,144,1200],[98,144,1200,1201],[98,144,1201],[98,144,1200,2888,2889],[98,144,2891],[98,144,2892],[98,144,2909],[98,144,1200,2825,2826,2827,2828,2829,2830,2831,2832,2833,2834,2835,2836,2837,2838,2839,2840,2841,2842,2843,2844,2845,2846,2847,2848,2849,2850,2851,2852,2853,2854,2855,2856,2857,2858,2859,2860,2861,2862,2863,2864,2865,2866,2867,2868,2869,2870,2871,2872,2873,2874,2875,2876,2877,2878,2879,2880,2881,2882,2883,2884,2885,2886,2887,2890,2891,2892,2893,2894,2895,2896,2897,2898,2899,2900,2901,2902,2903,2904,2905,2906,2907,2908,2910,2911,2912,2913,2914,2915,2916,2917,2918,2919,2920,2921,2922,2923,2924,2925,2926,2927,2928,2929,2930,2931,2932,2933,2934,2935,2936,2937,2938,2939,2940,2941,2942,2943,2944,2945,2946,2947,2948,2949,2950,2951,2952,2953,2954,2955,2956,2957,2958,2959,2960,2961,2962,2963,2964,2965,2966,2967,2968,2969,2970,2971,2972,2973,2974,2975,2976,2977,2978,2979,2980,2981,2982,2983,2984,2986,2987,2988,2989,2990,2991,2992,2993,2994,2995,2996,2997,2998,2999,3000,3001,3002,3003,3004,3005,3010,3011,3012,3013,3014,3015,3016,3017,3018,3019,3020,3021,3022,3023,3024,3025,3026,3027,3028,3029,3030,3031,3032,3033,3034,3035,3036,3037,3038,3039,3040,3041,3042,3043,3044,3045,3046,3047,3048,3049,3050,3051,3052,3053,3054,3055,3056,3057,3058,3059,3060,3061,3062,3063,3064,3065,3066,3067,3068,3069,3070,3071,3072,3073,3074,3075,3076,3077],[98,144,2985],[98,144,1201,1202,1203,1204,1205,1206,1207,1208,1209,1210,1211,1212,1213,1214,1215,1216,1217,1218,1219,1220,1221,1222,1223,1224,1225,1226,1227,1228,1229,1230,1231,1232,1233,1234,1235,1236,1237,1238,1239,1240,1241,1242,1243,1244,1245,1246,1247,1248,1249,1250,1251,1252,1253,1254,1255,1256,1257,1258,1259,1260,1261,1262,1263,1264,1265,1266,1267,1268,1269,1270,1271,1272,1273,1274,1275,1276,1277,1278,1279,1280,1281,1282,1283,1284,1285,1286,1287,1288,1289,1290,1291,1292,1293,1294,1295,1296],[98,144,1200,2889,3009],[98,144,1201,3006,3007],[98,144,3008],[98,144,3006],[98,144,1199,1201],[98,144,830],[98,144,829],[98,144,545,546,3097,3098,4425],[98,144,3099],[98,144,2167,2168],[98,144,2167,2168,2169,2170],[98,144,2167,2169],[98,144,2167],[98,144,158,174,192],[98,144,4076,4079,4082,4084,4085,4086],[98,144,3677,3705,4076,4079,4082,4084,4086],[98,144,3677,3705,4076,4079,4082,4086],[98,144,4109,4110,4114],[98,144,4086,4109,4111,4114],[98,144,4086,4109,4111,4113],[98,144,3677,3705,4086,4109,4111,4112,4114],[98,144,4111,4114,4115],[98,144,4086,4109,4111,4114,4116],[98,144,3667,3677,3678,3679,3703,3704,3705],[98,144,3667,3678,3705],[98,144,3667,3677,3678,3705],[98,144,3680,3681,3682,3683,3684,3685,3686,3687,3688,3689,3690,3691,3692,3693,3694,3695,3696,3697,3698,3699,3700,3701,3702],[98,144,3667,3671,3677,3679,3705],[98,144,4087,4088,4108],[98,144,3677,3705,4109,4111,4114],[98,144,3677,3705],[98,144,4089,4090,4091,4092,4093,4094,4095,4096,4097,4098,4099,4100,4101,4102,4103,4104,4105,4106,4107],[98,144,3666,3677,3705],[98,144,4076,4077,4078,4082,4086],[98,144,4076,4079,4082,4086],[98,144,4076,4079,4080,4081,4086],[98,144,480],[98,144,430,491,492],[98,144,202,203,205,217,241,356,367,473],[98,144,205,236,237,238,240,473],[98,144,205,373,375,377,378,380,473,475],[98,144,205,239,276,473],[98,144,203,205,216,217,223,229,234,355,356,357,366,473,475],[98,144,473],[98,144,212,218,237,257,352],[98,144,205],[98,144,198,212,218],[98,144,384],[98,144,381,382,384],[98,144,381,383,473],[98,144,158,257,454,470],[98,144,158,328,331,347,352,470],[98,144,158,300,470],[98,144,360],[98,144,359,360,361],[98,144,359],[92,98,144,158,198,205,217,223,229,235,237,241,242,255,256,323,353,354,367,473,477],[98,144,202,205,239,276,373,374,379,473,525],[98,144,239,525],[98,144,202,256,425,473,525],[98,144,525],[98,144,205,239,240,525],[98,144,376,525],[98,144,242,355,358,365],[86,98,144,430],[98,144,169,212,227],[98,144,212,227],[86,98,144,297],[86,98,144,227],[86,98,144,218,227,430],[98,144,212,283,297,298,507,514],[98,144,282,508,509,510,511,513],[98,144,333],[98,144,333,334],[98,144,216,218,285,286],[98,144,218,292,293],[98,144,218,287,295],[98,144,292],[98,144,210,218,285,286,287,288,289,290,291,292,295],[98,144,218,285,292,293,294,296],[98,144,218,286,288,289],[98,144,286,288,291,293],[98,144,512],[98,144,218],[86,98,144,206,501],[86,98,144,185],[86,98,144,239,274],[86,98,144,239,367],[98,144,272,277],[86,98,144,273,479],[98,144,3105],[86,90,98,144,158,193,194,477,521],[98,144,158,218],[98,144,158,217,222,303,320,362,363,367,422,424,473,474],[98,144,255,364],[98,144,477],[98,144,204],[86,98,144,209,212,427,443,445],[98,144,169,212,427,442,443,444,524],[98,144,436,437,438,439,440,441],[98,144,438],[98,144,442],[98,144,227,391,392,394],[86,98,144,218,385,386,387,388,393],[98,144,391,393],[98,144,389],[98,144,390],[86,98,144,227,273,479],[86,98,144,227,478,479],[86,98,144,227,479],[98,144,320,321],[98,144,321],[98,144,158,474,479],[98,144,350],[98,143,144,349],[98,144,212,218,224,226,328,341,345,347,424,427,462,463,470,474],[98,144,218,267,289],[98,144,328,339,342,347],[86,98,144,209,212,328,331,347,350,384,431,432,433,434,435,446,447,448,449,450,451,452,453,525],[98,144,209,212,237,328,335,336,337,340,341],[98,144,174,218,237,339,346,427,428,470],[98,144,343],[98,144,158,169,206,218,222,232,264,265,268,320,323,388,422,423,462,473,474,475,477,525],[98,144,209,210,212],[98,144,328],[98,143,144,237,264,265,322,323,324,325,326,327,474],[98,144,347],[98,143,144,211,212,222,226,262,328,335,336,337,338,339,342,343,344,345,346,463],[98,144,158,262,263,335,474,475],[98,144,237,265,320,323,328,424,474],[98,144,158,473,475],[98,144,158,174,470,474,475],[98,144,158,169,198,212,217,224,226,229,232,239,259,264,265,266,267,268,303,304,306,309,311,314,315,316,317,319,367,422,424,470,473,474,475],[98,144,158,174],[98,144,205,206,207,235,470,471,472,477,479,525],[98,144,202,203,473],[98,144,396],[98,144,158,174,185,214,380,384,385,386,387,388,394,395,525],[98,144,169,185,198,212,214,226,229,265,304,309,319,320,373,400,401,402,408,411,412,422,424,470,473],[98,144,229,235,242,255,265,323,473],[98,144,158,185,206,217,226,265,406,470,473],[98,144,426],[98,144,158,396,409,410,419],[98,144,470,473],[98,144,325,463],[98,144,226,264,367,479],[98,144,158,169,204,309,369,373,402,408,411,414,470],[98,144,158,242,255,373,415],[98,144,205,266,367,417,473,475],[98,144,158,185,388,473],[98,144,158,239,266,367,368,369,378,396,416,418,473],[92,98,144,158,264,421,477,479],[98,144,318,422],[98,144,158,169,212,215,217,218,224,226,232,241,242,255,265,268,304,306,316,319,320,367,400,401,402,403,405,407,422,424,470,479],[98,144,158,174,242,408,413,419,470],[98,144,245,246,247,248,249,250,251,252,253,254],[98,144,259,310],[98,144,312],[98,144,310],[98,144,312,313],[98,144,158,216,217,218,222,223,474],[98,144,158,169,204,206,224,228,264,267,268,302,422,470,475,477,479],[98,144,158,169,185,208,215,216,226,228,265,420,463,469,474],[98,144,335],[98,144,336],[98,144,218,229,462],[98,144,337],[98,144,211],[98,144,213,225],[98,144,158,213,217,224],[98,144,220,225],[98,144,221],[98,144,213,214],[98,144,213,269],[98,144,213],[98,144,215,259,308],[98,144,307],[98,144,212,214,215],[98,144,215,305],[98,144,212,214],[98,144,264,367],[98,144,462],[98,144,158,185,224,226,230,264,367,421,424,427,428,429,455,456,458,461,463,470,474],[98,144,278,281,283,284,297,298],[86,98,144,195,196,197,227,457],[86,98,144,195,196,197,227,457,460],[98,144,351],[98,144,237,258,263,264,328,329,330,331,332,334,347,348,350,353,421,424,473,475],[98,144,297],[98,144,158,302,470],[98,144,302],[98,144,158,224,270,299,301,303,421,470,477,479],[98,144,278,279,280,281,283,284,297,298,478],[92,98,144,158,169,185,213,214,226,232,264,265,268,367,419,420,422,470,473,474,477],[98,144,209,212,219],[98,144,263,265,397,400],[98,144,263,398,464,465,466,467,468],[98,144,158,259,473],[98,144,158],[98,144,262,347],[98,144,261],[98,144,263,316],[98,144,260,262,473],[98,144,158,208,263,397,398,399,470,473,474],[86,98,144,212,218,296],[86,98,144,210],[98,144,200,201],[86,98,144,206],[86,98,144,212,282],[86,92,98,144,264,268,477,479],[98,144,206,501,502],[86,98,144,277],[86,98,144,169,185,204,271,273,275,276,479],[98,144,212,239,474],[98,144,212,404],[86,98,144,156,158,169,202,204,277,375,477,478],[86,98,144,193,194,477,522],[86,87,88,89,90,98,144],[98,144,149],[98,144,370,371,372],[98,144,370],[86,90,98,144,158,160,169,192,193,194,195,197,198,204,232,237,414,442,475,476,479,522],[98,144,487],[98,144,489],[98,144,493],[98,144,3106],[98,144,495],[98,144,497,498,499],[98,144,503],[91,98,144,481,486,488,490,494,496,500,504,506,516,517,519,523,524,525,526],[98,144,505],[98,144,515],[98,144,273],[98,144,518],[98,143,144,263,397,398,400,464,465,467,468,520,522],[98,144,192],[98,144,3236,3237,3242],[98,144,3238,3239,3241,3243],[98,144,3242],[98,144,3239,3241,3242,3243,3244,3246,3248,3249,3250,3251,3252,3253,3254,3258,3273,3284,3287,3291,3299,3300,3302,3305,3308,3311],[98,144,3242,3249,3262,3266,3275,3277,3278,3279,3306],[98,144,3242,3243,3259,3260,3261,3262,3264,3265],[98,144,3266,3267,3274,3277,3306],[98,144,3242,3243,3248,3267,3279,3306],[98,144,3243,3266,3267,3268,3274,3277,3306],[98,144,3239],[98,144,3245,3266,3273,3279],[98,144,3273],[98,144,3242,3262,3269,3271,3273,3306],[98,144,3266,3273,3274],[98,144,3275,3276,3278],[98,144,3306],[98,144,3255,3256,3257,3307],[98,144,3242,3243,3307],[98,144,3238,3242,3256,3258,3307],[98,144,3242,3256,3258,3307],[98,144,3242,3244,3245,3246,3307],[98,144,3242,3244,3245,3259,3260,3261,3263,3264,3307],[98,144,3264,3265,3280,3283,3307],[98,144,3279,3307],[98,144,3242,3266,3267,3268,3274,3275,3277,3278,3307],[98,144,3245,3281,3282,3283,3307],[98,144,3242,3307],[98,144,3242,3244,3245,3265,3307],[98,144,3238,3242,3244,3245,3259,3260,3261,3263,3264,3265,3307],[98,144,3242,3244,3245,3260,3307],[98,144,3238,3242,3245,3259,3261,3263,3264,3265,3307],[98,144,3245,3248,3307],[98,144,3248],[98,144,3238,3242,3244,3245,3247,3248,3249,3307],[98,144,3247,3248],[98,144,3242,3244,3248,3307],[98,144,3308,3309],[98,144,3238,3242,3248,3249,3307],[98,144,3242,3244,3286,3307],[98,144,3242,3244,3285,3307],[98,144,3242,3244,3245,3273,3288,3290,3307],[98,144,3242,3244,3290,3307],[98,144,3242,3244,3245,3273,3289,3307],[98,144,3242,3243,3244,3307],[98,144,3293,3307],[98,144,3242,3288,3307],[98,144,3295,3307],[98,144,3242,3244,3307],[98,144,3292,3294,3296,3298,3307],[98,144,3242,3244,3292,3297,3307],[98,144,3288,3307],[98,144,3273,3307],[98,144,3245,3246,3249,3250,3251,3252,3253,3254,3258,3273,3284,3287,3291,3299,3300,3302,3305,3310],[98,144,3242,3244,3273,3307],[98,144,3238,3242,3244,3245,3269,3270,3272,3273,3307],[98,144,3242,3251,3301,3307],[98,144,3242,3244,3303,3305,3307],[98,144,3242,3244,3305,3307],[98,144,3242,3244,3245,3303,3304,3307],[98,144,3243],[98,144,3240,3242,3243],[98,144,567],[98,144,565,567],[98,144,556,564,565,566,568,570],[98,144,554],[98,144,557,562,567,570],[98,144,553,570],[98,144,557,558,561,562,563,570],[98,144,557,558,559,561,562,570],[98,144,554,555,556,557,558,562,563,564,566,567,568,570],[98,144,570],[98,144,552,554,555,556,557,558,559,561,562,563,564,565,566,567,568,569],[98,144,552,570],[98,144,557,559,560,562,563,570],[98,144,561,570],[98,144,562,563,567,570],[98,144,555,565],[98,144,2150],[86,98,144,654,848,853,939,940],[98,144,939,941],[86,98,144,941],[98,144,941],[86,98,144,945],[86,98,144,945,946],[86,98,144,618],[86,98,144,617],[98,144,618,619,620],[86,98,144,957,958,959,960],[86,98,144,653,958,959],[98,144,961],[86,98,144,654,655,928],[86,98,144,665],[86,98,144,664,665,666,667,668,669,670,671,672],[86,98,144,663,664],[98,144,665],[86,98,144,644,645],[98,144,646],[86,98,144,617,618,1103,1104,1106],[98,144,1107],[86,98,144,621,1103,1107],[86,98,144,1103,1104,1105,1107],[98,144,990],[86,98,144,968,970,989],[86,98,144,970],[98,144,970,971,972],[86,98,144,968,969],[86,98,144,970,981,998,999],[98,144,998,1000],[86,98,144,878],[98,144,878,879,880,881,882,883,884],[86,98,144,653,878],[86,98,144,648],[86,98,144,649,650],[98,144,648,649,651,652],[86,98,144,1113],[98,144,838,839],[86,98,144,837],[86,98,144,838],[98,144,656,658,659,660],[86,98,144,647,655],[86,98,144,656,657],[86,98,144,656],[86,98,144,1134],[86,98,144,654,846,847],[86,98,144,848],[98,144,848,849,850,851,852],[86,98,144,851],[86,98,144,847,848,849,850],[86,98,144,1008],[86,98,144,1008,1009],[98,144,1012,1013],[86,98,144,1008,1010,1011],[98,144,1166,1167],[86,98,144,1165,1167],[86,98,144,1165,1166],[86,98,144,861],[86,98,144,861,864],[86,98,144,862,863],[98,144,859,861,865,866,867,869,870,871],[86,98,144,860],[98,144,861],[86,98,144,861,866],[86,98,144,859,861,865,866,867,868],[86,98,144,861,868,869],[86,98,144,930],[98,144,931],[86,98,144,653,926,927,929],[86,98,144,925,930],[98,144,978,979,980],[86,98,144,970,973,978],[86,98,144,654,655],[98,144,1032,1033,1034],[86,98,144,1026],[86,98,144,1031],[86,98,144,853,1026,1030,1031,1032,1033],[98,144,1026,1031],[86,98,144,1026,1030],[98,144,1026,1027,1030,1036],[86,98,144,846],[86,98,144,1026,1027,1028,1029],[86,98,144,915],[98,144,915,1043],[86,98,144,915,1042],[86,98,144,615,616],[86,98,144,842,843],[86,98,144,841,842,844,845],[86,98,144,2779],[86,98,144,2778],[98,144,3708],[86,98,144,3667,3676,3705,3707],[98,144,4083,4116,4117],[98,144,4118],[98,144,3705,3706],[98,144,3667,3671,3676,3677,3705],[98,144,546,575,576],[98,144,676],[98,144,536],[98,144,3673],[98,111,115,144,185],[98,111,144,174,185],[98,106,144],[98,108,111,144,182,185],[98,144,163,182],[98,106,144,192],[98,108,111,144,163,185],[98,103,104,107,110,144,155,174,185],[98,111,118,144],[98,103,109,144],[98,111,132,133,144],[98,107,111,144,177,185,192],[98,132,144,192],[98,105,106,144,192],[98,111,144],[98,105,106,107,108,109,110,111,112,113,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,133,134,135,136,137,138,144],[98,111,126,144],[98,111,118,119,144],[98,109,111,119,120,144],[98,110,144],[98,103,106,111,144],[98,111,115,119,120,144],[98,115,144],[98,109,111,114,144,185],[98,103,108,111,118,144],[98,144,174],[98,106,111,132,144,190,192],[98,144,3671,3675],[98,144,3666,3671,3672,3674,3676],[98,144,3920,3921,3922,3923,3924,3925,3926,3928,3929,3930,3931,3932,3933,3934,3935],[98,144,3922],[98,144,3922,3927],[98,144,3668],[98,144,3669,3670],[98,144,3666,3669,3671],[98,144,587,588],[98,144,587],[98,144,542],[98,144,155,156,158,159,160,163,174,182,185,191,192,542,543,544,546,547,549,550,551,571,572,573,574,575,576],[98,144,542,543,544,548],[98,144,544],[98,144,546,576],[98,144,541,607,2164],[98,144,580,599,600,2164],[98,144,533,540,580,592,593,2164],[98,144,602],[98,144,581],[98,144,533,541,580,582,592,601,2164],[98,144,585],[98,144,147,156,174,533,538,540,576,580,582,585,586,589,592,594,595,598,601,603,604,606,2164],[98,144,580,599,600,601,2164],[98,144,576,605,606],[98,144,580,582,589,592,594,2164],[98,144,190,595],[98,144,147,156,174,533,538,540,576,580,581,582,585,586,589,592,593,594,595,598,599,600,601,602,603,604,605,606,2164],[98,144,147,156,174,190,532,533,538,540,541,576,580,581,582,585,586,589,592,593,594,595,598,599,600,601,602,603,604,605,606,2163,2164,2165,2166,2171],[98,144,227,2162,2172,2195,2196,3826,3906,3907],[86,98,144,227,1190,2196,2580,3184,3825],[98,144,227,1190,2202,2483,2580,3171],[86,98,144,227,1190,1191,2198,3824],[86,98,144,227,1190,1191,2195,2200,3824],[98,144,227,2172,2195,3828,3906,3907],[86,98,144,227,1190,2133,2143,2180,2183,2195,2199,2206,2442,2580,2755,2756,3124,3150,3826,3827,3828],[98,144,227],[98,144,227,2183,3828],[98,144,227,2162,2172,3911],[86,98,144,227,1298,2638,3910],[86,98,144,227,2205,2580],[86,98,144,227,2580],[98,144,227,2183,2509,3911],[86,98,144,227,2141,2183,2620],[98,144,227,2141,2176,2180,2183,2195],[86,98,144,227,2141,2162,2172,2176,2183,2195],[98,144,227,2141,2176,2180,2181,2183],[98,144,227,2141,2176,2183,2195],[86,98,144,227,2141,2162,2172,2176,2201,2202],[98,144,227,2141,2176,2180,2181,2183,2201],[98,144,227,2141,2176],[98,144,227,2141,2176,2181,2183,2446],[86,98,144,227,2162,2172,2176,2448],[86,98,144,227,2162,2172,2176,2450],[86,98,144,227,2162,2172,2176,2452],[86,98,144,227,2162,2172,2176,2454,2455],[98,144,227,2141,2176,2181,2454],[98,144,227,2172,2181],[98,144,227,2141],[98,144,227,2176,2458,2459],[98,144,227,2176,2181,2183,2458],[86,98,144,227,2141,2162,2172,2176,2462],[98,144,227,2141,2176,2181,2183],[86,98,144,227,2141,2162,2172,2176,2464],[86,98,144,227,2141,2162,2172,2176,2466],[98,144,227,2141,2176,2181],[86,98,144,227,2141,2162,2172,2176,2470],[86,98,144,227,1195,2162,2172,2176,2472],[98,144,227,1195,2141,2176,2181,2183],[98,144,227,2141,2176,2183,2472],[98,144,227,2141,2176,2183],[86,98,144,227,2141,2162,2172,2176,2183,2479],[86,98,144,227,2141,2162,2172,2176,2183,2481],[86,98,144,227,2141,2176,2181,2183],[86,98,144,227,2141,2162,2172,2176,2183,2483],[98,144,227,2138,2141,2176,2181,2183],[86,98,144,227,2141,2162,2172,2176,2486],[86,98,144,227,2141,2162,2172,2176,2488],[86,98,144,227,2141,2162,2172,2176,2490],[98,144,227,2141,2176,2181,2182],[86,98,144,227,2141,2162,2172,2176,2492],[86,98,144,227,2162,2172,2176,2494,2495],[98,144,227,2141,2176,2183,2494],[86,98,144,227,2162,2172,2176,2494,2497],[86,98,144,227,2162,2172,2176,2494,2499],[98,144,227,2141,2176,2180,2183,2494],[86,98,144,227,2162,2172,2176,2494],[86,98,144,227,2162,2172,2176,2494,2502],[86,98,144,227,2141,2162,2172,2176,2504],[86,98,144,227,2162,2172,2176,2506],[98,144,227,2176,2181,2508],[86,98,144,227,2162,2172,2176,2510],[98,144,227,2141,2176,2181,2183,2512],[86,98,144,227,2141,2162,2172,2176,2514],[86,98,144,227,2141,2162,2172,2176,2516],[86,98,144,227,2162,2172,2176,2183,2518],[98,144,227,2141,2176,2183,2506],[86,98,144,227,1194,2141,2162,2172,2176,2521],[98,144,227,1194,2141,2176,2181,2183],[86,98,144,227,1195,2141,2142,2162,2172,2176,2523],[98,144,227,1195,2141,2142,2176,2181,2183],[86,98,144,227,2141,2162,2172,2176,2182],[86,98,144,227,2141,2162,2172,2176,2526],[86,98,144,227,2141,2162,2172,2176,2528],[86,98,144,227,1193,2141,2162,2172,2176,2178,2179,2183],[86,98,144,227,516,1193,2141,2178,2179,2180,2182],[86,98,144,227,2185],[98,144,227,2162,2172,2185,2188],[98,144,227,2162,2172,2185,2190],[98,144,227,2162,2172,2185,2192],[86,98,144,227,2141,2162,2172,2176,2530],[86,98,144,227,2141,2162,2172,2176,2532],[86,98,144,227,1195,2142,2183],[86,98,144,227,516,2615,3109,3112,3118,3120,3121,3122],[98,144,227,2162,2172,2183,3129,3907],[86,98,144,227,1190,1195,1298,2133,2137,2141,2176,2183,2206,2486,2488,2523,2534,2561,2755,3124,3126,3127,3128,3150,3828],[86,98,144,227,1298,2162,2172,3130,3906],[86,98,144,227,1190,1298],[86,98,144,227,1298,2183,2486,3131],[98,144,227,2162,2172,2176,3196],[86,98,144,227,1190,1195,1298,2133,2137,2141,2176,2180,2183,2437,2462,2486,2488,2526,2534,2625,3127,3129,3130,3132,3133,3137,3149,3152,3153,3156,3166,3195],[98,144,227,2172,2534],[98,144,227,2162,2172,3720,3906],[98,144,227,2580,3717,3718,3719],[86,98,144,227,516,1195,2141,2179,2180,2446,2509,2523,2526,2611,2615,2643,3109,3120,3166,3196,3215,3223,3230,3234,3317,3325,3355,3600,3634,3647,3648,3712,3716,3722,3747,3755,3765,3771,3775,3776,3777,3786,3787,3797,3801,3804,3805,3814,3822],[86,98,144,227,1190,2133],[98,144,227,2162,2172,3906,4016],[86,98,144,227,1190,1298,2133],[86,98,144,227,1190,2133,2137,2138,2141,2538,2567,2638,3919,4034],[98,144,227,2162,2172,2541,4017],[86,98,144,227,2541],[98,144,227,2536],[86,98,144,227,504,2133,2541,4018],[98,144,227,2172,2541,4018],[98,144,227,2541],[98,144,227,2162,2172,2536,2541,4030],[86,98,144,227,2133,2138,2205,2536,2541,2542,2545,3709,4015,4017,4019,4021,4025,4026,4028,4029],[98,144,227,2162,2172,2567,4034],[86,98,144,227,1190,1298,2133,2137,2138,2141,2205,2536,2537,2538,2541,2542,2543,2546,2567,2609,3093,3161,3191,3627,3646,3709,3918,3936,3937,3938,3939,4007,4008,4009,4010,4011,4012,4013,4014,4015,4016,4017,4018,4019,4020,4021,4022,4023,4024,4025,4026,4027,4028,4029,4030,4031,4032,4033],[98,144,227,2162,2172,3906,4021],[86,98,144,227,1190,2133,2141,2205],[86,98,144,227,1190,1191,1298,2133],[98,144,227,2162,2172,2537,3906,4023],[86,98,144,227,1190,2537],[98,144,227,2172,2536,2567,4049],[98,144,227,2536,2567],[98,144,227,2162,2172,3906,4024],[98,144,227,2133],[86,98,144,227,1190,2133,2141,2537],[86,98,144,227,2133,2541,4027],[86,98,144,227,1190,2133,2541],[86,98,144,227,1190,2133,2137,2536],[98,144,227,2162,2172,3906,3918,4040],[86,98,144,227,1190,2133,2137,2538,2539,2541,2542,2567,3918,3936,3939,4018,4020,4038,4039],[98,144,227,2162,2172,2539,3906,4038,4040],[86,98,144,227,1190,2539,2580,2609,3161,3938,4036,4037,4040],[98,144,227,2162,2172,2541,4036],[86,98,144,227,2205,2541,2542,2580,3709,4019,4026,4029],[98,144,227,2162,2172,4039],[98,144,227,2162,2172,3906,4056],[98,144,227,2162,2172,2539,3906,4037],[98,144,227,1190,2539],[98,144,227,2172,2538,2539],[98,144,227,2538],[86,98,144,227,2141,2580,2596,2702,2809,3191,3918],[98,144,227,2162,2172,2543],[86,98,144,227,2134,2138,2541,2542],[86,98,144,227,2545],[98,144,227,2141,2541,3936],[98,144,227,2137,2141,2541,2542,4006],[98,144,227,2172,3312,4008],[98,144,227,2137,2141,2537,3312],[98,144,227,2172,3312,4009],[98,144,227,2137,2141,3312],[98,144,227,2172,4010],[98,144,227,2137,2141],[86,98,144,227,1298,2183,2508,3919,4034,4035,4040],[98,144,227,2172,2494,3906,3907,4064],[86,98,144,227,1190,1298,2133,2499,2523,2580,3184,4063],[98,144,227,2172,3907,4069],[86,98,144,227,1190,2133,2472,2580,4068],[98,144,227,1195,2172,3907,4068],[98,144,227,1023,1190,1195,3184],[98,144,227,2172,3906,3907,4062],[98,144,227,1190,1191,2133,2495,2612,2613],[98,144,227,2172,2494,3906,3907,4063],[86,98,144,227,1190,1191,2133,2494,2502,2612,2613],[86,98,144,227,1190,2172,2612,3906,3907],[86,98,144,227,1190,1195,2133,2141,2183,2523,2599,2611],[98,144,227,2172,2612,2613],[98,144,227,2612],[98,144,227,2172,2494,3906,3907,4065],[86,98,144,227,1023,1190,2133,2494,2523,2580,4062,4064],[98,144,227,2183,4065],[86,98,144,227,2183,4124],[98,144,227,524,527,3107,3108,3109,3110],[98,144,227,1193,2141,2162,2172,2176,2178,2182,4126],[86,98,144,227,516,1190,1193,2133,2141,2178,2179,2182,2476,2813,3120],[98,144,227,4126],[86,98,144,227,516,3093],[86,98,144,227,516,3647],[86,98,144,227,516,3648],[86,98,144,227,2162,2172,3779],[86,98,144,227,1190],[86,98,144,227,2162,2172,3781],[86,98,144,227,516,1193,2141,2177,2490,3778,3779,3780],[86,98,144,227,2162,2172,3780,3906],[86,98,144,227,2162,2172,3778],[86,98,144,227,516,3781],[86,98,144,227,1195,2162,2172,2672,3651],[86,98,144,227,1190,1195,1298,2547,2649,2672,2674,3649,3650],[86,98,144,227,1190,1298,2133,2137,2141,2180,2567,3138,3139,3140,3141],[98,144,227,1047,1190,1195,2141,2162,2172,2176,2625,3149,3906],[86,98,144,227,1047,1190,1195,1298,2141,2625,3139,3142,3148],[98,144,227,1047,1190,1195,2141,2172,2183,2625,3148,3906,3907],[86,98,144,227,1047,1190,1195,1298,2141,2180,2183,2466,2504,2521,2593,2625,3125,3134,3138,3144,3145,3146,3147],[98,144,227,2162,2172,3144],[86,98,144,227,982,1190,1194,1195,1298,2133,2134,2609,3143],[86,98,144,227,1190,2133,2571],[98,144,227,2172,3141,3907],[86,98,144,227,1190,2133,2567],[98,144,227,1190,2162,2172,3145],[86,98,144,227,1190,1298,2625,2681],[98,144,227,2172,3133],[98,144,227,2137,2141,2625],[98,144,227,1190,2162,2172,2625,3146],[86,98,144,227,1190,1298,2625],[86,98,144,227,1190,2133,2137,2141,3133],[98,144,227,1190,2162,2172,2176,2625,3134],[86,98,144,227,1190,1298,2133,2141,2504,2625],[98,144,227,2162,2172,3140,3906],[86,98,144,227,1190,1298,2133,2137,2141,2571,3157,3158,3159,3160,3162,3166],[98,144,227,2162,2172,3215,3906],[86,98,144,227,1190,1298,2137,2141,2183,2618,2719,3197,3198,3207,3209,3212,3213,3214],[98,144,227,2162,2172,2562,3906],[86,98,144,227,1190,2141],[86,98,144,227,2141,2162,2172,3223],[86,98,144,227,1190,1195,1298,2133,2137,2141,2180,2201,2442,2547,3220,3222],[86,98,144,227,1190,1191,1195,1298,2133,2141,2183,2593,2599,2603,2606,2607,2676,2677,3161,3217,3218,3219],[86,98,144,227,2162,2172,2201,3906,3907,4253],[86,98,144,227,1190,2133,2201],[86,98,144,227,2141,2162,2172,3218,3906,3907],[86,98,144,227,1190,2133,2141,2677],[98,144,227,2172,2201,3906,3907,4256],[86,98,144,227,1190,2201,4253],[86,98,144,227,1190,1298,2201],[98,144,227,2172,2677],[86,98,144,227,1190,2133,2676,3216],[86,98,144,227,1190,1191,1298,2141,2201,2437,2676,2677,2679,3217,3218,3219,3221],[98,144,227,2141,2201],[86,98,144,227,1190,2676],[86,98,144,227,1190,2141,2676,3216],[98,144,227,2162,2172,2755,3124,3150,3635,3828,3906],[98,144,227,1190,1298,2133,2755,3124,3150,3828],[98,144,227,2141,2162,2172,3635,3636],[86,98,144,227,1190,1298,2137,2141,3635],[86,98,144,227,1298,2141,2162,2172,3637,3638],[86,98,144,227,1190,1298,2137,2141,3637],[98,144,227,2141,2162,2172,3640],[86,98,144,227,1190,1298,2137,2141,3639],[98,144,227,2141,2172,3648,3907],[86,98,144,227,516,1190,1193,1298,2133,2137,2141,2178,2180,2205,2526,2580,2685,3150,3635,3636,3637,3638,3639,3640,3641,3642,3644,3645,3647],[86,98,144,227,1190,1298,2133,2685,3150,3233,3643],[98,144,227,2137,2141,2162,2172,3642,3906],[86,98,144,227,506,1298,2137,2141,2180,2437,2442],[86,98,144,227,2137,2141,3757],[86,98,144,227,1190,1298,2437],[98,144,227,2680],[98,144,227,2162,2172,2680,3906],[86,98,144,227,2133],[86,98,144,227,1190,1298,2137,2447],[98,144,227,2162,2172,2176,2446,2447],[86,98,144,227,1298,2137,2180,2183,2205,2206,2442,2443,2444,2445,2447],[86,98,144,227,1190,1298,2137,2446,2447],[98,144,227,2162,2172,2598],[86,98,144,227,1190,1298,2133,2137,2141,2437,2596,2597],[98,144,227,2137,2141,2172,3816,3906,3907],[86,98,144,227,1190,1191,2137,2141,3815],[86,98,144,227,1298,2137,2141,2437,3224,3226,3229],[86,98,144,227,1298,2437,3225],[98,144,227,2162,2172,4260],[86,98,144,227,3228],[98,144,227,2162,2172,3228],[86,98,144,227,1190,1298,2183,2567,2571],[86,98,144,227,1298,2137,2141,2682,3227,3228],[98,144,227,2162,2172,3227],[86,98,144,227,1298],[86,98,144,227,1190,2133,2205,2683,3709,4025,4026,4118],[86,98,144,227,516,1190,1191,2133,2141,2182,2567,2625,2683,2684,3709,3918,4013,4118,4119,4120,4121,4122,4123],[86,98,144,227,831,1190,2133,2683],[86,98,144,227,1190,1191,2133,2138,2141,3632],[86,98,144,227,1190,1191,2138,2141],[86,98,144,227,1190,1191,1298,2133,2141],[98,144,227,2138],[86,98,144,227,2683],[98,144,227,2172,2536,3646],[98,144,227,2138,2536,2541],[86,98,144,227,1190,2138],[86,98,144,227,1190,2133,2205,3709],[86,98,144,227,1190,1298,2137,2141,2180,2685,3231,3232,3233],[86,98,144,227,2162,2172,3231,3907],[86,98,144,227,1190,1191,1298,2141,2686],[98,144,227,2172,2685,2686],[98,144,227,2685],[86,98,144,227,1190,1298,2137,2141,2685],[86,98,144,227,1190,1298,2133,2137,2437,2685,2686,2755,3124,3150,3828],[86,98,144,227,2133,2685,2686],[86,98,144,227,1190,1298,2137,2141],[98,144,227,2162,2172,2176,3763],[86,98,144,227,1190,2176,2181,2183,2455,3759,3760,3762],[98,144,227,2162,2172,2176,3760],[86,98,144,227,1190,1191,2183,2448],[98,144,227,2162,2172,3759],[98,144,227,1190],[98,144,227,2162,2172,2176,2454,3762],[86,98,144,227,1190,1191,2183,2206,2450,2452,2454,2455,2580,3761],[98,144,227,2162,2172,2176,2454,3761],[86,98,144,227,1190,1191,2183,2454,2455],[86,98,144,227,1190,1298,2133,2195],[86,98,144,227,1298,2437],[98,144,227,1298,2162,2172,2672,3649],[98,144,227,1298,2672],[86,98,144,227,1190,1298,2133,2134,2141],[98,144,227,2162,2172,3184],[98,144,227,2172,2206,3906,3907],[98,144,227,2162,2172,3168,3906],[98,144,227,2162,2172,3717],[86,98,144,227,1190,2440,2561,2580],[98,144,227,2162,2172,3718,3906],[86,98,144,227,1190,2580],[98,144,227,2162,2172,3719,3906],[86,98,144,227,2593,2714],[98,144,227,2162,2172,2437,2441],[86,98,144,227,1298,2440],[98,144,227,2162,2172,2442],[98,144,227,1190,2437,2441],[98,144,227,2172,2566,3906,3907],[98,144,227,2162,2172,3186],[86,98,144,227,1190,3184],[98,144,227,2162,2172,3120],[98,144,227,2440,3119],[86,98,144,227,1023,1190,2133,2141,2442],[86,98,144,227,1298,2137,2437,2568],[86,98,144,227,1190,1298,2133,2567],[98,144,227,2162,2172,2188,2618],[98,144,227,1190,2188],[98,144,227,2162,2172,2594,3906],[86,98,144,227,1190,1298,2133,3161],[86,98,144,227,1298,2572],[86,98,144,227,1190,2133,2494],[86,98,144,227,1190,2172,2574,3906,3907],[86,98,144,227,1298,2141,2567,2579,2582,2583,2584],[98,144,227,2162,2172,2756,3906],[86,98,144,227,1190,2437],[86,98,144,227,1190,1195,2133,2523,2592],[86,98,144,227,1190,2133,2141,2561],[86,98,144,227,2162,2172,2623,2630,3906,3907],[86,98,144,227,1190,1298,2133,2623,2625,2626],[86,98,144,227,2162,2172,2623,2628,3906,3907],[86,98,144,227,2162,2172,2642,3906,3907],[86,98,144,227,1190,1298,2133,2567,2623,2627,2628,2629,2630,2636,2637,2639,2640,2641],[86,98,144,227,2162,2172,2639,3906,3907],[86,98,144,227,1298,2638],[98,144,227,2623,2626,2627,2628,2629,2630,2639,2640,2641,2642],[86,98,144,227,2162,2172,2631,2636,3906,3907],[86,98,144,227,1190,2133,2631,2634,2635],[86,98,144,227,2162,2172,2623,2631,2634,3906,3907],[86,98,144,227,1190,1298,2133,2547,2623,2631,2633],[86,98,144,227,2162,2172,2631,2632,2633,3906,3907],[86,98,144,227,1298,2133,2631,2632],[98,144,227,2172,2623,2631,2632],[98,144,227,2547,2623,2631],[98,144,227,2623],[98,144,227,2162,2172,2623,2631,2635],[86,98,144,227,2141,2623,2631],[86,98,144,227,2162,2172,2627,3906,3907],[86,98,144,227,1298,2437,2623,2624,2626],[98,144,227,2172,2626],[98,144,227,2625],[86,98,144,227,2162,2172,2629,3906,3907],[98,144,227,2137,2162,2172,2640],[86,98,144,227,2137,2141,2623,2625,2626],[98,144,227,2137,2162,2172,2641],[98,144,227,2137,2141,2162,2172,2176,2492,2601,3906],[86,98,144,227,1190,1298,2133,2137,2141,2176,2492,2593,2598,2599,2600],[98,144,227,2172,2469,3122,3907],[86,98,144,227,1190,2469],[98,144,227,2141,2162,2172,3818],[86,98,144,227,1190,1298,2133,2137,2141,2547,2565,2599],[98,144,227,2162,2172,2472,3807,3907],[86,98,144,227,1190,2183,2472,3806],[98,144,227,2162,2172,2472,3806,3907],[86,98,144,227,1190,1195,1298,2437,2547,2755,3124,3150,3828],[98,144,227,2162,2172,2523,3809,3907],[98,144,227,1190,2183,2523,3808],[98,144,227,2162,2172,2523,3808,3907],[86,98,144,227,1190,1298,2437,2523,2547,2599,2755,3124,3150,3828],[86,98,144,227,1190,1298,2137,2141,2567,3140],[86,98,144,227,1190,1298,2565,2571],[86,98,144,227,610,1190,1197,1298,2137,2141],[98,144,227,1197,2688],[98,144,227,610],[86,98,144,227,1190,1298,2137,2141,2689],[98,144,227,2172,2654,2655,3906,3907],[86,98,144,227,1190,2137,2523,2649,2650,2651,2652,2653,2654],[98,144,227,2172,2651,3907],[86,98,144,227,1190,2650],[98,144,227,2652,3907],[98,144,227,2172,2653,3906,3907],[98,144,227,2650,2655,2656],[98,144,227,1195,1298],[98,144,227,2172,2650,2656,3906,3907],[86,98,144,227,1190,1195,1298,2650,2655],[98,144,227,1298,2172,2596,2650,2654],[98,144,227,1298,2547,2596,2650],[86,98,144,227,1190,1298,2141,2437,3235,3313,3316],[98,144,227,2141,2162,2172,3355],[86,98,144,227,1190,2133,2137,2141,2180,2206,2692,2694,3334,3341,3343,3347,3350,3353,3354],[86,98,144,227,2162,2172,3341,3907],[86,98,144,227,1190,2137,2141,3333,3334,3335,3336,3337,3339,3340],[86,98,144,227,1190,2133,2141],[86,98,144,227,1190,2133,2137,2141,3326,3327,3328,3329,3330,3331,3332],[86,98,144,227,1298,3329,3330,3344],[86,98,144,227,1190,2162,2172,3346,3906],[86,98,144,227,1190,3332,3333,3345],[98,144,227,2162,2172,3327,3906],[98,144,227,2162,2172,3326,3906],[86,98,144,227,1190,1298,2133,2137,2141],[98,144,227,2693],[86,98,144,227,1190,1298,2137,2141,3334,3339],[86,98,144,227,1190,2133,2691,3351,3352],[98,144,227,2162,2172,2691,3351,3906],[86,98,144,227,2133,2691],[86,98,144,227,1190,2133,2690,2691,3341],[98,144,227,2141,2162,2172,3347],[86,98,144,227,1190,1298,2133,2137,2141,2437,2547,2580,2693,3334,3335,3336,3339,3340,3346],[98,144,227,2172,3334],[86,98,144,227,1190,2571],[86,98,144,227,1190,2141,2571,3334],[98,144,227,2162,2172,2692,3343],[86,98,144,227,1190,1298,2437,2692,2755,3124,3150,3334,3342,3828],[98,144,227,2141,2162,2172,3161],[86,98,144,227,1190,2141,2692],[98,144,227,2162,2172,3349,3906],[86,98,144,227,1190,1298,2133,2137,3348],[98,144,227,2162,2172,3350,3906],[86,98,144,227,1190,2133,2137,2141,3349],[98,144,227,2162,2172,3348,3906],[86,98,144,227,1190,1298,2133,2137],[98,144,227,2162,2172,2692,3338],[86,98,144,227,1190,2133,2692],[98,144,227,2162,2172,3339],[86,98,144,227,1190,2692,3338],[86,98,144,227,1190,2137,2141,2468,2580,2593],[86,98,144,227,2162,2172,3340,3906],[98,144,227,2162,2172,3906,4193],[86,98,144,227,1190,2133,2141,2176,2659,3319,3320,3321],[98,144,227,2141,2162,2172,2176,3325],[86,98,144,227,1298,2141,3318,3322,3324],[86,98,144,227,1023,1190,2133,2141,2176,2659,3319,3321,3323],[86,98,144,227,1190,2133,2141,2176,2659,2718,2759,2803],[86,98,144,227,3321,3907],[86,98,144,227,1298,2162,2172,3323,3907],[86,98,144,227,2162,2172,2637,3906,3907],[98,144,227,2172,3189],[98,144,227,2172,2599],[98,144,227,2172,2695],[98,144,227,1195,2141],[98,144,227,2141,2162,2172,3784],[86,98,144,227,1195,2141,2183,2560,2695,2719],[86,98,144,227,610,2141],[98,144,227,2172,2697],[98,144,227,1195],[86,98,144,227,2162,2172,2470,2716,3906,3907],[86,98,144,227,1190,2133,2470,2592],[98,144,227,2162,2172,2620,3907],[86,98,144,227,1190,2133,2141,2180,2183,2492,2523,2615,2618,2619],[98,144,227,2172,3918],[98,144,227,2138,2141,2541,2542,3266,3312],[98,144,227,2172,2541,4013],[98,144,227,2137,2138,2141,2541,2542,2545,3312],[86,98,144,227,1190,2437,2563],[86,98,144,227,1190,2479,2483,2485],[98,144,227,2141,2162,2172,2606,3906,3907],[86,98,144,227,1190,1298,2138,2141,2483,2604,2605],[86,98,144,227,1190,1191,2133,2138],[98,144,227,1192,2141,2162,2172,3617,3906],[86,98,144,227,1190,1192,1298,2133,2137,2138,2141,2180,3093,3604,3605,3606,3607,3608,3609,3611,3612,3613,3614,3615,3616],[98,144,227,3629,3633],[86,98,144,227,1190,1298,2141,2547,2580],[86,98,144,227,2162,2172,3606,3906],[86,98,144,227,1190,2138,2141,3617],[86,98,144,227,1190,1298,2133,2138],[86,98,144,227,1298,2138],[86,98,144,227,2137,2141,2162,2172,3620],[86,98,144,227,1190,1192,1298,2133,2137,2138,2141,3084,3093,3605,3607,3608,3609,3612,3613,3614,3615],[86,98,144,227,1190,1298,2138,2437,2547,2580,3093,3614,3620,3621,3634],[86,98,144,227,2141,2162,2172,2176,3629],[86,98,144,227,1190,1298,2133,2137,2138,2141,2176,2180,2481,2483,2618,2811,3093,3602,3603,3617,3618,3619,3622,3624,3625,3626,3627,3628],[86,98,144,227,2162,2172,3607],[86,98,144,227,1190,1298,2133,2605],[98,144,227,1192,2141,2162,2172,2176,3633],[86,98,144,227,1190,1192,1298,2133,2138,2141,2176,2811,3084,3093,3630,3631,3632],[86,98,144,227,1190,1298,2580,2604],[86,98,144,227,2162,2172,3612,3906],[86,98,144,227,1190,2162,2172,3609,3906],[86,98,144,227,1190,2133,2138],[86,98,144,227,1190,2133,2138,3614],[98,144,227,2138,2172,3601],[86,98,144,227,2137,2138,2141,2580,3601],[86,98,144,227,1190,1298,2138,2141,2176,2437,2483,2485,2755,3124,3150,3165,3828],[86,98,144,227,1190,2162,2172,3604],[86,98,144,227,1102,1190,2133,2138,3610],[86,98,144,227,2138,2162,2172,3630],[86,98,144,227,1190,1298,2133,2137,2138],[98,144,227,2138,2172],[86,98,144,227,1190,2137,2138,2141,2176],[98,144,227,2172,3614],[98,144,227,3803],[86,98,144,227,1023,1190,2133,2141,2176,2206,3802],[98,144,227,2162,2172,2176,2625,3135],[86,98,144,227,1047,1190,1298,2625,3134],[98,144,227,1047,2141,2162,2172,2176,3137],[86,98,144,227,1047,1190,1298,2137,2141,2180,2183,2206,2437,2462,3135,3136],[98,144,227,2141,2162,2172,2176,2625,3136],[86,98,144,227,1047,1190,1298,2141,2625,3134],[86,98,144,227,1190,1298,2141],[86,98,144,227,1298,2755,2756,3124,3150,3828],[98,144,227,1190,1195,1298,2437,2755,3124,3150,3828],[98,144,227,2162,2172,3152],[86,98,144,227,1190,1195,1298,2141,2755,2821,3124,3150,3151,3828],[98,144,227,2136,2137,2162,2172,2506,2518,3128,3906,3907],[86,98,144,227,1190,2136,2137,2506,2518],[86,98,144,227,1298,2437,2755,3124,3150,3828],[86,98,144,227,1298,2137,2141,2437],[86,98,144,227,2137,2141,2162,2172,2176,3156,3906],[86,98,144,227,1190,1194,1298,2133,2134,2137,2141,2206,2437,2486,2488,2534,2547,2571,2580,2609,2625,3127,3143,3154,3155],[98,144,227,1190,2141,2162,2172,2488,2492,2523,2530,3171,3906,3907],[98,144,227,1190,2141,2488,2492,2523,2530,2660],[98,144,227,2172,2660],[86,98,144,227,2162,2172,2488,2717,3906,3907],[86,98,144,227,1190,2133,2488,2592],[98,144,227,2162,2172,2714,3906,3907],[86,98,144,227,1190,2437,2561],[98,144,227,1191,2172],[98,144,227,638,1190],[86,98,144,227,1298,2162,2172,2625,2699,2755,3124,3126,3150,3828,3906],[98,144,227,1190,1298,2133,2437,2699,2755,3124,3125,3150,3828],[86,98,144,227,2162,2172,2625,3125],[86,98,144,227,2625],[98,144,227,1190,2137,2172],[86,98,144,227,1005,1120,1190,2136],[86,98,144,227,1190,1193,2172,2185,3118,3906,3907],[86,98,144,227,506,1190,1193,2133,2141,2179,2187,2190,2469,2509,2664,2813,3112,3113,3114,3115,3116,3117],[98,144,227,2172,3113,3906,3907],[86,98,144,227,1190,2133,2186,2204,2664],[98,144,227,2172,3114,3907],[86,98,144,227,1190,2133,2190],[98,144,227,2172,2662],[86,98,144,227,3115,3906,3907],[86,98,144,227,1190,2133,2185,2194],[98,144,227,2172,2185,3116,3906,3907],[86,98,144,227,1190,2133,2183,2185,2186,2187,2190,2192,2662],[98,144,227,2162,2172,3117,3906],[86,98,144,227,1190,2133,2813],[98,144,227,1193,2137,2141,2172,2615],[98,144,227,1191,1193,1194,1195,1196,1197,2135,2137,2138,2139,2140],[86,98,144,227,1298,3172,3173,3174],[86,98,144,227,2141,2162,2172,2176,2599,3716],[86,98,144,227,901,1023,1190,1195,1298,2133,2137,2141,2180,2206,2442,2492,2523,2547,2562,2564,2569,2570,2571,2573,2580,2585,2594,2599,2603,2606,2609,3171,3176,3195,3713,3714,3715],[86,98,144,227,1190,1298,2137,2597],[98,144,227,2162,2172,2611,3907],[86,98,144,227,1190,1195,1298,2133,2135,2137,2141,2176,2180,2183,2472,2492,2494,2521,2526,2547,2561,2562,2563,2564,2565,2566,2569,2570,2571,2573,2574,2585,2593,2594,2595,2599,2601,2602,2603,2606,2607,2609,2610],[98,144,227,1195,2172,3190,3906,3907],[86,98,144,227,1190,1195,2133,2137,2141,2183,2597,3079],[98,144,227,2172,2610],[86,98,144,227,2162,2172,2492,3721,3906,3907],[86,98,144,227,1023,1190,1298,2137,2141,2176,2437,2492,2523,2547,2571,2580,2603,2609,2649,3167,3171,3175,3178,3182],[86,98,144,227,2162,2172,3722],[86,98,144,227,1190,1298,2133,2137,2141,2206,2437,2442,2547,2571,2599,2603,2609,3171,3720,3721],[98,144,227,2172,2180,2617,2620,2621],[98,144,227,2180,2617,2620],[86,98,144,227,1190,1298,2137,2141,2580,3159,3160,3162],[86,98,144,227,1190,1298,2137,2141,2437,2580,2755,3124,3150,3163,3164,3165,3828],[86,98,144,227,1298,2141],[86,98,144,227,1190,1298,2141,2437],[98,144,227,2141,2162,2172,3173,3906],[86,98,144,227,1190,1298,2138,2141,2437],[86,98,144,227,1298,2141,2437],[86,98,144,227,2141,2162,2172,2702,3594,3906,3907],[86,98,144,227,1190,1298,2137,2141,2183,2702,2703,3593],[86,98,144,227,1190,1298,2137,2141,2183,2692,2702],[86,98,144,227,1190,1298,2133,2141],[86,98,144,227,1298,2162,2172,2702,3592,3906,3907],[86,98,144,227,1190,1298,2437,2702,2755,3124,3150,3591,3828],[98,144,227,2172,2703],[98,144,227,2702],[86,98,144,227,2162,2172,3597,3906,3907],[86,98,144,227,1298,2141,2162,2172,2702,3591,3906,3907],[86,98,144,227,1190,1298,2141,2437,2702],[98,144,227,2162,2172,3593,3907],[86,98,144,227,1190,1298,2162,2172,3600,3906,3907],[86,98,144,227,1190,1191,1298,2133,2141,2180,2206,2692,2702,2814,3356,3588,3589,3590,3592,3594,3595,3596,3597,3598,3599],[86,98,144,227,1190,1191,1298,2137,2141,2437,2692,2702,2809,3587],[98,144,227,2141,2162,2172,2702,3589,3906,3907],[86,98,144,227,1190,1298,2141,2437,2702,3588],[86,98,144,227,1298,2162,2172,2702,3356,3906,3907],[86,98,144,227,1190,1298,2437,2702,2755,3124,3150,3828],[86,98,144,227,2141,2162,2172,3596,3906,3907],[86,98,144,227,1190,1191,2141,2437],[86,98,144,227,1190,1298,2141,2183],[98,144,227,2141,2162,2172,2702,3191,3907],[86,98,144,227,1190,2141,2702],[86,98,144,227,1190,2133,2137,2141],[86,98,144,227,1190,1298,2137,2141,2180,3724,3726,3727,3746],[98,144,227,2705,3745],[86,98,144,227,1298,2133,2708,2709,3735,3738,3739,3740],[86,98,144,227,2133,2205,2542,2708,3709],[86,98,144,227,1190,2133,2708,3736,3737],[98,144,227,2542],[86,98,144,227,2137,2141,2542,2706,2708],[86,98,144,227,1298,3732],[86,98,144,227,2705,2706],[86,98,144,227,2137,2141,2705,2706,3728,3729,3730,3731,3733,3734,3741,3742,3743,3744],[86,98,144,227,1190,1298,2568,2580],[86,98,144,227,1190,1298,2133,2137,2205],[86,98,144,227,1190,1298,2580,3725],[86,98,144,227,1190,1298,2580,2705,3732],[98,144,227,2162,2172,2705,3731],[86,98,144,227,1298,2580,2705],[98,144,227,2172,2705,2706],[98,144,227,2705],[98,144,227,2141,2162,2172,3744],[86,98,144,227,1190,1298,2137,2141,2437,2547,2580,3723,3725],[86,98,144,227,1190,1298,2133,2141,2437,2625,2755,3124,3150,3723,3828],[98,144,227,2141,2706],[98,144,227,2172,2625],[98,144,227,2141,2162,2172,3647],[86,98,144,227,1190,1298,2137,2141,2437,2536,2541,2580,2625,2685,2755,3112,3118,3124,3150,3644,3646,3828],[98,144,227,1190,2137,2141,2172,3235,3906,3907],[86,98,144,227,1190,2137,2141,2579],[98,144,227,2162,2172,2575],[98,144,227,2162,2172,2576],[98,144,227,1190,2162,2172,2579,3906],[86,98,144,227,2575,2576,2577,2578],[98,144,227,2162,2172,2577,3906],[98,144,227,2162,2172,2578,3906],[86,98,144,227,1190,2133,2137,2183,2488,2509,2510,2512,2513,3314,3315],[86,98,144,227,1190,2512],[86,98,144,227,1023,1190,2133,2512],[86,98,144,227,1190,1298,2133,2136,2137,2141,2597],[86,98,144,227,504,1190,1298,2133,2137,2141,2176,2180,3748,3749],[98,144,227,3748,3749,3752,3753,3754],[98,144,227,1023,1190,2442,3749],[98,144,227,2141,2162,2172,2176,2180,3749,3754,3906],[86,98,144,227,1190,1298,2133,2137,2141,2176,2180,2206,3749,3750,3751,3753],[98,144,227,2137,2141,2162,2172,3752,3906],[86,98,144,227,1190,1191,1298,2133,2137,2141],[98,144,227,2162,2172,2547,3749,3753,3906],[86,98,144,227,1190,1298,2437,2547,2580,3749,3752],[98,144,227,2141,2162,2172,3765],[86,98,144,227,827,1190,1298,2136,2137,2141,2206,2671,3756,3758,3763,3764],[86,98,144,227,1190,2137,2183,2459,2461,2665],[86,98,144,227,1190,2137,2183,2206,2458,2459,2460,2461,2580,2665,3210,3211],[98,144,227,2162,2172,3211,3906],[98,144,227,2136,2137,2162,2172,2506,2520,3198,3906,3907],[86,98,144,227,1190,2133,2136,2137,2506,2520],[86,98,144,227,2162,2172,2477,2478,3624,3906],[86,98,144,227,1190,2133,2137,2477,2478,2567,2666,3623],[86,98,144,227,2162,2172,2666,3623,3906],[98,144,227,1190,2133,2568,2666],[98,144,227,2137,2141,2172,2666],[98,144,227,2162,2172,3200,3907],[86,98,144,227,1190,2136,2137,2514,2669,3199],[98,144,227,1190,2162,2172,3199,3907],[86,98,144,227,1190,1298,2668],[98,144,227,2162,2172,2176,3201],[86,98,144,227,2136,2137,2206,2514,2516,2669],[98,144,227,2136,2137,2162,2172,2514,2516,2669,3202],[86,98,144,227,1190,2136,2137,2514,2516,2669,3199],[98,144,227,2162,2172,3203],[98,144,227,2162,2172,2516,3204,3907],[98,144,227,1190,2516,2580,2668],[98,144,227,2162,2172,2176,3207],[86,98,144,227,1190,2516,2580,2668,2669,3200,3201,3202,3203,3204,3205,3206],[98,144,227,2162,2172,3205],[98,144,227,2162,2172,3206],[98,144,227,1190,2580],[98,144,227,2172,2669],[98,144,227,2516],[98,144,227,2162,2172,3208,3906],[86,98,144,227,1190,2621],[98,144,227,2137,2162,2172,3209],[98,144,227,1190,2137,2183,2526,2528,3208],[98,144,227,2162,2172,3764],[86,98,144,227,1184,1190,1298,2442,2671],[98,144,227,1190,2162,2172,2567,2584,3906],[86,98,144,227,1190,1191,1298,2137,2567,2581,2582,2583],[98,144,227,2162,2172,2581],[98,144,227,2141,2162,2172,2567,3313,3906],[86,98,144,227,1190,1298,2137,2141,2180,2206,2437,2486,2584,3125,3312],[98,144,227,1190,2162,2172,2582,2583,3906],[86,98,144,227,1190,1191,1298,2580,2582],[98,144,227,2162,2172,3318],[86,98,144,227,1298,2133,2718],[98,144,227,2162,2172,3653],[86,98,144,227,3119],[98,144,227,1191,2162,2172,2607,3906],[86,98,144,227,1190,1191,2597],[98,144,227,1190,1298,2133,2685,2755,3124,3150,3828],[98,144,227,1190,2137,2141,2162,2172,3213],[86,98,144,227,1190,1298,2136,2137,2141],[98,144,227,2162,2172,3770,3906,3907],[98,144,227,2162,2172,3769,3906,3907],[86,98,144,227,2580,3766],[98,144,227,3766,3767,3768,3769,3770],[98,144,227,2162,2172,2185,2190,2580,3766],[86,98,144,227,1190,2185,2190,2580],[98,144,227,2162,2172,3768,3906,3907],[98,144,227,2162,2172,3767,3906,3907],[98,144,227,2162,2172,3774,3906],[86,98,144,227,1190,1298,2133,2565,2571],[86,98,144,227,1194,1298,2137,2141,2437,3772,3773,3774],[86,98,144,227,1190,1194,1298,2133,2137,2141,2547,2565,2571,2580,2599,2611],[98,144,227,2162,2172,3938],[86,98,144,227,1190,1194,2141],[98,144,227,1194,2162,2172,3773],[86,98,144,227,1190,1194,1298,2437,2755,3124,3150,3828],[98,144,227,2141,2162,2172,3713,3907],[86,98,144,227,1298,2137,2141],[86,98,144,227,2572],[98,144,227,2162,2172,3178,3907],[86,98,144,227,2172,2572,3906,3907],[86,98,144,227,1190,1298,2133,2437,2563,2571],[98,144,227,2141,2162,2172,3180,3907],[86,98,144,227,1190,1298,2133,2137,2141,3179],[86,98,144,227,1190,2133,2547,2712,2818],[98,144,227,2172,3179],[98,144,227,2172,2710],[98,144,227,2141,2162,2172,2472,2488,2492,2523,2530,3195,3906,3907],[86,98,144,227,1190,1191,1298,2133,2137,2141,2176,2180,2183,2206,2437,2466,2492,2547,2562,2564,2570,2571,2580,2585,2599,2603,2606,2609,2710,3081,3167,3168,3169,3170,3171,3175,3176,3177,3178,3180,3181,3183,3194],[98,144,227,2162,2172,2180,2183,2526,3183,3195,3906,3907],[98,144,227,1023,1190,2133,2141,2180,2183,2526,2547,2818,3182,3195],[98,144,227,1195,2141,2162,2172,2183,2472,2695,3194,3906,3907],[86,98,144,227,1190,1195,1298,2133,2141,2176,2183,2437,2472,2547,2599,2695,2714,2755,3124,3150,3184,3193,3828],[86,98,144,227,1190,2137,2141,2162,2172,3714,3906,3907],[86,98,144,227,1190,2133,2137,2141,2565,2599,3171],[98,144,227,1195,2162,2172,3192,3906,3907],[86,98,144,227,1190,1194,1195,1298,2133,2137,2141,2180,2492,2494,2526,2562,2563,2564,2566,2570,2571,2574,2594,2602,2603,2606,2609,2611,3161,3177,3189,3191],[98,144,227,1195,2162,2172,2183,3185,3193,3907],[98,144,227,1195,2141,2162,2172,2183,2474,2494,3185,3193,3906,3907],[86,98,144,227,1190,1195,1298,2136,2137,2141,2180,2183,2206,2437,2474,2494,2526,2547,2563,3081,3170,3175,3185,3187,3188,3189,3190,3192],[98,144,227,2162,2172,3187,3906],[86,98,144,227,1190,2133,3184,3186],[86,98,144,227,1190,2162,2172,2180,3193],[86,98,144,227,1190,1195,2133,2141,2176,2593,2659,3320,3798],[86,98,144,227,1190,1298,2141,2714,2756,2757,3321,3798],[98,144,227,2172,3798,3907],[86,98,144,227,2162,2172,3801,3906,3907],[86,98,144,227,3799,3800],[98,144,227,2162,2172,3715],[98,144,227,1190,2133],[98,144,227,2162,2172,3119],[86,98,144,227,2440,2812],[86,98,144,227,1298,2137,2141,3112],[98,144,227,2141,2172],[86,98,144,227,1298,2141,2547,3224,3656,3662,3786],[86,98,144,227,2141,2162,2172,2192,2619,3906],[86,98,144,227,1298,2141,2192,2580],[86,98,144,227,2162,2172,3657],[86,98,144,227,1298,2672,3649],[86,98,144,227,2162,2172,3658],[86,98,144,227,1298,2672],[86,98,144,227,2162,2172,3659],[86,98,144,227,1023,1190,2547,2672],[98,144,227,2162,2172,3660],[86,98,144,227,2672,3657,3658,3659],[98,144,227,2141,2162,2172,3664],[86,98,144,227,1190,1298,2133,2141,2547,2625,2650,2657,2672,2673,2674,3185,3651,3660,3661,3662,3663],[98,144,227,2162,2172,3665],[86,98,144,227,1190,1298,2133,2547,3125,3653],[98,144,227,1195,2141,2162,2172,2183,2697,3662,3906],[86,98,144,227,1190,1298,2141,2183,2437,2547,2672,2697,3165,3193],[98,144,227,2162,2172,3663,3906],[86,98,144,227,1190,1298,2547,3165],[98,144,227,2162,2172,2672,3650,3906],[86,98,144,227,1023,1190,1298,2547,2672],[98,144,227,2162,2172,3710,3907],[86,98,144,227,1190,2141,3709],[86,98,144,227,1190,1298,2141,2162,2172,2183,2202,2464,2530,2532,3712,3907],[86,98,144,227,1190,1194,1195,1298,2133,2141,2180,2183,2202,2464,2530,2532,2547,2592,2657,2672,2673,2674,3318,3651,3652,3653,3655,3656,3660,3662,3664,3665,3710,3711],[86,98,144,227,2162,2172,3711],[86,98,144,227,2672],[98,144,227,2172,2674],[98,144,227,2141,2162,2172,3655],[86,98,144,227,1190,1298,2141,3653,3654],[86,98,144,227,2141,2162,2172,3786,3907],[86,98,144,227,516,1193,1195,1298,2141,2177,2611,3782,3783,3785],[86,98,144,227,1190,1298,2162,2172,3815,3906,3907],[86,98,144,227,1190,1298,2133,2180,2565,2571,2599],[98,144,227,2141,2162,2172,3795],[86,98,144,227,1190,1191,1298,2133,2137,2141,2608,3789,3793,3794],[98,144,227,2162,2172,2608,3793],[86,98,144,227,1190,1191,2133,2608],[86,98,144,227,1298,2137,2141,2180,2206,2437,2608,3788,3790,3792,3795,3796],[98,144,227,2162,2172,2567,3794],[98,144,227,2162,2172,2608,3796],[86,98,144,227,1190,2608,3791],[86,98,144,227,1190,1298,2133,2137,2141,2437,2608,2625,3791],[98,144,227,2141,2162,2172,3790],[86,98,144,227,1190,1298,2133,2137,2141,2567,3789],[98,144,227,1190,2162,2172,2608,2609],[86,98,144,227,1190,2141,2608],[98,144,227,2162,2172,2608,3788,3906],[86,98,144,227,1190,1298,2437,2442,2608,2625,2755,3124,3150,3828],[86,98,144,227,1190,1191,2133,2137,2141],[86,98,144,227,1023,1190,2133,2141,2176,2718,2759,3184,3810],[86,98,144,227,1190,2133,2718,2759,3184],[86,98,144,227,1190,1298,2547,2625,2713,2755,2756,2757,2758,3124,3150,3828],[98,144,227,2162,2172,2774],[86,98,144,227,2172,2773,3906,3907],[86,98,144,227,1190,2547],[98,144,227,2162,2172,3906,4381],[98,144,227,2141,2713,2714,2715,2716,2717,2760],[98,144,227,2763],[86,98,144,227,2172,2763,2764,3907],[86,98,144,227,2172,2764,2771,3906,3907],[86,98,144,227,1190,2763,2768,2769,2770],[86,98,144,227,2172,2764,2768,3906,3907],[98,144,227,2141,2162,2172,2718,2760,3814,3906,3907],[86,98,144,227,1195,1298,2141,2180,2713,2714,2718,2759,2760,2761,2803,3165,3193,3715,3807,3809,3811,3813],[86,98,144,227,2141,2162,2172,2176,2759,2760],[86,98,144,227,1195,2141,2176,2695,2718,2719,2759],[86,98,144,227,2162,2172,2793,3906],[98,144,227,1190,2133,2625,2718,2759,2765],[86,98,144,227,2162,2172,2790,2796,3906],[86,98,144,227,1190,2133,2790,2795],[98,144,227,2801,2802],[86,98,144,227,1190,2162,2172,2790,2797,3906],[86,98,144,227,1191,2790,2792,2793,2795,2796],[98,144,227,524,1190,2765,2779],[98,144,227,2162,2172,2759,2801,3906],[86,98,144,227,1190,2547,2718,2759,2765,2771,2772,2773,2774,2775,2776,2777,2780,2781,2789,2800],[86,98,144,227,1190,2133,2141,2176,2475,2547,2580,2713,2759,2762,2765,2766,2767,2781,2801],[86,98,144,227,1190,2162,2172,2790,2798,3906],[86,98,144,227,1190,1191,2790,2792,2795],[98,144,227,2790],[86,98,144,227,1190,2162,2172,2800],[98,144,227,2791,2797,2798,2799],[86,98,144,227,1190,2162,2172,2799,3906],[86,98,144,227,1190,2133,2792],[86,98,144,227,2162,2172,2795],[98,144,227,1190,2790,2794],[86,98,144,227,2162,2172,2794],[98,144,227,1190,2790],[98,144,227,2162,2172,2776],[98,144,227,1190,2765],[86,98,144,227,2759,2765],[98,144,227,2172,2718,3812],[98,144,227,2718],[86,98,144,227,1190,2133,2713,2718,2760,3812],[98,144,227,2137,2162,2172,2759,3906,4383],[98,144,227,524,2137,2759,2779],[86,98,144,227,1298,2755,3124,3150,3828],[98,144,227,2162,2172,2757],[98,144,227,1190,2782],[98,144,227,2782,2783,2788],[98,144,227,2782],[86,98,144,227,1190,2782,2784,2785],[86,98,144,227,1190,2133,2782,2786],[98,144,227,2172,2759,2783],[98,144,227,1190,2759,2783,2787],[98,144,227,2759,2782],[98,144,227,2162,2172,2758],[98,144,227,2713],[86,98,144,227,1190,2625],[86,98,144,227,2141,2183,2547],[86,98,144,227,2162,2172,2176,3822],[86,98,144,227,1190,1196,1298,2137,2141,2176,2180,2206,2547,2592,2600,2601,3816,3817,3818,3819,3821],[98,144,227,1190,1196,1298,2133,2437,2547,2755,3124,3150,3828],[98,144,227,1196,2162,2172,3819,3821],[86,98,144,227,1190,1196,1298,2437,2580,2755,3124,3150,3717,3718,3719,3819,3820,3828],[98,144,227,2162,2172,3820,3906],[86,98,144,227,1190,1298,2137,2141,2180,2206,2437,2547,2565,2580,2600,3815],[98,144,227,1195,2141,2142,2162,2172,2472,3185,3784,3785,3907],[86,98,144,227,1190,1195,1298,2133,2141,2437,2472,2492,2547,2599,2714,2716,2755,3124,3150,3184,3193,3784,3828],[86,98,144,227,1190,1191,2137],[86,98,144,227,1193,2141,2177,2178,2180],[98,144,227,2176],[86,98,144,227,2141],[98,144,227,2809],[98,144,227,2805,2806,2807,2808,2810],[86,98,144,227,1191,2141,2162,2172,2176,2814],[98,144,227,1191,2141,2176],[98,144,227,2141,2162,2172,3093,3615],[86,98,144,227,2137,2141,2822,3089,3093],[86,98,144,227,2138,2141],[86,98,144,227,1192,2137,2141,2811,2822,3089,3093],[86,98,144,227,2137,2141,2811,2822,3089,3093],[86,98,144,227,2141,2182],[98,144,227,2438,2439],[98,144,227,2139,2172],[98,144,227,2140,2172],[98,144,227,831],[98,144,227,1192,1193,2172],[98,144,227,1192],[98,144,227,2137,2172,2547],[98,144,227,2137],[98,144,227,2172,2822],[98,144,227,2172,2177,2178],[98,144,227,2177],[98,144,227,2172,3079],[98,144,227,3078],[98,144,227,2172,3081],[98,144,227,2172,2185],[98,144,227,2172,3084],[98,144,227,1192,2172],[98,144,227,2172,2604],[98,144,227,2172,2615],[98,144,227,2141,2172,2508],[98,144,227,2179],[98,144,227,2141,2172,2180],[98,144,227,1195,2172,2649],[98,144,227,2134,2172],[86,98,144,227,2162,2172,2176,2179,3109,3823],[98,144,227,2172,3099],[86,98,144,227,1298,2162,2172],[86,98,144,227,2162,2176],[98,144,227,2172,2183,2672,3662,3907],[98,144,165,227,608]],"fileInfos":[{"version":"c430d44666289dae81f30fa7b2edebf186ecc91a2d4c71266ea6ae76388792e1","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","impliedFormat":1},{"version":"ee7bad0c15b58988daa84371e0b89d313b762ab83cb5b31b8a2d1162e8eb41c2","impliedFormat":1},{"version":"27bdc30a0e32783366a5abeda841bc22757c1797de8681bbe81fbc735eeb1c10","impliedFormat":1},{"version":"8fd575e12870e9944c7e1d62e1f5a73fcf23dd8d3a321f2a2c74c20d022283fe","impliedFormat":1},{"version":"2ab096661c711e4a81cc464fa1e6feb929a54f5340b46b0a07ac6bbf857471f0","impliedFormat":1},{"version":"080941d9f9ff9307f7e27a83bcd888b7c8270716c39af943532438932ec1d0b9","affectsGlobalScope":true,"impliedFormat":1},{"version":"2e80ee7a49e8ac312cc11b77f1475804bee36b3b2bc896bead8b6e1266befb43","affectsGlobalScope":true,"impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fb0f136d372979348d59b3f5020b4cdb81b5504192b1cacff5d1fbba29378aa1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"a680117f487a4d2f30ea46f1b4b7f58bef1480456e18ba53ee85c2746eeca012","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"d6d7ae4d1f1f3772e2a3cde568ed08991a8ae34a080ff1151af28b7f798e22ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true,"impliedFormat":1},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true,"impliedFormat":1},{"version":"959d36cddf5e7d572a65045b876f2956c973a586da58e5d26cde519184fd9b8a","affectsGlobalScope":true,"impliedFormat":1},{"version":"965f36eae237dd74e6cca203a43e9ca801ce38824ead814728a2807b1910117d","affectsGlobalScope":true,"impliedFormat":1},{"version":"3925a6c820dcb1a06506c90b1577db1fdbf7705d65b62b99dce4be75c637e26b","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a3d63ef2b853447ec4f749d3f368ce642264246e02911fcb1590d8c161b8005","affectsGlobalScope":true,"impliedFormat":1},{"version":"8cdf8847677ac7d20486e54dd3fcf09eda95812ac8ace44b4418da1bbbab6eb8","affectsGlobalScope":true,"impliedFormat":1},{"version":"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a","affectsGlobalScope":true,"impliedFormat":1},{"version":"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004","affectsGlobalScope":true,"impliedFormat":1},{"version":"b4b67b1a91182421f5df999988c690f14d813b9850b40acd06ed44691f6727ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"df83c2a6c73228b625b0beb6669c7ee2a09c914637e2d35170723ad49c0f5cd4","affectsGlobalScope":true,"impliedFormat":1},{"version":"436aaf437562f276ec2ddbee2f2cdedac7664c1e4c1d2c36839ddd582eeb3d0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e3c06ea092138bf9fa5e874a1fdbc9d54805d074bee1de31b99a11e2fec239d","affectsGlobalScope":true,"impliedFormat":1},{"version":"87dc0f382502f5bbce5129bdc0aea21e19a3abbc19259e0b43ae038a9fc4e326","affectsGlobalScope":true,"impliedFormat":1},{"version":"b1cb28af0c891c8c96b2d6b7be76bd394fddcfdb4709a20ba05a7c1605eea0f9","affectsGlobalScope":true,"impliedFormat":1},{"version":"2fef54945a13095fdb9b84f705f2b5994597640c46afeb2ce78352fab4cb3279","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac77cb3e8c6d3565793eb90a8373ee8033146315a3dbead3bde8db5eaf5e5ec6","affectsGlobalScope":true,"impliedFormat":1},{"version":"56e4ed5aab5f5920980066a9409bfaf53e6d21d3f8d020c17e4de584d29600ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ece9f17b3866cc077099c73f4983bddbcb1dc7ddb943227f1ec070f529dedd1","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a6282c8827e4b9a95f4bf4f5c205673ada31b982f50572d27103df8ceb8013c","affectsGlobalScope":true,"impliedFormat":1},{"version":"1c9319a09485199c1f7b0498f2988d6d2249793ef67edda49d1e584746be9032","affectsGlobalScope":true,"impliedFormat":1},{"version":"e3a2a0cee0f03ffdde24d89660eba2685bfbdeae955a6c67e8c4c9fd28928eeb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811c71eee4aa0ac5f7adf713323a5c41b0cf6c4e17367a34fbce379e12bbf0a4","affectsGlobalScope":true,"impliedFormat":1},{"version":"51ad4c928303041605b4d7ae32e0c1ee387d43a24cd6f1ebf4a2699e1076d4fa","affectsGlobalScope":true,"impliedFormat":1},{"version":"60037901da1a425516449b9a20073aa03386cce92f7a1fd902d7602be3a7c2e9","affectsGlobalScope":true,"impliedFormat":1},{"version":"d4b1d2c51d058fc21ec2629fff7a76249dec2e36e12960ea056e3ef89174080f","affectsGlobalScope":true,"impliedFormat":1},{"version":"22adec94ef7047a6c9d1af3cb96be87a335908bf9ef386ae9fd50eeb37f44c47","affectsGlobalScope":true,"impliedFormat":1},{"version":"196cb558a13d4533a5163286f30b0509ce0210e4b316c56c38d4c0fd2fb38405","affectsGlobalScope":true,"impliedFormat":1},{"version":"73f78680d4c08509933daf80947902f6ff41b6230f94dd002ae372620adb0f60","affectsGlobalScope":true,"impliedFormat":1},{"version":"c5239f5c01bcfa9cd32f37c496cf19c61d69d37e48be9de612b541aac915805b","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"0bd5e7096c7bc02bf70b2cc017fc45ef489cb19bd2f32a71af39ff5787f1b56a","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac51dd7d31333793807a6abaa5ae168512b6131bd41d9c5b98477fc3b7800f9f","impliedFormat":1},{"version":"87d9d29dbc745f182683f63187bf3d53fd8673e5fca38ad5eaab69798ed29fbc","impliedFormat":1},{"version":"e6f3077b1780226627f76085397d10c77a4d851c7154fd4b3f1eb114f4c2e56d","affectsGlobalScope":true,"impliedFormat":1},{"version":"acd8fd5090ac73902278889c38336ff3f48af6ba03aa665eb34a75e7ba1dccc4","impliedFormat":1},{"version":"d6258883868fb2680d2ca96bc8b1352cab69874581493e6d52680c5ffecdb6cc","impliedFormat":1},{"version":"1b61d259de5350f8b1e5db06290d31eaebebc6baafd5f79d314b5af9256d7153","impliedFormat":1},{"version":"f258e3960f324a956fc76a3d3d9e964fff2244ff5859dcc6ce5951e5413ca826","impliedFormat":1},{"version":"643f7232d07bf75e15bd8f658f664d6183a0efaca5eb84b48201c7671a266979","impliedFormat":1},{"version":"21da358700a3893281ce0c517a7a30cbd46be020d9f0c3f2834d0a8ad1f5fc75","impliedFormat":1},{"version":"70521b6ab0dcba37539e5303104f29b721bfb2940b2776da4cc818c07e1fefc1","affectsGlobalScope":true,"impliedFormat":1},{"version":"ab41ef1f2cdafb8df48be20cd969d875602483859dc194e9c97c8a576892c052","affectsGlobalScope":true,"impliedFormat":1},{"version":"d153a11543fd884b596587ccd97aebbeed950b26933ee000f94009f1ab142848","affectsGlobalScope":true,"impliedFormat":1},{"version":"21d819c173c0cf7cc3ce57c3276e77fd9a8a01d35a06ad87158781515c9a438a","impliedFormat":1},{"version":"98cffbf06d6bab333473c70a893770dbe990783904002c4f1a960447b4b53dca","affectsGlobalScope":true,"impliedFormat":1},{"version":"ba481bca06f37d3f2c137ce343c7d5937029b2468f8e26111f3c9d9963d6568d","affectsGlobalScope":true,"impliedFormat":1},{"version":"6d9ef24f9a22a88e3e9b3b3d8c40ab1ddb0853f1bfbd5c843c37800138437b61","affectsGlobalScope":true,"impliedFormat":1},{"version":"1db0b7dca579049ca4193d034d835f6bfe73096c73663e5ef9a0b5779939f3d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","affectsGlobalScope":true,"impliedFormat":1},{"version":"f26b11d8d8e4b8028f1c7d618b22274c892e4b0ef5b3678a8ccbad85419aef43","affectsGlobalScope":true,"impliedFormat":1},{"version":"5929864ce17fba74232584d90cb721a89b7ad277220627cc97054ba15a98ea8f","impliedFormat":1},{"version":"763fe0f42b3d79b440a9b6e51e9ba3f3f91352469c1e4b3b67bfa4ff6352f3f4","impliedFormat":1},{"version":"25c8056edf4314820382a5fdb4bb7816999acdcb929c8f75e3f39473b87e85bc","impliedFormat":1},{"version":"c464d66b20788266e5353b48dc4aa6bc0dc4a707276df1e7152ab0c9ae21fad8","impliedFormat":1},{"version":"78d0d27c130d35c60b5e5566c9f1e5be77caf39804636bc1a40133919a949f21","impliedFormat":1},{"version":"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","impliedFormat":1},{"version":"1d6e127068ea8e104a912e42fc0a110e2aa5a66a356a917a163e8cf9a65e4a75","impliedFormat":1},{"version":"5ded6427296cdf3b9542de4471d2aa8d3983671d4cac0f4bf9c637208d1ced43","impliedFormat":1},{"version":"7f182617db458e98fc18dfb272d40aa2fff3a353c44a89b2c0ccb3937709bfb5","impliedFormat":1},{"version":"cadc8aced301244057c4e7e73fbcae534b0f5b12a37b150d80e5a45aa4bebcbd","impliedFormat":1},{"version":"385aab901643aa54e1c36f5ef3107913b10d1b5bb8cbcd933d4263b80a0d7f20","impliedFormat":1},{"version":"9670d44354bab9d9982eca21945686b5c24a3f893db73c0dae0fd74217a4c219","impliedFormat":1},{"version":"0b8a9268adaf4da35e7fa830c8981cfa22adbbe5b3f6f5ab91f6658899e657a7","impliedFormat":1},{"version":"11396ed8a44c02ab9798b7dca436009f866e8dae3c9c25e8c1fbc396880bf1bb","impliedFormat":1},{"version":"ba7bc87d01492633cb5a0e5da8a4a42a1c86270e7b3d2dea5d156828a84e4882","impliedFormat":1},{"version":"4893a895ea92c85345017a04ed427cbd6a1710453338df26881a6019432febdd","impliedFormat":1},{"version":"c21dc52e277bcfc75fac0436ccb75c204f9e1b3fa5e12729670910639f27343e","impliedFormat":1},{"version":"13f6f39e12b1518c6650bbb220c8985999020fe0f21d818e28f512b7771d00f9","impliedFormat":1},{"version":"9b5369969f6e7175740bf51223112ff209f94ba43ecd3bb09eefff9fd675624a","impliedFormat":1},{"version":"4fe9e626e7164748e8769bbf74b538e09607f07ed17c2f20af8d680ee49fc1da","impliedFormat":1},{"version":"24515859bc0b836719105bb6cc3d68255042a9f02a6022b3187948b204946bd2","impliedFormat":1},{"version":"ea0148f897b45a76544ae179784c95af1bd6721b8610af9ffa467a518a086a43","impliedFormat":1},{"version":"24c6a117721e606c9984335f71711877293a9651e44f59f3d21c1ea0856f9cc9","impliedFormat":1},{"version":"dd3273ead9fbde62a72949c97dbec2247ea08e0c6952e701a483d74ef92d6a17","impliedFormat":1},{"version":"405822be75ad3e4d162e07439bac80c6bcc6dbae1929e179cf467ec0b9ee4e2e","impliedFormat":1},{"version":"0db18c6e78ea846316c012478888f33c11ffadab9efd1cc8bcc12daded7a60b6","impliedFormat":1},{"version":"e61be3f894b41b7baa1fbd6a66893f2579bfad01d208b4ff61daef21493ef0a8","impliedFormat":1},{"version":"bd0532fd6556073727d28da0edfd1736417a3f9f394877b6d5ef6ad88fba1d1a","impliedFormat":1},{"version":"89167d696a849fce5ca508032aabfe901c0868f833a8625d5a9c6e861ef935d2","impliedFormat":1},{"version":"615ba88d0128ed16bf83ef8ccbb6aff05c3ee2db1cc0f89ab50a4939bfc1943f","impliedFormat":1},{"version":"a4d551dbf8746780194d550c88f26cf937caf8d56f102969a110cfaed4b06656","impliedFormat":1},{"version":"8bd86b8e8f6a6aa6c49b71e14c4ffe1211a0e97c80f08d2c8cc98838006e4b88","impliedFormat":1},{"version":"317e63deeb21ac07f3992f5b50cdca8338f10acd4fbb7257ebf56735bf52ab00","impliedFormat":1},{"version":"4732aec92b20fb28c5fe9ad99521fb59974289ed1e45aecb282616202184064f","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"bf67d53d168abc1298888693338cb82854bdb2e69ef83f8a0092093c2d562107","impliedFormat":1},{"version":"b52476feb4a0cbcb25e5931b930fc73cb6643fb1a5060bf8a3dda0eeae5b4b68","affectsGlobalScope":true,"impliedFormat":1},{"version":"e2677634fe27e87348825bb041651e22d50a613e2fdf6a4a3ade971d71bac37e","impliedFormat":1},{"version":"7394959e5a741b185456e1ef5d64599c36c60a323207450991e7a42e08911419","impliedFormat":1},{"version":"8c0bcd6c6b67b4b503c11e91a1fb91522ed585900eab2ab1f61bba7d7caa9d6f","impliedFormat":1},{"version":"8cd19276b6590b3ebbeeb030ac271871b9ed0afc3074ac88a94ed2449174b776","affectsGlobalScope":true,"impliedFormat":1},{"version":"696eb8d28f5949b87d894b26dc97318ef944c794a9a4e4f62360cd1d1958014b","impliedFormat":1},{"version":"3f8fa3061bd7402970b399300880d55257953ee6d3cd408722cb9ac20126460c","impliedFormat":1},{"version":"35ec8b6760fd7138bbf5809b84551e31028fb2ba7b6dc91d95d098bf212ca8b4","affectsGlobalScope":true,"impliedFormat":1},{"version":"5524481e56c48ff486f42926778c0a3cce1cc85dc46683b92b1271865bcf015a","impliedFormat":1},{"version":"68bd56c92c2bd7d2339457eb84d63e7de3bd56a69b25f3576e1568d21a162398","affectsGlobalScope":true,"impliedFormat":1},{"version":"3e93b123f7c2944969d291b35fed2af79a6e9e27fdd5faa99748a51c07c02d28","impliedFormat":1},{"version":"9d19808c8c291a9010a6c788e8532a2da70f811adb431c97520803e0ec649991","impliedFormat":1},{"version":"87aad3dd9752067dc875cfaa466fc44246451c0c560b820796bdd528e29bef40","impliedFormat":1},{"version":"4aacb0dd020eeaef65426153686cc639a78ec2885dc72ad220be1d25f1a439df","impliedFormat":1},{"version":"f0bd7e6d931657b59605c44112eaf8b980ba7f957a5051ed21cb93d978cf2f45","impliedFormat":1},{"version":"8db0ae9cb14d9955b14c214f34dae1b9ef2baee2fe4ce794a4cd3ac2531e3255","affectsGlobalScope":true,"impliedFormat":1},{"version":"15fc6f7512c86810273af28f224251a5a879e4261b4d4c7e532abfbfc3983134","impliedFormat":1},{"version":"58adba1a8ab2d10b54dc1dced4e41f4e7c9772cbbac40939c0dc8ce2cdb1d442","impliedFormat":1},{"version":"641942a78f9063caa5d6b777c99304b7d1dc7328076038c6d94d8a0b81fc95c1","impliedFormat":1},{"version":"714435130b9015fae551788df2a88038471a5a11eb471f27c4ede86552842bc9","impliedFormat":1},{"version":"855cd5f7eb396f5f1ab1bc0f8580339bff77b68a770f84c6b254e319bbfd1ac7","impliedFormat":1},{"version":"5650cf3dace09e7c25d384e3e6b818b938f68f4e8de96f52d9c5a1b3db068e86","impliedFormat":1},{"version":"1354ca5c38bd3fd3836a68e0f7c9f91f172582ba30ab15bb8c075891b91502b7","affectsGlobalScope":true,"impliedFormat":1},{"version":"27fdb0da0daf3b337c5530c5f266efe046a6ceb606e395b346974e4360c36419","impliedFormat":1},{"version":"2d2fcaab481b31a5882065c7951255703ddbe1c0e507af56ea42d79ac3911201","impliedFormat":1},{"version":"a192fe8ec33f75edbc8d8f3ed79f768dfae11ff5735e7fe52bfa69956e46d78d","impliedFormat":1},{"version":"ca867399f7db82df981d6915bcbb2d81131d7d1ef683bc782b59f71dda59bc85","affectsGlobalScope":true,"impliedFormat":1},{"version":"372413016d17d804e1d139418aca0c68e47a83fb6669490857f4b318de8cccb3","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e043a1bc8fbf2a255bccf9bf27e0f1caf916c3b0518ea34aa72357c0afd42ec","impliedFormat":1},{"version":"b4f70ec656a11d570e1a9edce07d118cd58d9760239e2ece99306ee9dfe61d02","impliedFormat":1},{"version":"3bc2f1e2c95c04048212c569ed38e338873f6a8593930cf5a7ef24ffb38fc3b6","impliedFormat":1},{"version":"6e70e9570e98aae2b825b533aa6292b6abd542e8d9f6e9475e88e1d7ba17c866","impliedFormat":1},{"version":"f9d9d753d430ed050dc1bf2667a1bab711ccbb1c1507183d794cc195a5b085cc","impliedFormat":1},{"version":"9eece5e586312581ccd106d4853e861aaaa1a39f8e3ea672b8c3847eedd12f6e","impliedFormat":1},{"version":"47ab634529c5955b6ad793474ae188fce3e6163e3a3fb5edd7e0e48f14435333","impliedFormat":1},{"version":"37ba7b45141a45ce6e80e66f2a96c8a5ab1bcef0fc2d0f56bb58df96ec67e972","impliedFormat":1},{"version":"45650f47bfb376c8a8ed39d4bcda5902ab899a3150029684ee4c10676d9fbaee","impliedFormat":1},{"version":"fad4e3c207fe23922d0b2d06b01acbfb9714c4f2685cf80fd384c8a100c82fd0","affectsGlobalScope":true,"impliedFormat":1},{"version":"74cf591a0f63db318651e0e04cb55f8791385f86e987a67fd4d2eaab8191f730","impliedFormat":1},{"version":"5eab9b3dc9b34f185417342436ec3f106898da5f4801992d8ff38ab3aff346b5","impliedFormat":1},{"version":"12ed4559eba17cd977aa0db658d25c4047067444b51acfdcbf38470630642b23","affectsGlobalScope":true,"impliedFormat":1},{"version":"f3ffabc95802521e1e4bcba4c88d8615176dc6e09111d920c7a213bdda6e1d65","impliedFormat":1},{"version":"809821b8a065e3234a55b3a9d7846231ed18d66dd749f2494c66288d890daf7f","impliedFormat":1},{"version":"ae56f65caf3be91108707bd8dfbccc2a57a91feb5daabf7165a06a945545ed26","impliedFormat":1},{"version":"a136d5de521da20f31631a0a96bf712370779d1c05b7015d7019a9b2a0446ca9","impliedFormat":1},{"version":"c3b41e74b9a84b88b1dca61ec39eee25c0dbc8e7d519ba11bb070918cfacf656","affectsGlobalScope":true,"impliedFormat":1},{"version":"4737a9dc24d0e68b734e6cfbcea0c15a2cfafeb493485e27905f7856988c6b29","affectsGlobalScope":true,"impliedFormat":1},{"version":"36d8d3e7506b631c9582c251a2c0b8a28855af3f76719b12b534c6edf952748d","impliedFormat":1},{"version":"1ca69210cc42729e7ca97d3a9ad48f2e9cb0042bada4075b588ae5387debd318","impliedFormat":1},{"version":"f5ebe66baaf7c552cfa59d75f2bfba679f329204847db3cec385acda245e574e","impliedFormat":1},{"version":"ed59add13139f84da271cafd32e2171876b0a0af2f798d0c663e8eeb867732cf","affectsGlobalScope":true,"impliedFormat":1},{"version":"b7c5e2ea4a9749097c347454805e933844ed207b6eefec6b7cfd418b5f5f7b28","impliedFormat":1},{"version":"b1810689b76fd473bd12cc9ee219f8e62f54a7d08019a235d07424afbf074d25","impliedFormat":1},{"version":"2b2bef0fbee391adb55bcd1fa38edf99e87233a94af47c30951d1b641fc46538","impliedFormat":1},{"version":"f21af9796e3aa1fe83b3d3e3b401ad4e15e39c15e8e0dab3bb946794b4d2e63f","impliedFormat":1},{"version":"17ed71200119e86ccef2d96b73b02ce8854b76ad6bd21b5021d4269bec527b5f","impliedFormat":1},{"version":"1cfa8647d7d71cb03847d616bd79320abfc01ddea082a49569fda71ac5ece66b","impliedFormat":1},{"version":"bb7a61dd55dc4b9422d13da3a6bb9cc5e89be888ef23bbcf6558aa9726b89a1c","impliedFormat":1},{"version":"413df52d4ea14472c2fa5bee62f7a40abd1eb49be0b9722ee01ee4e52e63beb2","impliedFormat":1},{"version":"db6d2d9daad8a6d83f281af12ce4355a20b9a3e71b82b9f57cddcca0a8964a96","impliedFormat":1},{"version":"446a50749b24d14deac6f8843e057a6355dd6437d1fac4f9e5ce4a5071f34bff","impliedFormat":1},{"version":"182e9fcbe08ac7c012e0a6e2b5798b4352470be29a64fdc114d23c2bab7d5106","impliedFormat":1},{"version":"2f4e6b4d39426a1b85ecf4bdeb9dddbf4d9b3397d95d8555d46f925c9519ec7d","impliedFormat":1},{"version":"78a2869ad0cbf3f9045dda08c0d4562b7e1b2bfe07b19e0db072f5c3c56e9584","impliedFormat":1},{"version":"89d5d28d4f57e000b836ac273079be1b75710e28ce14750d081fb420d37e2ca5","impliedFormat":1},{"version":"fd4e24ccff3966390600d7f5d6aa1fed5a512e92ada735ea5fbc933d313ad3d3","impliedFormat":1},{"version":"b7cddfe1aa6b86b5fad3c9ccb30d05b3ccb165aebbf112f48d2d8a5f69dd98b1","impliedFormat":1},{"version":"a86f82d646a739041d6702101afa82dcb935c416dd93cbca7fd754fd0282ce1f","impliedFormat":1},{"version":"ad0d1d75d129b1c80f911be438d6b61bfa8703930a8ff2be2f0e1f8a91841c64","impliedFormat":1},{"version":"bd2c7ada3dee03653d3f601011d30072194bc3970cd93208f9588fbdc0c69347","impliedFormat":1},{"version":"e480da45d32313e7174b265674da504f075f59ef326852f0c5a5d863b438ae85","impliedFormat":1},{"version":"ad54850f61fcf5d014e11be80d2f46fea9265cfa7e77456da876f7833ef81769","impliedFormat":1},{"version":"6f7c9e8bd2b5b6a080b07080065f94900bd3c7e5ebbd3047bc33fcce2fab1dd8","impliedFormat":1},{"version":"3e7efde639c6a6c3edb9847b3f61e308bf7a69685b92f665048c45132f51c218","impliedFormat":1},{"version":"df45ca1176e6ac211eae7ddf51336dc075c5314bc5c253651bae639defd5eec5","impliedFormat":1},{"version":"8a0e762ceb20c7e72504feef83d709468a70af4abccb304f32d6b9bac1129b2c","impliedFormat":1},{"version":"da5950ee2a90721df6f3fba45f5d05308f7e4c35835392215dd2cd404505e2de","impliedFormat":1},{"version":"ce75b1aebb33d510ff28af960a9221410a3eaf7f18fc5f21f9404075fba77256","impliedFormat":1},{"version":"f42d5fed19610d485c646a0c430e768115567d078c7fc855c57b0c578b3d6cd3","impliedFormat":1},{"version":"ee8df1cb8d0faaca4013a1b442e99130769ce06f438d18d510fed95890067563","impliedFormat":1},{"version":"d5630f2ad9b4541e5ce891648121022f9412ecdca1820baa1f0104f70fd7eff7","impliedFormat":1},{"version":"4d15375ab13497104bc8fe56fdef2b5fd6853f29255737d23a33fa306ff7fd69","impliedFormat":1},{"version":"2cd3fc1d0d6a1e85baffd2d4f50f5efb192b5446eef567e97c94765402f0aad4","impliedFormat":1},{"version":"e4cbf2f1e89ecccaddd2c045e600ae41b732295953fb06247c7dcbc2d281ed30","impliedFormat":1},{"version":"6dcedaef57dff0d79a05ab0ab602cde74db803d1e765468bf91263786a383e1b","impliedFormat":1},{"version":"8c1697d90c394a6fd955b98eae01238eff628e129b987a68aea10f898a48e7da","impliedFormat":1},{"version":"7580e62139cb2b44a0270c8d01abcbfcba2819a02514a527342447fa69b34ef1","impliedFormat":1},{"version":"2879a055439b6c0c0132a1467120a0f85b56b5d735c973ad235acd958b1b5345","impliedFormat":1},{"version":"f374cb24e93e7798c4d9e83ff872fa52d2cdb36306392b840a6ddf46cb925cb6","impliedFormat":1},{"version":"d10d63718e1646c2279e3b33831f82c60e31f622b2b7020f1196409ca4c09242","impliedFormat":1},{"version":"106c6025f1d99fd468fd8bf6e5bda724e11e5905a4076c5d29790b6c3745e50c","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"148679c6d0f449210a96e7d2e562d589e56fcde87f843a92808b3ff103f1a774","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"02436d7e9ead85e09a2f8e27d5f47d9464bced31738dec138ca735390815c9f0","impliedFormat":1},{"version":"f8d5ff8eafd37499f2b6a98659dd9b45a321de186b8db6b6142faed0fea3de77","impliedFormat":1},{"version":"c86fe861cf1b4c46a0fb7d74dffe596cf679a2e5e8b1456881313170f092e3fa","impliedFormat":1},{"version":"a22dd55aa4d39906252000ab8e8a1b83b195eef7f4274eb51e457c1f11cf6580","impliedFormat":1},{"version":"540cc83ab772a2c6bc509fe1354f314825b5dba3669efdfbe4693ecd3048e34f","impliedFormat":1},{"version":"121b0696021ab885c570bbeb331be8ad82c6efe2f3b93a6e63874901bebc13e3","impliedFormat":1},{"version":"612d9da66bb046a9c1e2e8d026245ded881fc4b9f98cbfae714415d57ee0ae0b","impliedFormat":1},{"version":"32c2ad9494dad5d11b0564a619fee18f388db6c1e9e2cd3c360b3122549691eb","impliedFormat":1},{"version":"6c301d40aec56a74ec7bd7324e31a728dadf9bfba3e96def02938d3d973534ec","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881","impliedFormat":1},{"version":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881","impliedFormat":1},{"version":"aa14cee20aa0db79f8df101fc027d929aec10feb5b8a8da3b9af3895d05b7ba2","impliedFormat":1},{"version":"493c700ac3bd317177b2eb913805c87fe60d4e8af4fb39c41f04ba81fae7e170","impliedFormat":1},{"version":"aeb554d876c6b8c818da2e118d8b11e1e559adbe6bf606cc9a611c1b6c09f670","impliedFormat":1},{"version":"acf5a2ac47b59ca07afa9abbd2b31d001bf7448b041927befae2ea5b1951d9f9","impliedFormat":1},{"version":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881","impliedFormat":1},{"version":"d71291eff1e19d8762a908ba947e891af44749f3a2cbc5bd2ec4b72f72ea795f","impliedFormat":1},{"version":"c0480e03db4b816dff2682b347c95f2177699525c54e7e6f6aa8ded890b76be7","impliedFormat":1},{"version":"25a5f6fd3a2243c859eddc99ab5fba11d970af2fe7a5df9c32b7668f76f97b01","impliedFormat":1},{"version":"8d207e1f9d2c30d6f77dfa693f3827c3fbf0d89240297e10bdfe1041d433df68","impliedFormat":1},{"version":"b620391fe8060cf9bedc176a4d01366e6574d7a71e0ac0ab344a4e76576fcbb8","impliedFormat":1},{"version":"6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff","impliedFormat":1},{"version":"2652448ac55a2010a1f71dd141f828b682298d39728f9871e1cdf8696ef443fd","impliedFormat":1},{"version":"d682336018141807fb602709e2d95a192828fcb8d5ba06dda3833a8ea98f69e3","impliedFormat":1},{"version":"6124e973eab8c52cabf3c07575204efc1784aca6b0a30c79eb85fe240a857efa","impliedFormat":1},{"version":"0d891735a21edc75df51f3eb995e18149e119d1ce22fd40db2b260c5960b914e","impliedFormat":1},{"version":"3b414b99a73171e1c4b7b7714e26b87d6c5cb03d200352da5342ab4088a54c85","impliedFormat":1},{"version":"4fbd3116e00ed3a6410499924b6403cc9367fdca303e34838129b328058ede40","impliedFormat":1},{"version":"9c82171d836c47486074e4ca8e059735bf97b205e70b196535b5efd40cbe1bc5","impliedFormat":1},{"version":"8c70ddc0c22d85e56011d49fddfaae3405eb53d47b59327b9dd589e82df672e7","impliedFormat":1},{"version":"2f9c89cbb29d362290531b48880a4024f258c6033aaeb7e59fbc62db26819650","impliedFormat":1},{"version":"a365c4d3bed3be4e4e20793c999c51f5cd7e6792322f14650949d827fbcd170f","impliedFormat":1},{"version":"c5426dbfc1cf90532f66965a7aa8c1136a78d4d0f96d8180ecbfc11d7722f1a5","impliedFormat":1},{"version":"65a15fc47900787c0bd18b603afb98d33ede930bed1798fc984d5ebb78b26cf9","impliedFormat":1},{"version":"9d202701f6e0744adb6314d03d2eb8fc994798fc83d91b691b75b07626a69801","impliedFormat":1},{"version":"de9d2df7663e64e3a91bf495f315a7577e23ba088f2949d5ce9ec96f44fba37d","impliedFormat":1},{"version":"c7af78a2ea7cb1cd009cfb5bdb48cd0b03dad3b54f6da7aab615c2e9e9d570c5","impliedFormat":1},{"version":"1ee45496b5f8bdee6f7abc233355898e5bf9bd51255db65f5ff7ede617ca0027","impliedFormat":1},{"version":"273782b8454e78f6a8b30d2cfbf6860499c930595095fcc1689637115f0eddda","affectsGlobalScope":true,"impliedFormat":1},{"version":"3fbdd025f9d4d820414417eeb4107ffa0078d454a033b506e22d3a23bc3d9c41","affectsGlobalScope":true,"impliedFormat":1},{"version":"dba114fb6a32b355a9cfc26ca2276834d72fe0e94cd2c3494005547025015369","impliedFormat":1},{"version":"a8f8e6ab2fa07b45251f403548b78eaf2022f3c2254df3dc186cb2671fe4996d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fa6c12a7c0f6b84d512f200690bfc74819e99efae69e4c95c4cd30f6884c526e","impliedFormat":1},{"version":"f1c32f9ce9c497da4dc215c3bc84b722ea02497d35f9134db3bb40a8d918b92b","impliedFormat":1},{"version":"b73c319af2cc3ef8f6421308a250f328836531ea3761823b4cabbd133047aefa","affectsGlobalScope":true,"impliedFormat":1},{"version":"e433b0337b8106909e7953015e8fa3f2d30797cea27141d1c5b135365bb975a6","impliedFormat":1},{"version":"9f9bb6755a8ce32d656ffa4763a8144aa4f274d6b69b59d7c32811031467216e","impliedFormat":1},{"version":"5c32bdfbd2d65e8fffbb9fbda04d7165e9181b08dad61154961852366deb7540","impliedFormat":1},{"version":"ddff7fc6edbdc5163a09e22bf8df7bef75f75369ebd7ecea95ba55c4386e2441","impliedFormat":1},{"version":"0c05e9842ec4f8b7bfebfd3ca61604bb8c914ba8da9b5337c4f25da427a005f2","impliedFormat":1},{"version":"faed7a5153215dbd6ebe76dfdcc0af0cfe760f7362bed43284be544308b114cf","impliedFormat":1},{"version":"7029e566b8df176f703fb59fd437a38670c7a0e02c58b2d66dfb5b2e2b2defdb","impliedFormat":1},{"version":"7f2aa4d4989a82530aaac3f72b3dceca90e9c25bee0b1a327e8a08a1262435ad","impliedFormat":1},{"version":"d96b39301d0ded3f1a27b47759676a33a02f6f5049bfcbde81e533fd10f50dcb","impliedFormat":1},{"version":"e9f147ecca73d9346a4c073432843c159ccbe50bdcb678a78f6da10eae2cecf4","impliedFormat":1},{"version":"de061f7d72bd65c06fc1419f841dfdcb29a8e22fe6fa527d1e6eb20b897d4de0","impliedFormat":1},{"version":"663beafc2446079574570cba86e9b15f986f908ddb1b01274509970126fee945","impliedFormat":1},{"version":"a3102887d5058bf4cb5b37fa6964c09e9527c42053b3b5c642b89878620748de","impliedFormat":1},{"version":"0aaaa1727edd29673d85c9b26d7ca4d54e5407a48586903c51b48b7f7d196f61","impliedFormat":1},{"version":"d35bca0b261bff02635758c48e8ab99c61c420d0dfabbcf467e847171d876b7d","impliedFormat":1},{"version":"3bc12c40d90c342ff88a3d876996c555ed5cbee5fe8c3308a240b321f401ee46","impliedFormat":1},{"version":"ba130768aae855a5477e9e148e5c879548e6e7ccbcc56fd1934c8a18ea5b7569","impliedFormat":1},{"version":"2e4f37ffe8862b14d8e24ae8763daaa8340c0df0b859d9a9733def0eee7562d9","impliedFormat":1},{"version":"d38530db0601215d6d767f280e3a3c54b2a83b709e8d9001acb6f61c67e965fc","impliedFormat":1},{"version":"6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff","impliedFormat":1},{"version":"b499af2054a037a162b3b72cd886f48bbf32a3502c865c6e29fac7d2ab3ce0b5","impliedFormat":1},{"version":"b83cb14474fa60c5f3ec660146b97d122f0735627f80d82dd03e8caa39b4388c","impliedFormat":1},{"version":"48773ca557b0319c2ee62ae249cf52a81709e8be139920d6479a66274de7c4ed","impliedFormat":1},{"version":"7274fbffbd7c9589d8d0ffba68157237afd5cecff1e99881ea3399127e60572f","impliedFormat":1},{"version":"b73cbf0a72c8800cf8f96a9acfe94f3ad32ca71342a8908b8ae484d61113f647","impliedFormat":1},{"version":"bae6dd176832f6423966647382c0d7ba9e63f8c167522f09a982f086cd4e8b23","impliedFormat":1},{"version":"20865ac316b8893c1a0cc383ccfc1801443fbcc2a7255be166cf90d03fac88c9","impliedFormat":1},{"version":"c9958eb32126a3843deedda8c22fb97024aa5d6dd588b90af2d7f2bfac540f23","impliedFormat":1},{"version":"461d0ad8ae5f2ff981778af912ba71b37a8426a33301daa00f21c6ccb27f8156","impliedFormat":1},{"version":"e927c2c13c4eaf0a7f17e6022eee8519eb29ef42c4c13a31e81a611ab8c95577","impliedFormat":1},{"version":"fcafff163ca5e66d3b87126e756e1b6dfa8c526aa9cd2a2b0a9da837d81bbd72","impliedFormat":1},{"version":"70246ad95ad8a22bdfe806cb5d383a26c0c6e58e7207ab9c431f1cb175aca657","impliedFormat":1},{"version":"f00f3aa5d64ff46e600648b55a79dcd1333458f7a10da2ed594d9f0a44b76d0b","impliedFormat":1},{"version":"772d8d5eb158b6c92412c03228bd9902ccb1457d7a705b8129814a5d1a6308fc","impliedFormat":1},{"version":"802e797bcab5663b2c9f63f51bdf67eff7c41bc64c0fd65e6da3e7941359e2f7","impliedFormat":1},{"version":"b01bd582a6e41457bc56e6f0f9de4cb17f33f5f3843a7cf8210ac9c18472fb0f","impliedFormat":1},{"version":"8b4327413e5af38cd8cb97c59f48c3c866015d5d642f28518e3a891c469f240e","impliedFormat":1},{"version":"4cceef18d7f088e797a463e90b7a9dad10c6bc667724b7686e3e740ae00122be","impliedFormat":1},{"version":"7ee86fbb3754388e004de0ef9e6505485ddfb3be7640783d6d015711c03d302d","impliedFormat":1},{"version":"cc1954b539604b1e562319119ac7e888172208b32ca873f9a357a92c826bd046","impliedFormat":1},{"version":"a67b87d0281c97dfc1197ef28dfe397fc2c865ccd41f7e32b53f647184cc7307","impliedFormat":1},{"version":"771ffb773f1ddd562492a6b9aaca648192ac3f056f0e1d997678ff97dbb6bf9b","impliedFormat":1},{"version":"43e96a3d5d1411ab40ba2f61d6a3192e58177bcf3b133a80ad2a16591611726d","impliedFormat":1},{"version":"232f70c0cf2b432f3a6e56a8dc3417103eb162292a9fd376d51a3a9ea5fbbf6f","impliedFormat":1},{"version":"bb8f2dbc03533abca2066ce4655c119bff353dd4514375beb93c08590c03e023","impliedFormat":1},{"version":"706dd95827e7ebaabda91d5db2b755233e0952d98570e9c032b0f066a15c1177","affectsGlobalScope":true,"impliedFormat":1},{"version":"0b103e9abfe82d14c0ad06a55d9f91d6747154ef7cacc73cf27ecad2bfb3afcf","impliedFormat":1},{"version":"cd9304972e6d616197fb44fce00540a904f38b54306a1951b5dbeaf3c01ab5bd","impliedFormat":1},{"version":"77438e2c397a3db78407621cfc57241a305b310ddea2c185f1d555248297f587","impliedFormat":1},{"version":"120599fd965257b1f4d0ff794bc696162832d9d8467224f4665f713a3119078b","impliedFormat":1},{"version":"43ba4f2fa8c698f5c304d21a3ef596741e8e85a810b7c1f9b692653791d8d97a","impliedFormat":1},{"version":"5433f33b0a20300cca35d2f229a7fc20b0e8477c44be2affeb21cb464af60c76","impliedFormat":1},{"version":"db036c56f79186da50af66511d37d9fe77fa6793381927292d17f81f787bb195","impliedFormat":1},{"version":"a6805fcafed712aea7759f8bc731014f9d22738c1d6ef9d43b8091d1d48346d5","impliedFormat":1},{"version":"c49469a5349b3cc1965710b5b0f98ed6c028686aa8450bcb3796728873eb923e","impliedFormat":1},{"version":"4a889f2c763edb4d55cb624257272ac10d04a1cad2ed2948b10ed4a7fda2a428","impliedFormat":1},{"version":"7bb79aa2fead87d9d56294ef71e056487e848d7b550c9a367523ee5416c44cfa","impliedFormat":1},{"version":"d88ea80a6447d7391f52352ec97e56b52ebec934a4a4af6e2464cfd8b39c3ba8","impliedFormat":1},{"version":"142617b3cdf902b69c6464c9fbd942b60ab3e733ca18c032b19e0f7e2adbefe8","impliedFormat":1},{"version":"0b603555f1881f87256ffd6344d3e3ed6d466c2e701eabf381f28be8c2125892","impliedFormat":1},{"version":"897e4f7662488e3ecc79e743bdd3b78f13bdb69a97851afa5b440c4211e32ea9","impliedFormat":1},{"version":"e2e1c6d3b2d93add5200bd7bc1a8cccb4e446836b2111ece45db8683a2c765de","impliedFormat":1},{"version":"251b03d5cd243854ce870d9a9a39f491faf69898c5d6b5eee28cc7649c57417b","impliedFormat":1},{"version":"27ff4196654e6373c9af16b6165120e2dd2169f9ad6abb5c935af5abd8c7938c","impliedFormat":1},{"version":"2c4de79f406d137390608e8c0a44fba2ff8e00bacfcae7c9d1781fef10e9440d","impliedFormat":1},{"version":"07ba23a10465791be5d22deaf5ef7de7658774ddff53721e5ea17fedea1bc721","impliedFormat":1},{"version":"dca8c645c5afeb03b1ecedbf16323f33e7d0afaa6256c8e047e6e38087a97f53","impliedFormat":1},{"version":"775f181bd4a533d6f8b5e55ec1d9f1624559720ae8a70e9432258da26b38d27c","impliedFormat":1},{"version":"796273b2edc72e78a04e86d7c58ae94d370ab93a0ddf40b1aa85a37a1c29ecd7","impliedFormat":1},{"version":"5df15a69187d737d6d8d066e189ae4f97e41f4d53712a46b2710ff9f8563ec9f","impliedFormat":1},{"version":"7715134a0cf07dd41a9da2895d708625a3a303a0385e355ecaaf0b8bfaef2550","impliedFormat":1},{"version":"6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff","impliedFormat":1},{"version":"622694a8522b46f6310c2a9b5d2530dde1e2854cb5829354e6d1ff8f371cf469","impliedFormat":1},{"version":"cd8ce8d68567f62dd580b3c3c37777ac3f5b81944c7417f5ea83030eab533385","impliedFormat":1},{"version":"e5c939d896565dcac0f6fbdbada11284e7728ef26a069561c09aa5aa4a788393","impliedFormat":1},{"version":"9e2739b32f741859263fdba0244c194ca8e96da49b430377930b8f721d77c000","impliedFormat":1},{"version":"a9e6c0ff3f8186fccd05752cf75fc94e147c02645087ac6de5cc16403323d870","impliedFormat":1},{"version":"49af4b52f0d4d2304c5f2c6fe5fab3e153e0acc38830d0202821b877c097dd02","impliedFormat":1},{"version":"49c346823ba6d4b12278c12c977fb3a31c06b9ca719015978cb145eb86da1c61","impliedFormat":1},{"version":"bfac6e50eaa7e73bb66b7e052c38fdc8ccfc8dbde2777648642af33cf349f7f1","impliedFormat":1},{"version":"92f7c1a4da7fbfd67a2228d1687d5c2e1faa0ba865a94d3550a3941d7527a45d","impliedFormat":1},{"version":"f53b120213a9289d9a26f5af90c4c686dd71d91487a0aa5451a38366c70dc64b","impliedFormat":1},{"version":"e68b8e5a1df7c1be2bc105141456ecba70215806e1c28bfbc5c12bfce4be6e68","impliedFormat":1},{"version":"511c8f02329808d47d00b859c532ae9115590048b17325a946c74dac48428650","impliedFormat":1},{"version":"57d67b72e06059adc5e9454de26bbfe567d412b962a501d263c75c2db430f40e","impliedFormat":1},{"version":"b5f9e66625783eefcbe3d2da074b2e7ba2066d61ce3fc6ef4f22805ad946cab4","impliedFormat":1},{"version":"e37115962d284b9f7a37c2bdd2add50f88365dde41f5e0ff591ffc48a8ec7575","impliedFormat":1},{"version":"6459054aabb306821a043e02b89d54da508e3a6966601a41e71c166e4ea1474f","impliedFormat":1},{"version":"bb37588926aba35c9283fe8d46ebf4e79ffe976343105f5c6d45f282793352b2","impliedFormat":1},{"version":"f89488602bec98a142072fae7ea5ba99431a569ff580c64b7be39896474799d8","impliedFormat":1},{"version":"bbbc47961f39a57df103cf4ca3bb8f8732b4b6678a18225a0aa76d59c466956c","impliedFormat":1},{"version":"2e6114a7dd6feeef85b2c80120fdbfb59a5529c0dcc5bfa8447b6996c97a69f5","impliedFormat":1},{"version":"2ffb043dc5163458e473b7010859f86e01dc4edffcae0a93d885d028b426a546","impliedFormat":1},{"version":"c8f004e6036aa1c764ad4ec543cf89a5c1893a9535c80ef3f2b653e370de45e6","impliedFormat":1},{"version":"dd80b1e600d00f5c6a6ba23f455b84a7db121219e68f89f10552c54ba46e4dc9","impliedFormat":1},{"version":"b064c36f35de7387d71c599bfcf28875849a1dbc733e82bd26cae3d1cd060521","impliedFormat":1},{"version":"05c7280d72f3ed26f346cbe7cbbbb002fb7f15739197cbbee6ab3fd1a6cb9347","impliedFormat":1},{"version":"8de9fe97fa9e00ec00666fa77ab6e91b35d25af8ca75dabcb01e14ad3299b150","impliedFormat":1},{"version":"04b7b2e0832dfd3c31e81df3975e8d8fda28e7ff999b0aa2932608a8f6661d5c","impliedFormat":1},{"version":"ca2d34c6ed5cbd3070b8b6f32f42ae54adcc6499c1e4b99f0a5798b3f27cc653","impliedFormat":1},{"version":"9ec68995e66dd6b9dac834bf5ae85fde802714ea2e82151a5d1d53ef01b463ef","impliedFormat":1},{"version":"5c4d626b4902f2ef8a1cc146d761d276cef988016dc674e3b98fbad70e64bc9f","impliedFormat":1},{"version":"fdfaa0aad899524962e2955287b5b991ffe3be50f64e02eb60c933ca44644a94","impliedFormat":1},{"version":"53c972a0f9bc3a4ec70fff7314123ea8cfcf75b3703046f767d2dc1eea87b2fb","impliedFormat":1},{"version":"f974e4a06953682a2c15d5bd5114c0284d5abf8bc0fe4da25cb9159427b70072","impliedFormat":1},{"version":"50256e9c31318487f3752b7ac12ff365c8949953e04568009c8705db802776fb","impliedFormat":1},{"version":"7d73b24e7bf31dfb8a931ca6c4245f6bb0814dfae17e4b60c9e194a631fe5f7b","impliedFormat":1},{"version":"d130c5f73768de51402351d5dc7d1b36eaec980ca697846e53156e4ea9911476","impliedFormat":1},{"version":"413586add0cfe7369b64979d4ec2ed56c3f771c0667fbde1bf1f10063ede0b08","impliedFormat":1},{"version":"06472528e998d152375ad3bd8ebcb69ff4694fd8d2effaf60a9d9f25a37a097a","impliedFormat":1},{"version":"7303b45138d2511035056a5901a1490ebdcbf055cbb1276f8629c5121cbe733e","impliedFormat":1},{"version":"27f874cd5327507eeff699a74567f60c1215b94509f4308633a7b01922471ed2","impliedFormat":1},{"version":"a401617604fa1f6ce437b81689563dfdc377069e4c58465dbd8d16069aede0a5","impliedFormat":1},{"version":"2c6cf04bc525caf6546e859e8ef10bfb9573837ec0bc5ec7b53a7b1b8ca72781","impliedFormat":1},{"version":"8695dec09ad439b0ceef3776ea68a232e381135b516878f0901ed2ea114fd0fe","impliedFormat":1},{"version":"304b44b1e97dd4c94697c3313df89a578dca4930a104454c99863f1784a54357","impliedFormat":1},{"version":"0a437ae178f999b46b6153d79095b60c42c996bc0458c04955f1c996dc68b971","impliedFormat":1},{"version":"74b2a5e5197bd0f2e0077a1ea7c07455bbea67b87b0869d9786d55104006784f","impliedFormat":1},{"version":"4a7baeb6325920044f66c0f8e5e6f1f52e06e6d87588d837bdf44feb6f35c664","impliedFormat":1},{"version":"87cc05fe13108f02e12da7e3efd8e360fef78d96a0c9e11408ea1b1b9fb3e03d","impliedFormat":1},{"version":"1abbf67c218d23c2ce76887caac2df6c7dab3d97ba2b65348432b876f510002a","impliedFormat":1},{"version":"1a82deef4c1d39f6882f28d275cad4c01f907b9b39be9cbc472fcf2cf051e05b","impliedFormat":1},{"version":"4b20fcf10a5413680e39f5666464859fc56b1003e7dfe2405ced82371ebd49b6","impliedFormat":1},{"version":"c06ef3b2569b1c1ad99fcd7fe5fba8d466e2619da5375dfa940a94e0feea899b","impliedFormat":1},{"version":"f7d628893c9fa52ba3ab01bcb5e79191636c4331ee5667ecc6373cbccff8ae12","impliedFormat":1},{"version":"1d879125d1ec570bf04bc1f362fdbe0cb538315c7ac4bcfcdf0c1e9670846aa6","impliedFormat":1},{"version":"dad97c99382889e9c7d1a9d8275500ff71235130fae9f8916fdbf3641d56e592","impliedFormat":1},{"version":"a6dba407fc287f1e25454e75028c91bbc00675f2d1c4e8b3edcc36c08611a486","impliedFormat":1},{"version":"d663134457d8d669ae0df34eabd57028bddc04fc444c4bc04bc5215afc91e1f4","impliedFormat":1},{"version":"e91f7b1344577a02f051b9b471f33044fef8334a76dc9e1de003d17595a5219b","impliedFormat":1},{"version":"c0723195c85e19656d6b5b9fdb81d3f3403c1ae4679e722c6ea058c516b38d12","impliedFormat":1},{"version":"b55eb9f72166093b5460d34b34f5d8699c968de3bc3fc696e40f2c93f2ebf650","impliedFormat":1},{"version":"71d9eb4c4e99456b78ae182fb20a5dfc20eb1667f091dbb9335b3c017dd1c783","impliedFormat":1},{"version":"cfa846a7b7847a1d973605fbb8c91f47f3a0f0643c18ac05c47077ebc72e71c7","impliedFormat":1},{"version":"1594da19968752a22b2ac48c2d0e60575700e745c577a8a4a676b841238ad5bb","impliedFormat":1},{"version":"e0cee12109e0a10a4c3d6769fcc7644b7c1ea7f52365bea51728f5af29f8a137","impliedFormat":1},{"version":"7d4254b4c6c67a29d5e7f65e67d72540480ac2cfb041ca484847f5ae70480b62","impliedFormat":1},{"version":"3536968defef8a75514f547ead5e2e9c1e984820290ec9b00c5fdfb6ef786535","impliedFormat":1},{"version":"d83773870080c30a230e322ce13a9c6f3398e8dacea4ea8a83e26370f3bac23e","impliedFormat":1},{"version":"dcfeaf98d66314fec29a9076c4290e45d0b196a65827becc19138e9c7b855f37","impliedFormat":1},{"version":"6849fe9210fe4946d5f085bfed36758f33dc6ae15a751338d178dd4daa017c46","impliedFormat":1},{"version":"888cda0fa66d7f74e985a3f7b1af1f64b8ff03eb3d5e80d051c3cbdeb7f32ab7","impliedFormat":1},{"version":"60681e13f3545be5e9477acb752b741eae6eaf4cc01658a25ec05bff8b82a2ef","impliedFormat":1},{"version":"ffae4e1e06aa848a1e4bcef162cd1c48e5909b26223515981310af9c036bdfc7","impliedFormat":1},{"version":"a57b1802794433adec9ff3fed12aa79d671faed86c49b09e02e1ac41b4f1d33a","impliedFormat":1},{"version":"34e16eb7c31768a11a08aebcfb3d70d7b8f0b016197e98d8419e566ceae6d6c8","impliedFormat":1},{"version":"f94ec1f7e4b709d26960306c9082a7a1b728a6e13089346aa48ba57c74cbf47e","impliedFormat":1},{"version":"9a11cb4033405e96c247cd5aa29790212aaffdd127869e8a5219103f0b389fd5","impliedFormat":1},{"version":"01479d9d5a5dda16d529b91811375187f61a06e74be294a35ecce77e0b9e8d6c","impliedFormat":1},{"version":"aff5213585cb72e94054dfe17250ff315f3569b3919d1ef1ad235f37c4ee894e","impliedFormat":1},{"version":"fb2ea35e1be6388d722d7725e2b49c697d34d9c890c3b96758faaeb86d35cef8","impliedFormat":1},{"version":"ce0df82a9ae6f914ba08409d4d883983cc08e6d59eb2df02d8e4d68309e7848b","impliedFormat":1},{"version":"1a4dc28334a926d90ba6a2d811ba0ff6c22775fcc13679521f034c124269fd40","impliedFormat":1},{"version":"f05315ff85714f0b87cc0b54bcd3dde2716e5a6b99aedcc19cad02bf2403e08c","impliedFormat":1},{"version":"5fad3b31fc17a5bc58095118a8b160f5260964787c52e7eb51e3d4fcf5d4a6f0","impliedFormat":1},{"version":"72105519d0390262cf0abe84cf41c926ade0ff475d35eb21307b2f94de985778","impliedFormat":1},{"version":"456006a6975b26c0a1785feddae165f6d307e2d601ffde27e21fc4a790e448a4","impliedFormat":1},{"version":"c857e0aae3f5f444abd791ec81206020fbcc1223e187316677e026d1c1d6fe08","impliedFormat":1},{"version":"ccf6dd45b708fb74ba9ed0f2478d4eb9195c9dfef0ff83a6092fa3cf2ff53b4f","impliedFormat":1},{"version":"1fe0d18b111e1145a7e7601855bccd4ca20f24e3b9a5aba6bb1fa9d1a7059170","impliedFormat":1},{"version":"5632c3c26d420c063eebe64c45b1248b9492a67bf44f1d0c57e9dc8f6cf449bb","impliedFormat":1},{"version":"0df5aa619ab12993a39ea6dae062ee46eadbb4d738916460e636ada52bced75b","impliedFormat":1},{"version":"8fca3039857709484e5893c05c1f9126ab7451fa6c29e19bb8c2411a2e937345","impliedFormat":1},{"version":"35069c2c417bd7443ae7c7cafd1de02f665bf015479fec998985ffbbf500628c","impliedFormat":1},{"version":"10ab7be91f87ebe8916b62cf28af2e45b5601fc7b0e311adf838f912c6b31dd8","impliedFormat":1},{"version":"bc636fbc08e0979ceb7eb0731a33000283d77a33b62e1f71ee65be50394e40ba","impliedFormat":1},{"version":"7e0b7f91c5ab6e33f511efc640d36e6f933510b11be24f98836a20a2dc914c2d","impliedFormat":1},{"version":"045b752f44bf9bbdcaffd882424ab0e15cb8d11fa94e1448942e338c8ef19fba","impliedFormat":1},{"version":"2894c56cad581928bb37607810af011764a2f511f575d28c9f4af0f2ef02d1ab","impliedFormat":1},{"version":"0a72186f94215d020cb386f7dca81d7495ab6c17066eb07d0f44a5bf33c1b21a","impliedFormat":1},{"version":"75bbd3be047d539988a0ff0b56384ef7a6a25f3b676ad96bee547d44c31622a7","impliedFormat":1},{"version":"42960001a776b089ade681ab5cfddc936e0afb0615133ec1841f3dee89d3e1bf","impliedFormat":1},{"version":"0aedb02516baf3e66b2c1db9fef50666d6ed257edac0f866ea32f1aa05aa474f","impliedFormat":1},{"version":"da47712b394d944328245482603bc6f416d3949b67c9392279caab595076b510","affectsGlobalScope":true,"impliedFormat":1},{"version":"37d0071d8f0a06dc55c2c5e0ec3391affd4fd107c53410bf358196ec0bf3923f","impliedFormat":1},{"version":"b213dad76ca37fd552274c9499056e1c0d9c1bd38a55bb7f68b22ba6b84c3ad7","impliedFormat":1},{"version":"2879a055439b6c0c0132a1467120a0f85b56b5d735c973ad235acd958b1b5345","impliedFormat":1},{"version":"d90b9f1520366d713a73bd30c5a9eb0040d0fb6076aff370796bc776fd705943","impliedFormat":1},{"version":"05321b823dd3781d0b6aac8700bfdc0c9181d56479fe52ba6a40c9196fd661a8","impliedFormat":1},{"version":"736a8712572e21ee73337055ce15edb08142fc0f59cd5410af4466d04beff0f9","affectsGlobalScope":true,"impliedFormat":1},{"version":"bef86adb77316505c6b471da1d9b8c9e428867c2566270e8894d4d773a1c4dc2","impliedFormat":1},{"version":"5a49adaef698b7ad7e6127949fa1b0bbd3d46b7cbd11c54e392a4dcdd51f5190","impliedFormat":1},{"version":"6ee598cdfdd0fa52039dca135b3dfff7b49035dc13292143e0a93843e3861967","impliedFormat":1},{"version":"27be6622e2922a1b412eb057faa854831b95db9db5035c3f6d4b677b902ab3b7","impliedFormat":1},{"version":"5c634644d45a1b6bc7b05e71e05e52ec04f3d73d9ac85d5927f647a5f965181a","impliedFormat":1},{"version":"2489bf04d77dc025ba67f49f1a56eb24b9db477d5ff88123d887e163ed1776aa","impliedFormat":1},{"version":"63a7595a5015e65262557f883463f934904959da563b4f788306f699411e9bac","impliedFormat":1},{"version":"4ba137d6553965703b6b55fd2000b4e07ba365f8caeb0359162ad7247f9707a6","impliedFormat":1},{"version":"0b77b819b5417775fccb20c678293cf614c054a5b1a65421a5b933a9124ba998","impliedFormat":1},{"version":"eb5acb58487367e502d994b57e2c58255d8241f481ea8efa8e79af23af3f41c2","impliedFormat":1},{"version":"9252d498a77517aab5d8d4b5eb9d71e4b225bbc7123df9713e08181de63180f6","impliedFormat":1},{"version":"b1f1d57fde8247599731b24a733395c880a6561ec0c882efaaf20d7df968c5af","impliedFormat":1},{"version":"6715dc4eb59c8ea9abe2b78c235ed331dc710a06fe56798868dbc4d40cd1b707","impliedFormat":1},{"version":"35e6379c3f7cb27b111ad4c1aa69538fd8e788ab737b8ff7596a1b40e96f4f90","impliedFormat":1},{"version":"1fffe726740f9787f15b532e1dc870af3cd964dbe29e191e76121aa3dd8693f2","impliedFormat":1},{"version":"5a3ea721d03a361ccbdd7390ccd75f6e84cbca3a3f01f4b331ecc9af31890c49","impliedFormat":1},{"version":"e7dfaee4af38d45b1cab8a1ee0b3bc1f85ddcf64545ed391d675d78ae6526274","affectsGlobalScope":true,"impliedFormat":1},{"version":"e8daa443eaf9a27fd382cc1f8ebe30330c0f4d89511cfb469166874806751d35","impliedFormat":1},{"version":"af48e58339188d5737b608d41411a9c054685413d8ae88b8c1d0d9bfabdf6e7e","impliedFormat":1},{"version":"616775f16134fa9d01fc677ad3f76e68c051a056c22ab552c64cc281a9686790","impliedFormat":1},{"version":"65c24a8baa2cca1de069a0ba9fba82a173690f52d7e2d0f1f7542d59d5eb4db0","impliedFormat":1},{"version":"f9fe6af238339a0e5f7563acee3178f51db37f32a2e7c09f85273098cee7ec49","impliedFormat":1},{"version":"1de8c302fd35220d8f29dea378a4ae45199dc8ff83ca9923aca1400f2b28848a","impliedFormat":1},{"version":"77e71242e71ebf8528c5802993697878f0533db8f2299b4d36aa015bae08a79c","impliedFormat":1},{"version":"98a787be42bd92f8c2a37d7df5f13e5992da0d967fab794adbb7ee18370f9849","impliedFormat":1},{"version":"332248ee37cca52903572e66c11bef755ccc6e235835e63d3c3e60ddda3e9b93","impliedFormat":1},{"version":"94e8cc88ae2ef3d920bb3bdc369f48436db123aa2dc07f683309ad8c9968a1e1","impliedFormat":1},{"version":"4545c1a1ceca170d5d83452dd7c4994644c35cf676a671412601689d9a62da35","impliedFormat":1},{"version":"320f4091e33548b554d2214ce5fc31c96631b513dffa806e2e3a60766c8c49d9","impliedFormat":1},{"version":"a2d648d333cf67b9aeac5d81a1a379d563a8ffa91ddd61c6179f68de724260ff","impliedFormat":1},{"version":"d90d5f524de38889d1e1dbc2aeef00060d779f8688c02766ddb9ca195e4a713d","impliedFormat":1},{"version":"07ed3ddab975995eea41b22f3010506fb9f5fb301d04820b07d7a1aee5477d7c","impliedFormat":1},{"version":"969d8b0965849f4bae7cab0ba90bd1e1220e95999c2c6f01117fa7500901c017","impliedFormat":1},{"version":"6ec840ee5e2bc103f557fe38b1d585ee250540468713d7634ee066de372bf332","impliedFormat":1},{"version":"b0309e1eda99a9e76f87c18992d9c3689b0938266242835dd4611f2b69efe456","impliedFormat":1},{"version":"47699512e6d8bebf7be488182427189f999affe3addc1c87c882d36b7f2d0b0e","impliedFormat":1},{"version":"6ceb10ca57943be87ff9debe978f4ab73593c0c85ee802c051a93fc96aaf7a20","impliedFormat":1},{"version":"1de3ffe0cc28a9fe2ac761ece075826836b5a02f340b412510a59ba1d41a505a","impliedFormat":1},{"version":"e46d6cc08d243d8d0d83986f609d830991f00450fb234f5b2f861648c42dc0d8","impliedFormat":1},{"version":"1c0a98de1323051010ce5b958ad47bc1c007f7921973123c999300e2b7b0ecc0","impliedFormat":1},{"version":"ff863d17c6c659440f7c5c536e4db7762d8c2565547b2608f36b798a743606ca","impliedFormat":1},{"version":"5412ad0043cd60d1f1406fc12cb4fb987e9a734decbdd4db6f6acf71791e36fe","impliedFormat":1},{"version":"ad036a85efcd9e5b4f7dd5c1a7362c8478f9a3b6c3554654ca24a29aa850a9c5","impliedFormat":1},{"version":"fedebeae32c5cdd1a85b4e0504a01996e4a8adf3dfa72876920d3dd6e42978e7","impliedFormat":1},{"version":"e297c0a524edee7677939122f90027bfbe5f2698939d9a85728e5044b39c7124","impliedFormat":1},{"version":"cdf21eee8007e339b1b9945abf4a7b44930b1d695cc528459e68a3adc39a622e","impliedFormat":1},{"version":"bc9ee0192f056b3d5527bcd78dc3f9e527a9ba2bdc0a2c296fbc9027147df4b2","impliedFormat":1},{"version":"b62381cae176db34f003cc6172ee8f3e0122014889d66391aa73698105cf4934","impliedFormat":1},{"version":"1d9c0a9a6df4e8f29dc84c25c5aa0bb1da5456ebede7a03e03df08bb8b27bae6","impliedFormat":1},{"version":"84380af21da938a567c65ef95aefb5354f676368ee1a1cbb4cae81604a4c7d17","impliedFormat":1},{"version":"1af3e1f2a5d1332e136f8b0b95c0e6c0a02aaabd5092b36b64f3042a03debf28","impliedFormat":1},{"version":"30d8da250766efa99490fc02801047c2c6d72dd0da1bba6581c7e80d1d8842a4","impliedFormat":1},{"version":"03566202f5553bd2d9de22dfab0c61aa163cabb64f0223c08431fb3fc8f70280","impliedFormat":1},{"version":"41eb514d9ce0a6e87957f08a4b7af70d93f87637f37dee706e2d92a6601c25a9","impliedFormat":1},{"version":"e7765aa8bcb74a38b3230d212b4547686eb9796621ffb4367a104451c3f9614f","impliedFormat":1},{"version":"1de80059b8078ea5749941c9f863aa970b4735bdbb003be4925c853a8b6b4450","impliedFormat":1},{"version":"1d079c37fa53e3c21ed3fa214a27507bda9991f2a41458705b19ed8c2b61173d","impliedFormat":1},{"version":"5bf5c7a44e779790d1eb54c234b668b15e34affa95e78eada73e5757f61ed76a","impliedFormat":1},{"version":"5835a6e0d7cd2738e56b671af0e561e7c1b4fb77751383672f4b009f4e161d70","impliedFormat":1},{"version":"4b7f74b772140395e7af67c4841be1ab867c11b3b82a51b1aeb692822b76c872","impliedFormat":1},{"version":"7bd01f0f28cd3aeb2046274d85208e245965f6f2948edf4f7b2057bcf9f22ccc","impliedFormat":99},{"version":"d2f2cf2b8cc92bea913cda4a076e0f790b23a21e84f989d12f0116a7fe3906e0","impliedFormat":99},{"version":"6de125ea94866c736c6d58d68eb15272cf7d1020a5b459fea1c660027eca9a90","affectsGlobalScope":true,"impliedFormat":1},{"version":"f5b20bc288ee49989c95b20847fc93b96bf61cc0845598897a6a53a967dd7d07","affectsGlobalScope":true,"impliedFormat":1},{"version":"064ac1c2ac4b2867c2ceaa74bbdce0cb6a4c16e7c31a6497097159c18f74aa7c","impliedFormat":1},{"version":"3dc14e1ab45e497e5d5e4295271d54ff689aeae00b4277979fdd10fa563540ae","impliedFormat":1},{"version":"d3b315763d91265d6b0e7e7fa93cfdb8a80ce7cdd2d9f55ba0f37a22db00bdb8","impliedFormat":1},{"version":"b789bf89eb19c777ed1e956dbad0925ca795701552d22e68fd130a032008b9f9","impliedFormat":1},{"version":"6e5c0359aaf1c506b4eb5ee4ae40d2d1ad96bce9db6915683618d35b78267826","affectsGlobalScope":true},"7ad303e40d4fddf44f156129e397511953a71481c5cfd86b1862649aaaf240cc",{"version":"023de20b47f68944cb18fa80ffe3999fcac1e13f19037c4d9814840b77d3e4e9","signature":"50583aa3ee54d8fa0ffa5f3f232659e5d6e979fb1043c1e1f02cc6ffd2728dd4","affectsGlobalScope":true},{"version":"04471dc55f802c29791cc75edda8c4dd2a121f71c2401059da61eff83099e8ab","impliedFormat":99},{"version":"5c54a34e3d91727f7ae840bfe4d5d1c9a2f93c54cb7b6063d06ee4a6c3322656","impliedFormat":99},{"version":"db4da53b03596668cf6cc9484834e5de3833b9e7e64620cf08399fe069cd398d","impliedFormat":99},{"version":"ac7c28f153820c10850457994db1462d8c8e462f253b828ad942a979f726f2f9","impliedFormat":99},{"version":"f9b028d3c3891dd817e24d53102132b8f696269309605e6ed4f0db2c113bbd82","impliedFormat":99},{"version":"fb7c8d90e52e2884509166f96f3d591020c7b7977ab473b746954b0c8d100960","impliedFormat":99},{"version":"0bff51d6ed0c9093f6955b9d8258ce152ddb273359d50a897d8baabcb34de2c4","impliedFormat":99},{"version":"ef13c73d6157a32933c612d476c1524dd674cf5b9a88571d7d6a0d147544d529","impliedFormat":99},{"version":"13918e2b81c4288695f9b1f3dcc2468caf0f848d5c1f3dc00071c619d34ff63a","impliedFormat":99},{"version":"120a80aa556732f684db3ed61aeff1d6671e1655bd6cba0aa88b22b88ac9a6b1","affectsGlobalScope":true,"impliedFormat":99},{"version":"a7ca8df4f2931bef2aa4118078584d84a0b16539598eaadf7dce9104dfaa381c","impliedFormat":1},{"version":"10073cdcf56982064c5337787cc59b79586131e1b28c106ede5bff362f912b70","impliedFormat":99},{"version":"72950913f4900b680f44d8cab6dd1ea0311698fc1eefb014eb9cdfc37ac4a734","impliedFormat":1},{"version":"151ff381ef9ff8da2da9b9663ebf657eac35c4c9a19183420c05728f31a6761d","impliedFormat":1},{"version":"ee70b8037ecdf0de6c04f35277f253663a536d7e38f1539d270e4e916d225a3f","affectsGlobalScope":true,"impliedFormat":1},{"version":"a660aa95476042d3fdcc1343cf6bb8fdf24772d31712b1db321c5a4dcc325434","impliedFormat":1},{"version":"36977c14a7f7bfc8c0426ae4343875689949fb699f3f84ecbe5b300ebf9a2c55","impliedFormat":1},{"version":"ff0a83c9a0489a627e264ffcb63f2264b935b20a502afa3a018848139e3d8575","impliedFormat":99},{"version":"161c8e0690c46021506e32fda85956d785b70f309ae97011fd27374c065cac9b","affectsGlobalScope":true,"impliedFormat":1},{"version":"f582b0fcbf1eea9b318ab92fb89ea9ab2ebb84f9b60af89328a91155e1afce72","impliedFormat":1},{"version":"402e5c534fb2b85fa771170595db3ac0dd532112c8fa44fc23f233bc6967488b","impliedFormat":1},{"version":"52dcc257df5119fb66d864625112ce5033ac51a4c2afe376a0b299d2f7f76e4a","impliedFormat":1},{"version":"e5bab5f871ef708d52d47b3e5d0aa72a08ee7a152f33931d9a60809711a2a9a3","impliedFormat":1},{"version":"e16dc2a81595736024a206c7d5c8a39bfe2e6039208ef29981d0d95434ba8fcf","impliedFormat":1},{"version":"cc4a4903fb698ca1d961d4c10dce658aa3a479faf40509d526f122b044eaf6a4","impliedFormat":1},{"version":"19ee8416e6473ed6c7adb868fa796b5653cf0fa2a337658e677eaa0d134388c3","impliedFormat":1},{"version":"1328ab4e442614b28cdb3d4b414cf68325c0da0dca07287a338d0654b7a00261","impliedFormat":1},{"version":"a039dc21f045919f3cbee2ec13812cc6cc3eebc99dae4be00973230f468d19a6","impliedFormat":1},{"version":"3fbe57af01460e49dcd29df55d6931e1672bc6f1be0fb073d11410bc16f9037d","impliedFormat":1},{"version":"f760be449e8562ec5c09bb5187e8e1eabf3c113c0c58cddda53ef8c69f3e2131","impliedFormat":1},{"version":"44325ed13294fce6ab825b82947bbeed2611db7dad9d9135260192f375e5a189","impliedFormat":1},{"version":"e392e8fb5b514eafc585601c1d781485aa6dd6a320e75daf1064a4c6918a1b45","impliedFormat":1},{"version":"46e4a36e8ddbdfb4e7330e11c81c970dc8b218611df9183d39c41c5f8c653b55","impliedFormat":1},{"version":"370bde134aa8c2abc926d0e99d3a4d5d5dba65c6ee65459137e4f02670cbf841","impliedFormat":1},{"version":"6332f565867cf4a740a70e30f31cefba37ef7cebcf74f22eab8d744fde6d193e","impliedFormat":1},{"version":"2977b7884aedc895a1d0c9c210c7cf3272c29d6959a08a6fa3ff71e0aff08175","impliedFormat":1},{"version":"17f2922d41ddd032830a91371c948cd9ce903b35c95adca72271a54584f19b0b","impliedFormat":1},{"version":"3eed76ede2a1a14d7c9bb0a642041282dcc264811139d3dd275c9fe14efc9840","impliedFormat":1},{"version":"e3cf0611709328b449ec13f8c436712d62003620ce480139fae46ce001c2ee9f","impliedFormat":1},{"version":"8d369483f0c2b9ee388129cfdb6a43bc8112b377e86a41884bd06e19ce04f4c1","impliedFormat":99},{"version":"960bd764c62ac43edc24eaa2af958a4b4f1fa5d27df5237e176d0143b36a39c6","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ec16d7a4e366c06a4573d299e15fe6207fc080f41beac5da06f4af33ea9761e","impliedFormat":1},{"version":"59f8dc89b9e724a6a667f52cdf4b90b6816ae6c9842ce176d38fcc973669009e","affectsGlobalScope":true,"impliedFormat":1},{"version":"e4af494f7a14b226bbe732e9c130d8811f8c7025911d7c58dd97121a85519715","impliedFormat":1},{"version":"cbb1c5ba5dbabe42c19ca31b83e48fec95895484fe1d1a8fb649b69ea224c5b8","impliedFormat":99},{"version":"45cec9a1ba6549060552eead8959d47226048e0b71c7d0702ae58b7e16a28912","impliedFormat":99},{"version":"6907b09850f86610e7a528348c15484c1e1c09a18a9c1e98861399dfe4b18b46","impliedFormat":99},{"version":"12deea8eaa7a4fc1a2908e67da99831e5c5a6b46ad4f4f948fd4759314ea2b80","impliedFormat":99},{"version":"f0a8b376568a18f9a4976ecb0855187672b16b96c4df1c183a7e52dc1b5d98e8","impliedFormat":99},{"version":"8124828a11be7db984fcdab052fd4ff756b18edcfa8d71118b55388176210923","impliedFormat":99},{"version":"092944a8c05f9b96579161e88c6f211d5304a76bd2c47f8d4c30053269146bc8","impliedFormat":99},{"version":"b34b5f6b506abb206b1ea73c6a332b9ee9c8c98be0f6d17cdbda9430ecc1efab","impliedFormat":99},{"version":"75d4c746c3d16af0df61e7b0afe9606475a23335d9f34fcc525d388c21e9058b","impliedFormat":99},{"version":"fa959bf357232201c32566f45d97e70538c75a093c940af594865d12f31d4912","impliedFormat":99},{"version":"d2c52abd76259fc39a30dfae70a2e5ce77fd23144457a7ff1b64b03de6e3aec7","impliedFormat":99},{"version":"e6233e1c976265e85aa8ad76c3881febe6264cb06ae3136f0257e1eab4a6cc5a","impliedFormat":99},{"version":"f73e2335e568014e279927321770da6fe26facd4ac96cdc22a56687f1ecbb58e","impliedFormat":99},{"version":"317878f156f976d487e21fd1d58ad0461ee0a09185d5b0a43eedf2a56eb7e4ea","impliedFormat":99},{"version":"324ac98294dab54fbd580c7d0e707d94506d7b2c3d5efe981a8495f02cf9ad96","impliedFormat":99},{"version":"9ec72eb493ff209b470467e24264116b6a8616484bca438091433a545dfba17e","impliedFormat":99},{"version":"d6ee22aba183d5fc0c7b8617f77ee82ecadc2c14359cc51271c135e23f6ed51f","impliedFormat":99},{"version":"49747416f08b3ba50500a215e7a55d75268b84e31e896a40313c8053e8dec908","impliedFormat":99},{"version":"81e634f1c5e1ca309e7e3dc69e2732eea932ef07b8b34517d452e5a3e9a36fa3","impliedFormat":99},{"version":"34f39f75f2b5aa9c84a9f8157abbf8322e6831430e402badeaf58dd284f9b9a6","impliedFormat":99},{"version":"427fe2004642504828c1476d0af4270e6ad4db6de78c0b5da3e4c5ca95052a99","impliedFormat":1},{"version":"2eeffcee5c1661ddca53353929558037b8cf305ffb86a803512982f99bcab50d","impliedFormat":99},{"version":"9afb4cb864d297e4092a79ee2871b5d3143ea14153f62ef0bb04ede25f432030","affectsGlobalScope":true,"impliedFormat":99},{"version":"891694d3694abd66f0b8872997b85fd8e52bc51632ce0f8128c96962b443189f","impliedFormat":99},{"version":"69bf2422313487956e4dacf049f30cb91b34968912058d244cb19e4baa24da97","impliedFormat":99},{"version":"971a2c327ff166c770c5fb35699575ba2d13bba1f6d2757309c9be4b30036c8e","impliedFormat":99},{"version":"4f45e8effab83434a78d17123b01124259fbd1e335732135c213955d85222234","impliedFormat":99},{"version":"7bd51996fb7717941cbe094b05adc0d80b9503b350a77b789bbb0fc786f28053","impliedFormat":99},{"version":"b62006bbc815fe8190c7aee262aad6bff993e3f9ade70d7057dfceab6de79d2f","impliedFormat":99},{"version":"13497c0d73306e27f70634c424cd2f3b472187164f36140b504b3756b0ff476d","impliedFormat":99},{"version":"a23a08b626aa4d4a1924957bd8c4d38a7ffc032e21407bbd2c97413e1d8c3dbd","impliedFormat":99},{"version":"c320fe76361c53cad266b46986aac4e68d644acda1629f64be29c95534463d28","impliedFormat":99},{"version":"7bbff6783e96c691a41a7cf12dd5486b8166a01b0c57d071dbcfca55c9525ec4","impliedFormat":99},{"version":"65aa4a600336032b6760aa71b18cbad49fbba3999162406e17454a9646b54f70","signature":"4b96dd19fd2949d28ce80e913412b0026dc421e5bf6c31d87c7b5eb11b5753b4"},{"version":"8ef0b457802d1883c0c185f90610a8aaf33283250633a31853de01bb45565b7b","signature":"b730dbc27807d6a94494d69e0154827379b8ed4606f3dd3a4584a1e2242b1e53"},{"version":"764fec087122d840f12f9f24e1dc1e4cc2dcb222f3d13d2a498bf332fbe460d7","impliedFormat":1},{"version":"92ee216a93c16d3724ce70c9a20f56b05659c7c67b86827d481ff89c1a5d23d9","impliedFormat":1},{"version":"05d1a8f963258d75216f13cf313f27108f83a8aa2bff482da356f2bfdfb59ab2","impliedFormat":1},{"version":"1a848ab32f6114131218358c47b81a2b6fd71789d3c9cda62a6218194cba5ecb","impliedFormat":1},{"version":"b1fb9f004934ac2ae15d74b329ac7f4c36320ff4ada680a18cc27e632b6baa82","impliedFormat":1},{"version":"f13c5c100055437e4cf58107e8cbd5bb4fa9c15929f7dc97cb487c2e19c1b7f6","impliedFormat":1},{"version":"ee423b86c3e071a3372c29362c2f26adc020a2d65bcbf63763614db49322234e","impliedFormat":1},{"version":"77d30b82131595dbb9a21c0e1e290247672f34216e1af69a586e4b7ad836694e","impliedFormat":1},{"version":"78d486dac53ad714133fc021b2b68201ba693fab2b245fda06a4fc266cead04a","impliedFormat":1},{"version":"06414fbc74231048587dedc22cd8cac5d80702b81cd7a25d060ab0c2f626f5c8","impliedFormat":1},{"version":"b8533e19e7e2e708ac6c7a16ae11c89ffe36190095e1af146d44bb54b2e596a1","impliedFormat":1},{"version":"b5f70f31ef176a91e4a9f46074b763adc321cd0fdb772c16ca57b17266c32d19","impliedFormat":1},{"version":"169035d6d96186b82cd6456a1dd0dca511abf191d4f59d8ab012d9a5ce25c2e0","impliedFormat":1},{"version":"a78a334d8e93cf70b3dded844963e5d0c529546b12ec3a8668afa05f707e8222","impliedFormat":1},{"version":"503d068eb2b24456c90d15b2331a3cb04aa03b07d35699dac828d8c654d22c4e","impliedFormat":1},{"version":"c133900491138f79cecffb0dca079393b8e704899e4fcf9a9d8b399f8b91c3db","impliedFormat":1},{"version":"0b43cdc862f70c9b37bca929513eab72ab764845ea5d83cef47d148a1ff3f0d5","impliedFormat":1},{"version":"4a193963d67a56bff9331232db719a9dc71ff8a7795cb9de2f047d0de214d709","impliedFormat":1},{"version":"59ce6c57619857ab7dfc367715a3dbf300880cd16e7c84c12ac4ba1e39cdee63","impliedFormat":1},{"version":"5a1c84eb2e4797d0a021fcb4033a1189941265d03d6a1930bf6132143ee4065d","impliedFormat":1},{"version":"d38293b3bcb73ba1c719ba50497859a2f37fa64a6de7f22eeb32ae9f3b1bcefc","impliedFormat":1},{"version":"d67484f1551a676c22ebb9be78723e839d630d6459794e32cc050aaab7641621","impliedFormat":1},{"version":"5eaf2e0f6ea59e43507586de0a91d17d0dd5c59f3919e9d12cbab0e5ed9d2d77","impliedFormat":1},{"version":"be97b1340a3f72edf8404d1d717df2aac5055faaff6c99c24f5a2b2694603745","impliedFormat":1},{"version":"1754df61456e51542219ee17301566ac439115b2a1e5da1a0ffb2197e49ccefe","impliedFormat":1},{"version":"2c90cb5d9288d3b624013a9ca40040b99b939c3a090f6bdca3b4cfc6b1445250","impliedFormat":1},{"version":"3c6d4463866f664a5f51963a2849cb844f2203693be570d0638ee609d75fe902","impliedFormat":1},{"version":"752677ae7ebfef0fa54a6642b48ad671654223c3cde56259ce41292081ef0f0e","impliedFormat":1},{"version":"e88b42f282b55c669a8f35158449b4f7e6e2bccec31fd0d4adb4278928a57a89","impliedFormat":1},{"version":"2a1ed52adfc72556f4846b003a7e5a92081147beef55f27f99466aa6e2a28060","impliedFormat":1},{"version":"a4cf825c93bb52950c8cdc0b94c5766786c81c8ee427fc6774fafb16d0015035","impliedFormat":1},{"version":"4acc7fae6789948156a2faabc1a1ba36d6e33adb09d53bccf9e80248a605b606","impliedFormat":1},{"version":"f9613793aa6b7d742e80302e65741a339b529218ae80820753a61808a9761479","impliedFormat":1},{"version":"b182e2043a595bca73dd39930020425d55c5ff2aae1719d466dadeadc78273c7","impliedFormat":1},{"version":"5b978a20707f2b3b4fa39ca3ba9d0d12590bf4c4167beb3195bcd1421115256f","impliedFormat":1},{"version":"ed1ee10044d15a302d95b2634e6344b9f630528e3d5d7ce0eacad5958f0976c3","impliedFormat":1},{"version":"d18588312a7634d07e733e7960caf78d5b890985f321683b932d21d8d0d69b7b","impliedFormat":1},{"version":"d1dac573a182cc40c170e38a56eb661182fcd8981e9fdf2ce11df9decb73485d","impliedFormat":1},{"version":"c264198b19a4b9718508b49f61e41b6b17a0f9b8ecbf3752e052ad96e476e446","impliedFormat":1},{"version":"9c488a313b2974a52e05100f8b33829aa3466b2bc83e9a89f79985a59d7e1f95","impliedFormat":1},{"version":"e306488a76352d3dd81d8055abf03c3471e79a2e5f08baede5062fa9dca3451c","impliedFormat":1},{"version":"ad7bdd54cf1f5c9493b88a49dc6cec9bc9598d9e114fcf7701627b5e65429478","impliedFormat":1},{"version":"0d274e2a6f13270348818139fd53316e79b336e8a6cf4a6909997c9cbf47883c","impliedFormat":1},{"version":"78664c8054da9cce6148b4a43724195b59e8a56304e89b2651f808d1b2efb137","impliedFormat":1},{"version":"a0568a423bd8fee69e9713dac434b6fccc5477026cda5a0fc0af59ae0bfd325c","impliedFormat":1},{"version":"2a176a57e9858192d143b7ebdeca0784ee3afdb117596a6ee3136f942abe4a01","impliedFormat":1},{"version":"c8ee4dd539b6b1f7146fa5b2d23bca75084ae3b8b51a029f2714ce8299b8f98e","impliedFormat":1},{"version":"c58f688364402b45a18bd4c272fc17b201e1feddc45d10c86cb7771e0dc98a21","impliedFormat":1},{"version":"2904898efb9f6fabfe8dcbe41697ef9b6df8e2c584d60a248af4558c191ce5cf","impliedFormat":1},{"version":"c13189caa4de435228f582b94fb0aae36234cba2b7107df2c064f6f03fc77c3d","impliedFormat":1},{"version":"c97110dbaa961cf90772e8f4ee41c9105ee7c120cb90b31ac04bb03d0e7f95fb","impliedFormat":1},{"version":"c30864ed20a4c8554e8025a2715ba806799eba20aba0fd9807750e57ee2f838f","impliedFormat":1},{"version":"b182e2043a595bca73dd39930020425d55c5ff2aae1719d466dadeadc78273c7","impliedFormat":1},{"version":"5b978a20707f2b3b4fa39ca3ba9d0d12590bf4c4167beb3195bcd1421115256f","impliedFormat":1},{"version":"ed1ee10044d15a302d95b2634e6344b9f630528e3d5d7ce0eacad5958f0976c3","impliedFormat":1},{"version":"c30864ed20a4c8554e8025a2715ba806799eba20aba0fd9807750e57ee2f838f","impliedFormat":1},{"version":"e0cd55e58a4a210488e9c292cc2fc7937d8fc0768c4a9518645115fe500f3f44","impliedFormat":1},{"version":"d0307177b720b32a05c0bbb921420160cba0d3b6e81b1d961481d9abe4a17f60","impliedFormat":1},{"version":"8c25b00a675743d7a381cf6389ae9fbdce82bdc9069b343cb1985b4cd17b14be","impliedFormat":1},{"version":"e72b4624985bd8541ae1d8bde23614d2c44d784bbe51db25789a96e15bb7107a","impliedFormat":1},{"version":"0fb1449ca2990076278f0f9882aa8bc53318fc1fd7bfcbde89eed58d32ae9e35","impliedFormat":1},{"version":"c2625e4ba5ed1cb7e290c0c9eca7cdc5a7bebab26823f24dd61bf58de0b90ad6","impliedFormat":1},{"version":"a20532d24f25d5e73f05d63ad1868c05b813e9eb64ec5d9456bbe5c98982fd2e","impliedFormat":1},{"version":"d0307177b720b32a05c0bbb921420160cba0d3b6e81b1d961481d9abe4a17f60","impliedFormat":1},{"version":"7a17edfdf23eaaf79058134449c7e1e92c03e2a77b09a25b333a63a14dca17ed","impliedFormat":1},{"version":"e78c5d07684e1bb4bf3e5c42f757f2298f0d8b364682201b5801acf4957e4fad","impliedFormat":99},{"version":"4085598deeaff1b924e347f5b6e18cee128b3b52d6756b3753b16257284ceda7","impliedFormat":99},{"version":"c58272e3570726797e7db5085a8063143170759589f2a5e50387eff774eadc88","impliedFormat":1},{"version":"e3d8342c9f537a4ffcab951e5f469ac9c5ed1d6147e9e2a499184cf45ab3c77f","impliedFormat":1},{"version":"bc3ee6fe6cab0459f4827f982dbe36dcbd16017e52c43fec4e139a91919e0630","impliedFormat":1},{"version":"41e0d68718bf4dc5e0984626f3af12c0a5262a35841a2c30a78242605fa7678e","impliedFormat":1},{"version":"6c747f11c6b2a23c4c0f3f440c7401ee49b5f96a7fe4492290dfd3111418321b","impliedFormat":1},{"version":"a6b6c40086c1809d02eff72929d0fc8ec33313f1c929398c9837d31a3b05c66b","impliedFormat":1},{"version":"4e87a7aa00637afd8ccbaf04f8d7fdbd61eb51438e8bd6718debcfd7e55e5d14","impliedFormat":1},{"version":"55d70bb1ac14f79caae20d1b02a2ad09440a6b0b633d125446e89d25e7fd157d","impliedFormat":1},{"version":"c27930b3269795039e392a9b27070e6e9ba9e7da03e6185d4d99b47e0b7929bc","impliedFormat":1},{"version":"ae22e71c8ebcf07a6ca7efb968a9bcdbfb1c2919273901151399c576b2bed4b8","impliedFormat":1},{"version":"47f30de14aa377b60f0cd43e95402d03166d3723f42043ae654ce0a25bc1b321","impliedFormat":1},{"version":"0edcda97d090708110daea417cfd75d6fd0c72c9963fec0a1471757b14f28ae5","impliedFormat":1},{"version":"f730a314c6e3cb76b667c2c268cd15bde7068b90cb61d1c3ab93d65b878d3e76","impliedFormat":1},{"version":"c60096bf924a5a44f792812982e8b5103c936dd7eec1e144ded38319a282087e","impliedFormat":1},{"version":"f9acf26d0b43ad3903167ac9b5d106e481053d92a1f3ab9fe1a89079e5f16b94","impliedFormat":1},{"version":"014e069a32d3ac6adde90dd1dfdb6e653341595c64b87f5b1b3e8a7851502028","impliedFormat":1},{"version":"ac46b462f6ae83bee6d3f61176f8da916c6fd43774b79142a6d1508745fbd152","impliedFormat":1},{"version":"ac46b462f6ae83bee6d3f61176f8da916c6fd43774b79142a6d1508745fbd152","impliedFormat":1},{"version":"ac46b462f6ae83bee6d3f61176f8da916c6fd43774b79142a6d1508745fbd152","impliedFormat":1},{"version":"ac46b462f6ae83bee6d3f61176f8da916c6fd43774b79142a6d1508745fbd152","impliedFormat":1},{"version":"ac46b462f6ae83bee6d3f61176f8da916c6fd43774b79142a6d1508745fbd152","impliedFormat":1},{"version":"86c8f1a471f03ac5232073884775b77d7673516a1eff3b9c4a866c64a5b1693a","impliedFormat":1},{"version":"5545aa84048e8ae5b22838a2b437abd647c58acc43f2f519933cd313ce84476c","impliedFormat":1},{"version":"0d2af812b3894a2daa900a365b727a58cc3cc3f07eb6c114751f9073c8031610","impliedFormat":1},{"version":"30be069b716d982a2ae943b6a3dab9ae1858aa3d0a7218ab256466577fd7c4ca","impliedFormat":1},{"version":"797b6a8e5e93ab462276eebcdff8281970630771f5d9038d7f14b39933e01209","impliedFormat":1},{"version":"549232dd97130463d39dac754cf7faa95c4c71511d11dd9b1d37c225bf675469","impliedFormat":1},{"version":"747779d60c02112794ca81f1641628387d68c8e406be602b87af9ae755d46fd6","impliedFormat":1},{"version":"0a22c78fc4cbf85f27e592bea1e7ece94aadf3c6bd960086f1eff2b3aedf2490","impliedFormat":1},{"version":"fea1857ed9f8e33be23a5a3638c487b25bb44b21032c6148144883165ad10fb0","impliedFormat":1},{"version":"d0cffd20a0deb57297c2bd8c4cd381ed79de7babf9d81198e28e3f56d9aff0db","impliedFormat":1},{"version":"77876c19517f1a79067a364423ba9e4f3c6169d01011320a6fde85a95e8f8f5c","impliedFormat":1},{"version":"84cf3736a269c74c711546db9a8078ad2baaf12e9edd5b33e30252c6fb59b305","impliedFormat":1},{"version":"8309b403027c438254d78ca2bb8ddd04bfaf70260a9db37219d9a49ad6df5d80","impliedFormat":1},{"version":"6a9d4bd7a551d55e912764633a086af149cc937121e011f60f9be60ee5156107","impliedFormat":1},{"version":"f1cea620ee7e602d798132c1062a0440f9d49a43d7fafdc5bdc303f6d84e3e70","impliedFormat":1},{"version":"5769d77cb83e1f931db5e3f56008a419539a1e02befe99a95858562e77907c59","impliedFormat":1},{"version":"1607892c103374a3dc1f45f277b5362d3cb3340bfe1007eec3a31b80dd0cf798","impliedFormat":1},{"version":"402da75bfdaf5b2cf388450cb56a4c5ba2ed67bc9f930eba0e7ce7fc57cddf11","impliedFormat":1},{"version":"220aafeafa992aa95f95017cb6aecea27d4a2b67bb8dd2ce4f5c1181e8d19c21","impliedFormat":1},{"version":"a71dd28388e784bf74a4bc40fd8170fa4535591057730b8e0fef4820cf4b4372","impliedFormat":1},{"version":"0e411566240d81c51c2d95e5f3fa2e8a35c3e7bbe67a43f4eb9c9a2912fdff05","impliedFormat":1},{"version":"4e4325429d6a967ef6aa72ca24890a7788a181d28599fe1b3bb6730a6026f048","impliedFormat":1},{"version":"dcbb4c3abdc5529aeda5d6b0a835d8a0883da2a76e9484a4f19e254e58faf3c6","impliedFormat":1},{"version":"0d81307f711468869759758160975dee18876615db6bf2b8f24188a712f1363b","impliedFormat":1},{"version":"22ddd9cd17d33609d95fb66ece3e6dff2e7b21fa5a075c11ef3f814ee9dd35c7","impliedFormat":1},{"version":"cb43ede907c32e48ba75479ca867464cf61a5f962c33712436fee81431d66468","impliedFormat":1},{"version":"549232dd97130463d39dac754cf7faa95c4c71511d11dd9b1d37c225bf675469","impliedFormat":1},{"version":"1e89d5e4c50ca57947247e03f564d916b3b6a823e73cde1ee8aece5df9e55fc9","impliedFormat":1},{"version":"8538eca908e485ccb8b1dd33c144146988a328aaa4ffcc0a907a00349171276e","impliedFormat":1},{"version":"7b878f38e8233e84442f81cc9f7fb5554f8b735aca2d597f7fe8a069559d9082","impliedFormat":1},{"version":"bf7d8edbd07928d61dbab4047f1e47974a985258d265e38a187410243e5a6ab9","impliedFormat":1},{"version":"747779d60c02112794ca81f1641628387d68c8e406be602b87af9ae755d46fd6","impliedFormat":1},{"version":"40b33243bbbddfe84dbdd590e202bdba50a3fe2fbaf138b24b092c078b541434","impliedFormat":1},{"version":"fea1857ed9f8e33be23a5a3638c487b25bb44b21032c6148144883165ad10fb0","impliedFormat":1},{"version":"f21d84106071ae3a54254bcabeaf82174a09b88d258dd32cafb80b521a387d42","impliedFormat":1},{"version":"21129c4f2a3ae3f21f1668adfda1a4103c8bdd4f25339a7d7a91f56a4a0c8374","impliedFormat":1},{"version":"7c4cf13b05d1c64ce1807d2e5c95fd657f7ef92f1eeb02c96262522c5797f862","impliedFormat":1},{"version":"eebe1715446b4f1234ce2549a8c30961256784d863172621eb08ae9bed2e67a3","impliedFormat":1},{"version":"64ad3b6cbeb3e0d579ebe85e6319d7e1a59892dada995820a2685a6083ea9209","impliedFormat":1},{"version":"5ebdc5a83f417627deff3f688789e08e74ad44a760cdc77b2641bb9bb59ddd29","impliedFormat":1},{"version":"a514beab4d3bc0d7afc9d290925c206a9d1b1a6e9aa38516738ce2ff77d66000","impliedFormat":1},{"version":"d80212bdff306ee2e7463f292b5f9105f08315859a3bdc359ba9daaf58bd9213","impliedFormat":1},{"version":"86b534b096a9cc35e90da2d26efbcb7d51bc5a0b2dde488b8c843c21e5c4701b","impliedFormat":1},{"version":"75519029c9e9389852d22714aec5956e00f090d18082e49f21d2875d554ebd26","impliedFormat":1},{"version":"e46d7758d8090d9b2c601382610894d71763a9909efb97b1eebbc6272d88d924","impliedFormat":1},{"version":"03af1b2c6ddc2498b14b66c5142a7876a8801fcac9183ae7c35aec097315337a","impliedFormat":1},{"version":"294b7d3c2afc0d8d3a7e42f76f1bac93382cb264318c2139ec313372bbfbde4f","impliedFormat":1},{"version":"a7bc0f0fd721b5da047c9d5a202c16be3f816954ad65ab684f00c9371bc8bac2","impliedFormat":1},{"version":"4bf7b966989eb48c30e0b4e52bfe7673fb7a3fb90747bdc5324637fc51505cd1","impliedFormat":1},{"version":"468308e0d01d8c073a6c442b6cbd5f0f7fcb68fbeabd3c30b0719cda2f5bfc38","impliedFormat":1},{"version":"c2d3538fabf7d43abd7599ff74c372800130e67674eb50b371a6c53646d2b977","impliedFormat":1},{"version":"10e006d13225983120773231f9fcc0f747a678056161db5c3c134697d0b4cb60","impliedFormat":1},{"version":"b456eb9cb3ff59d2ad86d53c656a0f07164e9dccbc0f09ac6a6f234dc44714ea","impliedFormat":1},{"version":"0fff2dbabbb30a467bbfef04d44819cb0b1baa84e669b46d4682c9d70ba11605","impliedFormat":1},{"version":"8baf3ec31869d4e82684fe062c59864b9d6d012b9105252e5697e64212e38b74","impliedFormat":1},{"version":"36a9827e64fa8e2af7d4fd939bf29e7ae6254fa9353ccebd849c894a4fd63e1b","impliedFormat":1},{"version":"3af8cee96336dd9dc44b27d94db5443061ff8a92839f2c8bbcc165ca3060fa6c","impliedFormat":1},{"version":"85d786a0accda19ef7beb6ae5a04511560110faa9c9298d27eaa4d44778fbf9e","impliedFormat":1},{"version":"7362683317d7deaa754bbf419d0a4561ee1d9b40859001556c6575ce349d95ea","impliedFormat":1},{"version":"408b6e0edb9d02acaf1f2d9f589aa9c6e445838b45c3bfa15b4bb98dc1453dc4","impliedFormat":1},{"version":"f8faa497faf04ffba0dd21cf01077ae07f0db08035d63a2e69838d173ae305bc","impliedFormat":1},{"version":"f8981c8de04809dccb993e59de5ea6a90027fcb9a6918701114aa5323d6d4173","impliedFormat":1},{"version":"7c9c89fd6d89c0ad443f17dc486aa7a86fa6b8d0767e1443c6c63311bdfbd989","impliedFormat":1},{"version":"a3486e635db0a38737d85e26b25d5fda67adef97db22818845e65a809c13c821","impliedFormat":1},{"version":"7c2918947143409b40385ca24adce5cee90a94646176a86de993fcdb732f8941","impliedFormat":1},{"version":"bdbf3acd48d637f947a0ef48c2301898e2eb8e5f9c1ad1d17b1e3f0d0ce3764c","impliedFormat":1},{"version":"55a36a053bfd464be800af2cd1b3ed83c6751277125786d62870bf159280b280","impliedFormat":1},{"version":"a8e7c075b87fda2dd45aa75d91f3ccb07bec4b3b1840bd4da4a8c60e03575cd2","impliedFormat":1},{"version":"f7b193e858e6c5732efa80f8073f5726dc4be1216450439eb48324939a7dd2be","impliedFormat":1},{"version":"f971e196cdf41219f744e8f435d4b7f8addacd1fbe347c6d7a7d125cd0eaeb99","impliedFormat":1},{"version":"fd38ff4bedf99a1cd2d0301d6ffef4781be7243dfbba1c669132f65869974841","impliedFormat":1},{"version":"e41e32c9fc04b97636e0dc89ecffe428c85d75bfc07e6b70c4a6e5e556fe1d6b","impliedFormat":1},{"version":"3a9522b8ed36c30f018446ec393267e6ce515ca40d5ee2c1c6046ce801c192cd","impliedFormat":1},{"version":"0e781e9e0dcd9300e7d213ce4fdec951900d253e77f448471d1bc749bd7f5f7c","impliedFormat":1},{"version":"bf8ea785d007b56294754879d0c9e7a9d78726c9a1b63478bf0c76e3a4446991","impliedFormat":1},{"version":"dbb439938d2b011e6b5880721d65f51abb80e09a502355af16de4f01e069cd07","impliedFormat":1},{"version":"f94a137a2b7c7613998433ca16fb7f1f47e4883e21cadfb72ff76198c53441a6","impliedFormat":1},{"version":"8296db5bbdc7e56cabc15f94c637502827c49af933a5b7ed0b552728f3fcfba8","impliedFormat":1},{"version":"ad46eedfff7188d19a71c4b8999184d1fb626d0379be2843d7fc20faea63be88","impliedFormat":1},{"version":"9ebac14f8ee9329c52d672aaf369be7b783a9685e8a7ab326cd54a6390c9daa6","impliedFormat":1},{"version":"dee395b372e64bfd6e55df9a76657b136e0ba134a7395e46e3f1489b2355b5b0","impliedFormat":1},{"version":"cf0ce107110a4b7983bacca4483ea8a1eac5e36901fc13c686ebef0ffbcbbacd","impliedFormat":1},{"version":"a4fc04fdc81ff1d4fdc7f5a05a40c999603360fa8c493208ccee968bd56e161f","impliedFormat":1},{"version":"8a2a61161d35afb1f07d10dbef42581e447aaeececc4b8766450c9314b6b4ee7","impliedFormat":1},{"version":"b817f19d56f68613a718e41d3ed545ecfd2c3096a0003d6a8e4f906351b3fb7d","impliedFormat":1},{"version":"bbdf5516dc4d55742ab23e76e0f196f31a038b4022c8aa7944a0964a7d36985e","impliedFormat":1},{"version":"981cca224393ac8f6b42c806429d5c5f3506e65edf963aa74bcef5c40b28f748","impliedFormat":1},{"version":"7239a60aab87af96a51cd8af59c924a55c78911f0ab74aa150e16a9da9a12e4f","impliedFormat":1},{"version":"258cbdcac1da6d114455af3ac7ca87eeff074001765e3b154dd57f25bda5fcb5","impliedFormat":1},{"version":"022e48d4e1ebd512e3fa5c3a321262ce05b53e8773fdb4b7de80d5288720993a","impliedFormat":1},{"version":"95fab99f991a8fb9514b3c9282bfa27ffc4b7391c8b294f2d8bf2ae0a092f120","impliedFormat":1},{"version":"62e46dac4178ba57a474dad97af480545a2d72cd8c0d13734d97e2d1481dbf06","impliedFormat":1},{"version":"3f3bc27ed037f93f75f1b08884581fb3ed4855950eb0dc9be7419d383a135b17","impliedFormat":1},{"version":"55fef00a1213f1648ac2e4becba3bb5758c185bc03902f36150682f57d2481d2","impliedFormat":1},{"version":"6fe2c13736b73e089f2bb5f92751a463c5d3dc6efb33f4494033fbd620185bff","impliedFormat":1},{"version":"6e249a33ce803216870ec65dc34bbd2520718c49b5a2d9afdee7e157b87617a2","impliedFormat":1},{"version":"e58f83151bb84b1c21a37cbc66e1e68f0f1cf60444b970ef3d1247cd9097fd94","impliedFormat":1},{"version":"83e46603ea5c3df5ae2ead2ee7f08dcb60aa071c043444e84675521b0daf496b","impliedFormat":1},{"version":"8baf3ec31869d4e82684fe062c59864b9d6d012b9105252e5697e64212e38b74","impliedFormat":1},{"version":"84de46efa2d75741d9d9bbdfdfe9f214b20f00d3459af52ef574d9f4f0dcc73a","impliedFormat":1},{"version":"fb02e489b353b21e32d32ea8aef49bdbe34d6768864cc40b6fb46727ac9d953a","impliedFormat":1},{"version":"c6ade0291b5eef6bf8a014c45fbac97b24eeae623dbacbe72afeab2b93025aa2","impliedFormat":1},{"version":"2c5e9ca373f23c9712da12f8efa976e70767a81eb3802e82182a2d1a3e4b190e","impliedFormat":1},{"version":"06bac29b70233e8c57e5eb3d2bda515c4bea6c0768416cd914b0336335f7069b","impliedFormat":1},{"version":"fded99673b5936855b8b914c5bdf6ada1f7443c773d5a955fa578ff257a6a70c","impliedFormat":1},{"version":"8e0e4155cdf91f9021f8929d7427f701214f3ba5650f51d8067c76af168a5b99","impliedFormat":1},{"version":"ef344f40acc77eafa0dd7a7a1bc921e0665b8b6fc70aeea7d39e439e9688d731","impliedFormat":1},{"version":"36a1dffdbb2d07df3b65a3ddda70f446eb978a43789c37b81a7de9338daff397","impliedFormat":1},{"version":"bcb2c91f36780ff3a32a4b873e37ebf1544fb5fcc8d6ffac5c0bf79019028dae","impliedFormat":1},{"version":"d13670a68878b76d725a6430f97008614acba46fcac788a660d98f43e9e75ba4","impliedFormat":1},{"version":"7a03333927d3cd3b3c3dd4e916c0359ab2e97de6fd2e14c30f2fb83a9990792e","impliedFormat":1},{"version":"fc6fe6efb6b28eb31216bd2268c1bc5c4c4df3b4bc85013e99cd2f462e30b6fc","impliedFormat":1},{"version":"6cc13aa49738790323a36068f5e59606928457691593d67106117158c6091c2f","impliedFormat":1},{"version":"68255dbc469f2123f64d01bfd51239f8ece8729988eec06cea160d2553bcb049","impliedFormat":1},{"version":"c3bd50e21be767e1186dacbd387a74004e07072e94e2e76df665c3e15e421977","impliedFormat":1},{"version":"3106b08c40971596efc54cc2d31d8248f58ba152c5ec4d741daf96cc0829caea","impliedFormat":1},{"version":"219d9a049a24c69d917d0d87d09edc4d009d527e6eb77b7eab97e560f8e59039","impliedFormat":1},{"version":"6df4ad74f47da1c7c3445b1dd7c63bd3d01bbc0eb31aaebdea371caa57192ce5","impliedFormat":1},{"version":"dcc26e727c39367a46931d089b13009b63df1e5b1c280b94f4a32409ffd3fa36","impliedFormat":1},{"version":"36979d4a469985635dd7539f25facd607fe1fb302ad1c6c2b3dce036025419e8","impliedFormat":1},{"version":"670a1df5b6f9df0d001d22620a50776153e04f8541d5b17298a6b8afced71e20","impliedFormat":1},{"version":"7e138dc97e3b2060f77c4b6ab3910b00b7bb3d5f8d8a747668953808694b1938","impliedFormat":1},{"version":"5b6d83c94236cf3e9e19315cc6d62b9787253c73a53faea34ead697863f81447","impliedFormat":1},{"version":"6d448f6bfeeef15718b82fd6ac9ae8871f7843a3082c297339398167f8786b2e","impliedFormat":1},{"version":"55cdcbc0af1398c51f01b48689e3ce503aa076cc57639a9351294e23366a401d","impliedFormat":1},{"version":"7e553f3b746352b0200dd91788b479a2b037a6a7d8d04aa6d002da09259f5687","impliedFormat":1},{"version":"32615eb16e819607b161e2561a2cd75ec17ac6301ba770658d5a960497895197","impliedFormat":1},{"version":"ac14cc1d1823cec0bf4abc1d233a995b91c3365451bf1859d9847279a38f16ee","impliedFormat":1},{"version":"f1142315617ac6a44249877c2405b7acda71a5acb3d4909f4b3cbcc092ebf8bd","impliedFormat":1},{"version":"3356f7498c6465efb74d0a6a5518b6b8f27d9e096abd140074fd24e9bd483dbd","impliedFormat":1},{"version":"73a0ee6395819b063df4b148211985f2e1442945c1a057204cf4cf6281760dc3","affectsGlobalScope":true,"impliedFormat":1},{"version":"d05d8c67116dceafc62e691c47ac89f8f10cf7313cd1b2fb4fe801c2bf1bb1a7","impliedFormat":1},{"version":"3c5bb5207df7095882400323d692957e90ec17323ccff5fd5f29a1ecf3b165d0","impliedFormat":1},{"version":"648ae35c81ab9cb90cb1915ede15527b29160cce0fa1b5e24600977d1ba11543","impliedFormat":1},{"version":"ddc0e8ba97c5ad221cf854999145186b917255b2a9f75d0de892f4d079fa0b5c","impliedFormat":1},{"version":"a9fc166c68c21fd4d4b4d4fb55665611c2196f325e9d912a7867fd67e2c178da","impliedFormat":1},{"version":"2f60a32bb6a05a722c42bb9709f917bb37f2484375367eb9c03bdafd9de42daf","impliedFormat":1},{"version":"d571fae704d8e4d335e30b9e6cf54bcc33858a60f4cf1f31e81b46cf82added4","impliedFormat":1},{"version":"b9406c40955c0dcf53a275697c4cddd7fe3fca35a423ade2ac750f3ba17bd66d","impliedFormat":1},{"version":"d7eb2711e78d83bc0a2703574bf722d50c76ef02b8dd6f8a8a9770e0a0f7279f","impliedFormat":1},{"version":"323127b2ac397332f21e88cd8e04c797ea6a48dedef19055cbd2fc467a3d8c84","impliedFormat":1},{"version":"f17613239e95ffcfa69fbba3b0c99b741000699db70d5e8feea830ec4bba641d","impliedFormat":1},{"version":"fff6aa61f22d8adb4476adfd8b14473bcdb6d1c9b513e1bfff14fe0c165ced3c","impliedFormat":1},{"version":"bdf97ac70d0b16919f2713613290872be2f3f7918402166571dbf7ce9cdc8df4","impliedFormat":1},{"version":"8667f65577822ab727b102f83fcd65d9048de1bf43ab55f217fbf22792dafafb","impliedFormat":1},{"version":"58f884ab71742b13c59fc941e2d4419aaf60f9cf7c1ab283aa990cb7f7396ec3","impliedFormat":1},{"version":"2c7720260175e2052299fd1ce10aa0a641063ae7d907480be63e8db508e78eb3","impliedFormat":1},{"version":"506823d1acd8978aa95f9106dfe464b65bdcd1e1539a994f4a9272db120fc832","impliedFormat":1},{"version":"d6a30821e37d7b935064a23703c226506f304d8340fa78c23fc7ea1b9dc57436","impliedFormat":1},{"version":"94a8650ade29691f97b9440866b6b1f77d4c1d0f4b7eea4eb7c7e88434ded8c7","impliedFormat":1},{"version":"bf26b847ce0f512536bd1f6d167363a3ae23621da731857828ce813c5cebc0db","impliedFormat":1},{"version":"87af268385a706c869adc8dd8c8a567586949e678ce615165ffcd2c9a45b74e7","impliedFormat":1},{"version":"affad9f315b72a6b5eb0d1e05853fa87c341a760556874da67643066672acdaf","impliedFormat":1},{"version":"6216f92d8119f212550c216e9bc073a4469932c130399368a707efb54f91468c","impliedFormat":1},{"version":"f7d86f9a241c5abf48794b76ac463a33433c97fc3366ce82dfa84a5753de66eb","impliedFormat":1},{"version":"01dab6f0b3b8ab86b120b5dd6a59e05fc70692d5fc96b86e1c5d54699f92989c","impliedFormat":1},{"version":"fe06598ceca505b18966573fbae84dfc1fda6f4e2adbb4369f3b3e2aef16bada","impliedFormat":1},{"version":"1ca7c8e38d1f5c343ab5ab58e351f6885f4677a325c69bb82d4cba466cdafeda","impliedFormat":1},{"version":"17c9ca339723ded480ca5f25c5706e94d4e96dcd03c9e9e6624130ab199d70e1","impliedFormat":1},{"version":"01aa1b58e576eb2586eedb97bcc008bbe663017cc49f0228da952e890c70319f","impliedFormat":1},{"version":"d57e64f90522b8cedf16ed8ba4785f64c297768ff145b95d3475114574c5b8e2","impliedFormat":1},{"version":"6a37dd9780f837be802142fe7dd70bb3f7279425422c893dd91835c0869cb7ac","impliedFormat":1},{"version":"167456e78d7c3a638170cbbca07a9b02df2bee81fbd995e2a0b1719a4e34f16b","impliedFormat":1},{"version":"22e1e1b1e1df66f6a1fdb7be8eb6b1dbb3437699e6b0115fbbae778c7782a39f","impliedFormat":1},{"version":"1a47e278052b9364140a6d24ef8251d433d958be9dd1a8a165f68cecea784f39","impliedFormat":1},{"version":"f7af9db645ecfe2a1ead1d675c1ccc3c81af5aa1a2066fe6675cd6573c50a7e3","impliedFormat":1},{"version":"3a9d25dcbb2cdcb7cd202d0d94f2ac8558558e177904cfb6eaff9e09e400c683","impliedFormat":1},{"version":"f65a5aa0e69c20579311e72e188d1df2ef56ca3a507d55ab3cb2b6426632fe9b","impliedFormat":1},{"version":"1144d12482a382de21d37291836a8aca0a427eb1dc383323e1ddbcf7ee829678","impliedFormat":1},{"version":"7a68ca7786ca810eb440ae1a20f5a0bd61f73359569d6faa4794509d720000e6","impliedFormat":1},{"version":"8f5f7f06129ffd3b4e4c4cf886faa54d85f79debd2651a17d9332b8289306b1a","impliedFormat":1},{"version":"5e97563ec4a9248074fdf7844640d3c532d6ce4f8969b15ccc23b059ed25a7c4","impliedFormat":1},{"version":"7d67d7bd6308dc2fb892ae1c5dca0cdee44bfcfd0b5db2e66d4b5520c1938518","impliedFormat":1},{"version":"0ba8f23451c2724360edfa9db49897e808fa926efb8c2b114498e018ed88488f","impliedFormat":1},{"version":"3e618bc95ef3958865233615fbb7c8bf7fe23c7f0ae750e571dc7e1fefe87e96","impliedFormat":1},{"version":"b901e1e57b1f9ce2a90b80d0efd820573b377d99337f8419fc46ee629ed07850","impliedFormat":1},{"version":"f720eb538fc2ca3c5525df840585a591a102824af8211ac28e2fd47aaf294480","impliedFormat":1},{"version":"ae9d0fa7c8ba01ea0fda724d40e7f181275c47d64951a13f8c1924ac958797bc","impliedFormat":1},{"version":"346d9528dcd89e77871a2decebd8127000958a756694a32512fe823f8934f145","impliedFormat":1},{"version":"d831ae2d17fd2ff464acbd9408638f06480cb8eb230a52d14e7105065713dca4","impliedFormat":1},{"version":"0a3dec0f968c9463b464a29f9099c1d5ca4cd3093b77a152f9ff0ae369c4d14b","impliedFormat":1},{"version":"a3fda2127b3185d339f80e6ccc041ce7aa85fcb637195b6c28ac6f3eed5d9d79","impliedFormat":1},{"version":"b238a1a5be5fbf8b5b85c087f6eb5817b997b4ce4ce33c471c3167a49524396c","impliedFormat":1},{"version":"ba849c0aba26864f2db0d29589fdcaec09da4ba367f127efdac1fcb4ef007732","impliedFormat":1},{"version":"ed10bc2be0faa78a2d1c8372f8564141c2360532e4567b81158ffe9943b8f070","impliedFormat":1},{"version":"b432f4a1f1d7e7601a870ab2c4cff33787de4aa7721978eb0eef543c5d7fe989","impliedFormat":1},{"version":"3f9d87ee262bd1620eb4fb9cb93ca7dc053b820f07016f03a1a653a5e9458a7a","impliedFormat":1},{"version":"a61d92e4a3c244f5b3f156def2671b10a727a777dc07e52c5e53e0ea2ddeefc8","impliedFormat":1},{"version":"de716ad71873d3d56e0d611a3d5c1eae627337c1f88790427c21f3cb47a7b6f7","impliedFormat":1},{"version":"a8072ae5bc04fea741eba493fddf84c8e6d242d2a847467428bf2cbab0b790a7","impliedFormat":1},{"version":"ce055e5bea657486c142afbf7c77538665e0cb9a2dc92a226c197d011be3e908","impliedFormat":1},{"version":"673b1fc746c54e7e16b562f06660ffdae5a00b0796b6b0d4d0aaf1f7507f1720","impliedFormat":1},{"version":"710202fdeb7a95fbf00ce89a67639f43693e05a71f495d104d8fb13133442cbc","impliedFormat":1},{"version":"11754fdc6f8c9c04e721f01d171aad19dac10a211ae0c8234f1d80f6c7accfd4","impliedFormat":1},{"version":"eb394bd8fe37e4f59057ef97404d6b4849bd636921101c25620d933f32ccebac","impliedFormat":1},{"version":"ebed2d323bfc3cb77205b7df5ad82b7299a22194d7185aba1f3aa9367d0582e2","impliedFormat":1},{"version":"199f93a537e4af657dc6f89617e3384b556ab251a292e038c7a57892a1fa479c","impliedFormat":1},{"version":"ead16b329693e880793fe14af1bbcaf2e41b7dee23a24059f01fdd3605cac344","impliedFormat":1},{"version":"ba14614494bccb80d56b14b229328db0849feb1cbfd6efdc517bc5b0cb21c02f","impliedFormat":1},{"version":"6c3760df827b88767e2a40e7f22ce564bb3e57d799b5932ec867f6f395b17c8f","impliedFormat":1},{"version":"885d19e9f8272f1816266a69d7e4037b1e05095446b71ea45484f97c648a6135","impliedFormat":1},{"version":"afcc443428acd72b171f3eba1c08b1f9dcbba8f1cc2430d68115d12176a78fb0","impliedFormat":1},{"version":"8ef33387e4661678691489e4a2cab1765efd8fad7cb5cb47f46f0ece1ad7903e","impliedFormat":1},{"version":"029774092e2d209dbf338eebc52f1163ddf73697a274cfdd9fa7046062b9d2b1","impliedFormat":1},{"version":"594692b6c292195e21efbddd0b1af9bd8f26f2695b9ffc7e9d6437a59905889e","impliedFormat":1},{"version":"092a816537ec14e80de19a33d4172e3679a3782bf0edfd3c137b1d2d603c923e","impliedFormat":1},{"version":"60f0efb13e1769b78bd5258b0991e2bf512d3476a909c5e9fd1ca8ee59d5ef26","impliedFormat":1},{"version":"3cfd46f0c1fe080a1c622742d5220bd1bf47fb659074f52f06c996b541e0fc9b","impliedFormat":1},{"version":"e8d8b23367ad1f5124f3d8403cf2e6d13b511ebb4c728f90ec59ceeb1d907cc1","impliedFormat":1},{"version":"291b182b1e01ded75105515bcefd64dcf675f98508c4ca547a194afd80331823","impliedFormat":1},{"version":"75ddb104faa8f4f84b3c73e587c317d2153fc20d0d712a19f77bea0b97900502","impliedFormat":1},{"version":"135785aa49ae8a82e23a492b5fc459f8a2044588633a124c5b8ff60bbb31b5d4","impliedFormat":1},{"version":"267d5f0f8b20eaeb586158436ba46c3228561a8e5bb5c89f3284940a0a305bd8","impliedFormat":1},{"version":"1d21320d3bf6b17b6caf7e736b78c3b3e26ee08b6ac1d59a8b194039aaaa93ae","impliedFormat":1},{"version":"8b2efbff78e96ddab0b581ecd0e44a68142124444e1ed9475a198f2340fe3ef7","impliedFormat":1},{"version":"6eff0590244c1c9daf80a3ac1e9318f8e8dcd1e31a89983c963bb61be97b981b","impliedFormat":1},{"version":"95f17c73be9d73da53780321cdce58737e915102ac334a75d3798333f5fe2a21","impliedFormat":1},{"version":"a069aef689b78d2131045ae3ecb7d79a0ef2eeab9bc5dff10a653c60494faa79","impliedFormat":1},{"version":"680db60ad1e95bbefbb302b1096b5ad3ce86600c9542179cc52adae8aee60f36","impliedFormat":1},{"version":"8fe6d4285c9486741b09ca3b32dde2da3cf94d18ae1ec490217ee8980c9f7eee","impliedFormat":1},{"version":"b775bfe85c7774cafc1f9b815c17f233c98908d380ae561748de52ccacc47e17","impliedFormat":1},{"version":"5a81c7117f8f1c393c09b3a108549825df175b4b388d2dbc7f11e6a1d234c0d4","impliedFormat":1},{"version":"ebe41fb9fe47a2cf7685a1250a56acf903d8593a8776403eca18d793edc0df54","impliedFormat":1},{"version":"4eb2a7789483e5b2e40707f79dcbd533f0871439e2e5be5e74dc0c8b0f8b9a05","impliedFormat":1},{"version":"984dcccd8abcfd2d38984e890f98e3b56de6b1dd91bf05b8d15a076efd7d84c0","impliedFormat":1},{"version":"d9f4968d55ba6925a659947fe4a2be0e58f548b2c46f3d42d9656829c452f35e","impliedFormat":1},{"version":"57fd651cc75edc35e1aa321fd86034616ec0b1bd70f3c157f2e1aee414e031a0","impliedFormat":1},{"version":"97fec1738c122037ca510f69c8396d28b5de670ceb1bd300d4af1782bd069b0b","impliedFormat":1},{"version":"74a16af8bbfaa038357ee4bceb80fad6a28d394a8faaac3c0d0aa0f9e95ea66e","impliedFormat":1},{"version":"044c44c136ae7fb9ff46ac0bb0ca4e7f41732ca3a3991844ba330fa1bfb121a2","impliedFormat":1},{"version":"d47c270ad39a7706c0f5b37a97e41dbaab295b87964c0c2e76b3d7ad68c0d9d6","impliedFormat":1},{"version":"13e6b949e30e37602fdb3ef961fd7902ccdc435552c9ead798d6de71b83fe1e3","impliedFormat":1},{"version":"f7884f326c4a791d259015267a6b2edbeef3b7cb2bc38dd641ce2e4ef76862e7","impliedFormat":1},{"version":"0f51484aff5bbb48a35a3f533be9fdc1eccac65e55b8a37ac32beb3c234f7910","impliedFormat":1},{"version":"17011e544a14948255dcaa6f9af2bcf93cce417e9e26209c9aa5cbd32852b5b2","impliedFormat":1},{"version":"e12c35fe5d5132ad688215a725ca48d15e5b1bfa26948de18f9e43e7d2cc07ad","impliedFormat":1},{"version":"db7fa2be9bddc963a6fb009099936a5108494adb9e70fd55c249948ea2780309","impliedFormat":1},{"version":"25db4e7179be81d7b9dbb3fde081050778d35fabcc75ada4e69d7f24eb03ce66","impliedFormat":1},{"version":"43ceb16649b428a65b23d08bfc5df7aaaba0b2d1fee220ba7bc4577e661c38a6","impliedFormat":1},{"version":"f3f2e18b3d273c50a8daa9f96dbc5d087554f47c43e922aa970368c7d5917205","impliedFormat":1},{"version":"c17c4fc020e41ddbe89cd63bed3232890b61f2862dd521a98eb2c4cb843b6a42","impliedFormat":1},{"version":"eb77c432329a1a00aac36b476f31333260cd81a123356a4bf2c562e6ac8dc5a4","impliedFormat":1},{"version":"6d2f991e9405c12b520e035bddb97b5311fed0a8bf82b28f7ef69df7184f36c2","impliedFormat":1},{"version":"8e002fd1fc6f8d77200af3d4b5dd6f4f2439a590bf15e037a289bb528ecc6a12","impliedFormat":1},{"version":"2d0748f645de665ca018f768f0fd8e290cf6ce86876df5fc186e2a547503b403","impliedFormat":1},{"version":"7cd50e4c093d0fe06f2ebe1ae5baeefae64098751fb7fa6ae03022035231cc97","impliedFormat":1},{"version":"334bfc2a6677bc60579dbf929fe1d69ac780a0becd1af812132b394e1f6a3ea6","impliedFormat":1},{"version":"ed8e02a44e1e0ddee029ef3c6804f42870ee2b9e17cecad213e8837f5fcd756b","impliedFormat":1},{"version":"b13b25bbfa55a784ec4ababc70e3d050390347694b128f41b3ae45f0202d5399","impliedFormat":1},{"version":"b9fc71b8e83bcc4b5d8dda7bcf474b156ef2d5372de98ac8c3710cfa2dc96588","impliedFormat":1},{"version":"85587f4466c53be818152cbf7f6be67c8384dcf00860290dca05e0f91d20f28d","impliedFormat":1},{"version":"9d4943145bd78babb9f3deb4fccd09dabd14005118ffe30935175056fa938c2b","impliedFormat":1},{"version":"325501db2249efa7194d7baf8f49782709d91bc3d93812b2636e1a7fd127b067","impliedFormat":1},{"version":"944fcf2e7415a20278f025b4587fb032d7174b89f7ba9219b8883affa6e7d2e3","impliedFormat":1},{"version":"589b3c977372b6a7ba79b797c3a21e05a6e423008d5b135247492cc929e84f25","impliedFormat":1},{"version":"ab16a687cfc7d148a8ae645ffd232c765a5ed190f76098207c159dc7c86a1c43","impliedFormat":1},{"version":"1aa722dee553fc377e4406c3ec87157e66e4d5ea9466f62b3054118966897957","impliedFormat":1},{"version":"55bf2aecbdc32ea4c60f87ae62e3522ef5413909c9a596d71b6ec4a3fafb8269","impliedFormat":1},{"version":"7832c3a946a38e7232f8231c054f91023c4f747ad0ce6b6bc3b9607d455944f7","impliedFormat":1},{"version":"696d56df9e55afa280df20d55614bb9f0ad6fcac30a49966bb01580e00e3a2d4","impliedFormat":1},{"version":"07e20b0265957b4fd8f8ce3df5e8aea0f665069e1059de5d2c0a21b1e8a7de09","impliedFormat":1},{"version":"08424c1704324a3837a809a52b274d850f6c6e1595073946764078885a3fa608","impliedFormat":1},{"version":"f5d9a7150b0782e13d4ed803ee73cf4dbc04e99b47b0144c9224fd4af3809d4d","impliedFormat":1},{"version":"551d60572f79a01b300e08917205d28f00356c3ee24569c7696bfd27b2e77bd7","impliedFormat":1},{"version":"8570e9ce13cf15050f0a825e46499c6dedd1989216657799c2c5d5a471d7acff","impliedFormat":1},{"version":"f04efd0fae5202872be8f8b6782b42802ff17de45af734f2baba0b9cc5105e12","impliedFormat":1},{"version":"36d4ae6f8e4c60dfffc8e8ce9ec7a61d01891a081c84856aeba083cb2d756552","impliedFormat":1},{"version":"243d3055f8cb29f0dd09f2f2cdd31b28b7b5ae441a8db32f28bd884f694720f9","impliedFormat":1},{"version":"367a2dbfd74532530c5b2d6b9c87d9e84599e639991151b73d42c720aa548611","impliedFormat":1},{"version":"3df200a7de1b2836c42b3e4843a6c119b4b0e4857a86ebc7cc5a98e084e907f0","impliedFormat":1},{"version":"ae05563905dc09283da42d385ca1125113c9eba83724809621e54ea46309b4e3","impliedFormat":1},{"version":"722fb0b5eff6878e8ad917728fa9977b7eaff7b37c6abb3bd5364cd9a1d7ebc3","impliedFormat":1},{"version":"8d4b70f717f7e997110498e3cfd783773a821cfba257785815b697b45d448e46","impliedFormat":1},{"version":"3735156a254027a2a3b704a06b4094ef7352fa54149ba44dd562c3f56f37b6ca","impliedFormat":1},{"version":"166b65cc6c34d400e0e9fcff96cd29cef35a47d25937a887c87f5305d2cb4cac","impliedFormat":1},{"version":"cf0e1a8d3d1739e50ab4b351cef347959c98c27d1a5ea3b3d922e346a18e4524","impliedFormat":1},{"version":"d17f800659c0b683ea73102ca542ab39009c0a074acf3546321a46c1119faf90","impliedFormat":1},{"version":"e6d61568c240780aaf02c717f950ba4a993c65f3b34ff1bacd9aeff88fa3ac4c","impliedFormat":1},{"version":"f89a15f66cf6ba42bce4819f10f7092cdecbad14bf93984bfb253ffaacf77958","impliedFormat":1},{"version":"822316d43872a628af734e84e450091d101b8b9aa768db8e15058c901d5321e6","impliedFormat":1},{"version":"f20e43033f56cec37fee8ea310a1fb32773afedb382fd33c4d0d109714291cbb","impliedFormat":1},{"version":"53f80bf906602b9cb84bb6ca737bfd71dd45b75949937cc898d0ddffb7a59cde","impliedFormat":1},{"version":"16cccc9037b4bab06d3a88b14644aa672bf0985252d782bbf8ff05df1a7241e8","impliedFormat":1},{"version":"0154d805e3f4f5a40d510c7fb363b57bf1305e983edde83ccd330cef2ba49ed0","impliedFormat":1},{"version":"89da9aeab1f9e59e61889fb1a5fdb629e354a914519956dfa3221e2a43361bb2","impliedFormat":1},{"version":"9703f7408c354bf0264ab25c88c74d7bfee7c6f164661e75813bc68c93836575","impliedFormat":1},{"version":"5ced0582128ed677df6ef83b93b46bffba4a38ddba5d4e2fb424aa1b2623d1d5","impliedFormat":1},{"version":"f1cc60471b5c7594fa2d4a621f2c3169faa93c5a455367be221db7ca8c9fddb1","impliedFormat":1},{"version":"d1bf63146a0dbbe04ba27877020724f165d3f40c4a26aeab373a4ceafc081dc5","impliedFormat":1},{"version":"2739797a759c3ebcab1cb4eb208155d578ef4898fcfb826324aa52b926558abc","impliedFormat":1},{"version":"33ce098f31987d84eb2dd1d6984f5c1c1cae06cc380cb9ec6b30a457ea03f824","impliedFormat":1},{"version":"59683bee0f65ae714cc3cf5fa0cb5526ca39d5c2c66db8606a1a08ae723262b8","impliedFormat":1},{"version":"bc8eb1da4e1168795480f09646dcb074f961dfe76cd74d40fc1c342240ac7be4","impliedFormat":1},{"version":"8d513d33766e10e9c34174600579ece2b57e70e4a6cb8639d3b47f6ae1d40ab5","impliedFormat":1},{"version":"4b31302539066a3c659827d9bfc8a8b87ced23f93bb3a2addc69de2b9755a9b3","impliedFormat":1},{"version":"03b9959bee04c98401c8915227bbaa3181ddc98a548fb4167cd1f7f504b4a1ea","impliedFormat":1},{"version":"2d18b7e666215df5d8becf9ffcfef95e1d12bfe0ac0b07bc8227b970c4d3f487","impliedFormat":1},{"version":"d7ebeb1848cd09a262a09c011c9fa2fc167d0dd6ec57e3101a25460558b2c0e3","impliedFormat":1},{"version":"6c27c0042aed02a14cc458bff4cf45b4da4ae3b26a68e1da66dbf5a1be8d0640","impliedFormat":1},{"version":"07df5b8be0ba528abc0b3fdc33a29963f58f7ce46ea3f0ccfaf4988d18f43fff","impliedFormat":1},{"version":"b0e19c66907ad996486e6b3a2472f4d31c309da8c41f38694e931d3462958d7f","impliedFormat":1},{"version":"3880b10e678e32fcfd75c37d4ad8873f2680ab50582672896700d050ce3f99b6","impliedFormat":1},{"version":"1a372d53e61534eacd7982f80118b67b37f5740a8e762561cd3451fb21b157ff","impliedFormat":1},{"version":"3784f188208c30c6d523d257e03c605b97bc386d3f08cabe976f0e74cd6a5ee5","impliedFormat":1},{"version":"49586fc10f706f9ebed332618093aaf18d2917cf046e96ea0686abaae85140a6","impliedFormat":1},{"version":"921a87943b3bbe03c5f7cf7d209cc21d01f06bf0d9838eee608dfab39ae7d7f4","impliedFormat":1},{"version":"1741f9ea7301b7e61c43bf79b067ffbc22daa0990f06ae6e6dcc0eb55ebb5ede","impliedFormat":1},{"version":"f0885de71d0dbf6d3e9e206d9a3fce14c1781d5f22bca7747fc0f5959357eeab","impliedFormat":1},{"version":"ddebc0a7aada4953b30b9abf07f735e9fec23d844121755309f7b7091be20b8d","impliedFormat":1},{"version":"6fdc397fc93c2d8770486f6a3e835c188ccbb9efac1a28a3e5494ea793bc427c","impliedFormat":1},{"version":"9cc02f7c626b430b3c3b783806262d7c18e9f3fd5a9b6eabb4f943340feaefb5","impliedFormat":1},{"version":"ea694ad54dd168114509a1c3e96141fb1cfbafe09e41180af3ecee66b063f997","impliedFormat":1},{"version":"b6e4cafbcb84c848dfeffeb9ca7f5906d47ed101a41bc068bb1bb27b75f18782","impliedFormat":1},{"version":"9799e6726908803d43992d21c00601dc339c379efabe5eee9b421dbd20c61679","impliedFormat":1},{"version":"dfa5d54c4a1f8b2a79eaa6ecb93254814060fba8d93c6b239168e3d18906d20e","impliedFormat":1},{"version":"858c71909635cf10935ce09116a251caed3ac7c5af89c75d91536eacb5d51166","impliedFormat":1},{"version":"b3eb56b920afafd8718dc11088a546eeb3adf6aa1cbc991c9956f5a1fe3265b3","impliedFormat":1},{"version":"605940ddc9071be96ec80dfc18ab56521f927140427046806c1cfc0adf410b27","impliedFormat":1},{"version":"1a350245a56fdf1f7bac061fce62689f940ea7dd38dee8ccbfc593619eeb4649","impliedFormat":1},{"version":"5194a7fd715131a3b92668d4992a1ac18c493a81a9a2bb064bcd38affc48f22d","impliedFormat":1},{"version":"b7dce3b64ac90cfb272ff277f0a250791829d4b3efc772f2d1c44c30a0218a8b","impliedFormat":1},{"version":"0d7dcf40ed5a67b344df8f9353c5aa8a502e2bbdad53977bc391b36b358a0a1c","impliedFormat":1},{"version":"093ad5bb0746fdb36f1373459f6a8240bc4473829723300254936fc3fdaee111","impliedFormat":1},{"version":"f2367181a67aff75790aa9a4255a35689110f7fb1b0adb08533913762a34f9e6","impliedFormat":1},{"version":"4a1a4800285e8fd30b13cb69142103845c6cb27086101c2950c93ffcd4c52b94","impliedFormat":1},{"version":"c295f6c684e8121b6f25f4767202e5baf9826fe16eec42f4a2bb2966da0f5898","impliedFormat":1},{"version":"fe255676a54e5a01f951e6f773c715391f7d902d197d9ca11a4f9c6b79ffa2ad","impliedFormat":1},{"version":"739708e7d4f5aba95d6304a57029dfbabe02cb594cf5d89944fd0fc7d1371c3a","impliedFormat":1},{"version":"22f31306ddc006e2e4a4817d44bf9ac8214caae39f5706d987ade187ecba09e3","impliedFormat":1},{"version":"4237f49cdd6db9e33c32ccc1743d10b01fdd929c74906e7eecd76ce0b6f3688a","impliedFormat":1},{"version":"4ed726e8489a57adcf586687ff50533e7fe446fb48a8791dbc75d8bf77d1d390","impliedFormat":1},{"version":"bbde826b04c01b41434728b45388528a36cc9505fda4aa3cdd9293348e46b451","impliedFormat":1},{"version":"02a432db77a4579267ff0a5d4669b6d02ebc075e4ff55c2ff2a501fc9433a763","impliedFormat":1},{"version":"086b7a1c4fe2a9ef6dfa030214457b027e90fc1577e188c855dff25f8bcf162c","impliedFormat":1},{"version":"68799ca5020829d2dbebfda86ed2207320fbf30812e00ed2443b2d0a035dda52","impliedFormat":1},{"version":"dc7f0f8e24d838dabe9065f7f55c65c4cfe68e3be243211f625fa8c778c9b85c","impliedFormat":1},{"version":"92169f790872f5f28be4fce7e371d2ccf17b0cc84057a651e0547ad63d8bcb68","impliedFormat":1},{"version":"765b8fe4340a1c7ee8750b4b76f080b943d85e770153e78503d263418b420358","impliedFormat":1},{"version":"12d71709190d96db7fbb355f317d50e72b52e16c3451a20dae13f4e78db5c978","impliedFormat":1},{"version":"7367c0d3442165e6164185b7950b8f70ea2be0142b2175748fef7dc23c6d2230","impliedFormat":1},{"version":"d66efc7ed427ca014754343a80cf2b4512ceaa776bc4a9139d06863abf01ac5c","impliedFormat":1},{"version":"cb0e8923b4d8d8a5bbcea59abc731a1cca90f69aef74f6b27df0bd890d6a00ed","impliedFormat":1},{"version":"dbeb4c3a24b95fe4ad6fdff9577455f5868fbb5ad12f7c22c68cb24374d0996d","impliedFormat":1},{"version":"c1a6eb35cd952ae43b898cc022f39461f7f31360849cdaff12ac56fc5d4cb00d","impliedFormat":1},{"version":"7393dadbd583b53cce10c7644f399d1226e05de29b264985968280614be9e0dd","impliedFormat":1},{"version":"5cd0e12398a8584c4a287978477dab249dc2a490255499a4f075177d1aba0467","impliedFormat":1},{"version":"e60ec884263e7ffcebaf4a45e95a17fc273120a5d474963d4d6d7a574e2e9b97","impliedFormat":1},{"version":"6fd6c4c9eef86c84dd1f09cbd8c10d8feb3ed871724ba8d96a7bd138825a0c1a","impliedFormat":1},{"version":"a420fa988570675d65a6c0570b71bebf0c793f658b4ae20efc4f8e21a1259b54","impliedFormat":1},{"version":"05e9608dfef139336fb2574266412a6352d605857de2f94b2ce454d53e813cd6","impliedFormat":1},{"version":"02de191d16b2797feb7dcebb865562ad148a9507e523c0470d308c5eef158eec","impliedFormat":1},{"version":"bb1c6786ef387ac7a2964ea61adfb76bf9f967bbd802b0494944d7eec31fea2e","impliedFormat":1},{"version":"df407b6c3a8a3ef06519fbe16923df440cbd0fb536effdaa15b312ac8e89dac2","impliedFormat":1},{"version":"77144f05a89288283c8647d605ad49a0b155d0619ed0ea91a15f50174480624f","impliedFormat":1},{"version":"318957769f5b75529bc378b984dacbd42fbfc0db7481bc69cd1b29de812ad54b","impliedFormat":1},{"version":"a5e704ce23f12bfe9df4e9d564656ccaa5a9a896fa7c70537eadec4c74d2a3dc","impliedFormat":1},{"version":"3ee349cda390e8f285b3d861fb5a78e9f69be0d7303607334e08a75ce925928f","impliedFormat":1},{"version":"1efcaa13b1dd8738ba7261f7be898b2d80516e3b9aa091a790b2818179f2cf78","impliedFormat":1},{"version":"111a4c948e8a448d677bfc92166f8a596de03f66045bc1bec50a2f36edb710d2","impliedFormat":1},{"version":"9d7437397cb58f2410f4d64d86a686a6281c5811b17d41b077d6ec0c45d0312e","impliedFormat":1},{"version":"2fdde32fbf21177400da4d10665802c5b7629e2d4012df23d3f9b6e975c52098","impliedFormat":1},{"version":"a8e6ea80509b241d29a62b478b1eb5f8cd2ef9f531056ffc62127ee68e3692f8","impliedFormat":1},{"version":"bbffb20bab36db95b858d13591b9c09e29f76c4b7521dc9366f89eb2aeead68d","impliedFormat":1},{"version":"61b25ce464888c337df2af9c45ca93dcae014fef5a91e6ecce96ce4e309a3203","impliedFormat":1},{"version":"1ac6ead96cc738705b3cc0ba691ae2c3198a93d6a5eec209337c476646a2bce3","impliedFormat":1},{"version":"d5c89d3342b9a5094b31d5f4a283aa0200edc84b855aba6af1b044d02a9cf3b2","impliedFormat":1},{"version":"9863cfd0e4cda2e3049c66cb9cd6d2fd8891c91be0422b4e1470e3e066405c12","impliedFormat":1},{"version":"c8353709114ef5cdaeea43dde5c75eb8da47d7dce8fbc651465a46876847b411","impliedFormat":1},{"version":"0c55d168d0c377ce0340d219a519d3038dd50f35aaadb21518c8e068cbd9cf5e","impliedFormat":1},{"version":"356da547f3b6061940d823e85e187fc3d79bd1705cb84bd82ebea5e18ad28c9c","impliedFormat":1},{"version":"6ee8db8631030efcdb6ac806355fd321836b490898d8859f9ba882943cb197eb","impliedFormat":1},{"version":"e7afb81b739a7b97b17217ce49a44577cfd9d1de799a16a8fc9835eae8bff767","impliedFormat":1},{"version":"ca7c244766ad374c1e664416ca8cc7cd4e23545d7f452bbe41ec5dc86ba81b76","impliedFormat":1},{"version":"46e3a0dfd8cf0e36d14ceaf852d8483bfccbfebe0245debffac0a3b227933c51","impliedFormat":1},{"version":"61e92305d8e3951cc6692064f222555acf25fe83d5313bc441d13098a3e1b4fe","impliedFormat":1},{"version":"dcb3c5cb5cdb73bdf62ffd2808468824ea91a5c258371c32991b97773a20b13e","impliedFormat":1},{"version":"41cf6213c047c4d02d08cdf479fdf1b16bff2734c2f8abbb8bb71e7b542c8a47","impliedFormat":1},{"version":"0c1083e755be3c23e2aab9620dae8282de8a403b643bd9a4e19fe23e51d7b2d3","impliedFormat":1},{"version":"0810e286e8f50b4ead6049d46c6951fe8869d2ea7ee9ea550034d04c14c5d3e2","impliedFormat":1},{"version":"ead36974e944dcbc1cbae1ba8d6de7a1954484006f061c09f05f4a8e606d1556","impliedFormat":1},{"version":"afe05dc77ee5949ccee216b065943280ba15b5e77ac5db89dfc1d22ac32fc74c","impliedFormat":1},{"version":"2030689851bc510df0da38e449e5d6f4146ae7eac9ad2b6c6b2cf6f036b3a1ea","impliedFormat":1},{"version":"25cd596336a09d05d645e1e191ea91fb54f8bfd5a226607e5c0fd0eeeded0e01","impliedFormat":1},{"version":"d95ac12e15167f3b8c7ad2b7fa7f0a528b3941b556a6f79f8f1d57cce8fba317","impliedFormat":1},{"version":"cab5393058fcb0e2067719b320cd9ea9f43e5176c0ba767867c067bc70258ddc","impliedFormat":1},{"version":"c40d5df23b55c953ead2f96646504959193232ab33b4e4ea935f96cebc26dfee","impliedFormat":1},{"version":"cbc868d6efdbe77057597632b37f3ff05223db03ee26eea2136bd7d0f08dafc1","impliedFormat":1},{"version":"a0e027058a6ae83fba027952f6df403e64f7bd72b268022dbb4f274f3c299d12","impliedFormat":1},{"version":"a986ec442c12bed15d981ebd3a193f864d39f017a1f11a0c2e7afaca64288e28","impliedFormat":1},{"version":"83e8fd527d4d28635b7773780cc95ae462d14889ba7b2791dc842480b439ea0b","impliedFormat":1},{"version":"00121d48e941209d282cd87847c665686b77e12e2c3534f20059ece8df0cb84e","impliedFormat":1},{"version":"2f344849d706d5d602830833092bfca2825d87742e2e77908a7d0a6c3d08fdd9","impliedFormat":1},{"version":"cb007806a535d04e11aefff0ce8cd5c8454cad1a5ed774b5fc94e5fc575a8b29","impliedFormat":1},{"version":"b25e13b5bb9888a5e690bbd875502777239d980b148d9eaa5e44fad9e3c89a7e","impliedFormat":1},{"version":"38af232cb48efae980b56595d7fe537a4580fd79120fc2b5703b96cbbab1b470","impliedFormat":1},{"version":"4c76af0f5c8f955e729c78aaf1120cc5c24129b19c19b572e22e1da559d4908c","impliedFormat":1},{"version":"c27f313229ada4914ab14c49029da41c9fdae437a0da6e27f534ab3bc7db4325","impliedFormat":1},{"version":"ff8a3408444fb94122191cbfa708089a6233b8e031ebd559c92a90cb46d57252","impliedFormat":1},{"version":"8c25b00a675743d7a381cf6389ae9fbdce82bdc9069b343cb1985b4cd17b14be","impliedFormat":1},{"version":"cd057861569fb30fea931a115767e6fa600f50e33fadb428c8dd16f2b6ca2567","impliedFormat":1},{"version":"f9ec7b8b285db6b4c51aa183044c85a6e21ea2b28d5c4337c1977e9fe6a88844","impliedFormat":1},{"version":"b4d9fae96173bbd02f2a31ff00b2cb68e2398b1fec5aaab090826e4d02329b38","impliedFormat":1},{"version":"9d0f5034775fb0a6f081f3690925602d01ba16292989bfcac52f6135cf79f56f","impliedFormat":1},{"version":"f5181fff8bba0221f8df77711438a3620f993dd085f994a3aea3f8eaac17ceff","impliedFormat":1},{"version":"9312039b46c4f2eb399e7dd4d70b7cea02d035e64764631175a0d9b92c24ec4b","impliedFormat":1},{"version":"9ddacc94444bfd2e9cc35da628a87ec01a4b2c66b3c120a0161120b899dc7d39","impliedFormat":1},{"version":"a8cb7c1e34db0649edddd53fa5a30f1f6d0e164a6f8ce17ceb130c3689f02b96","impliedFormat":1},{"version":"0aba2a2ff3fc7e0d77aaf6834403166435ab15a1c82a8d791386c93e44e6c6a4","impliedFormat":1},{"version":"c83c86c0fddf1c1d7615be25c24654008ae4f672cff7de2a11cfa40e8c7df533","impliedFormat":1},{"version":"348e5b9c2ee965b99513a09ef9a15aec8914609a018f2e012d0c405969a39a2e","impliedFormat":1},{"version":"49d62a88a20b1dbff8bcf24356a068b816fb2cc2cac94264105a0419b2466b74","impliedFormat":1},{"version":"a04c6362fd99f3702be24412c122c41ed2b3faf3d9042c970610fcd1b1d69555","impliedFormat":1},{"version":"aa6f8f0abe029661655108bc7a0ecd93658bf070ce744b2ffaee87f4c6b51bca","impliedFormat":1},{"version":"5ef75e07b37097e602b73f82e6658b5cbb0683edf35943f811c5b7735ec4a077","impliedFormat":1},{"version":"8c88ce6a3db25803c86dad877ff4213e3f6d26e183d0cde08bc42fbf0a6ddbbe","impliedFormat":1},{"version":"02dabdfe5778f5499df6f18916ff2ebe06725a4c2a13ee7fb09a290b5df4d4b2","impliedFormat":1},{"version":"d67799c6a005603d7e0fd4863263b56eecde8d1957d085bdbbb20c539ad51e8c","impliedFormat":1},{"version":"21af404e03064690ac6d0f91a8c573c87a431ed7b716f840c24e08ea571b7148","impliedFormat":1},{"version":"e919a39dc55737a39bbf5d28a4b0c656feb6ec77a9cbdeb6707785bb70e4f2db","impliedFormat":1},{"version":"b75fca19de5056deaa27f8a2445ed6b6e6ceca0f515b6fdf8508efb91bc6398a","impliedFormat":1},{"version":"ce3382d8fdb762031e03fe6f2078d8fbb9124890665e337ad7cd1fa335b0eb4c","impliedFormat":1},{"version":"fe2ca2bde7e28db13b44a362d46085c8e929733bba05cf7bf346e110320570d1","impliedFormat":1},{"version":"c58afb303be3d37d9969d6aa046201b89bb5cae34d8bafc085c0444f3d0b0435","impliedFormat":1},{"version":"a42d7e73a19bcab1212b419862293fc5ea80293523f08d6ff1f4d013cc6e9409","impliedFormat":1},{"version":"23b93ebd1a1014d6892f417137a0873826b8c21f6460e68d93cef9c0163e2914","impliedFormat":1},{"version":"3e1c36055eeb72af70e6435d1e54cdc9546bb6aa826108ef7fdb76919bc18172","impliedFormat":1},{"version":"e00ca18e9752fbd9aaeedb574e4799d5686732516e84038592dbbe2fa979da3f","impliedFormat":1},{"version":"b8e11b2ffb5825c56f0d71d68d9efa2ea2b62f342a2731467e33ae2fc9870e19","impliedFormat":1},{"version":"1a4e3036112cf0cebac938dcfb840950f9f87d6475c3b71f4a219e0954b6cab4","impliedFormat":1},{"version":"ec4245030ac3af288108add405996081ddf696e4fe8b84b9f4d4eecc9cab08e1","impliedFormat":1},{"version":"6f9d2bd7c485bea5504bc8d95d0654947ea1a2e86bbf977a439719d85c50733f","impliedFormat":1},{"version":"1cb6b6e4e5e9e55ae33def006da6ac297ff6665371671e4335ab5f831dd3e2cd","impliedFormat":1},{"version":"dbd75ef6268810f309c12d247d1161808746b459bb72b96123e7274d89ea9063","impliedFormat":1},{"version":"175e129f494c207dfc1125d8863981ef0c3fb105960d6ec2ea170509663662da","impliedFormat":1},{"version":"5c65d0454be93eecee2bec78e652111766d22062889ab910cbd1cd6e8c44f725","impliedFormat":1},{"version":"f5d58dfc78b32134ba320ec9e5d6cb05ca056c03cb1ce13050e929a5c826a988","impliedFormat":1},{"version":"b1827bed8f3f14b41f42fa57352237c3a2e99f3e4b7d5ca14ec9879582fead0f","impliedFormat":1},{"version":"1d539bc450578c25214e5cc03eaaf51a61e48e00315a42e59305e1cd9d89c229","impliedFormat":1},{"version":"c0ee0c5fe835ba82d9580bff5f1b57f902a5134b617d70c32427aa37706d9ef8","impliedFormat":1},{"version":"738058f72601fffe9cad6fa283c4d7b2919785978bd2e9353c9b31dcc4151a80","impliedFormat":1},{"version":"3c63f1d97de7ec60bc18bebe1ad729f561bd81d04aefd11bd07e69c6ac43e4ad","impliedFormat":1},{"version":"7b8d3f37d267a8a2deb20f5aa359b34570bf8f2856e483dd87d4be7e83f6f75b","impliedFormat":1},{"version":"761745badb654d6ff7a2cd73ff1017bf8a67fdf240d16fbe3e43dca9838027a6","impliedFormat":1},{"version":"e4f33c01cf5b5a8312d6caaad22a5a511883dffceafbb2ee85a7cf105b259fda","impliedFormat":1},{"version":"a368b04888b71c4475a667754b740f4aca7f55db2b7553eacaed36e6962ec48c","impliedFormat":1},{"version":"5b49365103ad23e1c4f44b9d83ef42ff19eea7a0785c454b6be67e82f935a078","impliedFormat":1},{"version":"a664ab26fe162d26ad3c8f385236a0fde40824007b2c4072d18283b1b33fc833","impliedFormat":1},{"version":"193337c11f45de2f0fc9d8ec2d494965da4ae92382ba1a1d90cc0b04e5eeebde","impliedFormat":1},{"version":"4a119c3d93b46bead2e3108336d83ec0debd9f6453f55a14d7066bf430bb9dca","impliedFormat":1},{"version":"02ba072c61c60c8c2018bba0672f7c6e766a29a323a57a4de828afb2bbbb9d54","impliedFormat":1},{"version":"88fe3740babbaa61402a49bd24ce9efcbe40385b0d7cceb96ac951a02d981610","impliedFormat":1},{"version":"1abe3d916ab50524d25a5fbe840bd7ce2e2537b68956734863273e561f9eb61c","impliedFormat":1},{"version":"2b44bc7e31faab2c26444975b362ece435d49066be89644885341b430e61bb7e","impliedFormat":1},{"version":"06763bb36ab0683801c1fa355731b7e65d84b012f976c2580e23ad60bccbd961","impliedFormat":1},{"version":"6a6791e7863eb25fa187d9f323ac563690b2075e893576762e27f862b8003f30","impliedFormat":1},{"version":"bd90f3a677579a8e767f0c4be7dfdf7155b650fb1293fff897ccada7a74d77ff","impliedFormat":1},{"version":"fa28c1f081aa3b9fe872f759f1eb95ced4e4d935b534d7f91797433aee9cd589","impliedFormat":1},{"version":"c1cefd1eccda6d3277d556202450d947a1c88dd8194aabe6fbb101f0149fafaf","impliedFormat":1},{"version":"47008c9a4f168c2490bebc92653f4227accb55fe4b75f06cd0d568bd6370c435","impliedFormat":1},{"version":"b5203823f084dcfaae1f506dfe9bd84bf8ea008a2a834fdd5c5d7d0144418e0b","impliedFormat":1},{"version":"76c2ad2b6e3ec3d09819d8e919ea3e055c9bd73a90c3c6994ba807fd0e12ab15","impliedFormat":1},{"version":"03eb569fd62a9035cac5ac9fd5d960d73de56a6704b7988c13ce6593bec015d1","impliedFormat":1},{"version":"f77ca1843ec31c769b7190f9aa4913e8888ffdfbc4b41d77256fad4108da2b60","impliedFormat":1},{"version":"2ce435b7150596e688b03430fd8247893013ec27c565cd601bba05ea2b97e99d","impliedFormat":1},{"version":"4ea6ab7f5028bedbbc908ab3085dc33077124372734713e507d3d391744a411b","impliedFormat":1},{"version":"909ecbb1054805e23a71612dd50dff18be871dcfe18664a3bcd40ef88d06e747","impliedFormat":1},{"version":"26309fe37e159fdf8aed5e88e97b1bd66bfd8fe81b1e3d782230790ea04603bd","impliedFormat":1},{"version":"dd0cf98b9e2b961a01657121550b621ecc24b81bbcc71287bed627db8020fe48","impliedFormat":1},{"version":"60b03de5e0f2a6c505b48a5d3a5682f3812c5a92c7c801fb8ffa71d772b6dd96","impliedFormat":1},{"version":"224a259ffa86be13ba61d5a0263d47e313e2bd09090ef69820013b06449a2d85","impliedFormat":1},{"version":"c260695b255841fcfbc6008343dae58b3ea00efdfc16997cc69992141f4728c6","impliedFormat":1},{"version":"c017165fe60c647f2dbd24291c48161a616e0ab220e9bd00334ef54ff8eff79d","impliedFormat":1},{"version":"88f46a47b213f376c765ef54df828835dfbb13214cfd201f635324337ebbe17f","impliedFormat":1},{"version":"3ce1188fd214883b087e7feb7bd95dd4a8ce9c1e148951edd454c17a23d54b41","impliedFormat":1},{"version":"a23cc04238f0b8a3805ddb406ee6d69bda510aee5f3c4aa85dbe52cb598cbb04","impliedFormat":1},{"version":"003502d5a8ec5d392a0a3120983c43f073c6d2fd1e823a819f25029ce40271e8","impliedFormat":1},{"version":"1fdbd12a1d02882ef538980a28a9a51d51fd54c434cf233822545f53d84ef9cf","impliedFormat":1},{"version":"419bad1d214faccabfbf52ab24ae4523071fcc61d8cee17b589299171419563c","impliedFormat":1},{"version":"74532476a2d3d4eb8ac23bac785a9f88ca6ce227179e55537d01476b6d4435ea","impliedFormat":1},{"version":"bf33e792a3bc927a6b0d84f428814c35a0a9ca3c0cc8a91246f0b60230da3b6c","impliedFormat":1},{"version":"71c99cd1806cc9e597ff15ca9c90e1b7ad823b38a1327ccbc8ab6125cf70118e","impliedFormat":1},{"version":"6170710f279fffc97a7dd1a10da25a2e9dac4e9fc290a82443728f2e16eb619b","impliedFormat":1},{"version":"3804a3a26e2fd68f99d686840715abc5034aeb8bcbf970e36ad7af8ab69b0461","impliedFormat":1},{"version":"67b395b282b2544f7d71f4a7c560a7225eac113e7f3bcd8e88e5408b8927a63e","impliedFormat":1},{"version":"fe301153d19ddb9e39549f3a5b71c5a94fec01fc8f1bd6b053c4ef42207bef2a","impliedFormat":1},{"version":"4b09036cb89566deddca4d31aead948cf5bdb872508263220582f3be85157551","impliedFormat":1},{"version":"c61d09ae1f70d3eed306dc991c060d57866127365e03de4625497de58a996ffc","impliedFormat":1},{"version":"16a64f8bdaa16d75f9523120f260fcfece9218471062bcc33c4ccb52aa2945b0","impliedFormat":1},{"version":"39e31b902b6b627350a41b05f9627faf6bb1919ad1d17f0871889e5e6d80663c","impliedFormat":1},{"version":"282fd78a91b8363e120a991d61030e2186167f6610a6df195961dba7285b3f17","impliedFormat":1},{"version":"ec571ed174e47dade96ba9157f972937b2e4844a85c399e26957f9aa6d288767","impliedFormat":1},{"version":"16ce742a2199b12a6498dee9f832e27ac5e523064d41f951a8b27cdf3c6b702f","impliedFormat":1},{"version":"bd1162d66a709d4adc49725f4a997925a5472b94a4ff376ed4c2c2428132d5e7","signature":"2835abdf7222fabc24b8bdd15e36271565a15fd5310a1ff67711cbcea7e3c6cd"},{"version":"198ab99660ad169e1d9c39ad9f70113dedf856756a5cd0e7dc88fb8e3b8b9b52","signature":"ef43830056524a915e12eee76024b778a8d4e97f76e2d46beb369b274029ae25"},{"version":"1d0628911b56f83b654ca0ba826d503b3605926e73b985e754513832eeab5592","signature":"45b43c38bc20ca8c0edcee887ed49ed8b771dbe78e1cb5b4e3ba794e853fa887"},{"version":"310c820b803950d18c0ed9376df2cd73def2f56cfcc993f9012008403cdd4843","signature":"6fb95390f4022e0327e4a170917a06de5caad8c8c563c8b00be3cd40a71c759e"},{"version":"94fd7ace61dec87a6561c919998a2b3621c55131f0ebdc1515d988efad0dd4ed","signature":"d0a789adb5d01d530fe090e023e024839394b23085f4f50f16925f6bd28c7031"},{"version":"1a4804bb53a9010b4c84efc51529639d50cf4c7fae4e5885cbd013175002b737","signature":"c6662f560a42b1865b772ce6ccb599eea10600041beb0767316782fbfc2a73e8"},{"version":"4ed96213860296593b569b425eec8dfac37cb5bdaffbce2206c000dc673007c7","signature":"0fbe920fa2bb3439dfa680647a4ea264b7a8ea9bfa75e4cdd9ff2507d69df783"},{"version":"9d90361f495ed7057462bcaa9ae8d8dbad441147c27716d53b3dfeaea5bb7fc8","impliedFormat":1},{"version":"799003c0ab928582fca04977f47b8d85b43a8de610f4eef0ad2d069fbb9f9399","impliedFormat":1},{"version":"d998eea476c695d8e4ff9d007d5b46d49ca2ffa052f74dc20ca516425abd57b1","impliedFormat":1},{"version":"f4e8f4151c3490cf7b68c685aabe901cbab19f962aaa2f118a97550e22689a76","impliedFormat":1},{"version":"0345bc0b1067588c4ea4c48e34425d3284498c629bc6788ebc481c59949c9037","impliedFormat":1},{"version":"e30f5b5d77c891bc16bd65a2e46cd5384ea57ab3d216c377f482f535db48fc8f","impliedFormat":1},{"version":"f113afe92ee919df8fc29bca91cab6b2ffbdd12e4ac441d2bb56121eb5e7dbe3","impliedFormat":1},{"version":"49d567cc002efb337f437675717c04f207033f7067825b42bb59c9c269313d83","impliedFormat":1},{"version":"1d248f707d02dc76555298a934fba0f337f5028bb1163ce59cd7afb831c9070f","impliedFormat":1},{"version":"5d8debffc9e7b842dc0f17b111673fe0fc0cca65e67655a2b543db2150743385","impliedFormat":1},{"version":"5fccbedc3eb3b23bc6a3a1e44ceb110a1f1a70fa8e76941dce3ae25752caa7a9","impliedFormat":1},{"version":"f4031b95f3bab2b40e1616bd973880fb2f1a97c730bac5491d28d6484fac9560","impliedFormat":1},{"version":"dbe75b3c5ed547812656e7945628f023c4cd0bc1879db0db3f43a57fb8ec0e2b","impliedFormat":1},{"version":"b754718a546a1939399a6d2a99f9022d8a515f2db646bab09f7d2b5bff3cbb82","impliedFormat":1},{"version":"2eef10fb18ed0b4be450accf7a6d5bcce7b7f98e02cac4e6e793b7ad04fc0d79","impliedFormat":1},{"version":"c46f471e172c3be12c0d85d24876fedcc0c334b0dab48060cdb1f0f605f09fed","impliedFormat":1},{"version":"7d6ddeead1d208588586c58c26e4a23f0a826b7a143fb93de62ed094d0056a33","impliedFormat":1},{"version":"7c5782291ff6e7f2a3593295681b9a411c126e3736b83b37848032834832e6b9","impliedFormat":1},{"version":"3a3f09df6258a657dd909d06d4067ee360cd2dccc5f5d41533ae397944a11828","impliedFormat":1},{"version":"ea54615be964503fec7bce04336111a6fa455d3e8d93d44da37b02c863b93eb8","impliedFormat":1},{"version":"2a83694bc3541791b64b0e57766228ea23d92834df5bf0b0fcb93c5bb418069c","impliedFormat":1},{"version":"b5913641d6830e7de0c02366c08b1d26063b5758132d8464c938e78a45355979","impliedFormat":1},{"version":"46c095d39c1887979d9494a824eda7857ec13fb5c20a6d4f7d02c2975309bf45","impliedFormat":1},{"version":"f6e02ca076dc8e624aa38038e3488ebd0091e2faea419082ed764187ba8a6500","impliedFormat":1},{"version":"4d49e8a78aba1d4e0ad32289bf8727ae53bc2def9285dff56151a91e7d770c3e","impliedFormat":1},{"version":"63315cf08117cc728eab8f3eec8801a91d2cd86f91d0ae895d7fd928ab54596d","impliedFormat":1},{"version":"a14a6f3a5636bcaebfe9ec2ccfa9b07dc94deb1f6c30358e9d8ea800a1190d5e","impliedFormat":1},{"version":"21206e7e81876dabf2a7af7aa403f343af1c205bdcf7eff24d9d7f4eee6214c4","impliedFormat":1},{"version":"cd0a9f0ffec2486cad86b7ef1e4da42953ffeb0eb9f79f536e16ff933ec28698","impliedFormat":1},{"version":"f609a6ec6f1ab04dba769e14d6b55411262fd4627a099e333aa8876ea125b822","impliedFormat":1},{"version":"6d8052bb814be030c64cb22ca0e041fe036ad3fc8d66208170f4e90d0167d354","impliedFormat":1},{"version":"851f72a5d3e8a2bf7eeb84a3544da82628f74515c92bdf23c4a40af26dcc1d16","impliedFormat":1},{"version":"59692a7938aab65ea812a8339bbc63c160d64097fe5a457906ea734d6f36bcd4","impliedFormat":1},{"version":"8cb3b95e610c44a9986a7eab94d7b8f8462e5de457d5d10a0b9c6dd16bde563b","impliedFormat":1},{"version":"f571713abd9a676da6237fe1e624d2c6b88c0ca271c9f1acc1b4d8efeea60b66","impliedFormat":1},{"version":"16c5d3637d1517a3d17ed5ebcfbb0524f8a9997a7b60f6100f7c5309b3bb5ac8","impliedFormat":1},{"version":"ca1ec669726352c8e9d897f24899abf27ad15018a6b6bcf9168d5cd1242058ab","impliedFormat":1},{"version":"bffb1b39484facf6d0c5d5feefe6c0736d06b73540b9ce0cf0f12da2edfd8e1d","impliedFormat":1},{"version":"f1663c030754f6171b8bb429096c7d2743282de7733bccd6f67f84a4c588d96e","impliedFormat":1},{"version":"dd09693285e58504057413c3adc84943f52b07d2d2fd455917f50fa2a63c9d69","impliedFormat":1},{"version":"d94c94593d03d44a03810a85186ae6d61ebeb3a17a9b210a995d85f4b584f23d","impliedFormat":1},{"version":"c7c3bf625a8cb5a04b1c0a2fbe8066ecdbb1f383d574ca3ffdabe7571589a935","impliedFormat":1},{"version":"7a2f39a4467b819e873cd672c184f45f548511b18f6a408fe4e826136d0193bb","impliedFormat":1},{"version":"f8a0ae0d3d4993616196619da15da60a6ec5a7dfaf294fe877d274385eb07433","impliedFormat":1},{"version":"2cca80de38c80ef6c26deb4e403ca1ff4efbe3cf12451e26adae5e165421b58d","impliedFormat":1},{"version":"0070d3e17aa5ad697538bf865faaff94c41f064db9304b2b949eb8bcccb62d34","impliedFormat":1},{"version":"53df93f2db5b7eb8415e98242c1c60f6afcac2db44bce4a8830c8f21eee6b1dd","impliedFormat":1},{"version":"d67bf28dc9e6691d165357424c8729c5443290367344263146d99b2f02a72584","impliedFormat":1},{"version":"932557e93fbdf0c36cc29b9e35950f6875425b3ac917fa0d3c7c2a6b4f550078","impliedFormat":1},{"version":"e3dc7ec1597fb61de7959335fb7f8340c17bebf2feb1852ed8167a552d9a4a25","impliedFormat":1},{"version":"b64e15030511c5049542c2e0300f1fe096f926cf612662884f40227267f5cd9f","impliedFormat":1},{"version":"1932796f09c193783801972a05d8fb1bfef941bb46ac76fbe1abb0b3bfb674fa","impliedFormat":1},{"version":"d9575d5787311ee7d61ad503f5061ebcfaf76b531cfecce3dc12afb72bb2d105","impliedFormat":1},{"version":"5b41d96c9a4c2c2d83f1200949f795c3b6a4d2be432b357ad1ab687e0f0de07c","impliedFormat":1},{"version":"38ec829a548e869de4c5e51671245a909644c8fb8e7953259ebb028d36b4dd06","impliedFormat":1},{"version":"20c2c5e44d37dac953b516620b5dba60c9abd062235cdf2c3bfbf722d877a96b","impliedFormat":1},{"version":"875fe6f7103cf87c1b741a0895fda9240fed6353d5e7941c8c8cbfb686f072b4","impliedFormat":1},{"version":"c0ccccf8fbcf5d95f88ed151d0d8ce3015aa88cf98d4fd5e8f75e5f1534ee7ae","impliedFormat":1},{"version":"1b1f4aba21fd956269ced249b00b0e5bfdbd5ebd9e628a2877ab1a2cf493c919","impliedFormat":1},{"version":"939e3299952dff0869330e3324ba16efe42d2cf25456d7721d7f01a43c1b0b34","impliedFormat":1},{"version":"f0a9b52faec508ba22053dedfa4013a61c0425c8b96598cef3dea9e4a22637c6","impliedFormat":1},{"version":"d5b302f50db61181adc6e209af46ae1f27d7ef3d822de5ea808c9f44d7d219fd","impliedFormat":1},{"version":"19131632ba492c83e8eeadf91a481def0e0b39ffc3f155bc20a7f640e0570335","impliedFormat":1},{"version":"4581c03abea21396c3e1bb119e2fd785a4d91408756209cbeed0de7070f0ab5b","impliedFormat":1},{"version":"ebcd3b99e17329e9d542ef2ccdd64fddab7f39bc958ee99bbdb09056c02d6e64","impliedFormat":1},{"version":"4b148999deb1d95b8aedd1a810473a41d9794655af52b40e4894b51a8a4e6a6d","impliedFormat":1},{"version":"1781cc99a0f3b4f11668bb37cca7b8d71f136911e87269e032f15cf5baa339bf","impliedFormat":1},{"version":"33f1b7fa96117d690035a235b60ecd3cd979fb670f5f77b08206e4d8eb2eb521","impliedFormat":1},{"version":"01429b306b94ff0f1f5548ce5331344e4e0f5872b97a4776bd38fd2035ad4764","impliedFormat":1},{"version":"c1bc4f2136de7044943d784e7a18cb8411c558dbb7be4e4b4876d273cbd952af","impliedFormat":1},{"version":"5470f84a69b94643697f0d7ec2c8a54a4bea78838aaa9170189b9e0a6e75d2cf","impliedFormat":1},{"version":"36aaa44ee26b2508e9a6e93cd567e20ec700940b62595caf962249035e95b5e3","impliedFormat":1},{"version":"f8343562f283b7f701f86ad3732d0c7fd000c20fe5dc47fa4ed0073614202b4d","impliedFormat":1},{"version":"a53c572630a78cd99a25b529069c1e1370f8a5d8586d98e798875f9052ad7ad1","impliedFormat":1},{"version":"4ad3451d066711dde1430c544e30e123f39e23c744341b2dfd3859431c186c53","impliedFormat":1},{"version":"8069cbef9efa7445b2f09957ffbc27b5f8946fdbade4358fb68019e23df4c462","impliedFormat":1},{"version":"cd8b4e7ad04ba9d54eb5b28ac088315c07335b837ee6908765436a78d382b4c3","impliedFormat":1},{"version":"d533d8f8e5c80a30c51f0cbfe067b60b89b620f2321d3a581b5ba9ac8ffd7c3a","impliedFormat":1},{"version":"33f49f22fdda67e1ddbacdcba39e62924793937ea7f71f4948ed36e237555de3","impliedFormat":1},{"version":"710c31d7c30437e2b8795854d1aca43b540cb37cefd5900f09cfcd9e5b8540c4","impliedFormat":1},{"version":"b2c03a0e9628273bc26a1a58112c311ffbc7a0d39938f3878837ab14acf3bc41","impliedFormat":1},{"version":"a93beb0aa992c9b6408e355ea3f850c6f41e20328186a8e064173106375876c2","impliedFormat":1},{"version":"efdcba88fcd5421867898b5c0e8ea6331752492bd3547942dea96c7ebcb65194","impliedFormat":1},{"version":"a98e777e7a6c2c32336a017b011ba1419e327320c3556b9139413e48a8460b9a","impliedFormat":1},{"version":"ea44f7f8e1fe490516803c06636c1b33a6b82314366be1bd6ffa4ba89bc09f86","impliedFormat":1},{"version":"c25f22d78cc7f46226179c33bef0e4b29c54912bde47b62e5fdaf9312f22ffcb","impliedFormat":1},{"version":"d57579cfedc5a60fda79be303080e47dfe0c721185a5d95276523612228fcefc","impliedFormat":1},{"version":"a41630012afe0d4a9ff14707f96a7e26e1154266c008ddbd229e3f614e4d1cf7","impliedFormat":1},{"version":"298a858633dfa361bb8306bbd4cfd74f25ab7cc20631997dd9f57164bc2116d1","impliedFormat":1},{"version":"921782c45e09940feb232d8626a0b8edb881be2956520c42c44141d9b1ddb779","impliedFormat":1},{"version":"06117e4cc7399ce1c2b512aa070043464e0561f956bda39ef8971a2fcbcdbf2e","impliedFormat":1},{"version":"daccf332594b304566c7677c2732fed6e8d356da5faac8c5f09e38c2f607a4ab","impliedFormat":1},{"version":"4386051a0b6b072f35a2fc0695fecbe4a7a8a469a1d28c73be514548e95cd558","impliedFormat":1},{"version":"78e41de491fe25947a7fd8eeef7ebc8f1c28c1849a90705d6e33f34b1a083b90","impliedFormat":1},{"version":"3ccd198e0a693dd293ed22e527c8537c76b8fe188e1ebf20923589c7cfb2c270","impliedFormat":1},{"version":"2ebf2ee015d5c8008428493d4987e2af9815a76e4598025dd8c2f138edc1dcae","impliedFormat":1},{"version":"0dcc8f61382c9fcdafd48acc54b6ffda69ca4bb7e872f8ad12fb011672e8b20c","impliedFormat":1},{"version":"9db563287eb527ead0bcb9eb26fbec32f662f225869101af3cabcb6aee9259cf","impliedFormat":1},{"version":"068489bec523be43f12d8e4c5c337be4ff6a7efb4fe8658283673ae5aae14b85","impliedFormat":1},{"version":"838212d0dc5b97f7c5b5e29a89953de3906f72fce13c5ae3c5ade346f561d226","impliedFormat":99},{"version":"2223d68f66fbab4dcff52f2ccf81e8c487392288b2974cb2862721e9dbf9551d","impliedFormat":1},{"version":"b07047a60f37f65427574e262a781e6936af9036cf92b540311e033956fd49be","impliedFormat":1},{"version":"25ba804522003eb8212efb1e6a4c2d114662a894b479351c36bd9c7491ceb04f","impliedFormat":1},{"version":"6445fe8e47b350b2460b465d7df81a08b75b984a87ee594caf4a57510f6ec02e","impliedFormat":1},{"version":"425e1299147c67205df40ce396f52ff012c1bf501dcfbf1c7123bbd11f027ab0","impliedFormat":1},{"version":"3abf6b0a561eed97d2f2b58f2d647487ba33191c0ecb96764cc12be4c3dd6b55","impliedFormat":1},{"version":"01cc05d0db041f1733a41beec0ddaeea416e10950f47e6336b3be26070346720","impliedFormat":1},{"version":"e21813719193807d4ca53bb158f1e7581df8aa6401a6a006727b56720b62b139","impliedFormat":1},{"version":"f4f9ca492b1a0306dcb34aa46d84ca3870623db46a669c2b7e5403a4c5bcbbd6","impliedFormat":1},{"version":"492d38565cf9cce8a4f239d36353c94b24ef46a43462d3d411e90c8bef2f8503","impliedFormat":1},{"version":"9f94dc8fb29d482f80aec57af2d982858a1820a8c8872910f89ae2f7fd9bee7f","impliedFormat":1},{"version":"a23f14db3212d53b6c76c346caca80c3627bf900362ce7a896229675a67ae49b","impliedFormat":1},{"version":"f317cf0107576c3e70d3fc9040d767272e4eb5940a1a22666cc81ae491b69d12","impliedFormat":1},{"version":"f317cf0107576c3e70d3fc9040d767272e4eb5940a1a22666cc81ae491b69d12","impliedFormat":1},{"version":"eedb957064af583258d82b6fd845c4df7d0806868cb18cbc2c6a8b0b51eb00bd","impliedFormat":1},{"version":"b6967a67f087fd77eb1980a8abb701ad040679404ed62bd4d6b40406a621fc45","impliedFormat":1},{"version":"092f99777813f42f32abf6f2e4ef1649b6e74cd94db499f2df64fc78d3f969e4","impliedFormat":1},{"version":"3d86c7feb4ee3862d71fe42e3fc120131decf6aa4a21bdf8b3bb9f8c5228aed2","impliedFormat":1},{"version":"ab70ea5d6d02c8631da210783199dc0f6c51ac5dfbc4265fdb8f1526fa0fdc7f","impliedFormat":1},{"version":"427acaa3bbea7c0b1f57d7d9190bedbbb49c147ef36b9088f8f43d1c57974d6e","impliedFormat":1},{"version":"bbd32da0338c47c74e40436d262d787e9a61c11de6d70d431b830babe79aa679","impliedFormat":1},{"version":"cb852ce7eb0ab4281cd3c5a1710d819f54f58fba0f0e9d4b797195416f254883","impliedFormat":1},{"version":"34465f88f94a4b0748055fa5702528e54ef9937c039e29a6bcde810deefd73d0","impliedFormat":1},{"version":"c451606558ca4e1e71e38396f94778b7c9a553a3b33f376ab5e4991dd3633e28","impliedFormat":1},{"version":"22986fb5b95b473335e2bbcc62a9438e8a242ca3d1b28c220d8b99e0d5874678","impliedFormat":1},{"version":"838dc2c15fe68509985a94d1853e96b1e519992a711a7a0cd8568dfd36bf757e","impliedFormat":1},{"version":"bb894fb593532cd9819c43f747cc7b0901136a93758e78482a9f675563beacdf","impliedFormat":1},{"version":"9575c608269abe4889b7c1382762c09deb7493812284bde0a429789fa963838b","impliedFormat":1},{"version":"c8c57e8f7e28927748918e0420c0d6dd55734a200d38d560e16dc99858710f2b","impliedFormat":1},{"version":"64903d7216ed30f8511f03812db3333152f3418de6d422c00bde966045885fb7","impliedFormat":1},{"version":"8ff3e2f7d218a5c4498a2a657956f0ca000352074b46dbaf4e0e0475e05a1b12","impliedFormat":1},{"version":"498f87ea2a046a47910a04cf457a1b05d52d31e986a090b9abc569142f0d4260","impliedFormat":1},{"version":"5ac05c0f6855db16afa699dccfd9e3bd3a7a5160e83d7dce0b23b21d3c7353b9","impliedFormat":1},{"version":"7e792c18f8e4ac8b17c2b786e90f9e2e26cf967145ad615f5c1d09ab0303241f","impliedFormat":1},{"version":"a528a860066cc462a9f0bddc9dbe314739d5f8232b2b49934f84a0ce3a86de81","impliedFormat":1},{"version":"81760466a2f14607fcacf84be44e75ef9dcc7f7267a266d97094895a5c37cbac","impliedFormat":1},{"version":"ee05b32eccbf91646cb264de32701b48a37143708065b74ed0116199d4774e86","impliedFormat":1},{"version":"60f3443b1c23d4956fb9b239e20d31859ea57670cd9f5b827f1cd0cac24c9297","impliedFormat":1},{"version":"648eacd046cfe3e9cba80da0cf2dc69c68aa749be900d7ee4b25ce28099ffa72","impliedFormat":1},{"version":"6a69d5ec5a4ed88455753431cf4d72411d210f04bce62475f9f1a97c4cf4294e","impliedFormat":1},{"version":"11fb88d11384bea44dc08b42b7341a39e36719a68a6be5fed5da575cdaeb1ad8","impliedFormat":1},{"version":"2936dcfaf4b4d1585b73c5ae7ac6395f143e136474bc091cc95033aface47e5e","impliedFormat":1},{"version":"4719ef9fe00fb18f2c3844a1939111ebca55e64f1fa93b14ddcea050865b63f0","impliedFormat":1},{"version":"86edb0b4f12ce79243d5e6ca4bed776bdd7e7a774ce4961578905e775c994ea8","impliedFormat":1},{"version":"b4a4433d4d4601efe2aa677164dee3754e511de644080147421a8cac8d6aae68","impliedFormat":1},{"version":"09a2e34f98a73581d1fd923f2eafaf09bb3ebde6ea730779af09da35dffebbcd","impliedFormat":1},{"version":"f5b5545691bd2e4ca7cf306f99a088ba0ec7e80f3dfca53b87167dbbb44cd836","impliedFormat":1},{"version":"3bd5bd5fabd0b2c646e1413e4d4eb9bbca4bd5a9ffdc53c5375f50078c20c2e2","impliedFormat":1},{"version":"3bd5bd5fabd0b2c646e1413e4d4eb9bbca4bd5a9ffdc53c5375f50078c20c2e2","impliedFormat":1},{"version":"d5003e54842f82de63a808473357de001162f7ca56ab91266e5d790b620f6fdb","impliedFormat":1},{"version":"aa0761c822c96822508e663d9b0ee33ad12a751219565a12471da3e79c38f0ba","impliedFormat":1},{"version":"8338db69b3c23549e39ecf74af0de68417fcea11c98c4185a14f0b3ef833c933","impliedFormat":1},{"version":"85f208946133e169c6a8e57288362151b2072f0256dbed0a4b893bf41aab239a","impliedFormat":1},{"version":"e6957055d9796b6a50d2b942196ffece6a221ec424daf7a3eddcee908e1df7b0","impliedFormat":1},{"version":"e9142ff6ddb6b49da6a1f44171c8974c3cca4b72f06b0bbcaa3ef06721dda7b5","impliedFormat":1},{"version":"3961869af3e875a32e8db4641d118aa3a822642a78f6c6de753aa2dbb4e1ab77","impliedFormat":1},{"version":"4a688c0080652b8dc7d2762491fbc97d8339086877e5fcba74f78f892368e273","impliedFormat":1},{"version":"c81b913615690710c5bcfff0845301e605e7e0e1ebc7b1a9d159b90b0444fccf","impliedFormat":1},{"version":"2ced4431ecdda62fefcf7a2e999783759d08d802962adcff2b0105511f50056d","impliedFormat":1},{"version":"2ced4431ecdda62fefcf7a2e999783759d08d802962adcff2b0105511f50056d","impliedFormat":1},{"version":"e4c6c971ce45aef22b876b7e11d3cd3c64c72fcd6b0b87077197932c85a0d81d","impliedFormat":1},{"version":"7fd1258607eddcc1cf7d1fef9c120a3f224f999bba22da3a0835b25c8321a1d3","impliedFormat":1},{"version":"da3a1963324e9100d88c77ea9bec81385386dbb62acd45db8197d9aeb67284f7","impliedFormat":1},{"version":"f14deef45f1c4c76c96b765e2a7a2410c5e8ae211624fb99fe944d35da2f27c1","impliedFormat":1},{"version":"04dc76c64d88e872fafce2cceb7e25b00daa7180a678600be52c26387486a6d7","impliedFormat":1},{"version":"18c19498e351fb6f0ddbfa499a9c2c845a4d06ed076a976deb4ac28d7c613120","impliedFormat":1},{"version":"5738df287f7e6102687a9549c9b1402941632473e0423ef08bd8af6f394b2662","impliedFormat":1},{"version":"c67e42d11d442babad44a7821e5a18d55548271fdbe9dceb34e3f794e4e2c045","impliedFormat":1},{"version":"407bd942087ec965acd69dfb8f3196838337b07ce9bb3b6939b825bf01f6fb82","impliedFormat":1},{"version":"3d6e4bf3459c87e9cdf6016f51479c5f1e2535ef6b1e9d09ac5826c53d1f849c","impliedFormat":1},{"version":"c583b7e6c874476a42f22fb8afa7474f7ddedac69733e5e28fed9bde08418a3b","impliedFormat":1},{"version":"faf7c4d1fafaed99f524a1dc58b2c3f5602aebfb1a7cac119f279361bae6a0aa","impliedFormat":1},{"version":"d3ded63f1110dc555469fc51ce9873be767c72bff2df976e3afb771c34e91651","impliedFormat":1},{"version":"b0a1098565684d1291020613947d91e7ae92826ffbc3e64f2a829c8200bc6f05","impliedFormat":1},{"version":"1a5bbfae4f953a5552d9fa795efca39883e57b341f0d558466a0bf4868707eb4","impliedFormat":1},{"version":"fe542d91695a73fd82181e8d8898f3f5f3bec296c7480c5ff5e0e170fa50e382","impliedFormat":1},{"version":"891becf92219c25433153d17f9778dec9d76185bc8a86ca5050f6971eaf06a65","impliedFormat":1},{"version":"267f93fbddff4f28c34be3d6773ee8422b60c82f7d31066b6587dffa959a8a6a","impliedFormat":1},{"version":"276d36388f1d029c4543c0ddd5c208606aedcbaed157263f58f9c5016472057e","impliedFormat":1},{"version":"b018759002a9000a881dbb1f9394c6ef59c51fa4867705d00acba9c3245428ea","impliedFormat":1},{"version":"20bbf42534cbacbd0a8e1565d2c885152b7c423a3d4864c75352a8750bb6b52c","impliedFormat":1},{"version":"0ce3dbc76a8a8ed58f0f63868307014160c3c521bc93ed365de4306c85a4df33","impliedFormat":1},{"version":"d9a349eb9160735da163c23b54af6354a3e70229d07bb93d7343a87e1e35fd40","impliedFormat":1},{"version":"9bd17494fcb9407dcc6ace7bde10f4cf3fc06a4c92fe462712853688733c28a3","impliedFormat":1},{"version":"ba540f8efa123096aa3a7b6f01acb2dc81943fa88e5a1adb47d69ed80b949005","impliedFormat":1},{"version":"c6b20a3d20a9766f1dded11397bdba4531ab816fdb15aa5aa65ff94c065419cf","impliedFormat":1},{"version":"91e4a5e8b041f28f73862fb09cd855cfab3f2c7b38abe77089747923f3ad1458","impliedFormat":1},{"version":"2cebda0690ab1dee490774cb062761d520d6fabf80b2bd55346fde6f1f41e25d","impliedFormat":1},{"version":"bcc18e12e24c7eb5b7899b70f118c426889ac1dccfa55595c08427d529cc3ce1","impliedFormat":1},{"version":"6838d107125eeaf659e6fc353b104efd6d033d73cfc1db31224cb652256008f1","impliedFormat":1},{"version":"97b21e38c9273ccc7936946c5099f082778574bbb7a7ab1d9fc7543cbd452fd5","impliedFormat":1},{"version":"ae90b5359bc020cd0681b4cea028bf52b662dff76897f125fa3fe514a0b6727a","impliedFormat":1},{"version":"4596f03c529bd6c342761a19cf6e91221bee47faad3a8c7493abff692c966372","impliedFormat":1},{"version":"6682c8f50bd39495df3042d2d7a848066b63439e902bf8a00a41c3cfc9d7fafa","impliedFormat":1},{"version":"1b111caa0a85bcfd909df65219ecd567424ba17e3219c6847a4f40e71da9810b","impliedFormat":1},{"version":"b8df0a9e1e9c5bd6bcdba2ca39e1847b6a5ca023487785e6909b8039c0c57b16","impliedFormat":1},{"version":"2e26ca8ed836214ad99d54078a7dadec19c9c871a48cb565eaac5900074de31c","impliedFormat":1},{"version":"2b5705d85eb82d90680760b889ebedade29878dbb8cab2e56a206fd32b47e481","impliedFormat":1},{"version":"d131e0261dc711dd6437a69bac59ed3209687025b4e47d424408cf929ca6c17c","impliedFormat":1},{"version":"86c7f05da9abdecf1a1ea777e6172a69f80aec6f9d37c665bd3a761a44ec177b","impliedFormat":1},{"version":"840fe0bc4a365211bae1b83d683bfd94a0818121a76d73674ee38081b0d65454","impliedFormat":1},{"version":"1b6e2a3019f57e4c72998b4ddeea6ee1f637c07cc9199126475b0f17ba5a6c48","impliedFormat":1},{"version":"69920354aa42af33820391f6ec39605c37a944741c36007c1ff317fc255b1272","impliedFormat":1},{"version":"054186ff3657c66e43567635eed91ad9d10a8c590f007ba9eae7182e5042300b","impliedFormat":1},{"version":"1d543a56cb8c953804d7a5572b193c7feb3475f1d1f7045541a227eced6bf265","impliedFormat":1},{"version":"67374297518cf483af96aa68f52f446e2931b7a84fa8982ab85b6dd3fc4accce","impliedFormat":1},{"version":"cf9bfdf581e8998f45f486fdb1422edd7fc05cc9bc39a0bf45c293805176bf7d","impliedFormat":1},{"version":"cf9bfdf581e8998f45f486fdb1422edd7fc05cc9bc39a0bf45c293805176bf7d","impliedFormat":1},{"version":"849d09d5dc6836815767c3f8e2e4c561c8c1986d5398a8e876208aed2cc691c3","impliedFormat":1},{"version":"849d09d5dc6836815767c3f8e2e4c561c8c1986d5398a8e876208aed2cc691c3","impliedFormat":1},{"version":"0dd43d0e8bc78b0c73b1bd20ad29dac4c82163ab92744551bf2ab46512c33b6c","impliedFormat":1},{"version":"0dd43d0e8bc78b0c73b1bd20ad29dac4c82163ab92744551bf2ab46512c33b6c","impliedFormat":1},{"version":"54a527b58cf10aae5525481b5446b81a28b2ae459ce27dc97bd56b13508ea11c","impliedFormat":1},{"version":"54a527b58cf10aae5525481b5446b81a28b2ae459ce27dc97bd56b13508ea11c","impliedFormat":1},{"version":"d1880d157445fdbf521eead6182f47f4b3e5405afd08293ed9e224c01578e26a","impliedFormat":1},{"version":"ed2f74c2566e99295f366f820e54db67d304c3814efcb4389ce791410e9178b0","impliedFormat":1},{"version":"4f7f0dd2d715968cbc88f63784e3323ef0166566fbd121f0ebeb0d07d1ef886b","impliedFormat":1},{"version":"b45e4210d7ffd6339cc7c44484a287bd6578440e4885610067d44d6a084e6719","impliedFormat":1},{"version":"86c931b4aaddf898feee19e37ebdc9f29715bc71e39717138a8dbfb7b56e964d","impliedFormat":1},{"version":"b23d3623bbd2371f16961b7a8ab48f827ee14a0fc9e64aace665e4fc92e0fabe","impliedFormat":1},{"version":"95742365fd6f187354ad59aa45ec521f276b19acfb3636a065bc53728ede2aa6","impliedFormat":1},{"version":"4ac7cb98cbdde71287119827a1ec79c75e4b31847e18b7522cc8ff613f37d0d7","impliedFormat":1},{"version":"ae46812138452a8bf885321878a4f3f66060843b136322cf00e5bdd291596f5a","impliedFormat":1},{"version":"dd708604a523a1f60485ff5273811ff5a2581c0f9d0ccaa9dd7788b598c3e4cb","impliedFormat":1},{"version":"dbdd0616bc8801c73ded285458dddbc468bbae511e55a2b93db71a6fca9fc8fa","impliedFormat":1},{"version":"7682d3f8f04441f516ce74f85733583138039097779b0ac008785e4ecd440ca3","impliedFormat":1},{"version":"7619775d1c3f0bf6c49df7f1cf46bb0729b2f217e84c05e452ce4bb4c50347ba","impliedFormat":1},{"version":"2bd5ad36a78749bf88e7405712ad6cec774fd7646458612e80992a023f3a4da2","impliedFormat":1},{"version":"29a9495b4092f60dd5f079e664be6be1b967b8c2d600bfbf3986104e1d936e77","impliedFormat":1},{"version":"b966a1ceb3c4e8cc5a195ea43a962a6383d55d528ed3c33e97e65e14d2926e8e","impliedFormat":1},{"version":"524138093155f10c138b3ee9cc07284697bf6ba6d90a072106a1f0f7a23f8bea","impliedFormat":1},{"version":"4d44be7af68c7b5a537781bd4f28d48f2262dfd846ff5167f67f665aa93c342b","impliedFormat":1},{"version":"b5534cd11582a3025fb774fbda25a5bfb3a310befb36df425a954b23e2f1872a","impliedFormat":1},{"version":"1eb50ff7cef891bb6f7970802d061dbeb460bde39aef2690937e4e5dbadd74f7","impliedFormat":1},{"version":"b65353223b43764d9ac3a5b3f6bc80ac69b4bb53dfb733dca5dbe580cb2c95ee","impliedFormat":1},{"version":"a843a1a722ebd9a53aeb0823d40190907bde19df318bd3b0911d2876482bd9fa","impliedFormat":1},{"version":"c587631255497ef0d8af1ed82867bfbafaab2d141b84eb67d88b8c4365b0c652","impliedFormat":1},{"version":"b6d3cd9024ab465ec8dd620aeb7d859e323a119ec1d8f70797921566d2c6ac20","impliedFormat":1},{"version":"c5ccf24c3c3229a2d8d15085c0c5289a2bd6a16cb782faadf70d12fddcd672ff","impliedFormat":1},{"version":"a7fc49e0bee3c7ecdcd5c86bc5b680bfad77d0c4f922d4a2361a9aa01f447483","impliedFormat":1},{"version":"3dab449a3c849381e5edb24331596c46442ad46995d5d430c980d7388b158cf8","impliedFormat":1},{"version":"5886a079613cbf07cf7047db32f4561f342b200a384163e0a5586d278842b98e","impliedFormat":1},{"version":"9dae0e7895da154bdc9f677945c3b12c5cc7071946f3237a413bbaa47be5eaa3","impliedFormat":1},{"version":"2d9f27cd0e3331a9c879ea3563b6ad071e1cf255f6b0348f2a5783abe4ec57fb","impliedFormat":1},{"version":"8e6039bba2448ceddd14dafcefd507b4d32df96a8a95ca311be7c87d1ea04644","impliedFormat":1},{"version":"9466d70d95144bf164cd2f0b249153e0875b8db1d6b101d27dce790fd3844faf","impliedFormat":1},{"version":"223ff122c0af20e8025151f11100e3274c1e27234915f75f355881a5aa996480","impliedFormat":1},{"version":"e89a09b50458d1a1ef9992d4c1952d5b9f49f8cfdf82cada3feb4f906d290681","impliedFormat":1},{"version":"2d46726ef0883e699242f2f429b09605beb94ec2ed90d4cccdee650cfd38e9bf","impliedFormat":1},{"version":"a5d3817a1198f3c0f05501d3c23c37e384172bc5a67eaaccbf8b22e7068b607e","impliedFormat":1},{"version":"4ff787695e6ab16b1516e7045d9e8ecf6041c543b7fbed27e26d5222ee86dc7b","impliedFormat":1},{"version":"2b04c4f7b22dfa427973fa1ae55e676cbef3b24bd13e80266cf9e908d1911ce4","impliedFormat":1},{"version":"e89136e2df173f909cb13cdffbc5241b269f24721fe7582e825738dbb44fd113","impliedFormat":1},{"version":"88cf175787ba17012d6808745d3a66b6e48a82bb10d0f192f7795e9e3b38bee0","impliedFormat":1},{"version":"415f027720b1fd2ef33e1076d1a152321acb27fd838d4609508e60280b47ad74","impliedFormat":1},{"version":"1b4034b0a074f5736ae3ec4bf6a13a87ec399779db129f324e08e7fff5b303f2","impliedFormat":1},{"version":"dcd22923b72f9a979a1cea97be236b10fc1fa3ba592c587807bfe3e10d53dbb2","impliedFormat":1},{"version":"dcd22923b72f9a979a1cea97be236b10fc1fa3ba592c587807bfe3e10d53dbb2","impliedFormat":1},{"version":"f34f40704ea9f38ee0c7e1d8f28dfde5a2720577bfdfcd5c6566df140dbe0f7a","impliedFormat":1},{"version":"ea4034d0a7d4878f0710457807ae81cc00529a5f343594bc6e5fe3337561960a","impliedFormat":1},{"version":"2d3dbed1071ac8188a9d210ec745547bc4df0a6c7f4271ac28a36865bb76ee18","impliedFormat":1},{"version":"f71430f4f235cf6fe3ab8f30b763853fe711d186fc9dc1a5f4e11ba84f2000ad","impliedFormat":1},{"version":"5c4dac355c9c745a43de2b296ec350af4ee5548639728f238996df8e4c209b68","impliedFormat":1},{"version":"e8f5dbeb59708cde836d76b5bc1ff2fff301f9374782ffd300a0d35f68dce758","impliedFormat":1},{"version":"04967e55a48ca84841da10c51d6df29f4c8fa1d5e9bd87dec6f66bb9d2830fac","impliedFormat":1},{"version":"22f5e1d0db609c82d53de417d0e4ee71795841131ad00bbd2e0bd18af1c17753","impliedFormat":1},{"version":"afd5a92d81974c5534c78c516e554ed272313a7861e0667240df802c2a11f380","impliedFormat":1},{"version":"d29b6618f255156c4e5b804640aec4863aa22c1e45e7bd71a03d7913ab14e9e2","impliedFormat":1},{"version":"3f8ac93d4f705777ac6bb059bbe759b641f57ae4b04c8b6d286324992cb426e8","impliedFormat":1},{"version":"ba151c6709816360064659d1adfc0123a89370232aead063f643edf4f9318556","impliedFormat":1},{"version":"7957745f950830ecd78ec6b0327d03f3368cfb6059f40f6cdfc087a2c8ade5c0","impliedFormat":1},{"version":"e864f9e69daecb21ce034a7c205cbea7dfc572f596b79bcd67daab646f96722a","impliedFormat":1},{"version":"ebfba0226d310d2ef2a5bc1e0b4c2bc47d545a13d7b10a46a6820e085bc8bcb2","impliedFormat":1},{"version":"dac79c8b6ab4beefba51a4d5f690b5735404f1b051ba31cd871da83405e7c322","impliedFormat":1},{"version":"1ec85583b56036da212d6d65e401a1ae45ae8866b554a65e98429646b8ba9f61","impliedFormat":1},{"version":"8a9c1e79d0d23d769863b1a1f3327d562cec0273e561fd8c503134b4387c391a","impliedFormat":1},{"version":"b274fdc8446e4900e8a64f918906ba3317aafe0c99dba2705947bab9ec433258","impliedFormat":1},{"version":"ecf8e87c10c59a57109f2893bf3ac5968e497519645c2866fbd0f0fda61804b8","impliedFormat":1},{"version":"fe27166cc321657b623da754ca733d2f8a9f56290190f74cc72caad5cb5ef56f","impliedFormat":1},{"version":"74f527519447d41a8b1518fbbc1aca5986e1d99018e8fcd85b08a20dc4daa2e1","impliedFormat":1},{"version":"63017fb1cfc05ccf0998661ec01a9c777e66d29f2809592d7c3ea1cb5dab7d78","impliedFormat":1},{"version":"d08a2d27ab3a89d06590047e1902ee63ca797f58408405729d73fc559253bbc0","impliedFormat":1},{"version":"30dc37fb1af1f77b2a0f6ea9c25b5dc9f501a1b58a8aae301daa8808e9003cf6","impliedFormat":1},{"version":"2e03022de1d40b39f44e2e14c182e54a72121bd96f9c360e1254b21931807053","impliedFormat":1},{"version":"c1563332a909140e521a3c1937472e6c2dda2bb5d0261b79ed0b2340242bdd7b","impliedFormat":1},{"version":"4f297b1208dd0a27348c2027f3254b702b0d020736e8be3a8d2c047f6aa894dd","impliedFormat":1},{"version":"db4d4a309f81d357711b3f988fb3a559eaa86c693cc0beca4c8186d791d167d2","impliedFormat":1},{"version":"67cd15fcb70bc0ee60319d128609ecf383db530e8ae7bab6f30bd42af316c52c","impliedFormat":1},{"version":"c9ecba6a0b84fd4c221eb18dfbae6f0cbf5869377a9a7f0751754da5765e9d3f","impliedFormat":1},{"version":"394a9a1186723be54a2db482d596fd7e46690bda5efc1b97a873f614367c5cea","impliedFormat":1},{"version":"4fb9545dbfaa84b5511cb254aa4fdc13e46aaaba28ddc4137fed3e23b1ae669a","impliedFormat":1},{"version":"b265ebd7aac3bc93ba4eab7e00671240ca281faefddd0f53daefac10cb522d39","impliedFormat":1},{"version":"feadb8e0d2c452da67507eb9353482a963ac3d69924f72e65ef04842aa4d5c2e","impliedFormat":1},{"version":"46beac4ebdcb4e52c2bb4f289ba679a0e60a1305f5085696fd46e8a314d32ce6","impliedFormat":1},{"version":"1bf6f348b6a9ff48d97e53245bb9d0455bc2375d48169207c7fc81880c5273d6","impliedFormat":1},{"version":"1b5c2c982f14a0e4153cbf5c314b8ba760e1cd6b3a27c784a4d3484f6468a098","impliedFormat":1},{"version":"894ce0e7a4cfe5d8c7d39fab698da847e2da40650e94a76229608cb7787d19e6","impliedFormat":1},{"version":"7453cc8b51ffd0883d98cba9fbb31cd84a058e96b2113837191c66099d3bb5a6","impliedFormat":1},{"version":"25f5fafbff6c845b22a3af76af090ddfc90e2defccca0aa41d0956b75fe14b90","impliedFormat":1},{"version":"41e3ec4b576a2830ff017112178e8d5056d09f186f4b44e1fa676c984f1cb84e","impliedFormat":1},{"version":"5617b31769e0275c6f93a14e14774398152d6d03cc8e40e8c821051ef270340e","impliedFormat":1},{"version":"60f19b2df1ca4df468fae1bf70df3c92579b99241e2e92bc6552dfb9d690b440","impliedFormat":1},{"version":"52cac457332357a1e9ea0d5c6e910b867ca1801b31e3463b1dcbaa0d939c4775","impliedFormat":1},{"version":"cf08008f1a9e30cd2f8a73bc1e362cad4c123bd827058f5dffed978b1aa41885","impliedFormat":1},{"version":"582bf54f4a355529a69c3bb4e995697ff5d9e7f36acfddba454f69487b028c66","impliedFormat":1},{"version":"d342554d650b595f2e64cb71e179b7b6112823b5b82fbadf30941be62f7a3e61","impliedFormat":1},{"version":"f7bfc25261dd1b50f2a1301fc68e180ac42a285da188868e6745b5c9f4ca7c8a","impliedFormat":1},{"version":"61d841329328554af2cfa378a3e8490712de88818f8580bde81f62d9b9c4bf67","impliedFormat":1},{"version":"be76374981d71d960c34053c73d618cad540b144b379a462a660ff8fbc81eabe","impliedFormat":1},{"version":"8d9629610c997948d3cfe823e8e74822123a4ef73f4ceda9d1e00452b9b6bbf3","impliedFormat":1},{"version":"0c15ca71d3f3f34ebf6027cf68c8d8acae7e578bb6cc7c70de90d940340bf9bd","impliedFormat":1},{"version":"e5d0a608dca46a22288adac256ec7404b22b6b63514a38acab459bf633e258e0","impliedFormat":1},{"version":"c6660b6ccec7356778f18045f64d88068959ec601230bab39d2ad8b310655f99","impliedFormat":1},{"version":"aaca412f82da34fb0fd6751cea6bbf415401f6bb4aed46416593f7fcfaf32cb5","impliedFormat":1},{"version":"5e283ec6c1867adf73635f1c05e89ee3883ba1c45d2d6b50e39076e0b27f7cd9","impliedFormat":1},{"version":"2712654a78ad0736783e46e97ce91210470b701c916a932d2018a22054ee9751","impliedFormat":1},{"version":"347872376770cb6222066957f9b1ab45083552d415687f92c8b91cb246fd5268","impliedFormat":1},{"version":"24ecb13ea03a8baa20da7df564b4ba48505b396cd746cd0fe64b1f891574a0c9","impliedFormat":1},{"version":"1ded976e25a882defb5c44c3cf0d86f6157aadc85ff86b3f1d6b0796d842e861","impliedFormat":1},{"version":"c15bc8c0b0d3c15dec944d1f8171f6db924cc63bc42a32bc67fbde04cf783b5f","impliedFormat":1},{"version":"5b0c4c470bd3189ea2421901b27a7447c755879ba2fd617ab96feefa2b854ba5","impliedFormat":1},{"version":"08299cc986c8199aeb9916f023c0f9e80c2b1360a3ab64634291f6ff2a6837b1","impliedFormat":1},{"version":"1c49adea5ebea9fbf8e9b28b71e5b5420bf27fee4bf2f30db6dfa980fdad8b07","impliedFormat":1},{"version":"24a741caee10040806ab1ad7cf007531464f22f6697260c19d54ea14a4b3b244","impliedFormat":1},{"version":"b08dfe9e6da10dd03e81829f099ae983095f77c0b6d07ffdd4e0eaf3887af17e","impliedFormat":1},{"version":"40bd28334947aab91205e557963d02c371c02dc76a03967c04ae8451c3702344","impliedFormat":1},{"version":"62e9943dc2f067bda73b19fe8bcf20b81459b489b4f0158170dd9f3b38c68d30","impliedFormat":1},{"version":"267c58ef692839390c97bbb578bdd64f8a162760b4afbd3f73eacacf77d6ea6e","impliedFormat":1},{"version":"6d2496f03c865b5883deee9deda63b98d41f26d60b925204044cd4b78f0f8596","impliedFormat":1},{"version":"02988c4a472902b6ec5cb00809ef193c8a81ffde90b1759dfc34eb18674e0b02","impliedFormat":1},{"version":"7b2b386bb8e6842a4406164027fb53ab4bfef3fbc0eca440f741555dc212d0e8","impliedFormat":1},{"version":"35d669220fc1b97204dc5675e124932294d45b021feb425a9aa16888df44716d","impliedFormat":1},{"version":"bb7b865996627537dbaba9f2fd2f4195003370b02022937cd9eb57c0a0e461d0","impliedFormat":1},{"version":"28a2b8c6566e5a25119829e96a0ac0f0720df78ff55553f1a7529fbce5a87749","impliedFormat":1},{"version":"a1bb9a53774db78ea94042f996663ccac2ba1a1f695dd3e9931ff8ee898cbd06","impliedFormat":1},{"version":"0875537e7be2600acd9e872204840dcfadcc1fe4092a08bd0172a1b766019513","impliedFormat":1},{"version":"4227776f77e27c7d441fd5b8777d16b527928a7b62a0ef86ab8b9c67014cb81c","impliedFormat":1},{"version":"fbf3b2da9b15b5636cbc84578e26ce32e09ddbbac273d1af0313134858ada13e","impliedFormat":1},{"version":"af6f476584c7f0cc7840d26bd53b8f2cb2d297fdfbbce545f054f6098c156760","impliedFormat":1},{"version":"e0dcee233f86aa9a287c8e5021568a9d141faf5f312f348742d77e0a3e57e57d","impliedFormat":1},{"version":"feb50e2e786d7ffebe305337c5fcfe0a8cb2e9eb86542eafffaaf765526075c3","impliedFormat":1},{"version":"154c7aa0bb4266ec1ba8cbc132a6d6f4f5a501c6f557e42fab1551f12d7aadb4","impliedFormat":1},{"version":"ff580bb5932bafb0e88770659100ebb12da80897ed6cc7ffbdf3687048e46555","impliedFormat":1},{"version":"ef2c75a07f97f5214fb2da7bf59bbe82cbaeb6b9cc081e39b674aed5ebdf7905","impliedFormat":1},{"version":"d0c05fadcba345577656a05bf79d4b39a1f00acf76f22c8e4cf18ff30467750e","impliedFormat":1},{"version":"d0c05fadcba345577656a05bf79d4b39a1f00acf76f22c8e4cf18ff30467750e","impliedFormat":1},{"version":"7014093354b80dd4a938ea58d26de184454c4a08bd0500ae00e80eb9a4c19739","impliedFormat":1},{"version":"d06d271d2c714876d2e99a3e91426ed486ef86e92a46d7bd6183bd7849495162","impliedFormat":1},{"version":"da0fb569b713681bfa283495f9f53de3da5a0934fd1794baa99d83686f0eb243","impliedFormat":1},{"version":"1af351fa79e3f56d6ad665ffcd9c19e13d66a76e6d87e1889047729411c34105","impliedFormat":1},{"version":"97b738457d2e1311435022a93b7fa0105d54d3cab2a9557da6df6c3578b9cbdb","impliedFormat":1},{"version":"4cd82c54df6351d625a16e533463ed589155ca392257d5d5d29908be9f6c6ab0","impliedFormat":1},{"version":"c1a3b064d216c0d2503265a68444cd07638b9894575ebcd28fb3ed87ef401641","impliedFormat":1},{"version":"11ddb81d72d7c1e9b70bdec8d887f5d6737c78448477f34b0e66b9d38c5fe960","impliedFormat":1},{"version":"7f2db8b69950287573e65133460d6d0c55afcf99d415f18b00024bd5f55c4941","impliedFormat":1},{"version":"f279cd82f0d7a8c257e9750beafdd375085419733539e6d5ede1ab242de8957f","impliedFormat":1},{"version":"3bd004b8e866ef11ced618495781fd2c936a2a5989927137bdebb3e4755741fd","impliedFormat":1},{"version":"6d34100e5393cbee1869db0f370436d583045f3120c85c7c20bf52377ab6d548","impliedFormat":1},{"version":"92d7ba36531ea86b2be88729546129e1a1d08e571d9d389b859f0867cf26432a","impliedFormat":1},{"version":"f3a6050138891f2cdfdeacf7f0da8da64afc3f2fc834668daf4c0b53425876fb","impliedFormat":1},{"version":"9f260829b83fa9bce26e1a5d3cbb87eef87d8b3db3e298e4ea411a4a0e54f1f5","impliedFormat":1},{"version":"1c23a5cd8c1e82ded17793c8610ca7743344600290cedaf6b387d3518226455b","impliedFormat":1},{"version":"152d05b7e36aac1557821d5e60905bff014fcfe9750911b9cf9c2945cac3df8d","impliedFormat":1},{"version":"6670f4292fc616f2e38c425a5d65d92afc9fb1de51ea391825fa6d173315299a","impliedFormat":1},{"version":"c61a39a1539862fbd48212ba355b5b7f8fe879117fd57db0086a5cbb6acc6285","impliedFormat":1},{"version":"ae9d88113c68896d77b2b51a9912664633887943b465cd80c4153a38267bf70b","impliedFormat":1},{"version":"5d2c41dad1cb904e5f7ae24b796148a08c28ce2d848146d1cdf3a3a8278e35b8","impliedFormat":1},{"version":"b900fa4a5ff019d04e6b779aef9275a26b05794cf060e7d663c0ba7365c2f8db","impliedFormat":1},{"version":"5b7afd1734a1afc68b97cc4649e0eb8d8e45ee3b0ccb4b6f0060592070d05b6d","impliedFormat":1},{"version":"0c83c39f23d669bcb3446ce179a3ba70942b95ef53f7ba4ce497468714b38b8c","impliedFormat":1},{"version":"e9113e322bd102340f125a23a26d1ccf412f55390ae2d6f8170e2e602e2ae61b","impliedFormat":1},{"version":"456308ee785a3c069ec42836d58681fe5897d7a4552576311dd0c34923c883be","impliedFormat":1},{"version":"31e7a65d3e792f2d79a15b60b659806151d6b78eb49cb5fc716c1e338eb819b5","impliedFormat":1},{"version":"a9902721e542fd2f4f58490f228efdad02ebafa732f61e27bb322dbd3c3a5add","impliedFormat":1},{"version":"6e846536a0747aa1e5db6eafec2b3f80f589df21eea932c87297b03e9979d4bf","impliedFormat":1},{"version":"8bd87605aca1cb62caeca63fa442590d4fc14173aa27316ff522f1db984c5d37","impliedFormat":1},{"version":"0ecce2ac996dc29c06ed8e455e9b5c4c7535c177dbfa6137532770d44f975953","impliedFormat":1},{"version":"e2ddd4c484b5c1a1072540b5378b8f8dd8a456b4f2fdd577b0e4a359a09f1a5a","impliedFormat":1},{"version":"db335cb8d7e7390f1d6f2c4ca03f4d2adc7fc6a7537548821948394482e60304","impliedFormat":1},{"version":"b8beb2b272c7b4ee9da75c23065126b8c89d764f8edc3406a8578e6e5b4583b2","impliedFormat":1},{"version":"71e50d029b1100c9f91801f39fd02d32e7e2d63c7961ecb53ed17548d73c150f","impliedFormat":1},{"version":"9af2013e20b53a733dd8052aa05d430d8c7e0c0a5d821a4f4be2d4b672ec22ae","impliedFormat":1},{"version":"8fbe1bc4365212d10f188649f6f8cc17afb5bb3ff12336eb1a9bd5f966d23ad2","impliedFormat":1},{"version":"8fbe1bc4365212d10f188649f6f8cc17afb5bb3ff12336eb1a9bd5f966d23ad2","impliedFormat":1},{"version":"7c2ad9924e9d856fbefbe4ada292bfbf8ffa9b75c419934ad54c7480ef974255","impliedFormat":1},{"version":"7c2ad9924e9d856fbefbe4ada292bfbf8ffa9b75c419934ad54c7480ef974255","impliedFormat":1},{"version":"8033abdbffc86e6d598c589e440ab1e941c2edf53da8e18b84a2bef8769f0f31","impliedFormat":1},{"version":"e88eb1d18b59684cd8261aa4cdef847d739192e46eab8ea05de4e59038401a19","impliedFormat":1},{"version":"834c394b6fdac7cdfe925443170ecdc2c7336ba5323aa38a67aaaf0b3fd8c303","impliedFormat":1},{"version":"831124f3dd3968ebd5fac3ede3c087279acb5c287f808767c3478035b63d8870","impliedFormat":1},{"version":"21d06468c64dba97ef6ee1ccffb718408164b0685d1bff5e4aadd61fcc038655","impliedFormat":1},{"version":"967e26dd598db7de16c9e0533126e624da94bd6c883fd48fbccc92c86e1163c5","impliedFormat":1},{"version":"e2bb71f5110046586149930b330c56f2e1057df69602f8051e11475e9e0adcb0","impliedFormat":1},{"version":"54d718265b1257a8fa8ebf8abe89f899e9a7ae55c2bbeb3fbe93a9ee63c27c08","impliedFormat":1},{"version":"52d09b2ffcfe8a291d70dd6ec8c301e75aff365b891241e5df9943a5bd2cd579","impliedFormat":1},{"version":"c4c282bd73a1a8944112ec3501b7aed380a17a1e950955bb7e67f3ef2ae3eacd","impliedFormat":1},{"version":"b68bffb8ec0c31f104751b7783ea3fca54a27e5562dc6a36467a59af2b9f45d0","impliedFormat":1},{"version":"5f5befc12e7070c00db287c98ebff95b1978d57c94e5eb7f1dc2cdc4351a132a","impliedFormat":1},{"version":"a1fb885801e6a1b76618c7db3dd88d547d696c34b54afb37c6188fdc5c552495","impliedFormat":1},{"version":"d72c555ebec376d349d016576506f1dc171a136206fe75ef8ee36efe0671d5c3","impliedFormat":1},{"version":"e48eda19a17d77b15d627b032d2c82c16dbe7a8714ea7a136919c6fd187a87e9","impliedFormat":1},{"version":"64f38f3e656034d61f6617bff57f6fce983d33b96017a6b1d7c13f310f12a949","impliedFormat":1},{"version":"044028281a4a777b67073a9226b3a3a5f6720083bb7b7bab8b0eeafe70ccf569","impliedFormat":1},{"version":"0dac330041ba1c056fe7bacd7912de9aebec6e3926ff482195b848c4cef64f1c","impliedFormat":1},{"version":"302de1a362e9241903e4ebf78f09133bc064ee3c080a4eda399f6586644dab87","impliedFormat":1},{"version":"940851ac1f3de81e46ea0e643fc8f8401d0d8e7f37ea94c0301bb6d4d9c88b58","impliedFormat":1},{"version":"afab51b01220571ecff8e1cb07f1922d2f6007bfa9e79dc6d2d8eea21e808629","impliedFormat":1},{"version":"0a22b9a7f9417349f39e9b75fb1e1442a4545f4ed51835c554ac025c4230ac95","impliedFormat":1},{"version":"11b8a00dbb655b33666ed4718a504a8c2bf6e86a37573717529eb2c3c9b913ad","impliedFormat":1},{"version":"c4f529f3b69dfcec1eed08479d7aa2b5e82d4ab6665daa78ada044a4a36638c2","impliedFormat":1},{"version":"56fb9431fdb234f604d6429889d99e1fec1c9b74f69b1e42a9485399fd8e9c68","impliedFormat":1},{"version":"1abfd55d146ec3bfa839ccba089245660f30b685b4fdfd464d2e17e9372f3edc","impliedFormat":1},{"version":"5ea23729bee3c921c25cd99589c8df1f88768cfaf47d6d850556cf20ec5afca8","impliedFormat":1},{"version":"0def6b14343fb4659d86c60d8edb412094d176c9730dc8491ce4adabdbe6703a","impliedFormat":1},{"version":"7871d8a4808eab42ceb28bc7edefa2052da07c5c82124fb8e98e3b2c0b483d6c","impliedFormat":1},{"version":"f7e0da46977f2f044ec06fd0089d2537ff44ceb204f687800741547056b2752f","impliedFormat":1},{"version":"586e954d44d5c634998586b9d822f96310321ee971219416227fc4269ea1cdaf","impliedFormat":1},{"version":"33a7a07bc3b4c26441fa544f84403b1321579293d6950070e7daeee0ed0699d8","impliedFormat":1},{"version":"4d000e850d001c9e0616fd8e7cc6968d94171d41267c703bd413619f649bd12a","impliedFormat":1},{"version":"a2d30f0ed971676999c2c69f9f7178965ecbe5c891f6f05bc9cbcd9246eda025","impliedFormat":1},{"version":"f94f93ce2edf775e2eeb43bc62c755f65fb15a404c0507936cc4a64c2a9b2244","impliedFormat":1},{"version":"b4275488913e1befb217560d484ca3f3bf12903a46ade488f3947e0848003473","impliedFormat":1},{"version":"b173f8a2bd54cee0ae0d63a42ca59a2150dce59c828649fc6434178b0905bc05","impliedFormat":1},{"version":"613afe0af900bad8ecb48d9d9f97f47c0759aaebd7975aab74591f5fe30cf887","impliedFormat":1},{"version":"7c43dd250932457013546c3d0ed6270bfe4b9d2800c9a52ad32ece15fc834ef4","impliedFormat":1},{"version":"d0875863f16a9c18b75ef7eab23a1cf93c2c36677c9bb450307b1fa5b7521746","impliedFormat":1},{"version":"37154c245da711d32d653ad43888aac64c93d6f32a8392b0d4635d38dd852e57","impliedFormat":1},{"version":"9be1d0f32a53f6979f12bf7d2b6032e4c55e21fdfb0d03cb58ba7986001187c1","impliedFormat":1},{"version":"6575f516755b10eb5ff65a5c125ab993c2d328e31a9af8bb2de739b180f1dabc","impliedFormat":1},{"version":"5580c4cc99b4fc0485694e0c2ffc3eddfb32b29a9d64bba2ba4ad258f29866bc","impliedFormat":1},{"version":"3217967a9d3d1e4762a2680891978415ee527f9b8ee3325941f979a06f80cd7b","impliedFormat":1},{"version":"430c5818b89acea539e1006499ed5250475fdda473305828a4bb950ada68b8bd","impliedFormat":1},{"version":"a8e3230eab879c9e34f9b8adee0acec5e169ea6e6332bc3c7a0355a65fbf6317","impliedFormat":1},{"version":"62563289e50fd9b9cf4f8d5c8a4a3239b826add45cfb0c90445b94b8ca8a8e46","impliedFormat":1},{"version":"e1f6516caf86d48fd690663b0fd5df8cf3adf232b07be61b4d1c5ba706260a56","impliedFormat":1},{"version":"c5fd755dac77788acc74a11934f225711e49014dd749f1786b812e3e40864072","impliedFormat":1},{"version":"672ed5d0ebc1e6a76437a0b3726cb8c3f9dd8885d8a47f0789e99025cfb5480d","impliedFormat":1},{"version":"e15305776c9a6d9aac03f8e678008f9f1b9cb3828a8fc51e6529d94df35f5f54","impliedFormat":1},{"version":"4da18bcf08c7b05b5266b2e1a2ac67a3b8223d73c12ee94cfa8dd5adf5fdcd5e","impliedFormat":1},{"version":"a4e14c24595a343a04635aff2e39572e46ae1df9b948cc84554730a22f3fc7a3","impliedFormat":1},{"version":"0f604aef146af876c69714386156b8071cdb831cb380811ed6749f0b456026bd","impliedFormat":1},{"version":"4868c0fb6c030a7533deb8819c9351a1201b146a046b2b1f5e50a136e5e35667","impliedFormat":1},{"version":"8a1cfeb14ca88225a95d8638ee58f357fc97b803fe12d10c8b52d07387103ff1","impliedFormat":1},{"version":"fac0f34a32af6ff4d4e96cd425e8fefb0c65339c4cb24022b27eb5f13377531f","impliedFormat":1},{"version":"7ec5a106f7a6de5a44eac318bb47cdece896e37b69650dd9e394b18132281714","impliedFormat":1},{"version":"a015f74e916643f2fd9fa41829dea6d8a7bedbb740fe2e567a210f216ac4dcad","impliedFormat":1},{"version":"4dbabbde1b07ee303db99222ef778a6c2af8362bc5ce185996c4dc91cba6b197","impliedFormat":1},{"version":"0873baae7b37627c77a36f8ead0ab3eb950848023c9e8a60318f4de659e04d54","impliedFormat":1},{"version":"dc7d167f4582a21e20ac5979cb0a9f58a0541d468b406fd22c739b92cd9f5eec","impliedFormat":1},{"version":"edeec378c31a644e8fa29cfcb90f3434a20db6e13ae65df8298163163865186f","impliedFormat":1},{"version":"12300e3a7ca6c3a71773c5299e0bca92e2e116517ab335ab8e82837260a04db7","impliedFormat":1},{"version":"2e6128893be82a1cbe26798df48fcfb050d94c9879d0a9c2edece4be23f99d9f","impliedFormat":1},{"version":"2819f355f57307c7e5a4d89715156750712ea15badcb9fbf6844c9151282a2b8","impliedFormat":1},{"version":"4e433094ed847239c14ae88ca6ddaa6067cb36d3e95edd3626cec09e809abc3b","impliedFormat":1},{"version":"7c592f0856a59c78dbfa856c8c98ba082f4dafb9f9e8cdd4aac16c0b608aaacd","impliedFormat":1},{"version":"9fb90c7b900cee6a576f1a1d20b2ef0ed222d76370bc74c1de41ea090224d05d","impliedFormat":1},{"version":"c94cfa7c0933700be94c2e0da753c6d0cf60569e30d434c3d0df4a279df7a470","impliedFormat":1},{"version":"b208e4729b03a250bc017f1231a27776db6e5396104c4a5cfe40a8de4d3ab33e","impliedFormat":1},{"version":"b208e4729b03a250bc017f1231a27776db6e5396104c4a5cfe40a8de4d3ab33e","impliedFormat":1},{"version":"83624214a41f105a6dd1fef1e8ebfcd2780dd2841ce37b84d36d6ae304cba74e","impliedFormat":1},{"version":"bc63f711ce6d1745bb9737e55093128f8012d67a9735c958aaaf1945225c4f1d","impliedFormat":1},{"version":"951404d7300f1a479a7e70bca4469ea5f90807db9d3adc293b57742b3c692173","impliedFormat":1},{"version":"e93bba957a27b85afb83b2387e03a0d8b237c02c85209fde7d807c2496f20d41","impliedFormat":1},{"version":"4537c199f28f3cd75ab9d57b21858267c201e48a90009484ef37e9321b9c8dbb","impliedFormat":1},{"version":"faae84acef05342e6009f3fa68a2e58e538ef668c7173d0fc2eacac0ad56beef","impliedFormat":1},{"version":"7e19092d64b042f55f4d7b057629159a8167ee319d4cccc4b4bdd12d74018a6c","impliedFormat":1},{"version":"39196b72ec09bdc29508c8f29705ce8bd9787117863ca1bcf015a628bed0f031","impliedFormat":1},{"version":"3f727217522dabc9aee8e9b08fccf9d67f65a85f8231c0a8dbcc66cf4c4f3b8d","impliedFormat":1},{"version":"bbeb72612b2d3014ce99b3601313b2e1a1f5e3ce7fdcd8a4b68ff728e047ffcd","impliedFormat":1},{"version":"c89cc13bad706b67c7ca6fca7b0bb88c7c6fa3bd014732f8fc9faa7096a3fad8","impliedFormat":1},{"version":"2272a72f13a836d0d6290f88759078ec25c535ec664e5dabc33d3557c1587335","impliedFormat":1},{"version":"1074e128c62c48b5b1801d1a9aeebac6f34df7eafa66e876486fbb40a919f31a","impliedFormat":1},{"version":"87bba2e1de16d3acb02070b54f13af1cb8b7e082e02bdfe716cb9b167e99383b","impliedFormat":1},{"version":"a2e3a26679c100fb4621248defda6b5ce2da72943da9afefccaf8c24c912c1cb","impliedFormat":1},{"version":"3ee7668b22592cc98820c0cf48ad7de48c2ad99255addb4e7d735af455e80b47","impliedFormat":1},{"version":"643e9615c85c77bc5110f34c9b8d88bce6f27c54963f3724ab3051e403026d05","impliedFormat":1},{"version":"35c13baa8f1f22894c1599f1b2b509bdeb35f7d4da12619b838d79c6f72564bb","impliedFormat":1},{"version":"7d001913c9bf95dbdc0d4a14ffacf796dbc6405794938fc2658a79a363f43f65","impliedFormat":1},{"version":"9906fbdb7d8e18b0105f61569701a07c8aaa7ea0ef6dc63f8f9fbba7de8e044e","impliedFormat":1},{"version":"9906fbdb7d8e18b0105f61569701a07c8aaa7ea0ef6dc63f8f9fbba7de8e044e","impliedFormat":1},{"version":"6a0840f6ab3f97f9348098b3946941a7ca67beb47a6f2a75417376015bde3d62","impliedFormat":1},{"version":"24c75bd8d8ba4660a4026b89abc5457037ed709759ca1e9e26bd68c610817069","impliedFormat":1},{"version":"8cc6185d8186c7fefa97462c6dd9915df9a9542bd97f220b564b3400cdf3ad82","impliedFormat":1},{"version":"2cad19f3eae8e3a9176bf34b9cffa640d55a3c73b69c78b0b80808130d5120c6","impliedFormat":1},{"version":"a140d8799bc197466ac82feef5a8f1f074efc1bb5f02c514200269601279a6ff","impliedFormat":1},{"version":"48bda2797d1005604d21de42a41af85dfe7688391d28f02b90c90c06f6604781","impliedFormat":1},{"version":"1454f42954c53c719ae3f166a71c2a8c4fbc95ee8a5c9ddba3ec15b792054a3d","impliedFormat":1},{"version":"ae4890722031fcaa66eed85d5ce06f0fc795f21dedbe4c7c53f777c79caf01dd","impliedFormat":1},{"version":"1a6ff336c6c59fa7b44cf01dc0db00baa1592d7280be70932110fe173c3a3ed6","impliedFormat":1},{"version":"95fa82863f56a7b924814921beeab97aa064d9e2c6547eb87492a3495533be0f","impliedFormat":1},{"version":"248cdafd23df89eee20f1ef00daef4f508850cfcbad9db399b64cdb1c3530c06","impliedFormat":1},{"version":"936579eb15fe5cf878d90bddaf083a5dce9e8ca7d2222c2d96a2e55b8022e562","impliedFormat":1},{"version":"1bd19890e78429873f6eb45f6bd3b802743120c2464b717462ec4c9668ce7b89","impliedFormat":1},{"version":"756c0802bc098388018b4f245a15457083aee847ebcd89beb545d58ccbf29a9f","impliedFormat":1},{"version":"8e00226014fc83b74b47868bfac6919b2ca51e1dc612ea3f396a581ba7da8fdd","impliedFormat":1},{"version":"27930087468a6afd3d42fd75c37d8cc7df6a695f3182eb6230fcea02fce46635","impliedFormat":1},{"version":"b6d0a876f84484d9087e8eadde589e25b3f1975d32a11d188f6da0bc5dcf1d1d","impliedFormat":1},{"version":"5a282b327e397cf1637717c454d71f5dff2af2514d7f3766562bd51721d5eaab","impliedFormat":1},{"version":"fba971f62ec18b0de02357aba23b11c19aeb512eb525b9867f6cc2495d3a9403","impliedFormat":1},{"version":"69334948e4bc7c2b5516ed02225eaf645c6d97d1c636b1ef6b7c9cfc3d3df230","impliedFormat":1},{"version":"4231544515c7ce9251e34db9d0e3f74fc38365e635c8f246f2d8b39461093dea","impliedFormat":1},{"version":"963d469b265ce3069e9b91c6807b4132c1e1d214169cf1b43c26bfbcb829b666","impliedFormat":1},{"version":"387616651414051e1dd73daf82d6106bbaefcbad21867f43628bd7cbe498992f","impliedFormat":1},{"version":"f3b6f646291c8ddfc232209a44310df6b4f2c345c7a847107b1b8bbde3d0060a","impliedFormat":1},{"version":"8fbbfbd7d5617c6f6306ffb94a1d48ca6fa2e8108c759329830c63ff051320e1","impliedFormat":1},{"version":"9912be1b33a6dfc3e1aaa3ad5460ee63a71262713f1629a86c9858470f94967d","impliedFormat":1},{"version":"57c32282724655f62bff2f182ce90934d83dc7ed14b4ac3f17081873d49ec15b","impliedFormat":1},{"version":"fabb2dcbe4a45ca45247dece4f024b954e2e1aada1b6ba4297d7465fac5f7fb3","impliedFormat":1},{"version":"449fa612f2861c3db22e394d1ad33a9544fe725326e09ec1c72a4d9e0a85ccf1","impliedFormat":1},{"version":"5e80786f1a47a61be5afde06ebd2eae0d1f980a069d34cea2519f41e518b31e8","impliedFormat":1},{"version":"565fbcf5374afdcb53e1bf48a4dd72db5c201551ec1cdf408aab9943fec4f525","impliedFormat":1},{"version":"8334934b3c4b83da15be9025d15b61fdada52adfb6b3c81e24bf61e33e4a8f56","impliedFormat":1},{"version":"0bf7ddc236561ac7e5dcd04bcbb9ac34ea66d1e54542f349dc027c08de120504","impliedFormat":1},{"version":"329b4b6fb23f225306f6a64f0af065bc7d5858024b2b04f46b482d238abe01ef","impliedFormat":1},{"version":"c70a7411a384063543b9703d072d38cfec64c54d9bdcc0916a24fcb7945907c3","impliedFormat":1},{"version":"d74eccab1a21737b12e17a94bacff23954496ccad820ee1bd4769353825ea1f0","impliedFormat":1},{"version":"5a169268ac5488e3555a333964a538ce27a8702b91fffa7f2f900b67bf943352","impliedFormat":1},{"version":"85931e79bdd6b16953de2303cebbe16ba1d66375f302ffe6c85b1630c64d4751","impliedFormat":1},{"version":"ad9da00aa581dca2f09a6fec43f0d03eff7801c0c3496613d0eb1d752abf44d9","impliedFormat":1},{"version":"28ea9e12e665d059b80a8f5424e53aa0dd8af739da7f751cc885f30440b64a7f","impliedFormat":1},{"version":"cdc22634df9ab0cd1e1ab5a32e382d034bba97afd7c12db7862b9079e5e3c4c0","impliedFormat":1},{"version":"73940b704df78d02da631af2f5f253222821da6482c21cd96f64e90141b34d38","impliedFormat":1},{"version":"76e64c191fe381ecbbb91a3132eaf16b54e33144aee0e00728d4f8ba9d3be3c1","impliedFormat":1},{"version":"de49fed066a921f1897ca031e5a3d3c754663b9a877b01362cc08fb6a250a8b6","impliedFormat":1},{"version":"833b691a43b7b18f4251fdb305babad29234dd6c228cf5b931118301c922283d","impliedFormat":1},{"version":"a5f925f6ad83aa535869fb4174e7ef99c465e5c01939d2e393b6f8c0def6d95e","impliedFormat":1},{"version":"db80344e9c5463e4fb49c496b05e313b3ebcc1b9c24e9bcd97f3e34429530302","impliedFormat":1},{"version":"f69e0962918f4391e8e5e50a1b3eb1e3fd40f63ed082da8242b34dda16c519ba","impliedFormat":1},{"version":"012dcd1847240a35fd1de3132d11afab38bb63e99ce1ca2679c2376567f5ef74","impliedFormat":1},{"version":"c4e34c7b331584cd9018fb2d51d602d38cf9f2aeec0bad092b61dd10ff602bd5","impliedFormat":1},{"version":"06675fa918f0abfe5632adbfae821517a34af861cadab135d4240f0b0fd975a5","impliedFormat":1},{"version":"a4919817b89aadcc8fb7121d41c3924a30448d017454cb3d1e3570f8413f74a6","impliedFormat":1},{"version":"2a37bd0673e5f0b487f05880d143883abcbdc9682d0ed54d550eb44e775dab46","impliedFormat":1},{"version":"8ed0765cafa7e4b10224672c29056e8ee4a9936df65ba4ea3ffd841c47aa2393","impliedFormat":1},{"version":"a38694615d4482f8b6556f6b0915374bbf167c3e92e182ae909f5e1046ebbc97","impliedFormat":1},{"version":"a0ff175b270170dd3444ee37fdd71e824b934dcdae77583d4cdea674349f980e","impliedFormat":1},{"version":"99391c62be7c4a7dc23d4a94954973e5f1c1ca0c33fdd8f6bb75c1ddc7ffc3ad","impliedFormat":1},{"version":"ea58d165e86c3e2e27cf07e94175c60d1672810f873e344f7bc85ad4ebe00cef","impliedFormat":1},{"version":"85c8e99f8cd30d3a742c4c0fe5500db8561e0028b8153dc60c3d1e64ef2a507f","impliedFormat":1},{"version":"e272f75b77cffbfbb88ba377d7892d55e49f67378a8ffa7bddce1be53634ca3b","impliedFormat":1},{"version":"67448f432a710a322eac4b9a56fd8145d0033c65206e90fca834d9ed6601a978","impliedFormat":1},{"version":"7a319bad5a59153a92e455bebcfce1c8bc6e6e80f8e6cc3b20dd7465662c9c8e","impliedFormat":1},{"version":"2d7bed8ff2044b202f9bd6c35bf3bda6f8baad9e0f136a9c0f33523252de4388","impliedFormat":1},{"version":"308786774814d57fc58f04109b9300f663cf74bd251567a01dc4d77e04c1cdc1","impliedFormat":1},{"version":"68af14958b6a2faf118853f3ecb5c0dbee770bd1e0eb6c2ef54244b68cecf027","impliedFormat":1},{"version":"1255747e5c6808391a8300476bdb88924b13f32287270084ebd7649737b41a6e","impliedFormat":1},{"version":"37b6feaa304b392841b97c22617b43f9faa1d97a10a3c6d6160ca1ea599d53ce","impliedFormat":1},{"version":"79adb3a92d650c166699bb01a7b02316ea456acc4c0fd6d3a88cdd591f1849b0","impliedFormat":1},{"version":"0dc547b11ab9604c7a2a9ca7bf29521f4018a14605cc39838394b3d4b1fbaf6d","impliedFormat":1},{"version":"31fedd478a3a7f343ee5df78f1135363d004521d8edf88cd91b91d5b57d92319","impliedFormat":1},{"version":"88b7ed7312f01063f327c5d435224e137c6a2f9009175530e7f4b744c1e8957f","impliedFormat":1},{"version":"3cf0c7a66940943decbf30a670ab6077a44e9895e7aea48033110a5b58e86d64","impliedFormat":1},{"version":"11776f5fa09779862e18ff381e4c3cb14432dd188d30d9e347dfc6d0bda757a8","impliedFormat":1},{"version":"a7c12ec0d02212110795c86bd68131c3e771b1a3f4980000ec06753eb652a5c4","impliedFormat":1},{"version":"8d6b33e4d153c1cc264f6d1bb194010221907b83463ad2aaaa936653f18bfc49","impliedFormat":1},{"version":"4e0537c4cd42225517a5cdec0aea71fdaaacbf535c42050011f1b80eda596bbd","impliedFormat":1},{"version":"cf2ada4c8b0e9aa9277bfac0e9d08df0d3d5fb0c0714f931d6cac3a41369ee07","impliedFormat":1},{"version":"3bdbf003167e4dffbb41f00ddca82bb657544bc992ef307ed2c60c322f43e423","impliedFormat":1},{"version":"9d62d820685dfbed3d1da3c5d9707ae629eac65ee42eeae249e6444271a43f79","impliedFormat":1},{"version":"9fc1d71181edb6028002b0757a4de17f505fb538c8b86da2dabb2c58618e9495","impliedFormat":1},{"version":"895c35a7b8bdd940bda4d9c709acfc4dd72d302cc618ec2fd76ae2b8cd9fd534","impliedFormat":1},{"version":"e7eb43e86a2dfcb8a8158b2cc4eff93ff736cfec1f3bf776c2c8fb320b344730","impliedFormat":1},{"version":"7d2f0645903a36fe4f96d547a75ea14863955b8e08511734931bd76f5bbc6466","impliedFormat":1},{"version":"4d88daa298c032f09bc2453facf917d848fcd73b9814b55c7553c3bf0036ac3d","impliedFormat":1},{"version":"7e46cd381a3ac5dbb328d4630db9bf0d76aae653083fc351718efba4bd4bf3b3","impliedFormat":1},{"version":"23cca6a0c124bd1b5864a74b0b2a9ab12130594543593dc58180c5b1873a3d16","impliedFormat":1},{"version":"286c428c74606deaa69e10660c1654b9334842ef9579fbfbb9690c3a3fd3d8c5","impliedFormat":1},{"version":"e838976838d7aa954c3c586cd8efc7f8810ec44623a1de18d6c4f0e1bc58a2b6","impliedFormat":1},{"version":"fe7b3e4b7b62b6f3457f246aa5b26181da0c24dc5fc3a3b4f1e93f66c41d819f","impliedFormat":1},{"version":"ea15abd31f5884334fa04683b322618f1f4526a23f6f77839b446dbeee8eb9a1","impliedFormat":1},{"version":"e55b5d8322642dda29ae2dea9534464e4261cb8aa719fe8cec26ce2d70753db5","impliedFormat":1},{"version":"6074dbe82ec2c1325ecda241075fa8d814e6e5195a6c1f6315aa5a582f8eb4cf","impliedFormat":1},{"version":"c044c7f653a4aff233adfdee4c3d4e05da4fc071dfb6f8f32f5a8cd30e8aacaa","impliedFormat":1},{"version":"2f5f95be086b3c700fe1c0f1b20a5ff18a26a15ae9924b495231555a3bed7f05","impliedFormat":1},{"version":"fb4de4bc74a1997282181648fecd3ec5bb19d39cdb0ff3a4fb8ac134b2e03eb8","impliedFormat":1},{"version":"ada6919a8c3d26712dac8469dbe297980d97258fd7927aa4b4f68d8a0efeb20b","impliedFormat":1},{"version":"b1f2367947cf2dfba2cd6cc0d1ed3c49e55059f4ee0e648590daafecd1b49e63","impliedFormat":1},{"version":"e7aee498fe1438535033fdfe126a12f06874e3608cd77d8710ff9542ebb7ba60","impliedFormat":1},{"version":"0017e3bbd2f7b139daf97c0f27bef8531a6f44572ba9387f5451e417b62ecd55","impliedFormat":1},{"version":"91dda5226ec658c3c71dfb8689231f6bfea4d559d08f27237d0d02f4eb3e4aa6","impliedFormat":1},{"version":"e1e2ee6fc32ea03e5e8b419d430ea236b20f22d393ba01cc9021b157727e1c59","impliedFormat":1},{"version":"8adfd735c00b78c24933596cd64c44072689ac113001445a7c35727cb9717f49","impliedFormat":1},{"version":"999bfcbaae834b8d00121c28de9448c72f24767d3562fc388751a5574c88bd45","impliedFormat":1},{"version":"110a52db87a91246f9097f284329ad1eedd88ff8c34d3260dcb7f4f731955761","impliedFormat":1},{"version":"8929df495a85b4cc158d584946f6a83bf9284572b428bb2147cc1b1f30ee5881","impliedFormat":1},{"version":"22c869750c8452121f92a511ef00898cc02d941109e159a0393a1346348c144a","impliedFormat":1},{"version":"d96e2ff73f69bc352844885f264d1dfc1289b4840d1719057f711afac357d13e","impliedFormat":1},{"version":"a01928da03f46c245f2173ced91efd9a2b3f04a1a34a46bc242442083babaab9","impliedFormat":1},{"version":"c175f6dd4abdfac371b1a0c35ebeaf01c745dffbf3561b3a5ecc968e755a718b","impliedFormat":1},{"version":"d3531db68a46747aee3fa41531926e6c43435b59cd79ccdbcb1697b619726e47","impliedFormat":1},{"version":"c1771980c6bcd097876fe8b78a787e28163008e3d6d46885e9506483ac6b9226","impliedFormat":1},{"version":"8c2cc0d0b9b8650ef75f186f6c3aeeb3c18695e3cd3d0342cf8ef1d6aea27997","impliedFormat":1},{"version":"0a9bcf65e6abc0497fffcb66be835e066533e5623e32262b7620f1091b98776b","impliedFormat":1},{"version":"235a1b88a060bd56a1fc38777e95b5dda9c68ecb42507960ec6999e8a2d159cc","impliedFormat":1},{"version":"dde6b3b63eb35c0d4e7cc8d59a126959a50651855fd753feceab3bbad1e8000a","impliedFormat":1},{"version":"1f80185133b25e1020cc883e6eeadd44abb67780175dc2e21c603b8062a86681","impliedFormat":1},{"version":"f4abdeb3e97536bc85f5a0b1cced295722d6f3fd0ef1dd59762fe8a0d194f602","impliedFormat":1},{"version":"9de5968f7244f12c0f75a105a79813539657df96fb33ea1dafa8d9c573a5001a","impliedFormat":1},{"version":"87ab1102c5f7fe3cffbbe00b9690694cba911699115f29a1e067052bb898155d","impliedFormat":1},{"version":"a5841bf09a0e29fdde1c93b97e9a411ba7c7f9608f0794cbb7cf30c6dcd84000","impliedFormat":1},{"version":"e9282e83efd5ab0937b318b751baac2690fc3a79634e7c034f6c7c4865b635b4","impliedFormat":1},{"version":"7469203511675b1cfb8c377df00c6691f2666afb1a30c0568146a332e3188cb3","impliedFormat":1},{"version":"86854a16385679c4451c12f00774d76e719d083333f474970de51b1fd4aeaa9a","impliedFormat":1},{"version":"eb948bd45504f08e641467880383a9d033221c92d5e5f9057a952bbb688af0f2","impliedFormat":1},{"version":"8ad3462b51ab1a76a049b9161e2343a56a903235a87a7b6fb7ed5df6fc3a7482","impliedFormat":1},{"version":"c5e3f5a8e311c1be603fca2ab0af315bb27b02e53cd42edc81c349ffb7471c7e","impliedFormat":1},{"version":"0785979b4c5059cde6095760bc402d936837cbdeaa2ce891abe42ebcc1be5141","impliedFormat":1},{"version":"224881bef60ae5cd6bcc05b56d7790e057f3f9d9eacf0ecd1b1fc6f02088df70","impliedFormat":1},{"version":"3d336a7e01d9326604b97a23d5461d48b87a6acf129616465e4de829344f3d88","impliedFormat":1},{"version":"27ae5474c2c9b8a160c2179f2ec89d9d7694f073bdfc7d50b32e961ef4464bf0","impliedFormat":1},{"version":"e5772c3a61ac515bdcbb21d8e7db7982327bca088484bf0efdc12d9e114ec4c4","impliedFormat":1},{"version":"37d515e173e580693d0fdb023035c8fb1a95259671af936ea0922397494999f1","impliedFormat":1},{"version":"9b75d00f49e437827beeec0ecd652f0e1f8923ff101c33a0643ce6bed7c71ce1","impliedFormat":1},{"version":"bca71e6fb60fb9b72072a65039a51039ac67ea28fd8ce9ffd3144b074f42e067","impliedFormat":1},{"version":"d9b3329d515ac9c8f3760557a44cbca614ad68ad6cf03995af643438fa6b1faa","impliedFormat":1},{"version":"66492516a8932a548f468705a0063189a406b772317f347e70b92658d891a48d","impliedFormat":1},{"version":"20ecc73297ec37a688d805463c5e9d2e9f107bf6b9a1360d1c44a2b365c0657b","impliedFormat":1},{"version":"8e5805f4aab86c828b7fa15be3820c795c67b26e1a451608a27f3e1a797d2bf0","impliedFormat":1},{"version":"bb841b0b3c3980f91594de12fdc4939bb47f954e501bd8e495b51a1237f269d6","impliedFormat":1},{"version":"c40a182c4231696bd4ea7ed0ce5782fc3d920697866a2d4049cf48a2823195cc","impliedFormat":1},{"version":"c2f1079984820437380eba543febfb3d77e533382cbc8c691e8ec7216c1632ae","impliedFormat":1},{"version":"8737160dbb0d29b3a8ea25529b8eca781885345adb5295aa777b2f0c79f4a43f","impliedFormat":1},{"version":"78c5ee6b2e6838b6cbda03917276dc239c4735761696bf279cea8fc6f57ab9b7","impliedFormat":1},{"version":"11f3e363dd67c504e7ac9c720e0ddee8eebca10212effe75558266b304200954","impliedFormat":1},{"version":"ca53a918dbe8b860e60fec27608a83d6d1db2a460ad13f2ffc583b6628be4c5c","impliedFormat":1},{"version":"b278ba14ce1ea93dd643cd5ad4e49269945e7faf344840ecdf3e5843432dc385","impliedFormat":1},{"version":"f590aedb4ab4a8fa99d5a20d3fce122f71ceb6a6ba42a5703ea57873e0b32b19","impliedFormat":1},{"version":"1b94fcec898a08ad0b7431b4b86742d1a68440fa4bc1cd51c0da5d1faaf8fda4","impliedFormat":1},{"version":"a6ca409cb4a4fb0921805038d02a29c7e6f914913de74ab7dc02604e744820f7","impliedFormat":1},{"version":"9e938bdb31700c1329362e2246192b3cd2fac25a688a2d9e7811d7a65b57cd48","impliedFormat":1},{"version":"22ab05103d6c1b0c7e6fd0d35d0b9561f2931614c67c91ba55e2d60d741af1aa","impliedFormat":1},{"version":"aeebcee8599e95eb96cf15e1b0046024354cc32045f7e6ec03a74dcb235097ec","impliedFormat":1},{"version":"6813230ae8fba431d73a653d3de3ed2dcf3a4b2e965ca529a1d7fefdfd2bfc05","impliedFormat":1},{"version":"2111a7f02e31dd161d7c62537a24ddcbd17b8a8de7a88436cb55cd237a1098b2","impliedFormat":1},{"version":"dcac554319421fbc60da5f4401c4b4849ec0c92260e33a812cd8265a28b66a50","impliedFormat":1},{"version":"69e79a58498dbd57c42bc70c6e6096b782f4c53430e1dc329326da37a83f534d","impliedFormat":1},{"version":"6f327fc6d6ffcf68338708b36a8a2516090e8518542e20bb7217e2227842c851","impliedFormat":1},{"version":"5d770e4cc5df14482c7561e05b953865c2fdd5375c01d9d31e944b911308b13a","impliedFormat":1},{"version":"80ad25f193466f8945f41e0e97b012e1dafe1bd31b98f2d5c6c69a5a97504c75","impliedFormat":1},{"version":"30e75a9da9cd1ff426edcf88a73c6932e0ef26f8cbe61eed608e64e2ec511b6c","impliedFormat":1},{"version":"9ee91f8325ece4840e74d01b0f0e24a4c9b9ec90eeca698a6884b73c0151aa11","impliedFormat":1},{"version":"7c3d6e13ac7868d6ff1641406e535fde89ebef163f0c1237c5be21e705ed4a92","impliedFormat":1},{"version":"13f2f82a4570688610db179b0d178f1a038b17403b3a8c80eaa89dbdc74ddfd6","impliedFormat":1},{"version":"f805bae240625c8af6d84ac0b9e3cf43c5a3574c632e48a990bcec6de75234fb","impliedFormat":1},{"version":"fa3ce6af18df2e1d3adca877a3fe814393917b2f59452a405028d3c008726393","impliedFormat":1},{"version":"274b8ce7763b1a086a8821b68a82587f2cb1e08020920ae9ec8e28db0a88cd24","impliedFormat":1},{"version":"ea5e168745ac57b4ee29d953a42dc8252d3644ad3b6dab9d2f0c556f93ce05b4","impliedFormat":1},{"version":"830020b6fe24d742c1c3951e09b8b10401a0e753b5e659a3cbdea7f1348daeac","impliedFormat":1},{"version":"b1f68144e6659b378f0e02218f3bd8dfa71311c2e27814ab176365ed104d445a","impliedFormat":1},{"version":"a7a375e4436286bc6e68ce61d680ffeb431dc87f951f6c175547308d24d9d7ab","impliedFormat":1},{"version":"e41845dbc0909b2f555e7bcb1ebc55321982c446d58264485ca87e71bf7704a8","impliedFormat":1},{"version":"546291fd95c3a93e1fc0acd24350c95430d842898fc838d8df9ba40fdc653d6a","impliedFormat":1},{"version":"a6e898c90498c82f5d4fd59740cb6eb64412b39e12ffeca57851c44fa7700ed4","impliedFormat":1},{"version":"c8fb0d7a81dac8e68673279a3879bee6059bf667941694de802c06695f3a62a9","impliedFormat":1},{"version":"0a0a0bf13b17a7418578abea1ddb82bf83406f6e5e24f4f74b4ffbab9582321f","impliedFormat":1},{"version":"c4ea3ac40fbbd06739e8b681c45a4d40eb291c46407c04d17a375c4f4b99d72c","impliedFormat":1},{"version":"0f65b5f6688a530d965a8822609e3927e69e17d053c875c8b2ff2aecc3cd3bf6","impliedFormat":1},{"version":"443e39ba1fa1206345a8b5d0c41decfe703b7cdab02c52b220d1d3d8d675be6f","impliedFormat":1},{"version":"eaf7a238913b3f959db67fe7b3ea76cd1f2eedc5120c3ba45af8c76c5a3b70ad","impliedFormat":1},{"version":"8638625d1375bbb588f97a830684980b7b103d953c28efffa01bd5b1b5f775d2","impliedFormat":1},{"version":"ee77e7073de8ddc79acf0a3e8c1a1c4f6c3d11164e19eb725fa353ce936a93b0","impliedFormat":1},{"version":"ac39c31661d41f20ca8ef9c831c6962dc8bccbfca8ad4793325637c6f69207a3","impliedFormat":1},{"version":"80d98332b76035499ccce75a1526adcf4a9d455219f33f4b5a2e074e18f343fe","impliedFormat":1},{"version":"0490b6e27352ca7187944d738400e1e0ccb8ad8cc2fb6a939980cec527f4a3f9","impliedFormat":1},{"version":"7759aad02ab8c1499f2b689b9df97c08a33da2cb5001fbf6aed790aa41606f48","impliedFormat":1},{"version":"cb3c2b54a3eb8364f9078cfbe5a3340fa582b14965266c84336ab83fa933f3c7","impliedFormat":1},{"version":"7bc5668328a4a22c3824974628d76957332e653f42928354e5ac95f4cd00664d","impliedFormat":1},{"version":"b1905e68299346cc9ea9d156efb298d85cdb31a74cef5dbb39fda0ba677d8cfc","impliedFormat":1},{"version":"3ab80817857677b976b89c91cd700738fc623f5d0c800c5e1d08f21ac2a61f2a","impliedFormat":1},{"version":"cab9fb386ad8f6b439d1e125653e9113f82646712d5ba5b1b9fd1424aa31650c","impliedFormat":1},{"version":"20af956da2baefb99392218a474114007f8f6763f235ae7c6aae129e7d009cb6","impliedFormat":1},{"version":"6bfc9175ea3ade8c3dce6796456f106eb6ddc6ac446c41a71534a4cdce92777a","impliedFormat":1},{"version":"c8290d0b597260fd0e55016690b70823501170e8db01991785a43d7e1e18435f","impliedFormat":1},{"version":"002dfb1c48a9aa8de9d2cbe4d0b74edd85b9e0c1b77c865dcfcacd734c47dd40","impliedFormat":1},{"version":"17638e7a71f068c258a1502bd2c62cd6562e773c9c8649be283d924dc5d3bada","impliedFormat":1},{"version":"4b5e02a4d0b8f5ab0e81927c23b3533778000d6f8dfe0c2d23f93b55f0dcf62e","impliedFormat":1},{"version":"7bcdcafce502819733dc4e9fbbd97b2e392c29ae058bd44273941966314e46b1","impliedFormat":1},{"version":"39fefe9a886121c86979946858e5d28e801245c58f64f2ae4b79c01ffe858664","impliedFormat":1},{"version":"e68ec97e9e9340128260e57ef7d0d876a6b42d8873bfa1500ddead2bef28c71a","impliedFormat":1},{"version":"b944068d6efd24f3e064d341c63161297dc7a6ebe71fd033144891370b664e6d","impliedFormat":1},{"version":"9aee6c3a933af38de188f46937bdc5f875e10b016136c4709a3df6a8ce7ce01d","impliedFormat":1},{"version":"c0f4cd570839560ba29091ce66e35147908526f429fcc1a4f7c895a79bbbc902","impliedFormat":1},{"version":"3d44d824b1d25e86fb24a1be0c2b4d102b14740e8f10d9f3a320a4c863d0acad","impliedFormat":1},{"version":"f80511b23e419a4ba794d3c5dadea7f17c86934fa7a9ac118adc71b01ad290e3","impliedFormat":1},{"version":"633eabeec387c19b9ad140a1254448928804887581e2f0460f991edb2b37f231","impliedFormat":1},{"version":"f7083bbe258f85d7b7b8524dd12e0c3ee8af56a43e72111c568c9912453173a6","impliedFormat":1},{"version":"067a32d6f333784d2aff45019e36d0fc96fff17931bb2813b9108f6d54a6f247","impliedFormat":1},{"version":"0c85a6e84e5e646a3e473d18f7cd8b3373b30d3b3080394faee8997ad50c0457","impliedFormat":1},{"version":"f554099b0cfd1002cbacf24969437fabec98d717756344734fbae48fb454b799","impliedFormat":1},{"version":"1c39be289d87da293d21110f82a31139d5c6030e7a738bdf6eb835b304664fdd","impliedFormat":1},{"version":"5e9da3344309ac5aa7b64276ea17820de87695e533c177f690a66d9219f78a1e","impliedFormat":1},{"version":"1d4258f658eda95ee39cd978a00299d8161c4fef8e3ceb9d5221dac0d7798242","impliedFormat":1},{"version":"7df3bac8f280e1a3366ecf6e7688b7f9bbc1a652eb6ad8c62c3690cc444932e3","impliedFormat":1},{"version":"816c71bf50425c02608c516df18dfcb2ed0fca6baef0dbb30931c4b93fb6ab28","impliedFormat":1},{"version":"a32e227cdf4c5338506e23f71d5464e892416ef6f936bafa911000f98b4f6285","impliedFormat":1},{"version":"215474b938cc87665c20fe984755e5d6857374627953428c783d0456149c4bda","impliedFormat":1},{"version":"6b4915d3c74438a424e04cd4645b13b8b74733d6da8e9403f90e2c2775501f49","impliedFormat":1},{"version":"780c26fecbc481a3ef0009349147859b8bd22df6947990d4563626a38b9598b8","impliedFormat":1},{"version":"41a87a15fdf586ff0815281cccfb87c5f8a47d0d5913eed6a3504dc28e60d588","impliedFormat":1},{"version":"0973d91f2e6c5e62a642685913f03ab9cb314f7090db789f2ed22c3df2117273","impliedFormat":1},{"version":"082b8f847d1e765685159f8fe4e7812850c30ab9c6bd59d3b032c2c8be172e29","impliedFormat":1},{"version":"63033aacc38308d6a07919ef6d5a2a62073f2c4eb9cd84d535cdb7a0ab986278","impliedFormat":1},{"version":"f30f24d34853a57aed37ad873cbabf07b93aff2d29a0dd2466649127f2a905ff","impliedFormat":1},{"version":"1828d9ea4868ea824046076bde3adfd5325d30c4749835379a731b74e1388c2a","impliedFormat":1},{"version":"4ac7ee4f70260e796b7a58e8ea394df1eaa932cdaf778aa54ef412d9b17fe51a","impliedFormat":1},{"version":"9ddbe84084a2b5a20dd14ca2c78b5a1f86a328662b11d506b9f22963415e7e8d","impliedFormat":1},{"version":"871e5cd964fafda0cd5736e757ba6f2465fd0f08b9ae27b08d0913ea9b18bea1","impliedFormat":1},{"version":"95b61511b685d6510b15c6f2f200d436161d462d768a7d61082bfba4a6b21f24","impliedFormat":1},{"version":"3a0f071c1c982b7a7e5f9aaea73791665b865f830b1ea7be795bc0d1fb11a65e","impliedFormat":1},{"version":"6fcdac5e4f572c04b1b9ff5d4dace84e7b0dcccf3d12f4f08d296db34c2c6ea7","impliedFormat":1},{"version":"04381d40188f648371f9583e3f72a466e36e940bd03c21e0fcf96c59170032f8","impliedFormat":1},{"version":"5b249815b2ab6fdfe06b99dc1b2a939065d6c08c6acf83f2f51983a2deabebce","impliedFormat":1},{"version":"93333bd511c70dc88cc8a458ee781b48d72f468a755fd2090d73f6998197d6d4","impliedFormat":1},{"version":"1f64a238917b7e245930c4d32d708703dcbd8997487c726fcbadaa706ebd45dc","impliedFormat":1},{"version":"17d463fd5e7535eecc4f4a8fd65f7b25b820959e918d1b7478178115b4878de0","impliedFormat":1},{"version":"10d5b512f0eeab3e815a58758d40abe1979b420b463f69e8acccbb8b8d6ef376","impliedFormat":1},{"version":"e3c6af799b71db2de29cf7513ec58d179af51c7aef539968b057b43f5830da06","impliedFormat":1},{"version":"fbd151883aa8bb8c7ea9c5d0a323662662e026419e335a0c3bd53772bd767ec5","impliedFormat":1},{"version":"7b55d29011568662da4e570f3a87f61b8238024bc82f5c14ae7a7d977dbd42b6","impliedFormat":1},{"version":"1a693131491bf438a4b2f5303f4c5e1761973ca20b224e5e9dcd4db77c45f09b","impliedFormat":1},{"version":"09181ba5e7efec5094c82be1eb7914a8fc81780d7e77f365812182307745d94f","impliedFormat":1},{"version":"fb5a59f40321ec0c04a23faa9cf0a0640e8b5de7f91408fb2ecaaec34d6b9caf","impliedFormat":1},{"version":"0e2578d08d1c0139ba788d05ef1a62aa50373e0540fd1cad3b1c0a0c13107362","impliedFormat":1},{"version":"65f22fbb80df4ffdd06b9616ec27887d25b30fd346d971ced3ab6e35d459e201","impliedFormat":1},{"version":"adf56fbfbd48d96ff2525dae160ad28bcb304d2145d23c19f7c5ba0d28d1c0cf","impliedFormat":1},{"version":"e972d127886b4ba51a40ef3fa3864f744645a7eaeb4452cb23a4895ccde4943e","impliedFormat":1},{"version":"5af6ea9946b587557f4d164a2c937bb3b383211fef5d5fd33980dc5b91d31927","impliedFormat":1},{"version":"bffa47537197a5462836b3bb95f567236fa144752f4b09c9fa53b2bf0ac4e39a","impliedFormat":1},{"version":"76e485bb46a79126e76c8c40487497f5831c5faa8d990a31182ad5bf9487409c","impliedFormat":1},{"version":"34c367f253d9f9f247a4d0af9c3cfcfaabb900e24db79917704cd2d48375d74c","impliedFormat":1},{"version":"1b7b16cceca67082cd6f10eeaf1845514def524c2bc293498ba491009b678df3","impliedFormat":1},{"version":"81ad399f8c6e85270b05682461ea97e3c3138f7233d81ddbe4010b09e485fce0","impliedFormat":1},{"version":"8baaf66fecb2a385e480f785a8509ac3723c1061ca3d038b80828e672891cccf","impliedFormat":1},{"version":"6ed1f646454dff5d7e5ce7bc5e9234d4e2b956a7573ef0d9b664412e0d82b83e","impliedFormat":1},{"version":"6777b3a04a9ff554b3e20c4cb106b8eb974caad374a3d2651d138f7166202f59","impliedFormat":1},{"version":"cc2a85161dab1f8b55134792706ecf2cf2813ad248048e6495f72e74ecb2462c","impliedFormat":1},{"version":"c994de814eca4580bfad6aeec3cbe0d5d910ae7a455ff2823b2d6dce1bbb1b46","impliedFormat":1},{"version":"a8fdd65c83f0a8bdfe393cf30b7596968ba2b6db83236332649817810cc095b6","impliedFormat":1},{"version":"2cc71c110752712ff13cea7fb5d9af9f5b8cfd6c1b299533eeaf200d870c25db","impliedFormat":1},{"version":"07047dd47ed22aec9867d241eed00bccb19a4de4a9e309c2d4c1efb03152722f","impliedFormat":1},{"version":"ce8f3cd9fd2507d87d944d8cdb2ba970359ea74821798eee65fd20e76877d204","impliedFormat":1},{"version":"5e63289e02fb09d73791ae06e9a36bf8e9b8b7471485f6169a2103cb57272803","impliedFormat":1},{"version":"16496edeb3f8f0358f2a9460202d7b841488b7b8f2049a294afcba8b1fce98f7","impliedFormat":1},{"version":"5f4931a81fac0f2f5b99f97936eb7a93e6286367b0991957ccd2aa0a86ce67e8","impliedFormat":1},{"version":"0c81c0048b48ba7b579b09ea739848f11582a6002f00c66fde4920c436754511","impliedFormat":1},{"version":"2a9efc08880e301d05e31f876eb43feb4f96fa409ec91cd0f454afddbedade99","impliedFormat":1},{"version":"8b84db0f190e26aeed913f2b6f7e6ec43fb7aeec40bf7447404db696bb10a1aa","impliedFormat":1},{"version":"3faa4463234d22b90d546925c128ad8e02b614227fb4bceb491f4169426a6496","impliedFormat":1},{"version":"83dc14a31138985c30d2b8bdf6b2510f17d9c1cd567f7aadd4cbfd793bd320b8","impliedFormat":1},{"version":"4c21526acf3a205b96962c5e0dc8fa73adbce05dd66a5b3960e71527f0fb8022","impliedFormat":1},{"version":"8de35ab4fcd11681a8a7dae4c4c25a1c98e9f66fbd597998ca3cea58012801a8","impliedFormat":1},{"version":"40a50581f3fa685fda5bbd869f6951272e64ccb973a07d75a6babf5ad8a7ec51","impliedFormat":1},{"version":"5575fd41771e3ff65a19744105d7fed575d45f9a570a64e3f1357fe47180e2a2","impliedFormat":1},{"version":"ea94b0150a7529c409871f6143436ead5939187d0c4ec1c15e0363468c1025cc","impliedFormat":1},{"version":"b8deddcf64481b14aa88489617e5708fcb64d4f64db914f10abbd755c8deb548","impliedFormat":1},{"version":"e2e932518d27e7c23070a8bbd6f367102a00107b7efdd4101c9906ac2c52c3f3","impliedFormat":1},{"version":"1a1a8889de2d1c898d4e786b8edf97a33b8778c2bb81f79bcf8b9446b01663dd","impliedFormat":1},{"version":"bb66806363baa6551bd61dd79941a3f620f64d4166148be8c708bf6f998c980b","impliedFormat":1},{"version":"23b58237fc8fbbcb111e7eb10e487303f5614e0e8715ec2a90d2f3a21fd1b1c0","impliedFormat":1},{"version":"c63bb5b72efbb8557fb731dc72705f1470284093652eca986621c392d6d273ab","impliedFormat":1},{"version":"9495b9e35a57c9bfec88bfb56d3d5995d32b681317449ad2f7d9f6fc72877fd0","impliedFormat":1},{"version":"8974fe4b0f39020e105e3f70ab8375a179896410c0b55ca87c6671e84dec6887","impliedFormat":1},{"version":"7f76d6eef38a5e8c7e59c7620b4b99205905f855f7481cb36a18b4fdef58926d","impliedFormat":1},{"version":"a74437aba4dd5f607ea08d9988146cee831b05e2d62942f85a04d5ad89d1a57a","impliedFormat":1},{"version":"65faea365a560d6cadac8dbf33953474ea5e1ef20ee3d8ff71f016b8d1d8eb7c","impliedFormat":1},{"version":"1d30c65c095214469a2cfa1fd40e881f8943d20352a5933aa1ed96e53118ca7e","impliedFormat":1},{"version":"342e05e460b6d55bfbbe2cf832a169d9987162535b4127c9f21eaf9b4d06578b","impliedFormat":1},{"version":"8bfced5b1cd8441ba225c7cbb2a85557f1cc49449051f0f71843bbb34399bbea","impliedFormat":1},{"version":"9388132f0cb90e5f0a44a5255f4293b384c6a79b0c9206249b3bcf49ff988659","impliedFormat":1},{"version":"a7e8f748de2465278f4698fe8656dd1891e49f9f81e719d6fc3eaf53b4df87ce","impliedFormat":1},{"version":"1ef1dcd20772be36891fd4038ad11c8e644fe91df42e4ccdbc5a5a4d0cfddf13","impliedFormat":1},{"version":"3e77ee3d425a8d762c12bb85fe879d7bc93a0a7ea2030f104653c631807c5b2e","impliedFormat":1},{"version":"e76004b4d4ce5ad970862190c3ef3ab96e8c4db211b0e680e55a61950183ff16","impliedFormat":1},{"version":"b959e66e49bfb7ff4ce79e73411ebc686e3c66b6b51bf7b3f369cc06814095f7","impliedFormat":1},{"version":"3e39e5b385a2e15183fc01c1f1d388beca6f56cd1259d3fe7c3024304b5fd7aa","impliedFormat":1},{"version":"3a4560b216670712294747d0bb4e6b391ca49271628514a1fe57d455258803db","impliedFormat":1},{"version":"f9458d81561e721f66bd4d91fb2d4351d6116e0f36c41459ad68fdbb0db30e0a","impliedFormat":1},{"version":"c7d36ae7ed49be7463825d42216648d2fb71831b48eb191bea324717ba0a7e59","impliedFormat":1},{"version":"5a1ae4a5e568072f2e45c2eed8bd9b9fceeb20b94e21fb3b1cec8b937ea56540","impliedFormat":1},{"version":"acbbea204ba808da0806b92039c87ae46f08c7277f9a32bf691c174cb791ddff","impliedFormat":1},{"version":"055489a2a42b6ece1cb9666e3d68de3b52ed95c7f6d02be3069cc3a6c84c428c","impliedFormat":1},{"version":"3038efd75c0661c7b3ff41d901447711c1363ef4aef4485f374847a8a2fcb921","impliedFormat":1},{"version":"0022901e655f49011384f960d6b67c5d225e84e2ea66aa4aae1576974a4e9b40","impliedFormat":1},{"version":"0022901e655f49011384f960d6b67c5d225e84e2ea66aa4aae1576974a4e9b40","impliedFormat":1},{"version":"9d2106024e848eccaeaa6bd9e0fd78742a0c542f2fbc8e3bb3ab29e88ece73a9","impliedFormat":1},{"version":"668a9d5803e4afcd23cd0a930886afdf161faa004f533e47a3c9508218df7ecd","impliedFormat":1},{"version":"dd769708426135f5f07cd5e218ac43bf5bcf03473c7cbf35f507e291c27161e7","impliedFormat":1},{"version":"6067f7620f896d6acb874d5cc2c4a97f1aa89d42b89bd597d6d640d947daefb8","impliedFormat":1},{"version":"8fd3454aaa1b0e0697667729d7c653076cf079180ef93f5515aabc012063e2c1","impliedFormat":1},{"version":"f13786f9349b7afc35d82e287c68fa9b298beb1be24daa100e1f346e213ca870","impliedFormat":1},{"version":"5e9f0e652f497c3b96749ed3e481d6fab67a3131f9de0a5ff01404b793799de4","impliedFormat":1},{"version":"1ad85c92299611b7cd621c9968b6346909bc571ea0135a3f2c7d0df04858c942","impliedFormat":1},{"version":"08ef30c7a3064a4296471363d4306337b044839b5d8c793db77d3b8beefbce5d","impliedFormat":1},{"version":"b700f2b2a2083253b82da74e01cac2aa9efd42ba3b3041b825f91f467fa1e532","impliedFormat":1},{"version":"0edbad572cdd86ec40e1f27f3a337b82574a8b1df277a466a4e83a90a2d62e76","impliedFormat":1},{"version":"cc2930e8215efe63048efb7ff3954df91eca64eab6bb596740dceb1ad959b9d4","impliedFormat":1},{"version":"1cf8615b4f02bbabb030a656aa1c7b7619b30da7a07d57e49b6e1f7864df995f","impliedFormat":1},{"version":"2cbd0adfb60e3fed2667e738eba35d9312ab61c46dbc6700a8babed2266ddcf2","impliedFormat":1},{"version":"bed2e48fefb5a30e82f176e79c8bd95d59915d3ae19f68e8e6f3a6df3719503f","impliedFormat":1},{"version":"032a6c17ee79d48039e97e8edb242fe2bd4fc86d53307a10248c2eda47dbd11d","impliedFormat":1},{"version":"83b28226a0b5697872ea7db24c4a1de91bbf046815b81deaa572b960a189702a","impliedFormat":1},{"version":"8c08bc40a514c6730c5e13e065905e9da7346a09d314d09acc832a6c4da73192","impliedFormat":1},{"version":"b95a07e367ec719ecc96922d863ab13cce18a35dde3400194ba2c4baccfafdc0","impliedFormat":1},{"version":"36e86973743ca5b4c8a08633ef077baf9ba47038002b8bbe1ac0a54a3554c53e","impliedFormat":1},{"version":"b8c19863be74de48ff0b5d806d3b51dc51c80bcf78902a828eb27c260b64e9f1","impliedFormat":1},{"version":"3555db94117fb741753ef5c37ffdb79f1b3e64e9f24652eecb5f00f1e0b1941c","impliedFormat":1},{"version":"52b3bc9c614a193402af641bee64a85783cd2988a46a09bdfe4bddd33410d1b8","impliedFormat":1},{"version":"52b3bc9c614a193402af641bee64a85783cd2988a46a09bdfe4bddd33410d1b8","impliedFormat":1},{"version":"deb25b0ec046c31b288ad7f4942c83ad29e5e10374bdb8af9a01e669df33d59d","impliedFormat":1},{"version":"deb25b0ec046c31b288ad7f4942c83ad29e5e10374bdb8af9a01e669df33d59d","impliedFormat":1},{"version":"a3eb808480fe13c0466917415aa067f695c102b00df00c4996525f1c9e847e4f","impliedFormat":1},{"version":"5d5e54ce407a53ac52fd481f08c29695a3d38f776fc5349ab69976d007b3198e","impliedFormat":1},{"version":"6f796d66834f2c70dd13cfd7c4746327754a806169505c7b21845f3d1cabd80a","impliedFormat":1},{"version":"bde869609f3f4f88d949dc94b55b6f44955a17b8b0c582cdef8113e0015523fa","impliedFormat":1},{"version":"9c16e682b23a335013941640433544800c225dc8ad4be7c0c74be357482603d5","impliedFormat":1},{"version":"622abbfd1bb206b8ea1131bb379ec1f0d7e9047eddefcfbe104e235bfc084926","impliedFormat":1},{"version":"3e5f94b435e7a57e4c176a9dc613cd4fb8fad9a647d69a3e9b77d469cdcdd611","impliedFormat":1},{"version":"f00c110b9e44555c0add02ccd23d2773e0208e8ceb8e124b10888be27473872d","impliedFormat":1},{"version":"0be282634869c94b20838acba1ac7b7fee09762dbed938bf8de7a264ba7c6856","impliedFormat":1},{"version":"a640827fd747f949c3e519742d15976d07da5e4d4ce6c2213f8e0dac12e9be6c","impliedFormat":1},{"version":"56dee4cdfa23843048dc72c3d86868bf81279dbf5acf917497e9f14f999de091","impliedFormat":1},{"version":"7890136a58cd9a38ac4d554830c6afd3a3fbff65a92d39ab9d1ef9ab9148c966","impliedFormat":1},{"version":"9ebd2b45f52de301defb043b3a09ee0dd698fc5867e539955a0174810b5bdf75","impliedFormat":1},{"version":"cbad726f60c617d0e5acb13aa12c34a42dc272889ac1e29b8cb2ae142c5257b5","impliedFormat":1},{"version":"009022c683276077897955237ca6cb866a2dfa2fe4c47fadcf9106bc9f393ae4","impliedFormat":1},{"version":"b03e6b5f2218fd844b35e2b6669541c8ad59066e1427f4f29b061f98b79aceeb","impliedFormat":1},{"version":"8451b7c29351c3be99ec247186bb17c8bde43871568488d8eb2739acab645635","impliedFormat":1},{"version":"2c2e64c339be849033f557267e98bd5130d9cb16d0dccada07048b03ac9bbc79","impliedFormat":1},{"version":"39c6cc52fed82f7208a47737a262916fbe0d9883d92556bd586559c94ef03486","impliedFormat":1},{"version":"5c467e74171c2d82381bb9c975a5d4b9185c78006c3f5da03e368ea8c1c3a32e","impliedFormat":1},{"version":"ef1e298d4ff9312d023336e6089a93ee1a35d7846be90b5f874ddd478185eac6","impliedFormat":1},{"version":"d829e88b60117a6bc2ca644f25b6f8bbaa40fc8998217536dbbbfd760677ae60","impliedFormat":1},{"version":"e922987ed23d56084ec8cce2d677352355b4afb372a4c7e36f6e507995811c43","impliedFormat":1},{"version":"9cca233ee9942aaafcf19a8d1f2929fed21299d836f489623c9abfb157b8cd87","impliedFormat":1},{"version":"0dc1aac5e460ea012fe8c67d885e875dbdc5bf38d6cb9addf3f2a0cc3558a670","impliedFormat":1},{"version":"1e350495bd8b33f251c59539c7aef25287ea4907feb08dab5651b78a989a2e6a","impliedFormat":1},{"version":"1e350495bd8b33f251c59539c7aef25287ea4907feb08dab5651b78a989a2e6a","impliedFormat":1},{"version":"4181ed429a8aac8124ea36bfc716d9360f49374eb36f1cc8872dcbbf545969eb","impliedFormat":1},{"version":"948b77bdc160db8025bf63cc0e53661f27c5c5244165505cc48024a388a9f003","impliedFormat":1},{"version":"b3ae4b9b7ec83e0630ce00728a9db6c8bb7909c59608d48cded3534d8ed8fa47","impliedFormat":1},{"version":"c2fa2cba39fcabec0be6d2163b8bc76d78ebe45972a098cca404b1a853aa5184","impliedFormat":1},{"version":"f98232fe7507f6c70831a27ddd5b4d759d6c17c948ed6635247a373b3cfee79e","impliedFormat":1},{"version":"61db0df9acc950cc1ac82897e6f24b6ab077f374059a37f9973bf5f2848cfa56","impliedFormat":1},{"version":"c185ceb3a4cd31153e213375f175e7b3f44f8c848f73faf8338a03fffb17f12b","impliedFormat":1},{"version":"bfa04fde894ce3277a5e99b3a8bec59f49dde8caaaa7fb69d2b72080b56aedbd","impliedFormat":1},{"version":"f4405ec08057cd8002910f210922de51c9273f577f456381aeb8671b678653c9","impliedFormat":1},{"version":"631f50cc97049c071368bf25e269380fad54314ce67722072d78219bff768e92","impliedFormat":1},{"version":"c88a192e6d7ec5545ad530112a595c34b2181acd91b2873f40135a0a2547b779","impliedFormat":1},{"version":"ddcb839b5b893c67e9cc75eacf49b2d4425518cfe0e9ebc818f558505c085f47","impliedFormat":1},{"version":"d962bdaac968c264a4fe36e6a4f658606a541c82a4a33fe3506e2c3511d3e40a","impliedFormat":1},{"version":"549daccede3355c1ed522e733f7ab19a458b3b11fb8055761b01df072584130a","impliedFormat":1},{"version":"2852612c7ca733311fe9443e38417fab3618d1aac9ba414ad32d0c7eced70005","impliedFormat":1},{"version":"f86a58fa606fec7ee8e2a079f6ff68b44b6ea68042eb4a8f5241a77116fbd166","impliedFormat":1},{"version":"434b612696740efb83d03dd244cb3426425cf9902f805f329b5ff66a91125f29","impliedFormat":1},{"version":"e6edb14c8330ab18bdd8d6f7110e6ff60e5d0a463aac2af32630d311dd5c1600","impliedFormat":1},{"version":"f5e8edbedcf04f12df6d55dc839c389c37740aa3acaa88b4fd9741402f155934","impliedFormat":1},{"version":"794d44962d68ae737d5fc8607c4c8447955fc953f99e9e0629cac557e4baf215","impliedFormat":1},{"version":"8d1fd96e52bc5e5b3b8d638a23060ef53f4c4f9e9e752aba64e1982fae5585fa","impliedFormat":1},{"version":"4881c78bd0526b6e865fcf38e174014645e098ac115cacd46b40be01ac85f384","impliedFormat":1},{"version":"56e5e78ff2acc23ad1524fc50579780bc2a9058024793f7674ec834759efc9de","impliedFormat":1},{"version":"13b9d386e5ee49b2f5caff5e7ed25b99135610dcda45638027c5a194cc463e27","impliedFormat":1},{"version":"631634948d2178785c3a707d5567ae0250a75bf531439381492fc26ef57d6e7f","impliedFormat":1},{"version":"1058b9b3ba92dd408e70dd8ea75cdde72557204a8224f29a6e4a8e8354da9773","impliedFormat":1},{"version":"997c112040764089156e67bab2b847d09af823cc494fe09e429cef375ef03af9","impliedFormat":1},{"version":"9ddf7550e43329fa373a0694316ddc3d423ae9bffa93d84b7b3bb66cf821dfae","impliedFormat":1},{"version":"fdb2517484c7860d404ba1adb1e97a82e890ba0941f50a850f1f4e34cfd6b735","impliedFormat":1},{"version":"5116b61c4784252a73847f6216fdbff5afa03faaab5ff110d9d7812dff5ddc3f","impliedFormat":1},{"version":"f68c1ecd47627db8041410fcb35b5327220b3b35287d2a3fcca9bf4274761e69","impliedFormat":1},{"version":"9d1726afaf9e34a7f31f3be543710d37b1854f40f635e351a63d47a74ceef774","impliedFormat":1},{"version":"a3a805ec9621188f85f9d3dda03b87b47cd31a92b76d2732eba540cc2af9612d","impliedFormat":1},{"version":"0f9e65ffa38ea63a48cf29eb6702bb4864238989628e039a08d2d7588be4ab15","impliedFormat":1},{"version":"3993a8d6d3068092ed74bb31715d4e1321bf0bbb094db0005e8aa2f7fbab0f93","impliedFormat":1},{"version":"bcc3756f063548f340191869980e14ded6d5cb030b3308875f9e6e0ce52071ed","impliedFormat":1},{"version":"7da3fcacec0dc6c8067601e3f2c39662827d7011ea06b61e06af2d253b55a363","impliedFormat":1},{"version":"d101d3030fb8b29ed44f999d0d03e5ec532f908c58fefb26c4ecd248fe8819c5","impliedFormat":1},{"version":"2898bf44723a97450bf234b9208bce7c524d1e7735a1396d9aabcba0a3f48896","impliedFormat":1},{"version":"3f04902889a4eb04ef34da100820d21b53a0327e9e4a6ef63cd6a9682538dc6f","impliedFormat":1},{"version":"67b0df47d30dad3449ba62d2f4e9c382ee25cb509540eb536ded3f59fb3fdf41","impliedFormat":1},{"version":"526e0604ed8cf5ec53d629c168013d99f06c0673108281e676053f04ee3afc6d","impliedFormat":1},{"version":"79f84d0bccc2f08c62a74cc4fcf445f996ef637579191edfc8c7c5bf351d4bd2","impliedFormat":1},{"version":"26694ee75957b55b34e637e9752742c6eee761155e8b87f8cdec335aee598da4","impliedFormat":1},{"version":"017b4f63bafe1e29d69dc2fecc5c3e1f119e8aa8e3c7a0e82c2f5b572dbc8969","impliedFormat":1},{"version":"74faaea9ae62eea1299cc853c34404ac2113117624060b6f89280f3bc5ed27de","impliedFormat":1},{"version":"3b114825464c5cafc64ffd133b5485aec7df022ec771cc5d985e1c2d03e9b772","impliedFormat":1},{"version":"c6711470bc8e21805a45681f432bf3916e735e167274e788120bcef2a639ebef","impliedFormat":1},{"version":"ad379db2a69abb28bb8aaf09679d24ac59a10b12b1b76d1201a75c51817a3b7c","impliedFormat":1},{"version":"3be0897930eb5a7ce6995bc03fa29ff0a245915975a1ad0b9285cfaa3834c370","impliedFormat":1},{"version":"0d6cf8d44b6c42cd9cd209a966725c5f06956b3c8b653ba395c5a142e96a7b80","impliedFormat":1},{"version":"0242e0818acc4d6b9da05da236279b1d6192f929959ebbd41f2fc899af504449","impliedFormat":1},{"version":"dbf3580e00ea32ec07da17de068f8f9aa63ad02e225bc51057466f1dfed18c32","impliedFormat":1},{"version":"e87ad82343dae2a5183ef77ab7c25e2ac086f0359850af8bfaf31195fb51bebe","impliedFormat":1},{"version":"0659ac04895ce1bfb7231fe37361e628f616eb48336dad0182860c21c8731564","impliedFormat":1},{"version":"627ec421b4dfad81f9f8fcbfe8e063edc2f3b77e7a84f9956583bdd9f9792683","impliedFormat":1},{"version":"d428bae78f42e0a022ca13ad4cdf83cc215357841338c8d4d20a78e100069c49","impliedFormat":1},{"version":"4843347a4d4fc2ebbdf8a1f3c2c5dc66a368271c4bddc0b80032ed849f87d418","impliedFormat":1},{"version":"3e05200e625222d97cf21f15793524b64a8f9d852e1490c4d4f1565a2f61dc4d","impliedFormat":1},{"version":"5d367e88114f344516c440a41c89f6efb85adb953b8cc1174e392c44b2ac06b6","impliedFormat":1},{"version":"22dc8f5847b8642e75b847ba174c24f61068d6ad77db8f0c23f4e46febdb36bb","impliedFormat":1},{"version":"7350c18dd0c7133c8d2ec272b1aa10784a801104d28669efc90071564750da6d","impliedFormat":1},{"version":"45bd73d4cb89c3fb2003257a4579cbce04c01a19b01fda4b5f1a819bcea71a2e","impliedFormat":1},{"version":"6684e81b54855f813639599aa847578f51c78b9933ff7eee306b6ce1b178bc0c","impliedFormat":1},{"version":"36ecc67bce3e36e22ea8af1a17c3bfade5bf1119fb87190f47366a678e823129","impliedFormat":1},{"version":"dbcc536b6bc9365e611989560eb30b81a07140602a9db632cc4761c66228b001","impliedFormat":1},{"version":"cb0b26b99104ec6b125c364fe81991b1e4fb7acdcb0315fff04a1f0c939d5e5d","impliedFormat":1},{"version":"e77adac69fbf0785ad1624a1dbaf02794877f38d75c095facd150bfef9cb0cc5","impliedFormat":1},{"version":"44710cf3db1cc8d826e242d2e251aff0d007fd9736a77d449fbe82b15a931919","impliedFormat":1},{"version":"44710cf3db1cc8d826e242d2e251aff0d007fd9736a77d449fbe82b15a931919","impliedFormat":1},{"version":"0d216597eed091e23091571e8df74ed2cb2813f0c8c2ce6003396a0e2e2ea07d","impliedFormat":1},{"version":"b6a0d16f4580faa215e0f0a6811bdc8403306a306637fc6cc6b47bf7e680dcca","impliedFormat":1},{"version":"9b4b8072aac21a792a2833eb803e6d49fd84043c0fd4996aa8d931c537fe3a36","impliedFormat":1},{"version":"9b4b8072aac21a792a2833eb803e6d49fd84043c0fd4996aa8d931c537fe3a36","impliedFormat":1},{"version":"67bcfdec85f9c235e7feb6faa04e312418e7997cd7341b524fb8d850c5b02888","impliedFormat":1},{"version":"519f452d81a2890c468cca90b9b285742b303a9b9fd1f88f264bb3dda4549430","impliedFormat":1},{"version":"519f452d81a2890c468cca90b9b285742b303a9b9fd1f88f264bb3dda4549430","impliedFormat":1},{"version":"d58d25fa1c781a2e5671e508223bf10a3faf0cde1105bc3f576adf2c31dd8289","impliedFormat":1},{"version":"376bc1793d293b7cd871fe58b7e58c65762db6144524cb022ffc2ced7fcc5d86","impliedFormat":1},{"version":"40bd62bd598ec259b1fa17cf9874618efe892fa3c009a228cb04a792cce425c8","impliedFormat":1},{"version":"8f5ac4753bd52889a1fa42edefab3860a07f198d67b6b7d8ac781f0d8938667b","impliedFormat":1},{"version":"962287ca67eb84fe22656190668a49b3f0f9202ec3bc590b103a249dca296acf","impliedFormat":1},{"version":"3dab1e83f2adb7547c95e0eec0143c4d6c28736490e78015ac50ca0e66e02cb0","impliedFormat":1},{"version":"7f0cfb5861870e909cc45778f5e22a4a1e9ecdec34c31e9d5232e691dd1370c8","impliedFormat":1},{"version":"8c645a4aa022e976b9cedd711b995bcff088ea3f0fb7bc81dcc568f810e3c77a","impliedFormat":1},{"version":"4cc2d393cffad281983daaf1a3022f3c3d36f5c6650325d02286b245705c4de3","impliedFormat":1},{"version":"f0913fc03a814cebb1ca50666fce2c43ef9455d73b838c8951123a8d85f41348","impliedFormat":1},{"version":"a8cfdf77b5434eff8b88b80ccefa27356d65c4e23456e3dd800106c45af07c3c","impliedFormat":1},{"version":"494fdf98dfa2d19b87d99812056417c7649b6c7da377b8e4f6e4e5de0591df1d","impliedFormat":1},{"version":"989034200895a6eaae08b5fd0e0336c91f95197d2975800fc8029df9556103c4","impliedFormat":1},{"version":"0ac4c61bb4d3668436aa3cd54fb82824d689ad42a05da3acb0ca1d9247a24179","impliedFormat":1},{"version":"c889405864afce2e14f1cffd72c0fccddcc3c2371e0a6b894381cc6b292c3d32","impliedFormat":1},{"version":"6d728524e535acd4d13e04d233fb2e4e1ef2793ffa94f6d513550c2567d6d4b4","impliedFormat":1},{"version":"14d6af39980aff7455152e2ebb5eb0ab4841e9c65a9b4297693153695f8610d5","impliedFormat":1},{"version":"44944d3b25469e4c910a9b3b5502b336f021a2f9fe67dd69d33afc30b64133b3","impliedFormat":1},{"version":"7aa71d2fa9dfb6e40bdd2cfa97e9152f4b2bd4898e677a9b9aeb7d703f1ca9ad","impliedFormat":1},{"version":"1f03bc3ba45c2ddca3a335532e2d2d133039f4648f2a1126ff2d03fb410be5dd","impliedFormat":1},{"version":"8b6fadc7df773879c30c0f954a11ec59e9b7430d50823c6bfb36fcc67b59eb42","impliedFormat":1},{"version":"689cb95de8ea23df837129d80a0037fe6fbadba25042199d9bb0c9366ace83b7","impliedFormat":1},{"version":"eeb6c806376b9c3464f29b6058aecf113328f9ce290af0375e520f1a844529cf","signature":"61f11ef9f7b473f14c872a139f0a329251738f177edfdcaefb3745adf8967036"},{"version":"5128c2a5fb4f7ed3fbc1941daf38be2f46d4d254602742f9082764730d2b10f8","signature":"11ef15e6c437548d908fba2917027940aebd6d68599d4e848dd559f1a8b2c8b2"},{"version":"88cdbc3bcb4689a70130597de7c941b2450bb2760674a02ae816e0667a1958f6","signature":"6dbaf13dab6dc2db0cb7312fba7996ca7f548c7929bb627315cc89b43bf93ada"},{"version":"42b8fa71b5a9f74f951ff7dc8e56f2bdd153828422806d3448cc4befae1099a4","signature":"ca5fc69e2b35182c5f563ad51094b9d8b3653d7d86beba04cb2cb9985518930f"},{"version":"a0e10a6c87615a51591fd15b00963a22edd4dcda8be288ace7ebb5db222f407d","signature":"a5f536a284039fe580605b7548450a47d57b79036a51bbd7387d8889f406276d"},{"version":"30af16a8cc19021a7a377c1a600de0a200f8b9cfcbe2515ae94b53bb7a36b6db","signature":"646d3971a94a1d0471f10da8b101d27e1c7f67d1da535a2073edf0cfad37af47"},{"version":"d30f58d5da7d9543d1c4dfcc60d965d77d6dd111754c6b7df1c11a2933343f4e","signature":"2656fde6a296703ec7723f65410d7e8a8401375f27cdd265ed72620d4667b1a9"},{"version":"e648feddd4cbf2b1b0d9c7dd78b3a29e9d8871051f564e23834448a96ba79105","signature":"98fd6a4b59f7488478546bf7731cd1a7e77ed2b2a9fb9b1b87410cfc07febca7"},{"version":"93c88804801702c2ebf4d7e282ff71d90f118253ee206e7f0ba03305cc581546","signature":"0a7f51c3fb4b7c9a30745a92c15a4cb4eb88aa3ea69dec8f6286491fdfb99dab"},{"version":"447809300abe6967b66fe4226b18bcd8513258b0139a8fd1ac516767bc510372","signature":"6d31cb09b5e87e0588c937c73aff7673a15865e355903fe16ac3bdcb3d2894d6"},{"version":"ae77d81a5541a8abb938a0efedf9ac4bea36fb3a24cc28cfa11c598863aba571","impliedFormat":1},{"version":"3cfb7c0c642b19fb75132154040bb7cd840f0002f9955b14154e69611b9b3f81","impliedFormat":1},{"version":"8387ec1601cf6b8948672537cf8d430431ba0d87b1f9537b4597c1ab8d3ade5b","impliedFormat":1},{"version":"d16f1c460b1ca9158e030fdf3641e1de11135e0c7169d3e8cf17cc4cc35d5e64","impliedFormat":1},{"version":"a934063af84f8117b8ce51851c1af2b76efe960aa4c7b48d0343a1b15c01aedf","impliedFormat":1},{"version":"e3c5ad476eb2fca8505aee5bdfdf9bf11760df5d0f9545db23f12a5c4d72a718","impliedFormat":1},{"version":"462bccdf75fcafc1ae8c30400c9425e1a4681db5d605d1a0edb4f990a54d8094","impliedFormat":1},{"version":"5923d8facbac6ecf7c84739a5c701a57af94a6f6648d6229a6c768cf28f0f8cb","impliedFormat":1},{"version":"d0570ce419fb38287e7b39c910b468becb5b2278cf33b1000a3d3e82a46ecae2","impliedFormat":1},{"version":"3aca7f4260dad9dcc0a0333654cb3cde6664d34a553ec06c953bce11151764d7","impliedFormat":1},{"version":"a0a6f0095f25f08a7129bc4d7cb8438039ec422dc341218d274e1e5131115988","impliedFormat":1},{"version":"b58f396fe4cfe5a0e4d594996bc8c1bfe25496fbc66cf169d41ac3c139418c77","impliedFormat":1},{"version":"45785e608b3d380c79e21957a6d1467e1206ac0281644e43e8ed6498808ace72","impliedFormat":1},{"version":"bece27602416508ba946868ad34d09997911016dbd6893fb884633017f74e2c5","impliedFormat":1},{"version":"2a90177ebaef25de89351de964c2c601ab54d6e3a157cba60d9cd3eaf5a5ee1a","impliedFormat":1},{"version":"82200e963d3c767976a5a9f41ecf8c65eca14a6b33dcbe00214fcbe959698c46","impliedFormat":1},{"version":"b4966c503c08bbd9e834037a8ab60e5f53c5fd1092e8873c4a1c344806acdab2","impliedFormat":1},{"version":"b598deb1da203a2b58c76cf8d91cfc2ca172d785dacd8466c0a11e400ff6ab2d","impliedFormat":1},{"version":"34a8a5b4c21e7a6d07d3b6bce72371da300ec1aed58961067e13f1f4dc849712","impliedFormat":1},{"version":"bf7a2d0f6d9e72d59044079d61000c38da50328ccdff28c47528a1a139c610ec","impliedFormat":99},{"version":"e58c0b5226aff07b63be6ac6e1bec9d55bc3d2bda3b11b9b68cccea8c24ae839","affectsGlobalScope":true,"impliedFormat":99},{"version":"5a88655bf852c8cc007d6bc874ab61d1d63fba97063020458177173c454e9b4a","impliedFormat":99},{"version":"7e4dfae2da12ec71ffd9f55f4641a6e05610ce0d6784838659490e259e4eb13c","impliedFormat":99},{"version":"c30a41267fc04c6518b17e55dcb2b810f267af4314b0b6d7df1c33a76ce1b330","impliedFormat":1},{"version":"72422d0bac4076912385d0c10911b82e4694fc106e2d70added091f88f0824ba","impliedFormat":1},{"version":"da251b82c25bee1d93f9fd80c5a61d945da4f708ca21285541d7aff83ecb8200","impliedFormat":1},{"version":"64db14db2bf37ac089766fdb3c7e1160fabc10e9929bc2deeede7237e4419fc8","impliedFormat":1},{"version":"98b94085c9f78eba36d3d2314affe973e8994f99864b8708122750788825c771","impliedFormat":1},{"version":"13573a613314e40482386fe9c7934f9d86f3e06f19b840466c75391fb833b99b","impliedFormat":99},{"version":"5a2cdf6adeec348bbc876221be4367e8adff0bb78a5680ebd7d71e5c3bad6cc0","impliedFormat":99},{"version":"e004826eac62081f867c66dabd92d3ef7d126d93a70430a2c88429228c3ecc50","impliedFormat":99},{"version":"38d6857b58d2ac42442e396311c542062d4f0dad40f2adb496dd5fd0756ee400","impliedFormat":99},{"version":"34b7d1e2d15845cf08bcf5e3c01adbb92cea1ec27564ee249ba486cdfb28526c","impliedFormat":99},{"version":"6d575d93896c413b308c3726eed99ddd17e821a00bdd2cc5929510b46fe64de4","impliedFormat":99},{"version":"1beebd50610b0c9701d2de263e0183ec22aad6c051d0e15ce9e6cce295c6a40b","signature":"3c49b34b1c62e5d74c637b63276c9acacb605689334f5450cc3f67f560ac0ecf"},{"version":"c3966037c4406549f831cfce69030dadf21e1b393806ee1444a6e07f2ab46716","signature":"75d57bc24316ee41d516041387f8a150f5776774790bd72285671179c8652070"},{"version":"b06424097632400755c0257c3fa4786544fd132335045fc791a815d383543c08","signature":"1a04d84d03600646a3956356d88f8d8594881b47182f488f5eb011454858f9d8"},{"version":"f13cc653015347688208b6a2817d6a024cae82cea1b2be422061ae1fb40e86a3","signature":"9d34eaf37fb26f7e3b5d52527e1cb097956ecec3deece2590b99f360ab4428a7"},{"version":"cc319ff8f06a331d4b359aa39f43dc18f8d4402f1f3c446b22a197389c4067a6","signature":"a6d8aa22b2e3abe3192321c687b18ff88b15d42a8c3165a2ceef83a58045e9dd"},{"version":"d2f50145db5e30a8fe6c651d9be96d8b58fa8137f885bb29a0f5bf726ec89689","signature":"6670f738aff6aa9e79d8bfa6f042ec32f827f1b7316a794eb69f95c6393dfed6"},{"version":"c81430ff84e021f8e86715979190ec96946d8a5ee69b5d4bd1c23ca188c1a562","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2c4beeb10c123c02ff09c390cb92953e811cb0e846e2d040c65a8a0e73746894","signature":"5431108d0a4a15cc5f6d78abd5a358d13bcabb849877e09f3efc265eba21e6e2"},{"version":"5559d4fcad759cc07a71aca5a792755409db3b683788828abad2a84da3dcd7fc","signature":"c367bae6e0535dda7431e73df32e233511c1e9b1181082d551efc822fcbaae83"},{"version":"46ea0bba2f2e36762dae6bd527f342a19fe9f46c653dc4e8061d9c8530ba8dca","signature":"19467dd75b0a6bae42afc2ac103445b6ddc4a3e259599ac20b2ec70e75fff499"},{"version":"40f8a5ff101ec9d2a6a08af84db2d4865c35e2deb3da94075a482d94612ca24e","signature":"57e73f014bbd5a960cd0a3b39a240cddbf1842f7f06a09757be73de97a234a79"},{"version":"3d64c2914a71ab3af9fd253eda13ea735a784923284005d07af763636578b46e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5112478f7f7dcb622157981c8ac9a0fb3cad40c5eb87a19ff2e37674c75c0fd5","signature":"8ce6788984fbc5caf642946b8dc8a405629def762f166473ae6389aab4822034"},{"version":"178dc732f2d61a4fd094b6672b6c438d1b1d6cfd5489564206a84e3df486beff","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8e4c902d324edc1b873a1b0bc0f07f760268b57392266df84c01faeea3ee033d","signature":"0bd103c19e9fac90503e61110a3b59fc4e9c05dc79b9dc093b704c354ea17577"},{"version":"32666fa32e6247fe6f50ce32cfb0aac3f2bcb2ca0eaca635ae065948028f7254","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ffb9f584394403c5e07c9058c803383a5f127228e6fa911a35df6557108809b3","signature":"cb195125eeb33a1ec87e9a694af8449e518de894df290a34714e043053b883e8"},{"version":"7f8e8b3d1d986e5be4c13b7a642a6f4899f1af03978448c01183a00e828c12bb","signature":"2cca07a66a88e9bd88fba730dd137253950afbf99138a1bd5d5272b2c5d41b56"},{"version":"e1ae660c622a39b34c87f6dd244d59846a5d110d2c39bcf4316a499b166b6e7a","signature":"d65014fad921da41cfd383514c098293dbe40fba77dd7ded291edcf4e04b001a"},{"version":"7606ef9eeff41c0616d32c7f6fc2086c38b34c3d7221598ed9291aaf126eb178","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ec62117264f15406ca6734f497618d2971956185bba9d16fe336973ba99f2554","signature":"8f4e72fe2a5fa527cc58af1827fb63977ad7aa7ea54cd31f3adc523371e0c562"},{"version":"695e9a27d875aae3f404cbd6ff901fedf0d77c193cfd9552edba781658ed0e85","signature":"a299ef368d46feb485cacc1257882c710c962c3835c15268544a95d9385c6641"},{"version":"d0608ad5086ad006c7c2a10e4fce58cbdac946d73cda4c270717bbc11751afcb","signature":"79f1952bf72196b817faf37634b6a85b9c271443bee5e0d1e40c42d210fba354"},{"version":"effdd15505b8227993ecf9360d8b04c578fdda2242fe03ee92b538ad609c6d6c","signature":"f6777bc9b3d0283f46f8b4e1483716ecacc08f793ed91d950d6082386340af8d"},{"version":"42b18867a7543fec221e4f0321e077538e596cbacfec0595872df4876635dccb","signature":"baa8a117328606a5a80729fa29d3b99e604d1c58274ce6c705b1dd17550d4173"},{"version":"c07f3037b31e0bd7e1384c41a6fd6524ac141e8f56b93a4a12ec63d75a704edf","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6e925ea850d5c27ac93c1d0a26203779a347ded9a658643ac71febb557086d09","signature":"faf83f25a2e1c4c2cdd395f86877ce483031a5fde85d0bfa74cd27548f3139ff"},{"version":"3cef134032da5e1bfabba59a03a58d91ed59f302235034279bb25a5a5b65ca62","affectsGlobalScope":true,"impliedFormat":1},{"version":"b30435ebf6c77ce2d76cbe0bfc2fcc37e5d90e36c68a712301df136549212be5","signature":"8a2590718362dc9587a8e34376a541bbc5e80c3be375550d459cd7793ba5c996"},{"version":"41a4b706f190423fb86f0fe568b685dc59c4a76760b506b02c10a3eb70ff880d","impliedFormat":1},{"version":"e26d25ac80157bf4dfaf1b15917520d7d5aa515389c7c0464911ab0129e58835","impliedFormat":1},{"version":"e305ba550c25a1807b5c432b31563f897c82111c8bce166e560029c94df204c3","impliedFormat":1},{"version":"a3e0936d6b795d2fc46850dc9c590152942ee297f3271dbd3d8588c3983592c6","impliedFormat":1},{"version":"2013af14579369e7822fb5f20f01268f3338141d4a382d5969144fe9c6a25ccb","impliedFormat":1},{"version":"6b7fcb23322518af97092aafb777aefa9196a1db215071fe8f96ae6f6101b498","impliedFormat":1},{"version":"a98d81cd9207ec9f1bd54d454809a60b267043dd56b165bdb6912bf9d63d4759","impliedFormat":1},{"version":"cd1aff7bdfa3af19aae3a21c958b932af6941ed89af069bd789f9ba5ad43377c","impliedFormat":1},{"version":"33e9caf10988726e5f7be53d2a4ebaad8db16e51b5f81e5ed4dbb0068f3a88fb","impliedFormat":1},{"version":"cc0d535d54cfec869fe4d28b9acda7e3427b25d8d84fdfe495021ad4cdee301e","impliedFormat":1},{"version":"f3e743e40c9a3b22de28624c8428e1df4cedc2fa3b3d99c546ebb95592ba1978","impliedFormat":1},{"version":"f8dab311b48db5e3b4361d76a629201098efdaa1b785120353f0ad221b263682","impliedFormat":1},{"version":"261b2daf69470a9e9d1ef26aa5e0c23f1611af380eb5e5f031c776ba57367741","impliedFormat":1},{"version":"e181f3ee5845726fa8077b39742a875d4fbaeb35f036765f95a77afcf982f989","impliedFormat":1},{"version":"9227c7d1182bc93c52d39d65472fef3f850a0d0145e1fac7388758d995878ccb","impliedFormat":1},{"version":"d90fecb86a618f308931b2efa45d7beeb73185db71ff184c1066b4e5f5900771","impliedFormat":1},{"version":"69af21e2252e417f1f26d2e6b1ded1f20dba11f066ce0690c53946f4369e00cf","impliedFormat":1},{"version":"fd4ae6100e700ea9e36ab622968f12ec03792f98f7da286cc44649395022453f","impliedFormat":1},{"version":"1adede2d971ad167525195ed811b8e0af2d17c728350689c470b6d5c5a3aa60c","impliedFormat":1},{"version":"4486b61dcb644ea4527e90b5bfbe7600b9ed50371d334a0c01e8fe661a99d952","impliedFormat":1},{"version":"dae0c9cc1c106f7b91a726813cfafe489dec4fb1e3f4f7c684d14e5f99f96b95","impliedFormat":1},{"version":"bf5fe5a9da7069bca0bb83fdc31ba9f624ac778c3ce25a06a3099d83a92a4858","impliedFormat":1},{"version":"7a0958a789740f9ddba6b7d80fbb1ae4d97da910970c1d59965ebc80304ad22c","impliedFormat":1},{"version":"54c9f10d962306a39448b924e73e631bba8221b5797ca4b2dcfb00dca78827b8","impliedFormat":1},{"version":"7125d9240eb556853fdaf7c892b60c630f6d01bd11580a32e9f299b0b91ca4f5","impliedFormat":1},{"version":"b339079a6ce4d7e86dc68ea8af379ee8700dfb205da5546ffdd3cdfe4be43df4","impliedFormat":1},{"version":"a97af3b565a5649c8cd983df40878da9a4bc080cf1a918199574f82a25fa727f","impliedFormat":1},{"version":"6e5996003f6555309d988ceae5b9952f3de47fc6e5110e5d64c612d4f37cb490","impliedFormat":1},{"version":"f07a1939b55120050d3fb95633ac6f8ebdb594e63eaf8f14c123480cc4bec03a","impliedFormat":1},{"version":"325e48e1f58a88830852e00f4fe7c515afef925a1254975a3ba9c54c9156f3fb","impliedFormat":1},{"version":"79caaa6f2db6cd3a1362f50b48ebae937a234b421c66516b8c98282b7d713d12","impliedFormat":1},{"version":"e7f0aa1f6c72a395ef5ac8749c03a31b00ea527abca2db5aea63804fef2dbb72","impliedFormat":1},{"version":"edca8fd9f3960acb4223f2a596d1f944320193afb076cb8b96afc19fb83144ec","impliedFormat":1},{"version":"511a250228acb3131a8c9eabaf45a53c3015c8a5abffc1c2e8b37942b779ef31","impliedFormat":1},{"version":"496927b9d1a6a7bea58453b9749b878ee1040ddc22de6db6e689db11ec111ebb","impliedFormat":1},{"version":"d17c142d0bbf9f20c696ed0224d1740fbbda0b0211225f2e48732809e15f2505","impliedFormat":1},{"version":"67e2a7d5facca25f1df14bc4fa4167337405fdc98b47df49b98aa2dcdaff5696","impliedFormat":1},{"version":"e16f1bf72fcbea703f1b9dba81e6faaf65e29d24128f9e09504c9a862f41c9d1","impliedFormat":1},{"version":"2e47b85f55b2c6606b137b4be24ea386c58bd9621370d2373e95b530c955102e","impliedFormat":1},{"version":"1f9fcdb7e24f1f9b391bb9200e32f6353781a1c604159a6257e9d8f1df7b9bee","impliedFormat":1},{"version":"3c788378c8c8d9525c168ce0578d536607896146b88bb5f6670ab19834be30b3","impliedFormat":1},{"version":"d16aba532f5391ccb1a61f90cf8e904ecabf3105641c398df0f4a79cac27d472","impliedFormat":1},{"version":"8d3a30c921fa2ea91d1bffc7fd9b8c42dc74bc819c503208122bd2e84cbffb5f","impliedFormat":1},{"version":"e4fb93fd2ec72ebaaa1e657435d607ed155da0005e6e30c440257d9fa877d834","impliedFormat":1},{"version":"ad103c0c76b809a8830ca4f9a8c8cb43b53d578bc63dc8c63b1342f1de90f8d8","impliedFormat":1},{"version":"7e15e2f23da806eabcf4bd0f1e48d7a5baeface210f05837171b50eed7d2b894","impliedFormat":1},{"version":"fb5e2afe7c4f988b615085166421f8d88464f6234abcc740ed861eccc0e17ba4","impliedFormat":1},{"version":"652af46b250ee5144ecb0eef7bcfa1647c88fd170cca974bdff9bb315f349f52","impliedFormat":1},{"version":"790daabde36636c46a264b1628f488ae49b2b378810383a1059ee722e82ceab8","impliedFormat":1},{"version":"89a58d0ab59eec78a6b6532e5d748f9942568891619633c890638a2912224ad5","impliedFormat":1},{"version":"9af24ffe92056dea7acff1dee779be364ad35e5f9861ca417d17bfb447a0a230","impliedFormat":1},{"version":"8fab0b106acb9de629cc3f7bf784187cd59d506d734917c4f140d02f0dcd167d","impliedFormat":1},{"version":"d0ba3b6aaef0c96be907938b6fb2a3a04a5db59de34a40f7e426bc7f10bb46d6","impliedFormat":1},{"version":"d91c919538e393ed3c649270a73f239ea7cd9f312dcee7dad037869a6eb0eee0","impliedFormat":1},{"version":"fcf5f4ff294643e6ea5100d09f40668a3a8744b73b8f1c397fac4b17ccecc72f","impliedFormat":1},{"version":"af3bebc2d30fe79abc9a505bc890d16af72f8ea21ec59009e9d57c2d8f6e0b01","impliedFormat":1},{"version":"784c657f85bebb1a7d94ef05e10f1cad4abdf32798203ef8631f7c3aca2390dd","impliedFormat":1},{"version":"e488fdf1efc9112b9ae08aaf2be027c3cc5603b916582c45d73bb3885728543f","impliedFormat":1},{"version":"a43b695758408470608b548841c97ad3827e453fe81ad835e29b9871129785f7","impliedFormat":1},{"version":"96dd9a7f52627f94b64da26c1ce05d2350941487861c8c27a0014c67273c8a40","impliedFormat":1},{"version":"0e754d4ed9a6cd6c131515ba94f3f1095fb10ac3cb0c20c2cbeef9e895f924c9","impliedFormat":1},{"version":"72cac4a4359c6a5e2e5c0ece767455797e46871350324dfe42ab14238f675729","impliedFormat":1},{"version":"cd091878f6b6994d9307156bda8a4419c7c41c524228d9e830f5fa618d70672e","impliedFormat":1},{"version":"bd3bbe444bc7cc28757c7669fb186a9ba326d4b65dbf99e18b2b5b9ca66edeb9","impliedFormat":1},{"version":"b10c73b3d7703d2d870d35631428cdec737d4bbc06706b6fcc6f6e058b8e1594","impliedFormat":1},{"version":"2543b46883befbefe10c2de9f3a0e7809de7baa09e192edda748443fd15d38d3","impliedFormat":1},{"version":"01cdf83ab596024078a6ce08ff990770326ceaf16f9081a8e369b9bc5110cadd","impliedFormat":1},{"version":"bbb9a17f2654caa1d34f49428c0e48ca0fd0d9550f5f82da6f544c924afa17b3","impliedFormat":1},{"version":"a9d8ba2c15cd99f51aa291034a1afff2f67f1f88259d2162eddaa25e3644032a","impliedFormat":1},{"version":"affa88c9484982a9aa35b30480059dadfe98c3bbd92f76513ae2d1d7e68096d2","impliedFormat":1},{"version":"3436c4b40e71c333e253578f6f3176870d4963d5f4ec14862ba5e40794bef8bc","impliedFormat":1},{"version":"a02f786598d002e2e42854d9bb4fc5a4ac03589538055a0eca03c9ec7ad35457","impliedFormat":1},{"version":"348863ce75f819f43557d134e5e7ff11a8ae582ac879349cdf9156bb696012f7","impliedFormat":1},{"version":"235ede03bdeeb87ca21b68fb1398ebc4749a924e2a7219977b71bfc9c51574a7","impliedFormat":1},{"version":"ec83a163716e8a8c2324e3f6b64c907ba7e5247b43df47f52edef954232c0211","impliedFormat":1},{"version":"15a76d1390ae38fe474023a51778887a6e39cc4204f65519a448ed7d1931275f","impliedFormat":1},{"version":"d7a9a81362adcd395f9db48531a89df84461595d189a234796e85ef983399042","impliedFormat":1},{"version":"7ac3584d37571a5ba61326f50860d843072ea95673e53d23b0b684635db9db00","impliedFormat":1},{"version":"96354248b7b7fe792c9545b7b153ef20763677692e9baa9aa6d1afbb17376ba7","impliedFormat":1},{"version":"5dce9f1eea7d40ad9f10295dff47b7de6fbb24b33858c0ff91aa75043e9899d3","impliedFormat":1},{"version":"d324bb068a3a98f3d7ff92eed388935a5ed4bd46b7678c5bf057b5a2ee9416d3","impliedFormat":1},{"version":"fe6e76ab5933ca777c6ce422c7023d44799d4832a8c5ce35e3592c8430867329","impliedFormat":1},{"version":"cc5beca247a7da7286a82c0f3b84686a922d0a402ead5d11b9ddd0dcdef5c762","impliedFormat":1},{"version":"24661a3d44d16268a8ac8260f35651526c54496ed5f29e559c066b7b7e6776c9","impliedFormat":1},{"version":"7c7ddd5cfbbab70612c216ab1d1f982468118fead1d57948ed31b41cd692c2fb","impliedFormat":1},{"version":"461ffe558e162cb3e451654eed59d0090a267818fde655088616be907007d654","impliedFormat":1},{"version":"829df07ac748fd372b8bcace5b46e6ce0420d2fd65c23f64b86e8a099b69a21b","impliedFormat":1},{"version":"162ff76724612621b2623b71139fae21981b276595c5e93a909b464c6eaf6310","impliedFormat":1},{"version":"4ec0abadee52b5287cba7929ce1c34e81a67a046c0299908158497ee85ff20b7","impliedFormat":1},{"version":"3b60b6d30d8be5b573e75d148abd155fb74cbce0795055965ad505afa4b181f9","impliedFormat":1},{"version":"5ea0f9746d216da9d45885462fdad43ed26dc4473ac6d289a94661c9a1e7bbbf","impliedFormat":1},{"version":"96198fa503333a1856039319ad4bc45c6e32afe2ede6a050e23fee2127139a40","impliedFormat":1},{"version":"e9dbaa3c845b96ebd98a7a3c59296fad4f5cbe4c2e471d0c54a248dab6d575f0","impliedFormat":1},{"version":"63c5fe7a04273a69125008737aa3c18212a1276b3a2f3892080c346cb589a716","impliedFormat":1},{"version":"adca096d8a06e8fe1f6f8a1d95dd176e0ec2216f5dce683c9c3656a9bb1e1f10","impliedFormat":1},{"version":"fe5cfccf2b757c44e7251d6ac822f4892d63f0dbbab920da4f24b893e655e836","impliedFormat":1},{"version":"e2a5e2a231048f1b0a8c6c123d524adeb3eda1464ac2413fd039cf5afa57bc54","impliedFormat":1},{"version":"8701130ab14da66b4e908e13c3ece584b420399cc543bafca971c414059ee5e8","impliedFormat":1},{"version":"811e9e98ddaacadbbcc92015fafd5f5ce0dbebc14f3536cbe225094b1d61f885","impliedFormat":1},{"version":"f7c8e5f19d7159bde8f8e9c6561c6e517953457faa86018a7f963b72863380fe","impliedFormat":1},{"version":"c0bc4b28c78bccbb158fb2e8b3e37a86fee5f26b6098a857befd864790da7cd8","impliedFormat":1},{"version":"e7b604762369c8fa5ffafb6e238a2c7af296e5a25bfa25cb33191b525f064cd0","impliedFormat":1},{"version":"dcabfb44bd25183c919819f87428fc589b20c3b9586825ec456f94cdb67bd316","impliedFormat":1},{"version":"41c39405eb8d94777d8b30d2bb295c258391ac4a45deef8d2f569b29bb82938c","impliedFormat":1},{"version":"f5786f9b0a39c790d245b33436e75576990e41e995e1fff1b0919833a57f4357","impliedFormat":1},{"version":"970025f12906d26ea3c1c381199eb6702b9c8cb0bec44edc02e86e004cf95eb1","impliedFormat":1},{"version":"8dafc03ad3ee7acabdb9254c702b80755bdafa7d7548cc6ffc21814e83055abd","impliedFormat":1},{"version":"e0dba6e973edfd7a5d8a7307ad1e6ec014b51fb7dac507ae132d1f9429016252","impliedFormat":1},{"version":"cc2f61e4781ad29f2aa93d4850de1b1d8313f242631f10ca17cc99411eb63022","impliedFormat":1},{"version":"6679676c1dc90d9c371f3de8430bf070ed36d2677d9ce3d2a336b54c5c40c2d7","impliedFormat":1},{"version":"cf2b168364792895c95f8f98f8fb662f07787e518e6d25b0f7c4aca9927a1bb5","impliedFormat":1},{"version":"db115e097d9ccc281414e7cedebfb0435d5bb14022f147331fb1bdad09404885","impliedFormat":1},{"version":"aa78c2b93bce87b73fccd6726cac3cac4f62927460ff3495f2a05553b4c04d3e","impliedFormat":1},{"version":"298104d50f65103c256ad79ff5128141000e4544a6afaa998d3099bee3975b84","impliedFormat":1},{"version":"a99f5ced5c95d7603c94c66a4619dfd0e737351bf20757de516b7f8ca193cce9","impliedFormat":1},{"version":"341b20f291eecbfefa6760a69f7f3f18b2094edcc794f4e78e903a5f0dd86fa6","impliedFormat":1},{"version":"238dd354909fc4a682e0cc4bd0d1eee8ba03197a4efa3cb284e502355eaec8de","impliedFormat":1},{"version":"8a3156a33e38b19f00d543a9f7a96054a0a4b051533449fb04267b4e533f55ef","impliedFormat":1},{"version":"bfd14082a4db87c2847135aab3d617ad7b488b3e65ac82f1620742548ed630f3","impliedFormat":1},{"version":"e2aa5b5cbc067b485de95616efb852886f4a1a43685ed7ea0ee8e08fec961cb2","impliedFormat":1},{"version":"3d6d27c275808a7e8540b1778a5d3808542518acda03f5c1ea4c9c5831058ea0","impliedFormat":1},{"version":"f534c1a02cd756679611ca2b36431b51715a0c59a070d413e292dfa23b9b5c6d","impliedFormat":1},{"version":"e26cccd0ef5654714877908c1674bee29a8e53d60c8c2d82bdffc12cac6b0fb5","impliedFormat":1},{"version":"ccf581fb8928f37fbd6509a7d8fa0d32156fc4eb414f434bf81cf7bd6849f7b8","impliedFormat":1},{"version":"69b227120a5245cddb0805eea82a0bea405872bcf595d2fce9fc03dd16133291","impliedFormat":1},{"version":"d6f495bfd0020102c67a6f80e411b00b913a001c468001b7cbe8c592c748f301","impliedFormat":1},{"version":"eb4463ba66be74eb04aeba3bded1f485a6ee90bed8c28a2c2573f0c983834790","impliedFormat":1},{"version":"a76187885d25a8aed20f71760c116bfb89ed1612d125bc190ab25a1a7a87ed91","impliedFormat":1},{"version":"76d36099aa1a0c7ea690ee1675ac4dd86cc62e2643cd40c097899268f8d2f7a8","impliedFormat":1},{"version":"815d7ee4cb5383f94b88688b8f2d70ce3e5df4de147d3669d225f8bfcffad673","impliedFormat":1},{"version":"afc6a3b94e405b3ae5d5038fe66f3b2412300c93ba1250805ee1a7ad19964ff6","impliedFormat":1},{"version":"b168bf198df3af94f54863a77ca14dcdb67af689d4cd876328f7c70bbb7b985f","impliedFormat":1},{"version":"54a40fe6e389146cb444299ef2d7d6e4ec83b05a9df2e7611fd1d1d862b2743a","impliedFormat":1},{"version":"65ec140c7cde7edee0f611bc84ce505cfa71916571e76f5cc197ba1dcde32f2f","impliedFormat":1},{"version":"969a96cb343d30bd8a28bad66c77049aeb0fabd9f608ad82caee751082685eeb","impliedFormat":1},{"version":"b30e161d3bbbe3f8d15b0bc5d9b47d1935ffffc7ced1099fcd84536c906af711","impliedFormat":1},{"version":"3eaea558e36977f924d85b3207406594568053d7a52950081b7603d112edefce","impliedFormat":1},{"version":"4918bfaa32bc0f63380d84b19bf5adff3188797b17b055417bbfdb09bd531d1d","impliedFormat":1},{"version":"5750305060905aff115cc0a6f295357099856fe72d76a1193204c23b4b9417f9","impliedFormat":1},{"version":"98195f663b1c09572bf794bf2fd7351b05d5895ed471589c0c79ae2e7c7d6b97","impliedFormat":1},{"version":"355e8226f1a83a02c2c5dc22781defbcf171df314f6f3205318851fd4550132f","impliedFormat":1},{"version":"8abeb772b7a7763686fd699980b74d9895863a758f2a6b824edb3d5e5c235078","impliedFormat":1},{"version":"dc1e2e53c9935f62739ca37d9acd484e83fd25bc051e5a330c9be0acbe774253","impliedFormat":1},{"version":"71a542cb540a3c0d4d954d311937a4df56157b0797489ea5cb8a9e57f449aefe","impliedFormat":1},{"version":"49fbd0ae0b53936499ca6293450230273cf299e017ac4a1cc8de936d50d8e696","impliedFormat":1},{"version":"d1a6aec1239a47bbcbbc55324d6ed293f79d4554f6bb0e411e206a9e22c50aa6","impliedFormat":1},{"version":"4e81f6b7048f1022bd8dd7dc18e43b4aca8967c4ffbfc8fe80bd4277936e5be3","impliedFormat":1},{"version":"5ff6328b404fb34d2828b501bd16f75bf17590d2b03a66a469a0359da07a06fa","impliedFormat":1},{"version":"149fbdfda86091cc37779a6eb3f01ac5c73d6e6d34a71e76bb3702a1ebdd8bf6","impliedFormat":1},{"version":"3c1e92d9a7a0c81d02f016c47de63a39706bb0e36231f2e6727a08cbbef6cfa2","impliedFormat":1},{"version":"ebec459a7d4933732cb453254997b9b9e7d17319dd40a29946f985719e297927","impliedFormat":1},{"version":"394dec85f81a33891c71f4e6a1b9a40afdbe93210d5dec749a2791deb57df5e4","impliedFormat":1},{"version":"19d2786de07b0dd973e5515d84182d2734f1c1ecf602929f75b30081fb20fcce","impliedFormat":1},{"version":"728d3db3ffdbd649c96fd64cb5766993cc8cb10b4ef207403fb98304eba04f57","impliedFormat":1},{"version":"8e4ae5371abcf89ca3059e621b666f1a340db0575f0c8635431417528f2d6367","impliedFormat":1},{"version":"de4f5917a7bf2c62cd3ab4c171620fd6e88a4a92902f02c0808ae67793e6baad","impliedFormat":1},{"version":"8f09f0abdd346cdbb1e545a44c85dcefeb047d07080628562a328d3e88160beb","impliedFormat":1},{"version":"4557c1259460570a893f9adb1547b5bdd19948497740c53fbaf654b20ded5855","impliedFormat":1},{"version":"ddf8a6692f74ae9ebdece687ec1cd9ff63811c5af27381b40c7996053a1b0504","impliedFormat":1},{"version":"de735626154dab7ceb24b208728b7461aaa5ad8848152ab0b39192e7bc0aa4be","impliedFormat":1},{"version":"c6296acd69aca14033c815aebc1b4c7fe72b92e44145dfe6432fe855c8c7b463","impliedFormat":1},{"version":"fe7df3fef3d25c0455e7cd4f36fdff7ad7b4d2163e7285a74d6a98a6cb48a282","impliedFormat":1},{"version":"7e9bf7c76b60c4402bd48996bfb0d1fa552a576991f9f73dbb856d15b0346793","impliedFormat":1},{"version":"29199bf01375b374d516e5c8d5d8ce1dac3c07cc52b00eebc0a7ba7b05baeb1c","impliedFormat":1},{"version":"7696d51d4ce2f2f21e9f73214396bfd823bee6c57f65d39e6a2264c40dd021f2","impliedFormat":1},{"version":"4b74f4072dacae8ba4f21abf5d042e5da499d027b0aac8e2d8f42f5f591453a8","impliedFormat":1},{"version":"d3b884fb07719c9457497fa9d5146f7977fed29df303347ff4175f630b903fcf","impliedFormat":1},{"version":"665320db7cce83346ce47ab3baa657b3115d796d28d8f778a98e7f23962b1247","impliedFormat":1},{"version":"3f471b5d1f378519b8276a869cb746d5cfc9b2bf4d7e5cbd0bdec3f9b5f81fe7","impliedFormat":1},{"version":"4afef388856e350489141ad7d90ab0767a02954d53d953f558237e04c89b5d0c","impliedFormat":1},{"version":"7b16f7f12771f8e25117321d1cb607e06be688ed8db002fe2cb13b716d0662b7","impliedFormat":1},{"version":"9412339ec64000a6dce5898fd848f025d31ec3b446872070cc041ade876c0c1f","impliedFormat":1},{"version":"726bc5d2505bf6051e80ede05a576e21d65118c077a8407ef4f9eca8d5445464","impliedFormat":1},{"version":"05f2af853ef135671b9482077d19a2935e3d924debbcf2f4803110bde114f113","impliedFormat":1},{"version":"134078b75b0105535f6164680bd73f88f9ddaa84b7e0126aeddd2af7ea27bcb0","impliedFormat":1},{"version":"264f0b10beaa4141a6bf228d2e22b19ff7baa76b39388da319bffcd1ced741e5","impliedFormat":1},{"version":"98682512d496bb618315de173d7a25ec363676da2457939f42b75a23c5acab87","impliedFormat":1},{"version":"1471ca4439a9dc9787976d1c9ca2b913908f4029465c87372d2efe2069c375f0","impliedFormat":1},{"version":"54b8abfc4713160ce97f91758ee1e8a547ba67c7328177d5e4c40613a3e87f79","impliedFormat":1},{"version":"60894b9993d7e1a7be37933c9bfe96b228ebc206cada93a44bca9d30e12d8d8f","impliedFormat":1},{"version":"236ec9d640fd6438a08dd2be3fb739352f147de529b4aa1953e4d4f74d6638b8","impliedFormat":1},{"version":"e75ea841bf22a156a7ae8f95eb7b1d4479da8c291019148d0a643027356b76c4","impliedFormat":1},{"version":"4111a7444998538da1f6f76378412547281e30cc5a7249b32e7402f66f83a492","impliedFormat":1},{"version":"4b9a4b3456012104409fde7f7631be98068daaafe1c49b627b8d92f033960b67","impliedFormat":1},{"version":"7f7840713032b2ad3bbc379ee2d401489b4e563294f7c87dbaf2424a6682beb2","impliedFormat":1},{"version":"b52b80732805d494ffee704b80f689772e1db9440c1728b907f7b25b3328d5ea","impliedFormat":1},{"version":"a805a58a4c72c7d513295fa7102284dd9cf76c1470e1845a6d7e9afa4bafa609","impliedFormat":1},{"version":"724e0ca25a06f553306e33a45951c368346cf1fc4b26b8bf4bf88b1479131659","impliedFormat":1},{"version":"b0880e598b7256855af6b9ea2aebdf47372444114264b56da9d25ea5f95d064e","impliedFormat":1},{"version":"3ff6607fc3c3a85814ec3d6e05e358d99773ee7c7b5e9deed5e086e39f5372a6","impliedFormat":1},{"version":"62247290540b91ed85258d7e8c67c7786e38bc1111429da9fa42b1b34e4ffdd2","impliedFormat":1},{"version":"656ee3f8184b5dfb87200f73350e57827dba056130d15064406487c0993f0e6b","impliedFormat":1},{"version":"b14c19907984b69ce25e011e6391ff6a150bb31f344d643f2a3d5af9aeb4ba73","impliedFormat":1},{"version":"2b77a0e88653109c708203a50fa23bb50406a9c8c7f61883c92e009f778387d6","impliedFormat":1},{"version":"2aa50966f709107108e7ad733c129c81f9e42731492948a9c23d4f8a0de5ae1e","impliedFormat":1},{"version":"080afc7aa193ecf03c78afe2e3d81cc3b18fa482f1bd955b4dca67bcbf220eb1","impliedFormat":1},{"version":"7282df0d72afd1c283644f5827159ffe0c899850fe121811e6e7e3eb77416868","impliedFormat":1},{"version":"07c70d4602003ffd9f23f8f6fc2693b7cf024a323a6d6146b11659105ae588fa","impliedFormat":1},{"version":"79eb7464a0215c82cf6fdc55b2378dc0a3aed416f03dc647fb6956975d446ec1","impliedFormat":1},{"version":"8cd341d72d1ce25d33dbe1681a9a5f27fecfcf65d426a0d0bb80ce97a1e37d50","impliedFormat":1},{"version":"4f750b488d0d1019bf8b6651e68689debd6106312ca7f4fca22627fc2d0acc04","impliedFormat":1},{"version":"4307994ad4d3a8d842a7d7da76f45f84e5eeaf1580e9b6071dd5fa6b8b21de19","impliedFormat":1},{"version":"87b54711b1c9791dd95b4ff88f814b489089f5b128f29e8a5fb7f6b8123f739e","impliedFormat":1},{"version":"4763437e8a65ec15310aa20a4ec288eae3de1b94b9426336ac423fddd482d70f","impliedFormat":1},{"version":"e2398ace0c73a5da036dbd6cab98008a251c709c56f1b665b6202e99ae3450dd","impliedFormat":1},{"version":"1a59a1ed95ac47cb6d1798a4dee9088b847f41491e57545e788ede35ed1204e5","impliedFormat":1},{"version":"c89ec75e2ebb2f5c2dce2d5f85ab59951cdd217748a49a6e0bd102fe69f5eb75","impliedFormat":1},{"version":"e653e5173f22f243892807fcd85800dfa4efe84be40e0ed1cccd15d62e1c9da4","impliedFormat":1},{"version":"973c290e94835130e51e934d078ab80e975a7bdf5b9563f5fa8de080a06c588c","impliedFormat":1},{"version":"5abc40134600b35d15fcc7305d5bc9e29942c64dbec6413137b55e77b689da1d","impliedFormat":1},{"version":"85c3303460c6339a77ec37ed9b7e06974f6d8e1351181b3f6f0c5180e0e7a76f","impliedFormat":1},{"version":"88d761b9b53ee5aa4ffe94d18e1c05c31ba8778beddcb8418f303c184f4f61f4","impliedFormat":1},{"version":"8ad1059fc2cf09ab5208fdc50a974a1a0c0f3737d81c2aa0718c583716b49f7b","impliedFormat":1},{"version":"ded7cab1c0c297958efedb1c4569674117693e0ebb6e8a1366cf7a629ba490c6","impliedFormat":1},{"version":"997c088bb8c6e1d869a689a3db0157d3efea26fa3fe49ece5f01044321949769","impliedFormat":1},{"version":"aa4d9968e228a15f4f93ba782c05f19de5aab2598ef8acd819ce72d6f4c9953e","impliedFormat":1},{"version":"1c4420d12393decf20734fff3f09f44daea5433869633ed11e9fed9b316523ec","impliedFormat":1},{"version":"25e74c23ca9d123ad4726803694ca249b958270fa3e98eb3de7f339f15241a45","impliedFormat":1},{"version":"87537c5c41597d3355cd28531f715bcfa77b73f0622d49b28b6064b3c0ba0ab3","impliedFormat":1},{"version":"56acbddbfb96d3e9502c73d87f216fd16b9954ce7fc345adaa51a054bbd548cd","impliedFormat":1},{"version":"ea5f8a7e470eec67d9b3a57a311393d9b8146d59e7d3965fc2a07745aabb50d1","impliedFormat":1},{"version":"d75a15490d5dc9b8bdd209200429a4cd31c139b1503e22e1ef743e6d4fd160f2","impliedFormat":1},{"version":"fb238a904625a2a0942f8f0aad2c96d5ba7b684b59890599a10d73c1bfd3f771","impliedFormat":1},{"version":"35c91fef4484772dedb9c253a07ad91912a8e349838bef1e7c85b93b6acd39c8","impliedFormat":1},{"version":"b92d1f42b729031910871b073bbf05b8d68994a91dc43a7fc64f88abff16db54","impliedFormat":1},{"version":"beaeb7cae58c1b074e1e5c4c0cc4e205b1763f0e9f413d7062951e1cf539b450","impliedFormat":1},{"version":"2059b7deab3beb764a2368200ead025358b48fe470bd6850500785d94c8fb5cf","impliedFormat":1},{"version":"da08b48bf74446b6f988e95f501693844d59d445487d89d2364b8bf431c8a21e","impliedFormat":1},{"version":"17c4db14970964450f5bdcd1349e6f3035419db7f7f9f88ee966b3b34cbaa8b3","impliedFormat":1},{"version":"a9e6f34151c8629364892059c1689e2f99775b3642a24854d0330112d6892cff","impliedFormat":1},{"version":"bf5fb8cea51021d395c45a092c1e97534d498c91812b99fdb07c658cf5990585","impliedFormat":1},{"version":"453957dcd68b2ce4ca9e3964669137141e3a6b66be1438775183fae9bf4f0b1f","impliedFormat":1},{"version":"51954e948be6a5b728fcfaf561f12331b4f54f068934c77adfc8f70eea17d285","impliedFormat":1},{"version":"64ad2d8172bf54ff4e74ca59db7de05d73c04c3b5d81def9b3dceb1b3a17cf37","signature":"8d5f644b2c4b91cd120f33c4ad1970e93f43582b5a73f4f2e8dd0fbc95fe2791"},{"version":"2814cc3ada0566ab3ff2381f16f181d036a6ed5c840a9746e12fe4c60b890d29","signature":"536b9131c74c18137261bc2890cda45edf43d51c92fa96fdf5490f80d57c111a"},{"version":"dd893122f52f093bed0e313c60387de819fdd40dde8526ccd542782aad7c28a1","signature":"7f3eb2ab3a52fee353398658cc6cf5eb9f25517d0fb04f928ff743d0fb1fe8c4"},{"version":"9f2cb32010b0d18c36f44c303894db8c11bc2233cbff8ba32905500e29942a5a","signature":"6d6e3b1d30af0c368c85a34df9c95d2f1318f7080ef2e5749aa4bdaf637f073f"},{"version":"5229fc0ef7b4e59dd4b2befea6598c3567ac1a69515d6720d34ff21429c1d0a2","signature":"8a2a748f6f13d1fac4e3b927e8447a42ff1e15d92db3c8324e1bfa77e3b9604f"},{"version":"c4f3d9c6228f744351b3f3d6ac2593edba9e7cf0a965cc6ccb1805595f44f275","signature":"96dacaf48c43f86fe63578c47b008b14f31ea3b26ba6604869be23386548a0af"},{"version":"7585b47dbfeb576415ca6cc0998d52303c26a1ff9fc6a9b6120c7133074ec9d9","signature":"ade53ac26d8c9aece33e6e9fd2767d87cf4867000348b3863a1b495d0833d634"},{"version":"c0a6cf1f3d69954be23856d5a268c74f7b108c4d3e0a8af0c4b2a7999f337392","signature":"2a26fe619751492966df59b15b349b5027627a0b085c6ff836a768277003647d"},{"version":"3d1ac90c29f450b8b90705d05264fd29f1034b5ebb6c2d2e9807489969e0a33f","signature":"140c9f01bd8744fc1fbb72ee2a7747039b9637b3976f2284bf1423d1bcbc045c"},{"version":"6cdf25b7999cac1327ad91005d4aba65743aee71a2972eba11f5b1164e39fa4d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0d96088dc89a49f328fc47dd75713536556aca01e187da7dce124fbd2f395f09","signature":"77e82444cebc04e9edeb4d759ea3c8be067ac6bbc3b652d668a3f483b0d5f7fc"},{"version":"ef05bfe2ed3c6fc7575cca3a8ee25f4a4aa28878b83c5d986ee47f4483f73b3f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"dcdb66da2fdcdf0e80f084c8347e2114f573d97619a5e37f680a9ad5656f614b","signature":"f090f8e7e1db58d734c2c7434bcd43e2ea1c30e049be3443fa3a83a063e59324"},{"version":"d322344da57346fad32f22627e0028bdfe12501d7de9eb4a103d47c9a075a85b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2d5bb04855694db19d13174de99509d4d4799d6bf3468318bb74b54ffe994129","signature":"9a6473983696c0401765d2ba2558ef9b0670592e8d74239b4d5623c42d686600"},{"version":"dbbdee1f403eb2a952f5e8ea724cb1a20a2b4dd63d8232e375a750d4929baa88","signature":"7f1336dec949b3008a181a8873c5aebe07ea42b6730e6a5c6efaeff90abd09dc"},{"version":"28c7612edce38076988a57695e970654fd09467b806a8e37b38a26946afafabc","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2a40fc7affa68ec88b30b2d2d19639d8fa8dadee567da499d460257372e04ab3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7f13d1469822b3ce57bd440274f4a8d9c2d9925fa644d284fdeeb520f0ab43fa","signature":"248bf8fb49df3283090f3d2a137e3d74dbe93490ca12bcca8686a3e1004e5c40"},{"version":"0ed05108ce0f4538b5351bcf9d523912d200abe5b804951cf18b2e12feff6b70","signature":"f453c01ca04957da00f261867fea88fe674b34dde9b1da183dc55f2bed19f364"},{"version":"fe55ccd61eb15dd0480443215f5556cd56d9a68075a79a1294e2d9cae200d70d","signature":"9992a93411c1d80cef73f32ab5ac10acddd25700903cf8b5b47925eae8be2a60"},{"version":"7fd1557a212fb2a671abe96de2e1b6a3e5110a07c49076705cdaf9c3e0f81f7c","signature":"dc57421ea686f59b81eeb7885916d7a5cfcf6aac9113b8908c5edbe4d4a7a296"},{"version":"7ad085c66c15e665b43f1055b09ddc1d9c11a3d8f21174c9d3d9205d7dbc03c8","signature":"5b361261b9d93e4a2c0d2e02bf9f0dfc60fd8c761ef6fccdabf563bd3aebb419"},{"version":"68dbb99a0ef2ffb046b1385fca8116795a7b4bf5700193b7bf2fe9cfb62e72e6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"47bcc33a6a3e7c2ffc449508e70b7b42e55afb95ad4cb7b51ada3e48e59bf877","signature":"f6717ce9971f40f30af260a3e42d09f3edefdd725b5ea43006eeaa85fb176b57"},{"version":"dc39c5b409e677273ae4825a5b092506dbbb0130318e90cc1a51ee22333a3915","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4a96c42ed30bf77b8c127453fda697a212495a055f6dcafda400e42eb233d029","signature":"c4e5d6b5f65bfd77c192b36ab608481de02288d42739f978b0c01a812dc94321"},{"version":"f911a52096fa219660557bc3c9ccb5745889d8a357674e8ae67585678891e106","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3a2cadf5506cf6c268f65051dd63ddd5cdc82f5effcf658778af2b12e497726b","signature":"3d3c808c01d46ac6f212b5bf9a3780af30c9ac93fabf3f754e01eece8478207d"},{"version":"3f2074814adeb10d5270e703ae3d2ce2fb333c69ea292c4bb7a7374fc97b3293","signature":"63f9cc5e173cdf605d8378b64d795974920e6fea9b3c515807be08f4cd21667a"},{"version":"2e1f498ba2e37492111a97c6048e07af342ff8df126eeaac6cb99f94372f8ca7","signature":"4a997dea3de3d148c650f5ad6c57d75d5adb6655108e0af42e57f9661d5a9297"},{"version":"759a24229c02dafd2cd73ad6e3325c043d16ad85081d2e76296664bd96226ae2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e8ada2e17f6a59516e1512309931359fe24ceffc9476175e3443adc5916142c9","signature":"c23a1ef10af5ac8c09e7d18df8abbad98ecf9c84e5cd83e9cd7bab8c46d08f03"},{"version":"9dbc05a46db34b5966081f0a8866ad18f7727b0dd9dd88e4ba9b6d0340b27328","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"89f0ba8c9a02ad55e23f296ff35f0d1d6361a190811401c3f0d767a1aeeefec1","signature":"11a904b87a71e58a2283da4755e0042d78cd7f56395ab9a1b6ecb09290f3672b"},{"version":"ca3da68186289d0f9bd93dc9a7e5c3eb30dae2526ca55be4b106af239b63d0fd","signature":"d539ac9920f9a947cd986cb61772e65f48bc0442d1d94e2ce8d6e25f394cedac"},{"version":"fe17ac85f4e0b305bf44f3a4d81cb75397d9d7776aa2649ead1caf2291f0d416","signature":"b89fd4a6a0a474b1b01a3cf0f790261eb91e78b0c5a789e6b64a5496936a3ef4"},{"version":"22c8ef3ac26c1ded1d40c6e1c48b6382fdf0d56a5a3e77f8bcb3bb198a1f769c","signature":"d394730410e0700f09bd96049137fcf096ceed2f5e7bce00a2937aebc9bf4240"},{"version":"c7f6c31638211891b8f6ea106d4d9ca0cfe249adba1528b2d1ebd0f267fa324e","signature":"b70605e01f0bebcb73e3de21b8c1dfa27372859c9a142d66d8d69f1f91e99adc"},{"version":"5d513a6a908bdec9f0c3a72c4fce232063a7365983847c1ade848a9970e97aa9","signature":"302704f3830a828ac25e5b3d330b257810004892d2acf61a6a656b05978d7a2c"},{"version":"bcf829509dc2bdb8f3851efb4451010e917abcdd8a52e2ae302886045c0e38f1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"10ebed90f6cf7c3e637d5595d4dedc3377575aad097fd0b28e15312572c09622","signature":"7d26b77586708051f6f1735b57756edf0be83ca4670c4af58a8e28b965a33a08"},{"version":"0ba02535f72f0cc1712b1a073897c522f3460eb86620046f1f3b250ea79fa567","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2b4b00a99c93370ce060e954393ebac6d299d87a0400b9a69307c97e8021abcc","signature":"1af687725c0895163ae338b1c94acf5819a042e98cfac2dd6e83b993c57d5623"},{"version":"1f1d8292770ec1d17820eda3e3ca60550d3c356d4f4832e211e9ab2ae5d5c9f7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"07e108a85e9c41db3bfca18812565985fe9c4185c09e44cbfd365364daf67e80","signature":"7231ddbad84b7265695458d181b33e24e857a11dcf40f694a4dd42b3e265293d"},{"version":"7236d482e2ca1a6307e2182466f18dc8ee373209e5588f156b019f949cd9ece0","signature":"c92738c8f42ef530edebc3a1912a4ba2ec85ad86494839d23b6084782f9f2e91"},{"version":"da35015d12dac52832201a4900b07e4b0f4fc08f282eee6e7131d895db1930c5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"523337828cd2bce285ba37f1ae426df9c6c5c06dce846b9926c9183b8aba5710","signature":"9f5fbf5ae05a30484f7dd037840393bf37ea7af74e78c3d9c1f80e17b693f935"},{"version":"813c0ad3fe204a3fd51051a3a84ab71f60975401dc8a50994b4739293726bc60","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"24ac88c5cd809281eca45ae09b8b16dec4318221ea50d1b47888254798a046f9","signature":"c97c4207c753de5cccfb48d3488e193f8846f302690bd3ff73f4de951675b01a"},{"version":"63bb37a1d958427795e2e6ca7fb9451aff711e665b45bd4878ba693da9a140cc","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"03d2b4fe984c19eb740b564c177951ba5db16c8e7a3b58cf6d71d8be46077241","signature":"6afe405afc0648ae2dad5d74f9c4b2560496b133818fadad5ba6deef29792111"},{"version":"cc1e8e4c71cdab1eeba18d9057d1f95f2a4af1538a92681f9f683c564f2e4c72","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b581577a6affe98a790ea707971843ad284ae4cbd4bd1dc17642196e3c0de158","signature":"840d3e2bdea7d5a418436aafedac9749b1d9de78bda0f825a48869cfbb3e7f83"},{"version":"004d3bd387fc646ba3d76c6880c06461caa0b5bc15a184ae7605ee1f130f6ef7","signature":"212fdca7769790ac75031f925478591057411842339e39988f1ffe769ab88da5"},{"version":"ac94b15e69603d8aa96f6871176b4bf3b70b295f60ed7190fc1deb835a328605","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a3be9843036cf2c658821504c4931c7e1055db71db2a607b9bc5d8aa7be679a2","signature":"3a526554ad06e5c46700e8b1ac5e6f817fdca923787a3c9344acba81e8d17ff1"},{"version":"ae3566ec3d167f05176112f609460a77863016fec3216a87a15c36bef01d370d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c914982336f64f67076a86d5569fd1b878a42f415962a07d1d151a4d65d187d2","signature":"33831d2be2fefd1ecc0b722e8270094b857a42109f3eb3bdb5c5e666233c588c"},{"version":"4d5caf53ba49b41ff378943f6b1dad1ea8e042426fa713b57c9524219bbbf573","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"bd703e7c27cc5dd4780eba552a51a698b459c9f12dbddfeafa9e0d216a4e66b2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5946158af389cbe762eee6869f0a5fa5c93e87e633a1c1bba333e8b0af7be82e","signature":"f8fd457e54594676a0106e9e40e7de3217ab284fd52a60aa51406d5c35a53222"},{"version":"71e7240e131e0e0f5fa6b5102179bbcb4ec0aa0f969cd3c07a715f6729a2aa22","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1e4dd6a5a91d2798fa4dd70d7e6f682475942bedb36b9f870cbb15e5c1f1a54b","signature":"a8a191cc7d792c8cc2d87c992ffee823187689960dc717e122e158f24b77a242"},{"version":"0a5fe133b4ef41f0f2443b3cc82c4b99be11738a93d613fd12014e9d632c2fcd","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b7278ff0b0dfd9e3a3f9f92785d7166d8c50c34ad80da47abd946c08cae1461d","signature":"eb63e897dbc8b27643106520c69e2f49993ccf53af48ccf8c02f999bde56ea31"},{"version":"e9dc912bf8a7678feb40d7d996c9263bad3b7fdab9ad9ac95d4e4ed023403d29","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c7aa0820877b8341f78e794fde60d464746eec82b0e2624982000ebb19fb8f8c","signature":"488c8eb8a1054444f74a12eb49f8e21ae583aa2ba59ac9cfe4ffc71754c3b1f7"},{"version":"7866820b43b8c2b53cf5a1d2f4de019e059e681e9ec7cff7b5972800a8d78732","signature":"b26c1138cc869467f57022e668f1499192e359d44f7cfaaf0e72a576d79c491c"},{"version":"2f5bad333602ab4d3d7d470d1ab84f1dd5217a53bf720f918bf334835caba63e","signature":"053cec7f0a8bd24eeddfee887cbc9883f56cab39ebed9e143de9f0a6cf34d202"},{"version":"d878f9fb504fbde395cd7c61e48f2fc7bbc7d0cf14828004f95e5f9fe64f238c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3e8a4a82fa1baa0281a0ccf309c22c954488a949c13a7ab6fd7c171b1bafb361","signature":"13428a7125f291070d1c531f233c9e8719c80160dd3587d51adf86a3e41bb62f"},{"version":"b51fb5ae439f311990f5a5c2fc4914fda89e5ebd585b33d22d0251afe382d391","signature":"f49a7f528e4e42999b277a5ea73799e735e349e562a0ac6c97b99644a13a3ed0"},{"version":"d9df7df9ddd168648cb6e27223b325763738bccb1b57e965ba0d8443cd166fe4","signature":"d786daad1509af6e601e8de4259a2c6abae27fd33287b4936fd079fbfd1f0ce0"},{"version":"99f67ae9774e4bb88839948649b26a55dfbe8b99ec80ecab4c548102d2ddcaf3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1537c350b11115c0e713596a8dcd004151573eeae99ed1d2fe81049ca29857c8","signature":"17e770a9f59f622dfe33762933a978e74b5fe1c1bc65fc6c1c9d15f1c4ffe4a0"},{"version":"53ed7ccce63fb30e129e73dd0abff74d68929d92fbe3b20f8ade965780b2353c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3abcb2afd6c10eadcd9e5cfcffe3f93172b891ac80b079ad1421105d1574b983","signature":"2cdedb09674dadec42708ff08cf53e8ebfb3dc9402a0aa42464a061d228c7ef2"},{"version":"ade0a7d50f110afaed90ffd5f24f1617bf9b8a0d2f118530892d139ad67f29fe","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6aaeb7779ddef5bf76d08b6144956966d645f4218ab113e7e2527b4c618d0878","signature":"cfccf5311c906745df21e8fdc5a854d294f481fe2338cc95d94a01f35f67a784"},{"version":"a3311c2d4225d8eacfa5a9e662811aed57fdb4de78b824f1a702311b3f99b09a","signature":"651d947dcd8fb9009c702ecf43eea7c50c9ebe342b6e0619cbcda9d8a8b64e10"},{"version":"d7e17a1b90344a6d9c26f1462f77d6350a6882706064a36e5d640f5726ce49a6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"676e13621722013bb98194ce43eb027c2bd57c6b57a89ab572602960bb816d87","signature":"4612eb6d955a2af73fbae37db08a64b56e741388ab1747c358769814fa6dac3c"},{"version":"1e0995d997ddfbb7dbea2e3b041a39c8b97da9e43e24c7d6f234e714805a116e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6a073d10bc03e721d0b1ce2620253ae9b69daa32fbebb2f7a8c67e7e1579c6f2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e2c28116d0d3256a3ecadc6580d9d76a5c612eb9a86dd1d9d17909c59fd1753f","signature":"cd59b71cce3988ac1c5f91fc2d0b5489ee69e560df6e7987b497fcc1abb6e9fe"},{"version":"4025a1efab8877af2ed8d8edda349736055a800404134c6b24ee95cdfb0c0ba4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0a46d4afc023db18a3c9d7816e3f90d960122319e2765c5d686f76da661864bb","signature":"b5a97abd79ec3360bbef597b4f34eab1b9f0d3545d0c3f46e3b3e2ec6e91771b"},{"version":"4f649bd1b169333de666f079f7989d184c27109ce26544354957f5be00abd232","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0e30e16f547666851c4fe51505a9feeff6508b2d720b7b8c60691e5158db10d6","signature":"cc094a8b2d9686d5ff268266e02eedf9d66e2389a04c46fc49cc819d23134a39"},{"version":"1c03c99ee1b5f5bcf0704dac9398e922805929cd23a7ad246ad0d67cfc3826db","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b0d66f4c2b58973e10b5778d9fb2f6795790e8ba20ad97e6004c41178bccbf52","signature":"0c5260f26c1eaeb4ed1a23b60d9809144f6c842b66fb2acaabad8a73fdec11b2"},{"version":"56e64e37cd8e352a8312c8f90b2acbe1eee2a5bce1cbe2721b473afea5186eef","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"34ef539c1384da9fe858fb9c16368ddf1624a4914dc3f0fabc7a39811bcd2668","signature":"ed6cfc1cf330cbbc602b7a0305aebcef220219fcd5cd3e4493e5afe79058fcee"},{"version":"f7409e1093e57b3f7be327a71c71087e1f7a767333fc10cff7c2504af7222f88","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"26e16c1527313f624a2d67398e4122bdc32f389b2b107c6fa984f2b767376771","signature":"d9d073863d3d0cb154331182ae4ef77da4413a6ac9fcc52d2357bdb51b6dbfca"},{"version":"24803211766a60a0fca7ca541d6a158d376c65f7ae4827a8661063c2e70c06e5","signature":"6e2681371e356de1c4fa982616bd1d26b7c525b5ca9f75e1f688b83332e8a81f"},{"version":"9b0d1030e95dfd79db65b1429d5274053128824cf5f98c90f3e061f0b7089d6b","signature":"fb4582d6a3a9b2152a49c918d6c98ace7ce35978ebe850af889e7aaa526551eb"},{"version":"3fa0c656d89d31fa67f25c8b3eb9974b1aab6fd8fe5ae0fafd521e7d6808f71c","signature":"cf64d4b205595fbd260e6fced4216298b35c82faba7dce73a9e205add66ef85d"},{"version":"ad5ab4bc064063efa662328e47c8eb39c80888a25467fefd231a32e874162d6f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3de88511ad7fa251f77f93515beba64b330124d0c2eaf22032cd2dffb6c6dc7d","signature":"e36e8e0a80ee26a2398c86a0385012146b409e679800b2f59b0742c0b16b6d08"},{"version":"843ed25b4bb1b7debf9b796c9376a43d4399c0d64cb4146ca9a7ea0c541e8f9b","signature":"22a84c5708b83fc36ae54c9f73605a8bb70e435ad4cac23af334d61a88136828"},{"version":"71890e72c9a66a34b8617c1755fba9f68a9ef1915182c02220d6e6217b2d6bf8","signature":"14084429a03e8974f38aaa1890d6a80ea62d5c8f0b627e0f824afb0ac621a65c"},{"version":"63ce74c6a697650a7b1748618ecd898eca5b14018b8d3a9a5b7eb29f95d11a07","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ba54c22aa6c5dfbf26fe9ec5770830bf339d81f53cefaab1fc9c27b7228dc0e7","signature":"1571b1b7546d0267d42d0c0b3e1e4593b2ef990541b260a9652427dd82758bb7"},{"version":"a62a7e45347ca1dc80dd4df8d2094790870c03b1c890960cce9628934f695295","signature":"af0b9205baeb5b5d2e4470da33f26673b5c363db0ccfe761276f8af34cfe3e9b"},{"version":"02f299b9b66512f92cb7b80adc13b0a9bef33e9afee5f2e2efc3d2b635588462","signature":"0342e61cdf2eadde061c53e1c6fc7907ad69390beddb2aa50e656dc2a45a632b"},{"version":"380b919bfa0516118edaf25b99e45f855e7bc3fd75ce4163a1cfe4a666388804","impliedFormat":1},{"version":"0d89e5c4ce6e3096e64504e1fa45a8ddccf488cb5fdc1980ea09db2a451f0b91","impliedFormat":1},{"version":"fcf79300e5257a23ed3bacaa6861d7c645139c6f7ece134d15e6669447e5e6db","impliedFormat":1},{"version":"187119ff4f9553676a884e296089e131e8cc01691c546273b1d0089c3533ce42","impliedFormat":1},{"version":"aa2c18a1b5a086bbcaae10a4efba409cc95ba7287d8cf8f2591b53704fea3dea","impliedFormat":1},{"version":"5a0b15210129310cee9fa6af9200714bb4b12af4a04d890e15f34dbea1cf1852","impliedFormat":1},{"version":"0244119dbcbcf34faf3ffdae72dab1e9bc2bc9efc3c477b2240ffa94af3bca56","impliedFormat":1},{"version":"00baffbe8a2f2e4875367479489b5d43b5fc1429ecb4a4cc98cfc3009095f52a","impliedFormat":1},{"version":"a873c50d3e47c21aa09fbe1e2023d9a44efb07cc0cb8c72f418bf301b0771fd3","impliedFormat":1},{"version":"7c14ccd2eaa82619fffc1bfa877eb68a012e9fb723d07ee98db451fadb618906","impliedFormat":1},{"version":"49c36529ee09ea9ce19525af5bb84985ea8e782cb7ee8c493d9e36d027a3d019","impliedFormat":1},{"version":"df996e25faa505f85aeb294d15ebe61b399cf1d1e49959cdfaf2cc0815c203f9","impliedFormat":1},{"version":"4f6a12044ee6f458db11964153830abbc499e73d065c51c329ec97407f4b13dd","impliedFormat":1},{"version":"a5f9563c1315cffbc1e73072d96dcd42332f4eebbdffd7c3e904f545c9e9fe24","impliedFormat":1},{"version":"97980df4d75192f66df770bb4f658000cdfa1956eb313a9137e9a3a8646fe258","signature":"7a49a822cb790c72be6db966c7f0d69c641479732f211b020cbb08bc4f30a3d1"},{"version":"d65e2b8cefd2f33224e518dade637e998db11b0eb355ecac472eaeae028de3be","signature":"058ecacd85566ea678127a31760ea37e7d03ac2f56d6a73322e873d0aa6b0a9f"},{"version":"56d890ddcdbd24fe7922ae61d25e20c13841e7a7b081f200c414878238c35d03","signature":"c9d08e1a10a3fb2493a80388d30df61b48cf36492d1a38baee14f64bad2aa184"},{"version":"a84bbd7d67d78a825c4c8086203db73b5501a383c71d441a2662118f25058a60","signature":"16c726346c6d566cc00aced3a44414807649395d858aca64fc34569583709690"},{"version":"415128b10509c030be557c34519c906bc7c29f55afabf3ac0c280dad98e3302a","signature":"228a47d85e97c163450a668ba3439510b6038b3531e2666256a1bac7e69539d1"},{"version":"32e32aa365ea101e6f501a957a82ac71f36362241583ff5d4f34aa74a037c88e","signature":"7cb1b9a080742123f5c7fe01bbe8cdebedc24c3fc0b6df33da4fa42bc7211a12"},{"version":"77b7ac0dedf6bf56bd7c5efcdd3e50753b9cbfa148f36d1a48ba89881b124d5f","signature":"78f739f5b91e1135aadb4752b0fbd6b6bad0fa86b3f0e889900982b12176fc2e"},{"version":"176420ef3fd1dc5f5cbefdd5e81e4976450d4bf2808687a97147cd40b547f009","signature":"9d01797abc1ce5d2b2ca095bee592fa4887661c3cb1603e9f126b767d68bb57a"},{"version":"ad7787dd126b76b2148468f7a3e9945aa76f6e109e4be609dbef35404c9bb334","signature":"cf533088d48a0208786aa83c93a31f571bf9ba04190f0706322adc45cdbad20b"},{"version":"1bafd63c35d51b2d91755295abd9787a4a3ed1e8c96b440b27f3409ce9b20b6b","signature":"c54d0d991ecd2bc4626bcbcf9d32169b09174c3d6cb7bd174dc944bedd504989"},{"version":"3da3e581b7023a2092ff0337d867db83766cb4a74fef22da3b4a7f02bbef7e7e","signature":"63b2f936da9faea8c52723d6b78ca09ee0bb769833160bbcb000be8b7cb456ac"},{"version":"b9a896843e293ae4e9560af9ef4c7cb999eb2ba47c629b4b73f81f83e085eea4","signature":"1ba2728a760e3d34d737964dc465092e51239587b874db79e71539eb8d271ca8"},{"version":"72155a0464029e06986ff956599c76a2ffc09c1636810a0ebf798e1207d1f4d3","signature":"63d31ca52e6e6071c1d33b659cc3550fca4657ccac514119f05bb30996f9b18d"},{"version":"994e51755e33de4e85d180542261adb695ddd7653d76f07934746be31196a091","signature":"b4e98fe21b2b7cca7ccabb5169df346479a0e3bb6bf46c25946174977917d316"},{"version":"9aeea19d222920ce2be239b71373207a6982639fcf4e3f9902c1cf777a3916b2","signature":"80923ea73f37baf4c06eb870a02060c68149fc8a8e47daddf6b55af1047d2b0c"},{"version":"7bcdf5a55ddf85072339f1f2af726763b75c3426e0c8e1ed104e24990883b3e1","signature":"6179a86622a28cdafe5d99fb99e1ab06b1a06011bb9a8ae9d65d4697e61d5316"},{"version":"fa698e0418b205926b7fbbec8b5c2c4ec37ddce72fefc7777e8904dc5c3cc2c3","signature":"37a718acb4d240ce0d45b7082a821b9bc8d9c523df47980aed0547887beafcb0"},{"version":"04e2d30a62563c91cf725e1ce85cfa64e2bf937bcba6501b156d625a017fffa0","signature":"e724140889de1a68b6fac45652942a37dbe94d0951da3249196817e9004181c8"},{"version":"829b5cb87df9dfb327efb8a4e55644d809f3e03de209067122b99ffebf284f00","impliedFormat":1},{"version":"11182196acb1c4e02a0046f0551a6096a06e24c04596d685580f54208c715e73","signature":"bdc9efa668395fb9851321350d62d507b1d77bd0ac73b9008ab116707a384821"},{"version":"0915ada0e2155dcfb7982fe7f19107451e53a83d4c33cfdd5d7738f6e7ef2354","signature":"8c8cfeee741fffa0d501b737893870e334e8ce8ffb111206364cb4e78cb489f4"},{"version":"8be3f833458178dbcb0d5025dbd09a888944448d51a08211f6a2d7cee0498edc","signature":"bb43b720c161d7aa620d5d68b8bd9769b9252d5711cb29454694e6fcbd8040ae"},{"version":"84a39ee1b01d3471a7407a841dfaa68765cae405745f0758cf7d43a172af11bd","signature":"f945bacf4200911254056f47a0c70a277ff6f7e7beaf074e28a4a6d284654573"},{"version":"904a442eea43f370d28ff0e40044ce07e25caae4901b194781723e610c601aeb","signature":"70325771fa3fddf64123a4f0246466cc124fd4e95e1c9099811d3819b9b5c3cb"},{"version":"02448cbf2ab203ced15be88a14165899f06b45543dce72b0c9be68c62ad4d3ff","impliedFormat":99},{"version":"8ab646541fcf5c09c55e4e1440a5310ce72de13b8a473e6bc775fd9531d1ab80","impliedFormat":99},{"version":"7cf75d220713bc4c2437cad80fdfb94fa2ac2d23b34643a5fdf2cafcb037b969","impliedFormat":99},{"version":"a716a3392219b2febd2b291d43921cf2eae7f9aa794d45da388d51ef2d659473","impliedFormat":99},{"version":"6d1b22dad9078bcf671d5ff5d03c9645ccecedb9816869aec74778489faa52f0","impliedFormat":99},{"version":"95c893fbe6896bc4d41408222e601cb1accd34d5d4148c37351bceb68beacc32","impliedFormat":99},{"version":"fdac0d6a0a042a2930afed2f017f5c5df5da9ed97495574b2c15e6592e9cb9ed","impliedFormat":99},{"version":"3c8ea23396d23cc136984dba37c7317f87f5f93e61060c09a2d82325d57ee261","signature":"3db3dc1fe56ab55e5bf0641e0e5e74032a2008ebbc61082463a131a2926f85e4"},{"version":"fec74e459caae4f2284b67d7225202c16a59efadf2c45d5f418de2963ce64ddc","signature":"3787c7ffb670ae4e74506253c65fd0c50cfc2495ec02f3916b192317b9012fb9"},{"version":"b25b281e70937b1c7a33e77309ed1f78117d95f1cde60e49703ce36c8b777b11","signature":"8463aea741cf53ec7f3722308bcbfaf4db65f71c46c2f23f0dbd142576f5d83e"},{"version":"fa8dbed00530fb4114906cd93f7fb55512c8eb9551d2f2e9796c69a4da4b594f","impliedFormat":1},{"version":"6d9e1b7a1fa967fb8505a5fa33073efb38aec5e7b75f2dc6383c9f84f3b5c0ba","impliedFormat":1},{"version":"bcfb48b8b140a568d39423224406b59f18f6be358bb3cdde4ecd9356cb2425ee","signature":"35f2abe6c86b8ee3741319a9d7a8c3eb0230e9b42f7bd7d543db7a94dc4e9051"},{"version":"66b82c0b61a8d0f2f0984435abc86b210caede3389ac457a1ad55d9a19f0f4a9","signature":"443b3d66214796d6fda04e0fa046dad726466a02057743ee694d2486b1efc4b8"},{"version":"2163a878e5f82fd49b893bd69074ef3fb52b74a9fdf90c63067f2769a38d92ab","signature":"1fd2c5095ed136c58a63e6ecac817212c6cc3f773b662b473c7ce944790f3cf9"},{"version":"87b3c4492ce251073dcb09e5637c230238773ef858b87ad431a2308abc1003af","signature":"b0312121e01123f510e034bdf1a40c38b0ef4e0d64cbdd4bb34d65d203c73a3c"},{"version":"dab8790811b360ea1d3a69831c2cde589afc83729e3c1ea537edf629881e5004","signature":"a4d7376b6ce00df8eae10620748535a019f90134cec3b7c1028f067bff0e5025"},{"version":"101ed81d754f2c6cfc43ef72dd384ca90f5bc6779426eaa4589f27e0bf3fa210","signature":"d1d2eb65ade8ce281ad03f2b32cb9e59ecb8c09099c9898dae1f22bcd630d00c"},{"version":"d6f4b9a418add129da3898008b6036a68dab54ec3997b04dd0bee2534d59a2fc","signature":"2bde30c5f8e10d5820a401c68e63e2b81a23077352c01977a10cc10a15f266f9"},{"version":"098341410fe68e4a369cdfeeaab54586a3c98230c631f608791579beb1830ea2","signature":"4b3f4c9228ab5427308c651bd192f9f627d6e7465d7e3eaf63422d09e1a2e187"},{"version":"66450277f3b147b473d04080b525dbba940cf75bce8aec50d0bbcc487321c317","signature":"37c0b6b7e7724598b96189a0153a958a908b1b73546dbebb7fceef0986e3ed3a"},{"version":"c4f363b279a3f41e59b61659cfd26a36497131139e59c0a6308c594c2ff54426","signature":"419996c73365008124de6ed63224ae81323de45079174bcac6b0dbdfc0108b44"},{"version":"c83b7f75cb77196d9dbb5ba8cf04f98da7fb4c6ce1fa3671d9fa0a2e34b01289","signature":"611acc6aabb75529a70459d172d44ad46a29d7f4b560fd049c201d05b6d0e698"},{"version":"c92c5036b82435bfc5084da97ea7e487c377b8d823a08450321c743e81faeab6","signature":"dc1097eff258d192d1b76e71f09eb7a5a8c9b4776c6e8ce8af855e41ce73a274"},{"version":"92dc4e8b3d0e8dea1f5abbe30adfd3910a7be441c12ed6fadc738adb59f9bb2c","signature":"322baceb1c9f45aedb9e5100ebbbedd07a164fc03ab9e98c462e32b41cbdc90a"},{"version":"8b0a6e910cd7b149177db13559d763f3676b37ededa792c625b23c3fc455f5f9","signature":"fe0cea76d1c6a718447bcd594059ac0a4c7f60b9452227e9bd6af7d564519f45"},{"version":"20a5bbe03c428dd68decc6d79d27beb557b46b556f756c5359825c0ddb22f503","signature":"3f3f0fb51d3c7c9fbab033f6757b786168283559ba1e6649a99010ef60aada5a"},{"version":"bc766171f81681d21c4ace62fe0a93a878ec92c0b1e11a87da0cb9e9f15ebf94","signature":"93799ea217ffac697e3222caa0d5c60771c1cfea1136666c2963797f10d09ce4"},{"version":"7cc0ca04ac330f9f0808e33e4595a1f1961b10fe6b3c8beb0ac0c45967598564","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e839aeae55c3607f92b30ef0bf34b403a0874685ccdd1eec2ce3d284db40bfd8","signature":"5ff40a8d87e993b7d9798cfd183cad9e5cc58f9e4334ce2b76f69ef9294744d0"},{"version":"e219f6386da776b95ebcbf13d890d795dee10fd649006ff995814016fa85c77f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9b3281e5164a5a671ee8c995397cf4f10d3aab5a411eee995d7743b7babe040a","signature":"12ee1ad5a651c4484cbbdc6ea7d594fe1f8adc9684006988275bd671f510f581"},{"version":"92a902847173b9c651ac667f7d47785536063a1987da48ca414939218a4042a2","signature":"8124c31de224c31a76019e9eb48d1c002aa3746e3c25d24a7c61a06b41ba0787"},{"version":"1d5e1fa7e4101ffd4bf5173a0d4e83a0d399180aa5cd7cafa6b0e58a9ca3888c","signature":"50f602bb3c9cd89a1879bd9432e5bf3cb44f916ea27e4975977792b9bbd1b9c6"},{"version":"d4b75cbf9ba428230d1f1244b8f110a92d58d98dd634a29558f570abfd495577","signature":"b939fcdfe4d3196385a14b639ecb5b5f42928fb9b185affc8c5d2d513ec4f565"},{"version":"e1f90140453b0fd52c46cc6ebfec2ebe754483c05e50dc74caae321d41e7a9ac","signature":"da215cd8311e3d53ac952d9a12e0fcebedf7d76b9f5692525046d7bb0ecb1cc5"},{"version":"5563052849dd3f93ba63a20568ac06acf6b8e15c0b57aee1bd452e7e4b1d5d95","signature":"91db33413af7e79f8f5639385fa3fa68c2595993f3f5bd6d7efaa4fe19dc94bd"},{"version":"f22a38020548019be436f7d33fae243aecf68c9e068dd7fb5d88ec31fcfce2c5","signature":"990a86a4c51ffd7c3c146bc5b5e4f2a6eb31f8a08186da00d699ec62be38d14c"},{"version":"c4c7f14ebace079c50bb480c726a6acb914dff63ce2f4b267ef00a0e8e23ab85","signature":"c6e9b1f6d690ffae1f2c5f84c90f6879a049337ef380218e28f39908db853522"},{"version":"8ea6e4170db598a5280fcb3c55e7cf8b9526f8b4dd75008619360553051e5ec0","signature":"1dbbeceed3d29447d43642fc2aa512790ab62f31ea04678bb7bd0a40d158d24e"},{"version":"5a476bd9e815ede305081eff46a693f62f8a4d26b25c424b341c90da5820bb1f","signature":"8a996fb34f36d80fa98002a255626b4beceb48c0003273aa5cbcd21ccee92eea"},{"version":"98aefa362481f9a5facd919a238e0327b3b3f1b7edcb831c797907a553f8dc02","signature":"d86c8b7a6c6edbeb6a14c73aad61eec9d13cec0040264ef13c78a8c2f7ecbf43"},{"version":"0dd9aa0bb482736b3631ea347539400beef0ad854306090e828f6209809c48c1","signature":"7c56fc0ecedef369430a6cc78f797e1f72ac6aa30cf3861bea222fc9efe93d49"},{"version":"6a94f46ffdfc432b22ff44c13aa3e1c471a44d8ddefd842bca19cee8d8b1f130","signature":"d1d2efd128275df07279bf887018192c1b38c0cc2aea96243de78a8e92bc30ad"},{"version":"cdb81d640b2a62ba620d241ea08976d651695b27385643b461a6fadb76e07e27","signature":"29afa7f4d2f64a222d590227109f01361ddb9c6588355096f6aaa036b9d67d05"},{"version":"0cdab5351929ad0e2d94ecea2b8d60d11fcdb153ff2355ab9c5109e84a697ddb","signature":"91537516c066b5bda3446b1dbd01a6b3ff342925cde014d5acb7b6f8b99ed12c"},{"version":"d09b5dbb314f199a0d7ae3c2c2a9c7258b611f3f28d1cc83c2685d3c738aec3e","signature":"068d0597a17af822c2ec3af9b1c2a9b9a26c0a4387eb66f655a0f1d26e36ba84"},{"version":"29baa50d188f4ca03d95d58ef52bc20faed12a5500b4080d56c7588e207b6e5c","signature":"942546eaf5ae2d0c5948c6d25a748fba25b6f4d760911ed595b40f043fbae102"},{"version":"56b0cf470ea14b42ec6f4e622a52a45b95bfc80cf2ea52ea62d436cd0ef82c98","signature":"65bb767048368601ad35597c54f6b112e3147dc84fc338199931af5d57b8fe95"},{"version":"1ac040d4f47be4c1b44b4a521c7bc1264c97371b3c51bc2170326827fc8a5406","signature":"dd24f7d41609a7eb1c990ec2f7d7cdd63a419e355e6294050a67c029bdad0d78"},{"version":"a59e6af4854abb1a7f69231f6252836dc64035f9247f7976507926a66bd5e998","signature":"ab04dac1f806941027328926be16232e21366ff829c3be737572abc646b0ff3e"},{"version":"525c8f6dd7c9d90d1803e709f7701de0c9275f6cbcb6b861901de458780b8571","signature":"519c584866e4d804355422ce52d8088fda0124969a9668779f10b0d50e114153"},{"version":"c9fbc7d96e67dfaf8156b6aad26bedf9b6d699ebdec4175c3c47227e55822d21","signature":"daf6a8dc2319ee3b3da8a84c408542688ca901aecabb3c195e2dc54dfb44b8aa"},{"version":"d271df5506aa5eb845d1b0f717c2bedefb048a9fb9c6fd46360356e2d038feeb","signature":"5c975df906b720e560dc80cf99f12cb2763329a0d5c42ffe3039256137dc3a70"},{"version":"ad51a72ed0c2dd85008a26e360c42424b843c888410dc5998957f5de4b06f0e3","signature":"b4698bce6f7a4a17593cff994a72d565662855439386bcfabf5f0335ea8d4be1"},{"version":"b7ddb0a3428efd4dc28b6dc8acbb9a8d65f85b6ea623aac21cd3780f6d0fab83","signature":"97ebafc9d89ce29d62958a732cda28a5cd408a1257cfcac0d253584fcc850e6d"},{"version":"ac666e1afbcb757b0baa820b0d647e1f3db2d6b4b3b55d79eae265645919454d","signature":"4e69e7b65fb23298ec08b6e0cc86692fb6602df5473948f46baf219a07967697"},{"version":"828355649d02e47c03fde21cd7af40ea1311f41a93f27b89cb6e8a8b4e0ab1dc","signature":"5dfdc8e2a88f5126407c0af9050602ac7306bad7cc807e5f5b23b6fd104dbd48"},{"version":"28fb9f63457891e904b43acebe416188045c76ad70730f3b34c62be1b94a954f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c9483fc2a9d1e8b442e1e6ed44317cfc9af2b763960777d94b663deb2d761424","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b0534862eb5f90b5d89d31838944141c6145149f9098fe69a5ac44d130f8a527","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"00e644a0e3dfdd1461176b0143129c9a12a077507c114185c51e1b9aaad14652","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c6b47f0ea0108d741abf731f728b357a8370c238ffe73fcee007619484c24356","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"908b7b3f2c71140accb3333fa49485ce1ad10ac4e14faab10ed51c0c28c68782","signature":"d19e4f9294c2efb124088d0e5dc71b2faceaed9cb614ef974ab0c3b925374891"},{"version":"b15d5906d9090803407e10ef7858d2c7470dd78c7ed9a115d960ef9017904629","signature":"2e24bca723fca55268895430a62f030a00435b910ee7875844d127090673f98c"},{"version":"a645cfc27245e2a1f3282f0a93a86cd43c81edcf4795cf1f0545bdb28235bd3f","signature":"98d26fed15c1969891bc73c9dedc7278cdbd15f3afe3e34efe01c27dde514ba5"},{"version":"4f9ac21c4ded5c60695b528b367d889f2407d13378e2cd989219a5e85d1a1037","signature":"2c8f9281a7a4bb4a77894f0c4f76c50888be09b23ec04fefe9bf84c63513524e"},{"version":"80a3a9561b1e7ed1b11869acdbd73d0b751388fbe37d6ffa75cb7fd7808157a1","signature":"db1016666977bab29ab1854fb90c9ed76f0632bdf412c73c6fa81412a02bc5b6"},{"version":"db70e304de572bb7bbd27ceeb3d4ff9a5a95de389afd61c6d4af9f5725021de2","signature":"d4438e83c3a3e41f54007253c009f863e491bb7eef87d4d3d46991f8ea62ec23"},{"version":"7437a1f294d03c63c49ddbf214e25ab9410424b79b6dc01fd9cb3b23e0c0be06","signature":"d73e7d9f551a968dcbd471ca03440ca263efb053defa3a163b94c429ac47729c"},{"version":"e2e250a24ea41932c838b2d5ccf4bdf34e0676a21805a6c60827f4abd4afa641","signature":"ff960dfb3d25c7584dbd000c154da20bc32aaf43a0b47f2e05e12628fda1e805"},"6958e241f880588015372a690454d0f7c0727b78e0a9882f493e2ac0fada857e",{"version":"f16c4ddc807f1a4df16ca14d4471c342fbd6961c6ca2c88847a256be5f4dbd26","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7a732b03d5a0bd9c071d5887794887796b8a5cc30decef607a565735d8cdfc0d","signature":"f1983b8ef21692b453a2ef5ee21b5f8e5c32fea1e87bc833c55c781c109e45bf"},{"version":"c2d4dfa9bb5bbafa31b4423a78c2df02ccb51ad3f4abe7dcbbfaeb8dcf2cb82f","signature":"90b39c231c33d05240cbaabcfc21d94f68e05b5d9d2e972b644363be2133bebe"},{"version":"94adbb305113a8e6572989713200d1eba425e7a01443f1d02f4bd9a66f7f4fa3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3cdbef5d6bb0c6201d2edacc2f930322c36b9798b90131787398324b67bd2130","signature":"b84e31232011f6ac9ded7e727871f7c1ece606d8179950f791bcc03ac3c03b6e"},{"version":"3f8860ee39a7c1ca7824f29d948eb3f2160caa4cf4b1df9380a9b9b0c1ea184f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2d7cfcb10c36e23dcd60cc5371dd2c4716bba7950da92d836072ccedca2594db","signature":"de8b89e8f7e1489acfbf39531ee1eb5807be79db548a2ae53c4eaf740c0acb35"},{"version":"cbcd7e3639eae69860983466b671c160570ff8bef3295eb9d9cf3a918f7c3924","signature":"22039e2144d09a04262d19b5f0e4237ee40bec8322b5c9574403a9103af52044"},{"version":"775de23fb7da19fc3785f7649402d6d4e64f02b73819c2afe1c22e88e2cba6e6","signature":"cc9a2738a0b247ef64248e8bca32129c46b94dd155f2cd961eb59033964022ae"},{"version":"8cfea1c345d6bc3ba4d7d7ebda9ba3ca2186b0db9838efe6c79f4059a9618f53","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"298d271a03732dc27842cd85a0bb7f015147d6cf6cfd741579b1c40a8460ed68","signature":"d273813f1a71341c5f482788561acb719f12c65fcafb4f36423a6d409856d472"},{"version":"1fca48a9c511929eb58026762cf0bb7fac7a48488ca78ad1adc8414e2dcb1060","signature":"9e2bb88f173d3209e25d8856088cd88006b416949bf633f766578ae5b18f8488"},{"version":"fd9ff018f992e9f8f9f9fa2dfc37b89647cdc422a9220feac46a28d0c34ded90","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b401d5f995c95a3628dc388d0b8b1e33053a90bc6959aa29f7f40b55866d66dd","signature":"6e2429521c45ea225ec2778039723f2405a5c5504d57e906f5ac9d2a986ef4fe"},{"version":"89a59cf51385bc46238630d496c8954cb98857545ac63ef595665d048965d71c","signature":"bbdcb92189d07c0439c3828e5aea552bfc8a01d782608d85d96264fe292d96c7"},{"version":"123af1de4ea296966265ebcf6b6ca3c5c4ac8e3548f0c3fc88e9fc4d24f6ce7b","signature":"0cdaaf51916fc5c085e3fa90eb61001fdae7ce8819cae1adb20ebeecb582795a"},{"version":"f54cace057ebdc96d8beb876366a151fc354db93a0e0ac2f6215c9c5b4c88bc0","signature":"d4d3b854dee0def611af8377422b5caef70f3b8c2c2d10ee3bb9ffb97d51cd45"},{"version":"404d9825e0fe3cc10db20060d068fd4f33c85c245ec9d2f99a20d05b02291b20","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8164feb80d24e40d470ed6ffe52ff1201b15f27f3edaf48c3e7952f30c3d09d2","signature":"926e7f9849074dc409ecea79578349b5fd3131b473650d39d4b54190331df9e9"},{"version":"47928a15ba8a058a746a3fe7113775a2ae59003fb2ef6a6b82abcb72660f5717","signature":"20450b8a83fe349aea0ec611e1cfc508218d7b04eaa6e5d7e54a5e68efe7b174"},{"version":"1318f5ed0496352fb48c4c03ed01567db651e1794260df1b2da1e146a1aefab7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8732b365c97738e1e4b1da68affb78510c3b221043a2d7bb70ed76e2cbc476f5","signature":"ad2341da1c8562c9efb6dab3ec28be204fb5619a186320a26a6a65630800dd87"},{"version":"1a3e431b2f35ad9227aaf10b60ab4e0d1d736c45750dea729ebc84a96eaddd6e","signature":"1b1efaebf9198894a414851e919942c6ac8e03143ee26fc0f1ede061b98b492d"},"e6a0402ea87bfb937cea0e710472da29626189d13dbc6467c9a6814d7eb8fa43",{"version":"788d21aa71ffa4bc6d8b4b8aa7fcb795580e172452e77c84b20532863b3d9077","signature":"8c2a82eee7bedd60c6d52866d5132bdabe86cbb209f39ad04f8c3cf502a0afd0"},{"version":"5e375dd5811641f19e1d39189456db4bad92e32135084b5f69ddf5ea77f66cd8","signature":"d6a98119ba90f6f7583274d639f20d153cb98faf1abe1a7f75d7db6743bf5acf"},{"version":"7932a45b695ee2aa2f16d1275ee5f63213a5c9cb02b36d4ae77eda3c4cc7ac26","signature":"1b55eb87820809dab31fa1d0927f815d47f40a8c9e2190dcbe65bfa95ae8a447"},{"version":"05148c1f5633979c746685ebfd4181fc653789264e4d53786ae19ee2998d820b","signature":"97fe0e68b429cd9f38a896d30fdc73dbbd3cceaf175f12a024b5185d91d24aba"},{"version":"183a3f515ab3afbef80e474f9335568b1fbd853d79a7df05b1287347f29f5b11","signature":"13a68931ff0d91a64d7cf55770aa90edaa7673f96cca6fe42a937b6a51337a94"},{"version":"a8b2407ce333b90202cf576211457a3d5d0ef67dd7147b99701de3ba29602172","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"161f871f8102ec12fb0f8b16aa90544c4056ee4f5eda4c6b8b8bba67cf5ee451","signature":"c4c000f5db2334ea4e2bc0b9bc437d27c292ba078ea53202378b878846840865"},"9eed204f26aed45ba513a001aaa78dffd4bf0194ed42fb59fa4a5b48dc382767",{"version":"f8eb06c740f01a7d3cc3f3b135a498b412dc5499f9315c16610872e1b63d4068","signature":"f592c7e333a33b4e5dba58516b31a9ba2c3f5639c989fce88351556ba49606fc"},{"version":"8a09e6bb32bf2c7f677a94e3ba439c7c9f6552ad8bc2e1dcc8325fef50c7388a","signature":"571d3448b7e5dbb700ec919745d70b84c0859909793935026a8331b7666d91ed"},{"version":"8289d00aef316131b835c6fef2227e2b247641f3ecb434a3c93bb5692c7809e5","signature":"9ab6693aeebded592e13762f5108b1964d6edaaf634cb9a189df79643f88f7ad"},{"version":"9353b2925e9efb77e0775f5f0cc2d0eeeb0cd745413ada34687f191596acbec2","signature":"03da542307af2869e7b2c1de8d1237d05b584cf0acf36c9d40f8e994f21adb2e"},"5f1ab4340b3a3f3d2c88167d0b98d1d8ae6c6d4b1ed845f25c3069d7d1b902d1",{"version":"34d7855e8328561594808d2203cee5dbe16a446120ff63ecb9dfa2529fafcaff","signature":"181c39a0a8a88631f8d29f5abffa3d154ca1a5fa46b87bda27b690a424404325"},{"version":"75b1f4a95f21e55792f113a90848a777b5d93357d59f30940fb87bd5cd3e6c47","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"eaf4a58ef586c168ad73bf0bf2e4fc7b50b0207046cb03e70f064e23cc7410a2","signature":"dab2cd6f392f32d0e94793582857c06a2d1fc79ebafc631e2b80e4b0c40778c9"},{"version":"08e69d30c063db86ea2221adf2282b1c118723cec16131cbd40024f3f43e5a90","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a8dcef5aa8dc0cc898d702364b72088c97994fde70660b8fe22d2ad622beb007","signature":"2cf5e020f8143231e08aca82ea1647287a23472ef15d1b54bebad2064c0ccd10"},{"version":"b8e47815afbb0381e41b1580893fb527078db40eb65cabf8fdae4b59202d3ad6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"95f053ad6f9e8f22fb9a0309e14a768302ff5f8072b9bc24b8decdcbdaaad0ff","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ec9e46ec9ebcf2563a7710cc683300002129826c6892b86fbc905153728fe0be","signature":"48c60fa731386066e73339c81976306d6734f7e3f9040b52a34ef418c51c4280"},{"version":"615a4c247058e88e2b3c498fde704dfda190dacf8117a05a74268a9767b9df24","signature":"d4b8a67fd5df8d739582126306c6899bcf7429238696abe8b6f87915d81a64f0"},{"version":"afaea598c95b1ad2bd0ed30bfb6ad867d78bf021b73a048b8fdaeb90cff22d88","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6d870a6ebc4af392ccd2875f99d1dd4c90db167636e4b267de2e1d3ff14a6104","signature":"d02c70b928a2b77ba435d387595ed3b27e4466e3115d7b71a79c36c592238798"},{"version":"2f79a63626adba271a461cbaa34c976c26be8e474095b1bc8a80ce6e4fc68536","signature":"019c10e3a4d1413779d87a055ffea70d5dfd127b4b65a51cb2e20fca9e8f1f66"},{"version":"ef68d70baf9635137535142e9df63e515a9b8bdcf9906d6edaaf0e93313ac3aa","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d1d17705658519c7d57ce6c5cf95f0c894c2dd754ad1fcaeb1e2c4207d54e6e5","signature":"0bdd8b0d06cc0910bf6a36f2bdf87794634ab7014e178c7a7eb928437c6e5b76"},{"version":"b6f4d66c4cdcc5ca3d8345b7a33598a43c66721b58819fda629156153d89584c","signature":"122cee24c6792a6328d79fb0ef76cc48d82112306037e5fb7bf78e3a2f4367c0"},{"version":"bb040b86775b25fc0848d43e3cc03e1b5cf4f00c40cbd350dbce010c06404df1","signature":"6d46cfeb3c048590784e9c099f0dfb8de954141c9fbf25f6d9ec87981ebc6fe7"},{"version":"d20eaead87726a364899203678c3e81614bfc368630e7a38ed0008acf7f7c1f4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7859b822b52969cf5421d8d07df464fabd68c67579733fd51cc994c6f3b9452e","signature":"91d869ffb8dc40ecfa1ed7675197ed893ea5501d5eab07a48f22dbe192cbd9b0"},{"version":"90bdcbca0e0bf6c2b0bbb00673a340a12647ee9e9c0b7ffa08c1cbee51683f6a","signature":"081dc84b2cfc91d910771f621446c86f99ef16cba361a195bc5b6415671e3940"},{"version":"60971dfd52c63724a6ef0b69b35f16973a98724f931ed342993ce25e9ded184c","signature":"633831a38db2ca415a4b09e9ed29b5015715a1cbab7f3b58f10d8227802fa388"},{"version":"ee1f2990fc6dacd47567a6075f7266b0af2628940d4f39a14f2aacf7a0fd4e85","signature":"7deb1227fcff5b438b3dc694ab262f7801f66701f3a0824ca0ac060c5bf8c39d"},{"version":"50040e211ffeffee56ae1c7ab65e604dc0dbe3f91dbf53ff5d6d31d0953da70d","signature":"bc3902ec251a79518aa6ad225a42563566db06b0b76a0fc66fb0456b2e5cd332"},{"version":"f3094aca88df33c32dd8c3bf7a2d6b00cbd62824f56d83d5d5420cc0bffbb3c1","signature":"81497943bee616a246679fdfba6e2afd14a1357f5a39f36e50aabc90970f594e"},{"version":"4051f6311deb0ce6052329eeb1cd4b1b104378fe52f882f483130bea75f92197","impliedFormat":1},{"version":"7b6e8d32728e05107c573c5dc2b6fe9cb14332dd7c82fb530093a840d6b59dc7","signature":"22146890ab30bea45bc289ccc48192249fe1cead53510eda7d9af2b09e065189"},{"version":"e7c2f40dc99121500ad108a4f86541d29cac105ed018f994c7c5a2836e77b257","impliedFormat":1},{"version":"90e930283286ab117ab89f00589cf89ab5e9992bc57e79f303b36ee14649bdd9","impliedFormat":1},{"version":"6d48a6c907c668a6d6eda66acec4242e367c983e073100e35c1e234c424ad1a4","impliedFormat":1},{"version":"68a0e898d6c39160f1326ef922508914498c7a2d0b5a0d9222b7928d343214eb","impliedFormat":1},{"version":"69d96a8522b301a9e923ac4e42dd37fc942763740b183dffa3d51aca87f978d5","impliedFormat":1},{"version":"ff2fadad64868f1542a69edeadf5c5519e9c89e33bec267605298f8d172417c7","impliedFormat":1},{"version":"2866ae69517d6605a28d0c8d5dff4f15a0b876eeb8e5a1cbc51631d9c6793d3f","impliedFormat":1},{"version":"f8c4434aa8cbd4ede2a75cbc5532b6a12c9cac67c3095ed907e54f3f89d2e628","impliedFormat":1},{"version":"0b8adc0ae60a47acf65575952eee568b3d497f9975e3162f408052a99e65f488","impliedFormat":1},{"version":"ede9879d22f7ce68a8c99e455acab32fc45091c6eed9625549742b03e1f1ac1a","impliedFormat":1},{"version":"0e8c007c6e404da951c3d98a489ac0a3e9b6567648b997c03445ac69d7938c1c","impliedFormat":1},{"version":"f2a4866bed198a7c804b58ee39efe74c66ecdcf2dfebef0b9895d534a50790c4","impliedFormat":1},{"version":"ad72538d0c5e417ee6621e1b54691c274bcacaa1807c9895c5fa6d40b45fb631","impliedFormat":1},{"version":"4f851c59f3112702f6178e76204f839e3156daa98b5b7d7e3fc407a6c5764118","impliedFormat":1},{"version":"57511f723968d2f41dd2d55b9fbc5d0f3107af4e4227db0fb357c904bd34e690","impliedFormat":1},{"version":"9585df69c074d82dda33eadd6e5dccd164659f59b09bd5a0d25874770cf6042d","impliedFormat":1},{"version":"f6f6ce3e3718c2e7592e09d91c43b44318d47bca8ee353426252c694127f2dcb","impliedFormat":1},{"version":"4f70076586b8e194ef3d1b9679d626a9a61d449ba7e91dfc73cbe3904b538aa0","impliedFormat":1},{"version":"6d5838c172ff503ef37765b86019b80e3abe370105b2e1c4510d6098b0e84414","impliedFormat":1},{"version":"1876dac2baa902e2b7ebed5e03b95f338192dc03a6e4b0731733d675ba4048f3","impliedFormat":1},{"version":"8086407dd2a53ce700125037abf419bddcce43c14b3cf5ea3ac1ebded5cad011","impliedFormat":1},{"version":"c2501eb4c4e05c2d4de551a4bace9c28d06a0d89b228443f69eb3d7f9049fbd6","impliedFormat":1},{"version":"1829f790849d54ea3d736c61fdefd3237bede9c5784f4c15dfdafb7e0a9b8f63","impliedFormat":1},{"version":"5392feeda1bf0a1cc755f7339ea486b7a4d0d019774da8057ddc85347359ed63","impliedFormat":1},{"version":"c998117afca3af8432598c7e8d530d8376d0ca4871a34137db8caa1e94d94818","impliedFormat":1},{"version":"4e465f7e9a161a5a5248a18af79dbfbf06e8e1255bfdc8f63ab15475a2ba48bd","impliedFormat":1},{"version":"e0353c5070349846fe9835d782a8ce338d6d4172c603d14a6b364d6354957a4e","impliedFormat":1},{"version":"323133630008263f857a6d8350e36fb7f6e8d221ec0a425b075c20290570c020","impliedFormat":1},{"version":"c04e691d64b97e264ca4d000c287a53f2a75527556962cdbe3e8e2b301dac906","impliedFormat":1},{"version":"3733dba5107de9152f98da9bcb21bf6c91ac385f3b22f30ed08d0dc5e74c966f","impliedFormat":1},{"version":"d3ec922ddd9677696ee0552f10e95c4e59f85bb8c93fd76cd41b2dd93988ff39","impliedFormat":1},{"version":"0492c0d35e05c0fdd638980e02f3a7cdec18b311959fc730d85ed7e1d4ff38a7","impliedFormat":1},{"version":"c7122ba860d3497fa04a112d424ee88b50c482360042972bcf0917c5b82f4484","impliedFormat":1},{"version":"838f52090a0d39dce3c42e0ccb0db8db250c712c1fa2cd36799910c8f8a7f7bf","impliedFormat":1},{"version":"116ec624095373939de9edb03619916226f5e5b6e93cd761c4bda4efecb104fc","impliedFormat":1},{"version":"8e6b8259bfd8c8c3d6ed79349b7f2f69476d255aede2cd6c0acb0869ad8c6fdd","impliedFormat":1},{"version":"14d95b7d7f7b5a779d493d060f53e163b1a74787d6f9b4ccbe8936ca01dafb5f","signature":"5727ceb9e1b0c8cb49fbc478c9bfc4e9ed07b9dd137121f1c09debf15bb37b59"},{"version":"bb496dc8024d753c28f375a4c0df0002dbad2facb8e548f27062a2655414db19","signature":"1da3635633f03cbe281630d2314ae81655a7a61783520e93b82b0bfe25d8e15a"},{"version":"190c4ff7c01763d5b94815f7f110cf34bfb66c0a9dc2598df8fa3d9d0e128f77","signature":"15c15f737b3fc3aecd1b523378681a613ca49e3b4ebec59c974a5581a795916e"},{"version":"99cc33cda4afbc875fc13754b46b8179528179612fbae124093db14445b0ba8c","signature":"d559519162225e563c30f461537f694d6c3258441a87a2ac34625c0dedb50598"},{"version":"5e8891fb9927875494fc1b20f2e743e12421b18aef120f609d89b15ab6c35132","signature":"efd9fc177f6eafe832b1781a1a2e63282f7a5cf31ad314f0bb76351af765d6d9"},{"version":"9930757cf814a58885f76cf8399341c6b0ed7721d9bb14811134d6659419fcb3","signature":"dcf266c1eab20ad321e5bf1bb72699681899711d3a908e179f939d1edf24e013"},{"version":"5c6fad27889b29555ec4270fa34f6d318520fc36e3c368efef6988c2240f943d","signature":"e569b4fa591b1a8f2e8bc65df628ea7b8314ea9e9fc4de877bae1db7073f82d3"},{"version":"13f1766e906cf9cb4f91979818fcffdd4d57508d73aec9f2a369bb5c13a6e749","signature":"2bb5816f801dc67b86549dcb0c662be56b248c5dd1662c1042691c7329148f98"},{"version":"4ecad7680faf9f6da12ba4db55dbc6df9eaae54bd199f145686ef897dc7d2ef1","signature":"de8287721228df0725bb5775da05878176c0b7788985dd9784efabbf520e15ed"},{"version":"2fc9963541991db69ae93c38f5170a229671b0f0e7170734e4428b4a8a2abd38","signature":"1f957907589ccd8879d7a77c1d5b0d478a64d866f3cca8de4ae23c1fe26940d1"},{"version":"67598ed7b69f803aabffe4f3cf9f85568f40656f48c71b0ecafe20d1b1f81eea","signature":"fa7a41ca696b949f45f852191cb2f159ae3039d65354e0595606e496012b1168"},{"version":"d6ad5279f8d296ebb50264aabcbd50a5296edfef7f23ae4c159579028935d119","signature":"48cf5a32fd77ba27774c29824c8d1bf27cfe16081169b1a013e0874292c3709b"},{"version":"eb8fcd3ac7e251b9d845d1d6cba5c742f034427219cc1df07307cb4c75adbd06","signature":"f350851978868a72a6438216754895a618bb6e28e72c468cd95b38b6e7df88e6"},{"version":"80755d06dfedd338711c76c0315a65e31d8068555c0bdafe2d2124d922149414","signature":"aaeb521b6f9317f1358efeed044f7b8c9da2de643c9c444c86efa4b5974707ac"},{"version":"684cd270a4c9c63aa2064a89f61ac68c1dc88c2002ac7a5ba2162c86db878ff4","signature":"7edc93fe90f8fabb25092054cd2ba3454b5665dc1470229cd49300d2787f9256"},{"version":"11ff2ef106c72b4c29f40b6bd3bce1b367c743bc9284549e2bf0886969ed2cee","signature":"bb1bc2267b12d61504f42bb52c6aa47c88776574ed3150f2bb819226113d9d14"},{"version":"9c3809d98729933f6f435861b5538d484fbd667793d2089b8e2682c285141735","signature":"6905f829492addc100db593a31f563dd47f1c0c3f1a2b9fd5a35e2464c2aaa24"},{"version":"15ae24443465da312b873cd4fbf2503b5994b036f287db113297528b33d3b8b0","signature":"0fafd7d6cde3e37c7c4045f298b00355745dcb2147c0e8d0f1240fba867274de"},{"version":"4db3d1085355c215620263370b673744215deb126abfa2d4a1361a9f87efcd9e","signature":"e017494fbef6d93da248b22d25416f95d8412cb4a4e8cb12c7e64495f16de3ec"},{"version":"4fae5d378e41b4dbbca530b595619f801eacc5538834bb39a681554bc1f9511c","signature":"5405216cffa69c9f9a5fcd8feced66b22d58045299c9fcab1c802d538e8bcc2b"},{"version":"227c62ec248e9072b199f9bbb88e10cc2e57b7c0a36c07587a063b2fb8191b97","signature":"c2f157d50cb6cd3bb53df17f7e4b15a6597c8a8544ce36976307b698b45d15af"},{"version":"4d5f0b37853e5b348cc7f4a50c7e62f3aabeb59eab7b15280e61a2e2e95b3d94","signature":"d3e65013cbd33328df76d080bb674401fc80b1880b3adea79fe4f49569c3767c"},{"version":"4d8d7e049c7a369a07b41963903b7041bd8c88560b55af2b4b6c4fd7be645cd5","impliedFormat":1},{"version":"83f6b233e11c9f2855f7f318f608570e9a45db007ae924278e7a581d7ef99b35","impliedFormat":1},{"version":"015982f8608b059b38f287afb9e84d79f65eef4deabb8b1ced73b6869253efd1","signature":"e8b8e503a66283a53cb5197650eb1a6db822606f5e7216e19bb41047a2092bcf"},{"version":"0da8f1531846a6ca595707187e5a9e2ae7193ba426bdf3738a707ead043e4fb2","signature":"35444513a0600f3a35f1e67267dff8913a3cd02d8542c3da1ac90014dd905d8c"},{"version":"deb4df42f640706245617d22c38500d0d24e34689f405224004477c47b30a287","signature":"84225d531b0d673c7dee0a7abb7592e937c207fb0393e85d8e9808505e415642"},{"version":"05196723f295c088f92bd93f9edf2f9d72a0e6fb7a468f55aa270214519857a5","signature":"1c7c3f06b140f7f31f69c3f2a6f87659c1a487c552bc733f9de6ed963779a17a"},{"version":"01c55cc84a9a595b413f1fc1b25fb370b01de9098ff3d4d893451b6f33202b8e","signature":"93b8ca9c414deedbabc6f291b8129ec289fb392e499d0c4df2d9fb0d91263a10"},{"version":"60baecf2ee0b36e0b6f81536d77774d964bde3e3975c00328874e3b564a97e9e","signature":"a37134dd3223c23184711cd39086b2d518c984efc22d9e205d8155a9544847ed"},{"version":"c9e33faf41a15688f6a3d27f53167aa8238b5719e63ac75ce0f9bc608c7a429d","signature":"699f3f4cc048530f0e94b4e6c2c41e762eecdb2817c939de22b59d49b0029a4e"},{"version":"9394d990a82ad3db2079ea7b8f2d820c9e15e8b5131f7650a814ffa3c43f82c2","signature":"6ee7940135a66f481d7ffda0b6abc844e5d61fe14b9dc7866f9e0d7457d41d87"},{"version":"f652530413999bd6ca9f948e5866a365e9b22357ead5503f3bed18123ea93dcd","signature":"e4c5858df5ad3636f5bf6e13c2cc3a879e778ba55daca10f807d9f349e3e077c"},{"version":"f060e1946eb32ff62b101bbac21a6cd02835440c0892554566d0dde5d4838cec","signature":"036240f98ae8d5e07ad6f648996ff0630d6112eaee5b53fc3b309a1acd7c0721"},{"version":"7149bf3befb8b34de676bb8151a6c452b899500e81ff5991570e87187410ab6f","signature":"a2a12a3503d5bf9d004de85d49c7707a8c46b30953343089b0c7b14f804a11c7"},{"version":"5d30a3d967f4b5bef9a304a63de59081a22cfd199f01173c04903ef8b3f79771","signature":"81b5b8de19882f9eb71c2f0021647ccb258b831a32404136811492d2fc71ce34"},{"version":"72382689d6ed60f25f6db3887f8f6df7be429d8e7533e4309b9ddbedd5deefed","signature":"a00dfd5786abefb744f2a2083e60a1a18cfefc11400b1cab42e63040430dd27f"},{"version":"f655ee0bf0f6a46b13b8dbea184cf25547a6328ad29d2382b081ebacd88e501e","signature":"d4f22b5386cf23e091c22e4f0e33a7a9c0ff3a245afeaa97840bf05e7bf91984"},{"version":"c993179a2129274a21e8926cc3b0281338a695ee0e8dd93df185a1ed66c1d401","signature":"1cb18627b01c1cd32263f3d582b16ce629a2bad80e2ef8a1c9d3263b05d0544c"},{"version":"d0ac529320dc415e66f66077248585f8a33f093de52186c296698451b5b1e712","signature":"c745247621d6425e3a4bd08dcb43b23754d9b3c6f3ff8072775566b93a15da6b"},{"version":"9c6cf6f3f66814d3d66592523e2047cd3e6f2430f6ac1694eb01c70d0f51d079","signature":"66247c65872f191626b989b5400c0c1f13547591eb4dc827ec3dd8c8e768fd82"},{"version":"29074a158418a682faf7fd1fa514ed1cb23122b05dd41ab14e45e6384f11fe96","signature":"3f41de67b26fe2b45e304db927cd0877c998a2a42704d358d026d7468cc5984f"},{"version":"d77b8be301421fa907ffc98763d96bb894ee9c3f3ad5f9e51fa36af0a3cb4b22","signature":"b1f49412c86f3f892d4693c31da6947a22602259778385e69a3a989c6ad1eb2d"},{"version":"30817ea9d19c62648cef33b7404ce06d1da3edec3d5b90534e1807ce403c2b49","signature":"de13b48db3d00144030014260f98c37af7af4e2126b419f1d26a2b213fd85824"},{"version":"735af8f14d6e3866b5863742bec289903bdde848ba8754fb628afb54af74d5bf","signature":"cf08e90dd518ced00aafe1c8036b90d927e9355e98d870a177e4420702925830"},{"version":"b2e07d35b5d614ed285452cee2fbd1b47ab5b9aeed03577a91ff1b2f626a8f55","signature":"f874d87c06c9a63a1dc1754d69442198119d9b1686d12764d23d4abe9c6329c9"},{"version":"f4fae4d847d38a11e1810c0d1b14e9d88a156b8ab5e2183334e87c4cea9b7586","signature":"723c9dcf67e44fec32f209501750f322febe472b3b91dc090bc3dccd6cac5718"},"98ebfeb0805807ae08415404af1b664e76353e70e5e71e6f086c7ba264b76fba",{"version":"ec213e4001fcd5e8446fea02e4a123873df592e8205987405c0e6664886104fd","signature":"f5ff573f4a6451fc2e8c7e1e6ceb8537cd0f25338065baae8a5735c24f22d431"},{"version":"c38e3b9be1619f15bec612db3c9867c9e3435bcc9798dd0d8f6342ab0d8ae946","signature":"e8f50d40694344b6b22ef6d4c3d5fe9347601c7a435fb7e60f820bf37d881d0a"},{"version":"1d25e7af5af3830d0cbc77493b047838dc152a775920710be72c3c8472b980a6","signature":"3560dc1bdf53f078348f0e499ab1e3339be7659310d92861fba7a9277024ffa7"},{"version":"cb83a4611b876ddcf649c18d80609654bb2c80fc160da1e7539cd3be15811a60","signature":"4220ee655e09296874f3ae3d3145efdd76398677bfa27fbef8e133c1b09cf50d"},{"version":"8a8732c1da2dd4fd6c0a85309d5c19163a089077fd5cb232c7cb04227aabe2d5","signature":"475b03ccd54597a3115e13c07c1fe8ac417f6f6857d485114a00039d5d1ea179"},{"version":"817f663192fcc81412090c3e7167fa81c63eab9ea977e6a6ae8eeafb8742001e","signature":"77ce1243fed91f1a9f7be9c18192af18f4ea1603611a3dac2ba4f726c298de4c"},{"version":"4c31fd07fd4e530b6da7febec131d259907d5e179ef3bdac0a4a43cfa56b6935","signature":"b1eb5e11871638368fc4cab710e40fe4b23e0f7e9d4a6e22052edfa50d185fa9"},{"version":"9c5d15efba9e25be56ae6fe11057225c74316fb0c90e7d34b81ec8f633a9810e","signature":"a68280c12af5525ec8003356652c9ce50a24a6b3b6fc83bf793fafe60909fdbb"},{"version":"0172224d148517f7cf90527aa73f03cb436b3fe7f06ac70c039d6886b8f9dfdf","signature":"d01168279f1f9a417a578d3768c58eb5170a5b3445e7b2dedf880dbf0d1fe873"},{"version":"963c32aa76aee7050bf4b5749bec7c775046f03bcd791d54992b3345f7b80e2e","signature":"66be142f9806a1a6a875064f7a0416e1ccedcdcf6a9a209b1c633e475b975dd2"},{"version":"70047c5f97553530141aacef27e3dbff138c7606d2dd0934032bba2e84bc8dc5","signature":"219dfcb98664c09e2a901a0bebd0a1990dece13622fa81b99a4fd16e6352c936"},{"version":"0d264c10a2c5205c38a41763017bf769e758b5f252f8c98d51d5c62d8a3515e5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6a91c1a0055a5d6d43e17b5f31de67b4050162cff0d7e45f82d767f9be56a774","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"0e08f42e73c2f3cbd5d329b30470b5d534fe1f5e193d5a941484c3a733604de6",{"version":"69a81ee2b65949687067f23e7d5ca0fec2110620ff93ca8b92bc56740340b760","signature":"bc160a1badf1d1e0e9b92666b4848dfdc428b553d00ac5d2304b7ad80ac4cbea"},{"version":"eab5c45ccad2406d6da2068d7a2ea33a8738f39853674280f462718b3e2c9d54","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4dfaa3f58af09d51630ec76f6930e860fa49a21befda6568c9c9abd859df867b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ad7e4e17c67808e01a84f46591f2d4b763173ecc84a4863a727b7058b5786945","signature":"b7902bfba5bc8b901152f1ed5f8d9c2cbf2ba2790d351e5e5d61e00bdebbc624"},{"version":"3e5b494cda4d0ac2bce024928f0eacc77f8d9e4ac3d51eadde967ce542efc27e","signature":"b94b0bc72e5a26387c9314dc4f72106bddc0e5056469dcf4a43a351c1523c49c"},{"version":"1c230948c8120cd778cb258a0b70eec899b458db58229de16a2951a7ebdc57b8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0fef5a24cad5c4948eb776b00fa114e2560ce3dded3abc27db2e2b2b66832331","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a0bd46d587005aad4819980f6cf2dbcd80ebf584ed1a946202326a27158ba70e","impliedFormat":1},{"version":"07fcbb61a71bd69a92a5bbde69e60654666cf966b5675c2010c3bf9f436f056a","impliedFormat":1},{"version":"88b2eb23d36692162f2bf1e50577ebcde26de017260473e03ed9a0e61e2726a4","impliedFormat":1},{"version":"23ffbd8c0e20a697d2ea5a0cf7513fb6e42c955a7648f021da12541728f62182","impliedFormat":1},{"version":"43fba5fc019a4ce721a6f53ddb97fdc34c55049cfb793bc544d5c864ee5560b9","impliedFormat":1},{"version":"f4e12292c9a7663a13d152195019711c427c552eb0fa02705e0f61370cd5547a","impliedFormat":1},{"version":"c127ebf14d1b59d1604865008fb072865c5ca52277621f566092fe1f42ce0954","impliedFormat":1},{"version":"def638da26d84825a312113a20649d3086861de7c06a18ea13121278702976fd","impliedFormat":1},{"version":"fbaf86f8ba11298dea2727ce0da84b4ab6ae6c265e1919d44aff7d9b2bbc578a","impliedFormat":1},{"version":"c1010caaeaca8e420c6e040c2e822dbe18702459c93a7d2d5de38597d477b8cd","impliedFormat":1},{"version":"e1f0d8392efd9d71f2644eb97d3f33d90827e30ea8051d93b6f92bb11dff520a","impliedFormat":1},{"version":"085211167559ca307d4053bb8d2298d5ad83cbc3d2ae9bb4c8435a4cabf59369","impliedFormat":1},{"version":"55fc49198d8a85a73cdb79e596d9381cfdc9de93c32c77d42e661c1c1e7268ef","impliedFormat":1},{"version":"6a53fb3df8dd32ed1a65502ca30aeae19cfe80990e78ba68162d6cb2a7fed129","impliedFormat":1},{"version":"b5dcc18d7902597a5584a43c1146ca4fe0295ceb5125f724c1348f6a851dd6ed","impliedFormat":1},{"version":"0c6b0f3fbe6eb6a3805170b3766a341118c92ed7b6d1f193b9f35aa82f594846","impliedFormat":1},{"version":"60eaadb36cf157c5cae9c40e84fa367d04f52a150db3920dbe35139780739143","impliedFormat":1},{"version":"4680a32b1098c49dc87881329af1e68af9af94e051e1b9e19fed555a786f6ce6","impliedFormat":1},{"version":"89fcd129ec37f321cddcdb6b258ffe562de4281e90ec3ccbe7c1199ba39359ca","impliedFormat":1},{"version":"4313011f692861c2c1f5205d7f9a473e763adab6444f9853b96937b187fb19f7","impliedFormat":1},{"version":"caa57157e7bdb8d5f1efe56826fb84a6c8f22a1927bba7fa21fd54e2a44ccba2","impliedFormat":1},{"version":"6b74700abfe4a9b88be957fd8e373cfd998efb1a5f6ad122da49a92997e183ad","impliedFormat":1},{"version":"9ef1342f193bd8bae86c64e450c3ac468ef08652110355e1f3cdd45362eb95c4","impliedFormat":1},{"version":"6853c91662c36a2bf4c8371a87177c819007c76a23c293ef3f686ce9157ae4c8","impliedFormat":1},{"version":"9be1c5dabce43380d13fc621100676b03d420b5687b08d1288f479bee68ab7a8","impliedFormat":1},{"version":"8996d218010896712678e6a0337d8ef8b81c1066ab76f637dd8253f0d6ff838d","impliedFormat":1},{"version":"a15603bf387fc45defe28a68f405a6c29105e135c4e8538eeb6d0a1ef5b69a81","impliedFormat":1},{"version":"84e2532e4d42949a2775cdd8bb7b2b97370dd6ddb683d0c199b21bf6978b152d","impliedFormat":1},{"version":"22bf5f19f620db3b8392cfece44bdd587cdbed80ba39c88a53697d427135bf37","impliedFormat":1},{"version":"23ebbd8d484d07e1c1d8783169c20570ed8409966b28f6be6cf8e970d76ef491","impliedFormat":1},{"version":"18b6fa2c778cad6489f2febf76433453f5e2432ec3535f2d45ae7d803b93cc17","impliedFormat":1},{"version":"609d0d7419999cf44529e6ba687e2944b2fc7ad2570d278fd4e6b1683c075149","impliedFormat":1},{"version":"249cf421b8878a3fe948d9c02f6b0bae65491b3bb974c2ffc612341406fa78ff","impliedFormat":1},{"version":"b4aa22522d653428c8148ddbf1dcc1fb3a3471e15eb1964429a67c390d8c7f38","impliedFormat":1},{"version":"30b2cee905b1848b61c7d28082ebfa2675dd5545c0d25d1c093ce21a905cdccc","impliedFormat":1},{"version":"0a2a2eed4137368735205de97c245f2a685af1a7f1bf8d636b918a0ee4ff4326","impliedFormat":1},{"version":"69f342ce86706aa2835a62898e93ea7a1f21b1d89c70845da69371441bb6cd56","impliedFormat":1},{"version":"b5ab4282affcfd860dd1cc3201653f591509a586d110f8e5b1b010508ba79b2c","impliedFormat":1},{"version":"d396233f6cd3edf0d33c2fbfc84ded029c3ea4a05af3c94d09d31a367cced111","impliedFormat":1},{"version":"bc41a726c817624a5136ae893d7aac7c4dc93c771e8d243a670324bccf39b02b","impliedFormat":1},{"version":"710728600e4b3197f834c4dd1956443be787d2e647a72f190bf6519f235aaadd","impliedFormat":1},{"version":"a45097e01ef30ba26640fed365376ab3ccd5faf97d03f20daff3355a7e60286a","impliedFormat":1},{"version":"763cbb7c22199f43fd5c2b1566af5ba96bf7366f125dd31a038a2291cbc89254","impliedFormat":1},{"version":"031933bf279b7563e11100b5e1746397caf3a278596796a87bc0db23cf68dc9e","impliedFormat":1},{"version":"a4a54c1f58fc6e25a82e2c0f651bf680058bd7f72cfb2d43b85ee0ab5fe2e87e","impliedFormat":1},{"version":"9613d789b6f1037f2523a8f70e1b736f1da4566b470593da062be5c9e13dac57","impliedFormat":1},{"version":"0d2a320763a0c9c71493f8f1069971018c8720a6e7e5a8f10c26b6de79aa2f7d","impliedFormat":1},{"version":"817e0df27a237a268dc16e5acffc19f9a74467093af7a0ba164ee927007a4d25","impliedFormat":1},{"version":"43102521b5ca50ff1865188c3c60790feaed94dc9262b25d4adec4dbc76f9035","impliedFormat":1},{"version":"f99947f8d873b960b0115e506ef9c43f4e40c2071b1d20375564538af4a6023b","impliedFormat":1},{"version":"c1e5ad5ca89d18d2a36d25e8ec105623648cf35615825e202c7d8295a49d61ab","impliedFormat":1},{"version":"2b6c9cb81da4e0a2e32a58230e8c0dec49fc5b345efb7f7a3648b98956be4b13","impliedFormat":1},{"version":"99e34af3ede50062dcc826a1c3ce2d45562060dfd0f29f8066381a6ef548bf2a","impliedFormat":1},{"version":"49f5c2a23ea5fc4b2cdb4426f09d1c8b83f8409fa2af13ef38845cc9b9d4bc3d","impliedFormat":1},{"version":"e935227675144b64ecde3489e4a5e242eeb25fdd6b7464b8c21ad1f7a0faa88b","impliedFormat":1},{"version":"b42e6bbe88dc79c2d6dc5605fb9c15184e70f64bdd7b8d4069b802b90ce86df6","impliedFormat":1},{"version":"b9cd712399fdc00fdae07e96c9b39c3cb311e2a8a5425f1bd583f13cab35e44b","impliedFormat":1},{"version":"5a978550ae131b7fef441d67372fd972abab98ea9fdb9fa266e8bdc89edcb8d6","impliedFormat":1},{"version":"4f287919cfc1d26420db9f0457cd5c8780b1ef0a9f949570936abe48d3a43d91","impliedFormat":1},{"version":"496b23b2fd07e614bc01d90dd4388996cb18cd5f3a612d98201e9f683e58ad2e","impliedFormat":1},{"version":"dcfbe42824f37c5fb6dc7b9427ef2500791ec0d30825ecb614f15b8d5bf5a667","impliedFormat":1},{"version":"390124ad2361b46bf01851d25e331cd7eed355d04451d8b2a4aa985c9de4f8ce","impliedFormat":1},{"version":"14d94f17772c3a58eda01b6603490983d845ee2012cd643f7497b4e22566aacb","impliedFormat":1},{"version":"03ef2386c683707ce741a1c30cb126e8c51a908aa0acc01c3471fafb9baaacd5","impliedFormat":1},{"version":"66a372e03c41d2d5e920df5282dadcec2acae4c629cb51cab850825d2a144cea","impliedFormat":1},{"version":"5b48ba9a30a93176a93c87f9e0abf26a9df457eeb808928009439ca578b56f27","impliedFormat":1},{"version":"4707625392316d3c16edbd0716f4ac310e8ff5d346d58f4d01a2b7e0533a23df","impliedFormat":1},{"version":"154d58a4b2d9c552dc864ea39c223d66efd0ed2dd8b55bd13db5225d14322915","impliedFormat":1},{"version":"6a830433fa072931b4ea3eb9aa5fa7d283f470080586a27bfe69837a0f12de9a","impliedFormat":1},{"version":"d25e930e181f4f69b2b128514538f2abb54ef1d48a046ad776ac6f1cda885a72","impliedFormat":1},{"version":"0259b4c21bc93b52ca82c755f97fc90481072bcc44a8010131b2ea7326cf03fe","impliedFormat":1},{"version":"bea43a13a1104a640da0cb049db85c6993f484a6cc03660496b97824719ecc91","impliedFormat":1},{"version":"0224239d61fe66d4900544d912b2e11c2cca24b4707d53fdb94b874a01e29f48","impliedFormat":1},{"version":"2bce8fd2d16a9432110bbe0ba1e663fd02f7d8b8968cd10178ea7bc306c4a5df","impliedFormat":1},{"version":"9c4ad63738346873d685e5c086acbf41199e7022eff5b72bb668931e9ca42404","impliedFormat":1},{"version":"cfb6329bf8ce324e83fe4bbdee537d866a0d5328246f149a0958b75d033de409","impliedFormat":1},{"version":"efc3816f19ea87a7050c84271ea3d3aad9631a517c168013c4f4b6724c287ce0","impliedFormat":1},{"version":"f99f6737336140047e8dd4ade3859f08331aa4b17bc2bd5f156a25c54e0febbc","impliedFormat":1},{"version":"12a2b25c7c9c05c8994adf193e65749926acfcc076381f7166c2f709a97bdf0a","impliedFormat":1},{"version":"0f93a3fdd517c1e45218cd0027c1d6b82237e379dc6b66d693aab1fe74c82e81","impliedFormat":1},{"version":"03c753da0bee80ad0d0f1819b9b42dfe9bf9f436664caf15325aa426246fd891","impliedFormat":1},{"version":"18f5bf1dae429c451f20171427c9e3223fade4346af4dfd817725cbeb247a09d","impliedFormat":1},{"version":"a4eece5fab202e840dd84f7239e511017a8162edb8fc8b54ff2851c5c844125c","impliedFormat":1},{"version":"c4a94af483a63bf947d89f97553a55df5107c605ec8a26f0b9b8bdcc14bd6d89","impliedFormat":1},{"version":"19de2915ccebc0a1482c2337b34cb178d446def2493bf775c4018a4ea355adb8","impliedFormat":1},{"version":"9be8fc03c8b5392cd17d40fd61063d73f08d0ee3457ecf075dcb3768ae1427bd","impliedFormat":1},{"version":"3b568b63f0e8b3873629a4d7a918dce4266ad41461004ab979f8dcdfd13532bb","impliedFormat":1},{"version":"a5e5223c775fe30d606b8aaa521953c925d5ad176a531c2b69437d2461aaabbd","impliedFormat":1},{"version":"8cbf41d2d1ce8ac2066783ae00613c33feef07493796f638e30beaf892e4354a","impliedFormat":1},{"version":"e22ad737718160df198cd428f18da707177d0467934cecdeed4be6e067b0c619","impliedFormat":1},{"version":"15bf5ed8cb7c1a1e1db53fa9b45bc1a1c73c0497735343a8d0c59fdb596a3744","impliedFormat":1},{"version":"791fce84bce8b6948e4f23422d9cbbd7d08c74b3f91cca12dcae83d96079798b","impliedFormat":1},{"version":"8a2619c8e24305f6b9700b35af178394b995dcb28690a57a71cca87ee7e709ae","impliedFormat":1},{"version":"f95fd2fc3cc164921a891f5d6c935fa0d014a576223dd098fc64677e696b0025","impliedFormat":1},{"version":"8c9cecaaa9caba9a8caa47f46dcf24b524b27899b286d8edcc75a81b370d2ba3","impliedFormat":1},{"version":"2b7a82692ecc877c5379df9653902e23f2d0d0bc9f210ec3cf9e47be54413c5c","impliedFormat":1},{"version":"e2ad09c011cf9d7ee128875406bef787eeb504659495f42656a0098c15fe646c","impliedFormat":1},{"version":"eb518567ea6b0b2623f9a6d37c364e1b1ac9d8b508d79e558f64ac05c17e2685","impliedFormat":1},{"version":"630a48fb8f6b07161588e0aee3f9d301c59c97e1532c884118f89368baf4073b","impliedFormat":1},{"version":"14736c608aa46120f8d6d0bc5e0721b46b927bc7eba20e479600571935f27062","impliedFormat":1},{"version":"7574803692d2230db13205a7749b9c3587dccaccdf9e76f003f9e08078bb6d09","impliedFormat":1},{"version":"f3cc1588e666651c51353b1728460bee8acbc6e0f36be8c025eaaf292dca525d","impliedFormat":1},{"version":"0d4ea8a20527dcf3ad6cf1bd188b8ad4e449df174fad09b9e540ed81080af834","impliedFormat":1},{"version":"aa82876d59912d25becff5a79ed7341af04c71bfeb2221cc0417bc34531125e2","impliedFormat":1},{"version":"6f4b0389f439adc84cba35d45428668eabcfbdd351ba17e459d414ca51ab8eb8","impliedFormat":1},{"version":"d5dd33d15fbb07668c264b38065ac542a07a7650af4917727bbc09b58570e862","impliedFormat":1},{"version":"7d90202d0212e9cdc91a20bfddf04a539c89f09fe1d64db3343546fa2eb37e71","impliedFormat":1},{"version":"1a5d073c95a3a4480b17d2fa7fd41862a9df0cb2afaee86834b13649e96bdb45","impliedFormat":1},{"version":"2092495a5b3116c760527a690c4529748f2d8b126cdd5f56b2ce2230b48aba3f","impliedFormat":1},{"version":"620b29d6adbd4061bc0a8fedf145fcc8e8fc9648fb6e0a39726e33babb4e07bc","impliedFormat":1},{"version":"931eda51b5977f7f3fa7a0d9afde01cfd8b0cc1df0bb66dcf8c2cf6e7090384e","impliedFormat":1},{"version":"b084a412374bdd124048c52c4e8a82d64f3adec6c0a9ad5ecbb7317636039b0f","impliedFormat":1},{"version":"11199daa694c3ced3cc2a382a3fa7bd64e95eb40f9bbc3979fc8fb43f5ba38cc","impliedFormat":1},{"version":"2c86f279d7db3c024de0f21cd9c8c2c972972f842357016bfbbd86955723b223","impliedFormat":1},{"version":"dfb53b9d748df3e140b0fddb75f74d21d7623e800bb1f233817a1a2118d4bb24","impliedFormat":1},{"version":"8cfc293b33082003cacbf7856b8b5e2d6dd3bde46abbd575b0c935dc83af4844","impliedFormat":1},{"version":"7730c538d6d35efe95d2c0d246b1371565b13037e893178033360b4c9d2ac863","impliedFormat":1},{"version":"b256694544b0d45495942720852d9597116979d52f2b53c559fda31f635c60df","impliedFormat":1},{"version":"794e8831c68cc471671430ee0998397ea7a62c3b706b30304efdc3eaff77545a","impliedFormat":1},{"version":"9cfc1b227477e31988e3fb18d26b6988618f4a5da9b7da6bc3df7fc12fb2602e","impliedFormat":1},{"version":"264a292b6024567dd901fdabbf3239a8742bea426432cdbda4cf390b224188e1","impliedFormat":1},{"version":"f1556a28bb8e33862dcfa9da7e6f1dca0b149faf433fe6a50153ae76f3362db1","impliedFormat":1},{"version":"1d321aea1c6a77b2a44e02e5c2aeff290e3f1675ead1a86652b6d77f5fea2b32","impliedFormat":1},{"version":"4910efc2ce1f96d6e71a9e7c9437812ffae5764b33ab3831c614663f62294124","impliedFormat":1},{"version":"e3ceab51a36e8b34ab787af1a7cf02b9312b6651bac67c750579b3f05af646c1","impliedFormat":1},{"version":"baf9f145bcee1b765bed6e79fd45e1ff0ca297a81315944de81eb5d6fff2d13d","impliedFormat":1},{"version":"2afd62362b83db93cd20de22489fe4d46c6f51822069802620589a51ccad4b99","impliedFormat":1},{"version":"9f0cd9bd4ab608123b88328c78814738cbdee620f29258b89ef8cd923f07ff9c","impliedFormat":1},{"version":"801186c9e765583c825f28dab63a7ad12db5609e36dc6d9acbdc97d23888a463","impliedFormat":1},{"version":"96c515141c6135ccd6fb655fb9e3500074a9216ba956fb685dc8edc33f689594","impliedFormat":1},{"version":"416af6d65fc76c9ced6795f255cb1096c9d7947bede75b82289732b74d902784","impliedFormat":1},{"version":"a280c68b128ebba35fb044965d67895201c2f83b6b28281bb8b023ade68bf665","impliedFormat":1},{"version":"6fa118f15723b099a41d3beea98ed059bcd1b3eda708acf98c5eff0c7e88832f","impliedFormat":1},{"version":"dcbf582243e20ea50d283f28f4f64e9990b4ed4a608757e996160c63cff6aa99","impliedFormat":1},{"version":"efa432d8fd562529c4e9f859fd936676dd8fef5d3b4bedb06f754e4740056ea9","impliedFormat":1},{"version":"a59b66720b2ccf2e0150fafb49e8da8dabdf4e1be36244a4ccd92f5bd18e1e9e","impliedFormat":1},{"version":"c657fb1ec3b727d6a14a24c71ea20c41cb7d26a503e8e41b726bb919eb964534","impliedFormat":1},{"version":"50d6d3174868f6e974355bf8e8db8c8b3fcf059315282a0c359ecf799d95514a","impliedFormat":1},{"version":"86bf79091014a1424fc55122caa47f08622b721a4d614b97dd620e3037711541","impliedFormat":1},{"version":"7a63313dff3a57f824a926e49a7262f7bd14e0e833cf45fa5af6da25286769c2","impliedFormat":1},{"version":"36dcaeffe1a1aed1cb84d4feba32895bf442795170edccc874fa32232b2354e5","impliedFormat":1},{"version":"686c6962d04d90edafc174aa5940acb9c9db8949c8d425131c01d796cf9a3aef","impliedFormat":1},{"version":"2b1dbc3d5762d6865744b6e7be94b8b9004097698c37e93e06983e42dd8fe93b","impliedFormat":1},{"version":"eb5e8f74826bdf3a6a0644d37a0f48133f8ad0b5298cc2c574102868542ba4eb","impliedFormat":1},{"version":"c6a82a9673ba517cf04dd0803513257d0adf101aed2e3b162a54d840c9a1a3b2","impliedFormat":1},{"version":"fc9f0f415abaa323efcecc4a4e0b6763bfe576e32043546d44f1de6541b6399b","impliedFormat":1},{"version":"2c4d772ac7ac56a44deef82903364eb7c78dd7bc997701123df0ce4639fe39bb","impliedFormat":1},{"version":"9369ef11eed17c1c223fdea9c0fa39e83f3722914ef390b1448db3d71620c93a","impliedFormat":1},{"version":"aa84130dbc9049bba6095f87932138698f53259b642635f6c9e92dd0ddc7512c","impliedFormat":1},{"version":"084ceadd21efabd4b58667dca00d4f644306099151d2ee18cd28a395855b8009","impliedFormat":1},{"version":"b9503e29f06c99b352b7cae052da19e3599fa42899509d32b23a27c9bb5bebf6","impliedFormat":1},{"version":"75188920fe6ccc14070fe9a65c036049f1141d968c627b623d4a897ec3587e15","impliedFormat":1},{"version":"e2e1df7f45013d2b34f8d08e6ae5a9339724b0ea251b5445fcca3e170e640105","impliedFormat":1},{"version":"af06feb5d18a6ea11c088b683bdb571800d1f76b98d848eecdf41e5ec8f317fd","impliedFormat":1},{"version":"0596af52b95e0c8adc2c07f49f109d746b164739c5866fa8bb394dd6329a3725","impliedFormat":1},{"version":"c3365d08fe7a1ccc3b8e8638edc30123007f3241b4604e2585b9f14422ab97d8","impliedFormat":1},{"version":"a7a3d96b04bb0ec8cb7d2669767c4756f97dd70d08548f9e6522dde4de8e8a03","impliedFormat":1},{"version":"745e960e885a4ba04c872225cbb44bd67a7490d169ceaefab7c0dfc444768676","impliedFormat":1},{"version":"0b1ce1768cde3535493a9daf99e3bbb8c7dcc3a7f9d8cd358cb846af71ce5cdf","impliedFormat":1},{"version":"48b9603f6e8a7c94b727277592a089f94261baa64e6c9d18165da0481663a69e","impliedFormat":1},{"version":"3c20a3bb0c50c819419f44aa55acc58476dad4754a16884cef06012d02b0722f","impliedFormat":1},{"version":"4dc64902cb86e677a928293593658fbf53388f9a30d2b934140c70a7267b07ec","impliedFormat":1},{"version":"cb4fd56539a61d163ea9befe6b0292c32aa68a104c1f68f61416f1bc769bcfba","impliedFormat":1},{"version":"0d852bdc2b72b22393a8eebe374ee3efe3e0d44e630037b5e1b6087985388e62","impliedFormat":1},{"version":"b6c9a2deefb6a57ff68d2a38d33c34407b9939487fc9ee9f32ba3ecf2987a88a","impliedFormat":1},{"version":"f6b371377bab3018dac2bca63e27502ecbd5d06f708ad7e312658d3b5315d948","impliedFormat":1},{"version":"faa72893e85cb8ebb1dafde6b427e5204e60bb5f3ee6576bb64c01db1f255bc8","impliedFormat":1},{"version":"95b7ed47b31a6eaddcdd853ee0871f2bb61e39ce36a01d03dfafb83766f6c10c","impliedFormat":1},{"version":"19287d6b76288c2814f1633bdd68d2b76748757ffd355e73e41151644e4773d6","impliedFormat":1},{"version":"fc4e6ec7dade5f9d422b153c5d8f6ad074bd9cc4e280415b7dc58fb5c52b5df1","impliedFormat":1},{"version":"3aea973106e1184db82d8880f0ca134388b6cbc420f7309d1c8947b842886349","impliedFormat":1},{"version":"765e278c464923da94dda7c2b281ece92f58981642421ae097862effe2bd30fa","impliedFormat":1},{"version":"de260bed7f7d25593f59e859bd7c7f8c6e6bb87e8686a0fcafa3774cb5ca02d8","impliedFormat":1},{"version":"d95c4eaad4df9e564859f0c74a177fa0b2e5f8a155939b52580566ab6b311c3f","impliedFormat":1},{"version":"7192a6d17bfa06e83ba14287907b7c671bef9b7111c146f59c6ea753cfc736b9","impliedFormat":1},{"version":"5156d3d392db5d77e1e2f3ea723c0a8bd3ca8acffe3b754b10c84b12f55a6e10","impliedFormat":1},{"version":"a6494e7833ee04386a9f0c686726f7cb05f52f6e069d9293475ccb1e791ee0da","impliedFormat":1},{"version":"d9af0c89a310256851238f509a22aa1071a464d35dc22ea8c2a0bae42dd81bc5","impliedFormat":1},{"version":"291642a66e55e6ca38b029bc6921c7301f5c7b7acf21ae588a5f352e6c1f6d58","impliedFormat":1},{"version":"43cd7c37298b051d1ce0307d94105bcd792c6c7e017282c9d13f1097c27408e8","impliedFormat":1},{"version":"e00d8cce6e2e627654e49c543b582568ad0bf27c1d4ad1018d26aff78d7599df","impliedFormat":1},{"version":"ed13354f0d96fb6d5878655b1fead51722b54875e91d5e53ef16de5b71a0e278","impliedFormat":1},{"version":"fcb934d0fcdee06a8571bd90aa3a63aa288c784b3ebcecfe7ae90d3104d321f4","impliedFormat":1},{"version":"af682dfabe85688289b420d939020a10eb61f0120e393d53c127f1968b3e9f66","impliedFormat":1},{"version":"0dca04006bf13f72240c6a6a502df9c0b49c41c3cab2be75e81e9b592dcd4ea8","impliedFormat":1},{"version":"7dc0b5e3d7be8e1f451f0545448c2eaa02683f230797d24434b36f9820d5a641","impliedFormat":1},{"version":"247af61cdc3f4ec7876b9e993a2ecdd069e10934ff790c9cee5811842bff49eb","impliedFormat":1},{"version":"4be8c2c63d5cd1381081d90021ddfaef106881df4129eddeeaba906f2d0f75d0","impliedFormat":1},{"version":"012f621d6eb28172afb1b2dc23898d8bc74cf35a6d76b63e5581aa8e50fa71b3","impliedFormat":1},{"version":"3a561fa91097e4580c5349ce72e69d247c31c11d29f39e1d0bd3716042ff2c0b","impliedFormat":1},{"version":"bc9981a79dda3badea61d716d368a280c370267e900f43321f828495f4fef23c","impliedFormat":1},{"version":"2ed3b93d55aea416d7be8d49fe25016430caab0fe64c87d641e4c2c551130d17","impliedFormat":1},{"version":"3d66dfc31dd26092c3663d9623b6fc5cec90878606941a19e2b884c4eacd1a24","impliedFormat":1},{"version":"6916c678060af14a8ce8d78a1929d84184e9507fba7ab75142c1bcb646e1c789","impliedFormat":1},{"version":"3eea74afae095028597b3954bde69390f568afc66d457f64fff56e416ea47811","impliedFormat":1},{"version":"549fb2d19deb7d7cae64922918ddddf190109508cc6c7c47033478f7359556d2","impliedFormat":1},{"version":"e7023afc677a74f03f8ccb567532fe9eedd1f5241ee74be7b75ac2336514f6f6","impliedFormat":1},{"version":"ff55505622eac7d104b9ab9570f4cc67166ba47dd8f3badfb85605d55dd6bdc9","impliedFormat":1},{"version":"102fac015b1eebfa13305cb90fd91a4f0bbcabb10f2343556b3483bbb0a04b62","impliedFormat":1},{"version":"18a1f4493f2dbad5fd4f7d9bfba683c98cf5ed5a4fa704fa0d9884e3876e2446","impliedFormat":1},{"version":"f57e6707d035ab89a03797d34faef37deefd3dd90aa17d90de2f33dce46a2c56","impliedFormat":1},{"version":"cc8b559b2cf9380ca72922c64576a43f000275c72042b2af2415ce0fb88d7077","impliedFormat":1},{"version":"1a337ca294c428ba8f2eb01e887b28d080ee4a4307ae87e02e468b1d26af4a74","impliedFormat":1},{"version":"310fe80ff40a158c2de408efbe9de11e249c53d2de5e33ca32798e6f3fbc8822","impliedFormat":1},{"version":"d6ce96c7bb34945c1d444101f44e0f8ba0bba8ab7587a6cc009a9934b538c335","impliedFormat":1},{"version":"1b10a2715917601939a9288d49beccd45b591723256495b229569cd67bbe48a8","impliedFormat":1},{"version":"7498dfdeed2e003ec49cdf726ff6c293002d1d7fdadbc398ce8aafe6d0688de7","impliedFormat":1},{"version":"8492306a4864a1dc6fc7e0cc0de0ae9279cbd37f3aae3e9dc1065afcdc83dddc","impliedFormat":1},{"version":"9c86abbc4fd0248f56abc12aaecd76854517389af405d5ec2eb187fdb00a606f","impliedFormat":1},{"version":"9ffd906f14f8b059d6b95d6640920f530507e596e548f7a595da58ab66e3ce76","impliedFormat":1},{"version":"1884bccc10ce40adca470c2c371c1c938b36824f169c56f7f43d860416ca0a4c","impliedFormat":1},{"version":"986b55b4f920c99d77c1845f2542df6f746cb5adc9ab93eb1545a7e6ef37590d","impliedFormat":1},{"version":"cd00906068b81fbd8a22d021580ac505e272844408174520fafed0ae00627a5d","impliedFormat":1},{"version":"69fab68a769c17a52a24b868aeb644f3ee14abaa5064115f575ddd59231105ce","impliedFormat":1},{"version":"e181eb86b2caf80fe18c72efce6b913bc226e4a69a5456eaf4f859f1c29c6fd6","impliedFormat":1},{"version":"93f7871380478bc6acf02ad9f3dc7da0c21997caebbe782eb93a11b7bd06a46d","impliedFormat":1},{"version":"d00279ab020713264f570d5181c89ca362b7de8abddf96733de86bce0eca082c","impliedFormat":1},{"version":"f7db473f1d5d2a124f14886ac9dbfeccfbb94a98bbe1610a47c30c2933afa279","impliedFormat":1},{"version":"f44cf6c6d608ef925831e550b19841b5d71bd87195bd346604ff05644fb0d29c","impliedFormat":1},{"version":"154f23902d7a3fcdace4c20b654da7355fee4b7f807d1f77d6c9a24a8756013a","impliedFormat":1},{"version":"562f4f3c75a497d3ad7709381f850bb8c7646a9c6e94fdf8e91928e23d155411","impliedFormat":1},{"version":"4583380b676ee59b70a9696b42acfa986cd5f32430f37672e04f31f40b05df74","impliedFormat":1},{"version":"ad0a13f35a0d88803979f8ea9050ad7441e09d21a509abf2f303e18c1267af17","impliedFormat":1},{"version":"ba9781c718ab3d09cbde1216029072698d2da6135f0d2f856ba387d6caceb13e","impliedFormat":1},{"version":"d7c597c14698ba5fc8010076afa426f029b2d8edabb5073270c070cc645ba638","impliedFormat":1},{"version":"bd2afc69cf1d85cd950a99813bc7eff007d8afa496e7c2142a845cd1181d0474","impliedFormat":1},{"version":"558b462b23ea186d094dbff158d652acd58c0988c9fd53af81a8903412aa5901","impliedFormat":1},{"version":"0e984ae642a15973d652fd7b0d2712a284787d0d7a1db99aa49af0121e47f1df","impliedFormat":1},{"version":"0ad53ee208a23eef2a5cb3d85f2a9dc1019fd5e69179c4b0c02dc56c40d611c4","impliedFormat":1},{"version":"7a6898b26947bd356f33f4efef3eb23e61174d85dca19f41a8780d6bb4bfb405","impliedFormat":1},{"version":"9fe30349d26f34e85209fb06340bac34177f7eae3d6bb69dc12cd179d2c13ddf","impliedFormat":1},{"version":"d568c51d2c4360fd407445e39f4d86891dba04083402602bf5f24fd3969cacbb","impliedFormat":1},{"version":"b2483a924349ec835f4d778dd6787447a2f8bfbb651164851bff29d5b3d990a6","impliedFormat":1},{"version":"aae66889332cff4b2f7586c5c8758abc394d8d1c48f9b04b0c257e58f629d285","impliedFormat":1},{"version":"0f86c85130c64d6dbe6a9090bb3df71c4b0987bce4a08afe1ac4ece597655b9c","impliedFormat":1},{"version":"0ce28ad2671baed24517e1c1f4f2a986029137635bce788ee8fb542f002ac5b8","impliedFormat":1},{"version":"cd12e4fe77d24db98d66049360a4269299bcfb9dc3a1b47078ab1b4afac394cb","impliedFormat":1},{"version":"1589e5ac394b2b2e64264da3e1798d0e103b4f408f5bae1527d9e706f98269c7","impliedFormat":1},{"version":"ff8181aa0fde5ec2d737aecc5ebaa9e881379041f13e5ce1745620e17f78dcf9","impliedFormat":1},{"version":"0b2e54504b568c08df1e7db11c105786742866ba51e20486ab9b2286637d268f","impliedFormat":1},{"version":"bc1ffc3a2dca8ee715571739be3ec74d079e60505e1d0d2446e4978f6c75ba5c","impliedFormat":1},{"version":"770a40373470dff27b3f7022937ea2668a0854d7977c9d22073e1c62af537727","impliedFormat":1},{"version":"a0f8ce72cb02247a112ce4a2fa0f122478a8e99c90a5e6b676b41a68b1891ad2","impliedFormat":1},{"version":"6e957ea18b2bf951cf3995d115ad9bfa439e8d891aeb1afc901d793202c0b90d","impliedFormat":1},{"version":"a1c65bd78725f9172b5846c3c58ddf4bcbb43a30ab19e951f0102552fbfd3d5d","impliedFormat":1},{"version":"04718c7325e7df4bac9a6d026a0a2bd5a8b54501f274aaf93a03b5d1d0635bd1","impliedFormat":1},{"version":"405205f932d4e0ce688a380fa3150b1c7ff60e7fc89909e11a33eab7af240edb","impliedFormat":1},{"version":"566fc1a6616a522f8b45082032a33e6d37ff7df3f7d4d63c3cce9017d0345178","impliedFormat":1},{"version":"3b699b08db04559803b85aa0809748e61427b3d831f77834b8206e9f2ed20c93","impliedFormat":1},{"version":"b27242dd3af2a5548d0c7231db7da63d6373636d6c4e72d9b616adaa2acef7e1","impliedFormat":1},{"version":"e0ee7ba0571b83c53a3d6ec761cf391e7128d8f8f590f8832c28661b73c21b68","impliedFormat":1},{"version":"072bfd97fc61c894ef260723f43a416d49ebd8b703696f647c8322671c598873","impliedFormat":1},{"version":"e70875232f5d5528f1650dd6f5c94a5bed344ecf04bdbb998f7f78a3c1317d02","impliedFormat":1},{"version":"8e495129cb6cd8008de6f4ff8ce34fe1302a9e0dcff8d13714bd5593be3f7898","impliedFormat":99},{"version":"e3c2cc25f470bea78afb3af5ca6f30759cfd44e4b1212e84657fb36e45a3a1d3","signature":"6e0fa1db91df6484a033340dfafa435f2e98844b66e876a01f963ff84a878646"},{"version":"16bed64f9c23abdf4e18425ab522343d5c8f180214832e5b6d40135d838f7841","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5e6e6f01cb776da72cb3ca11adf3fa20c16d0f68841c2f550e485376491fe584","signature":"981a1e9bb280cbad4485d10bfb76e890079fcc75cdf11f502bd995e1065d2616"},{"version":"2e0902468e1a220489a3f33dc82d5eff8f70521cc4ad8eb35abee7a792a14a6f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"964244acf38c094ec67de89656b936d3a3f836b66719afc936249bc1fe097a6c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1652f9e9c84699da6c0050e0818ffbd6093fb9dc67e082bfb5d3df1fc92012b2","signature":"e7ed13b72a29fe7f0c628bb06ed80ba591e327268a8acf0cb66fa1725ce5e930"},{"version":"ccb92614f79729906fc8a9f5c9c18f163c1352d2e549cc49b42c4302fa591f30","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"666382b2c44dfe6f0c2855b47eded1c7834b41302dc74a7e6a67b8fc834bef74","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"eedbc489481913977cc00e055bd0f493c8ed3115928ca006929d1b353411e722","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"693bee8c30c3177aaf053f105c907f8be760c2f99c418530ae2e9fc9293f3b93","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"73a98325658b7d7509ec766ae0f0b34b9a4d9819b388d4e5b3c74ef8af5af185","signature":"211206a1cc64d3623a54c919fe8e7e8cb70032936ce7dd2625ba2f185580334a"},{"version":"938165e53c76725415bd8152fc8363d628ac4a95e04d4c2884f75349e3d337a9","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"469d3ea0db1d18de2755c0524ccd3ce841290f6c070e1e36d5d2a29814698a06","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ccae0e1f81234cd2641d83504765e64e37013fc26faec970ea5931946db96772","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"618bb47ac74b0ab39b90743cfe920a6e670b2d3bfa24f37d106b535c1c18ae37","signature":"4c8a45f845c330caf5dc2bc33da6e8e4f317035d08a20fd5f1da986bea511d60"},{"version":"681ab80103a45e835b91035d733228aa210d75cb0cd45355dbe6e72fcbe1806a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cfc4d1e3e0f3fa0a4a3de8483598fe4ca1f9677de760b092b4919748ca383fff","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f3d8c757e148ad968f0d98697987db363070abada5f503da3c06aefd9d4248c1","impliedFormat":1},{"version":"ac450542cbfd50a4d7bf0f3ec8aeedb9e95791ecc6f2b2b19367696bd303e8c6","impliedFormat":1},{"version":"8a190298d0ff502ad1c7294ba6b0abb3a290fc905b3a00603016a97c363a4c7a","impliedFormat":1},{"version":"5ba4a4a1f9fae0550de86889fb06cd997c8406795d85647cbcd992245625680c","impliedFormat":1},{"version":"57c98d540df72bdc219a9d4e23e7c1f844459414e4e62eda3439067fadf743ba","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f329dfad7970297cbf07ddc8fce2ad4a24e2a3855917c661922ef86eb24dd1f1","impliedFormat":1},{"version":"841784cfa9046a2b3e453d638ea5c3e53680eb8225a45db1c13813f6ea4095e5","affectsGlobalScope":true,"impliedFormat":1},{"version":"646ef1cff0ec3cf8e96adb1848357788f244b217345944c2be2942a62764b771","impliedFormat":1},{"version":"f8276f80a1e792110e3a21974c6db4821798b9b726a067d2326923ca3b8a111b","signature":"90ec9100c29e008c3d9194acd818e2cfa6dc6e177154bc8e10c5959aa35619ed"},{"version":"fe93c474ab38ac02e30e3af073412b4f92b740152cf3a751fdaee8cbea982341","impliedFormat":1},{"version":"3255b97f3f24af29c79cc1aa88004efb13b6285ebdde0a567bf32e19bb65250d","impliedFormat":1},{"version":"1e00b8bf9e3766c958218cd6144ffe08418286f89ff44ba5a2cc830c03dd22c7","impliedFormat":1},{"version":"7c27b6ea1ae8d5013b5a6e4f72fbe4e1c12a4c122c8e3ecbc1ebed7f4820ee56","signature":"1172a76e0f08ae2f3ee3945863e405b51be43b053879f519ceff4c565edf1c0f"},{"version":"57966149b133b2cc5be424026c5dec226936774de2993086b3db1d8396b69ca2","signature":"b0fe4ebd89323e0b58b2a06b45292ea27ed1f3a5f2bfd4dc9d7ec82367cd7095"},{"version":"31caf28f1dc08edfe3cb8e7c4011ced09f4bf3f5f725c50e5dc0b5ae573b498a","signature":"bae25bd2065e51d8f2981a602ea5c8510f947bc7d5fa9c8bb9d11573d631e38e"},{"version":"c03fe612af1138dcead8e808241a0ef89ce09eacf11ba92a7c863e164be98d61","signature":"0c6f146bf5402327aa93d97c9e263e92bb63b4d87f2af155416cc7d0490a8224"},{"version":"7afc8ef7ade1f7cb4e4ec2b5d8890649511bb0b684d870da6f25fbeed4cc4e19","signature":"a249fbe38c60a2a707d8381a5cc4246312d803f096870a9d77876f2629601662"},{"version":"2cc3d4e313dbb0ec148880a6caa35c8b23c7bc7fc6742e7144d53855fe6e3c1b","signature":"138cf0cf601045c4741faaf79f2d7024eca30e59e3b533a4d60e01a99209ad68"},{"version":"0f9e0759d865a9c490413b1211acbce0c29d3ca56d2437060dc1ddff96fd6fbf","signature":"335f66607c072668fef3e0563ad5619a7632e75eef9f85740e84e35523d1ee82"},{"version":"365b3b78ca0233643bad0d64e485031f83f9510e9358ce3de81594901784e125","signature":"d1c85428c55ff1c7d980d04feea74240a8bef90974b07aac3d867b44f91622c6"},{"version":"9e2af2b8470ef7dc52ebff4fdfc14267c0cfdc41f01f45653c330e2320138826","signature":"ab21447ef0584cf1fed179cc5df1b9b2c9d86a407e1dff3daa9168eb54366749"},{"version":"9e9fc0a89103169c53464d456f3bca79cd6fed85398d0c4be0589f91c41aca6c","signature":"410aae1dab008177682aafcfbeeb27bb71cf90e2644309d0473a9f4840d460b3"},{"version":"2ccec598dfa5899f2c207ac8f63aa8d00d3c9f942e83e858adb72408b1db445e","signature":"0cc24adad526e7c075f3223582ca642a555751bddc0088d47a1fe62ac19ebe31"},{"version":"8bffa50dd700f040b86076c6169484967f3d5f78eac8dd5ab8d8704c9d7e7971","signature":"4db2374e885b05cca098ccddbf73603e0c45cd5d27f131f90ffeec6c35eae100"},{"version":"c5ed0796ac973137391ab9755403837f9530f73c5da866798664126c7fa94c83","signature":"b0540a7a4d0339ff0999796b3fbf590929231141c424a21ec85c6477a1e5e176"},{"version":"9ac563bd745be78c7e249ee939c1419b4f4a0ebd2092e9075163db1a84163f15","signature":"322124f66182b890bc15265b8af6b872b31528ea3e0bf7e431a29a349648f67f"},{"version":"e0dd3aaf08541fa0c17a605ed21d7a6ac704d19595cdb851666e80c138dd4b68","signature":"acaee283946e562a6a4f999558a47c3d5110e5e2ae0581f90b5d2d4e35dd74cf"},{"version":"29a394b222f5c79ab158f4175cd08c7fa633ab156c338d70349c3b64d5ab7f76","signature":"e2b8769aa8875a46de5f18335040d74ae70dec517507f75d769b7e9922c196b8"},{"version":"23e4c56820f11593ad37c2f0ee6e57b022f3bad1e7b88e8e8cfafe4a5631d166","signature":"8545b0f7558460da62b0b9e7fa97d0c4a5bae26cb1b0ec5f4cef91a829dcf4a1"},{"version":"b81c813a557be66ee878d40d1f35ad2b043b1049095a998fe1ca3808d387afe5","signature":"4ea82f415bb35563eae553ebdd9cfc541d18967c536d1bd821f38ddf50836ec5"},{"version":"ccffe362350b830f2b4bd084c843dddb3cf1ae80fe33ce45f36f0ae8ce52dd23","signature":"e5e40cb7b930c754df523177c49f9bed8a660457768c13685d21c641d2f41023"},{"version":"d9aba09758928ec2439f08c4736980c6e52ddf6a0cab476c161b3592a34d44e5","signature":"ffaea7fcaed416769800cd74682a38d1335953e1eb903bb59c22e45cced12b52"},{"version":"9de6fc0c7fceb53327f21628d5ad52df39032e16f6367fb98180d11b38caafcf","signature":"5ad606a8ca9d6d3baf284b4af21e08329b8bb2b9963eb9c8730a4f4a0251026b"},{"version":"8a79199d466f8425622c4f825ae9d336b61df3f0bde29a6e29e61ca00f69937a","signature":"5bfb91d2e51019e18a467050246cc0c653bc49f1708e076d3f17717235ceecbd"},{"version":"69143702a1c121c24efe2527c4ff00a418941f242e93fc91f5758b59512e39a5","signature":"64d8352ec1af0b0f8829348a73910f97b2b823b77af8dafcde353933ef9d8cec"},{"version":"5eb9d2ce33e3f87b43b0403807e9ed55b48cfad7155fd7641473cad52e48555a","signature":"e9708da92b0cb69d4b46485f491a6f053fa07145eadaa8101b16fa6738100f9e"},{"version":"270a13a0e0d9ad66c43951af65b76e37902cd7f7b94cac791d6f09b4bd41ef16","signature":"8d88910cc0104f243e391b4773efc30f79f6f066d5f16868060f72211676a008"},{"version":"1092b346a04d1b0c3c7f85c13a8fd45f52bee0a6d8217870af5de17e63bce157","signature":"d316a8da36d661ba0c2110e7fa8961db330ee9c39732cdce1b693c8608a06100"},{"version":"e259976d7eb8e849e740683d5eaf48d663e575513ddb40e8209936f8cb9638ac","signature":"9784ebd09778c432d5098168d18baeef0b8990067538891236adf586c77c450d"},{"version":"c0387f85c1ed13210f4e91c2bc0ca0ce30a6c92139c59e62edc3c6cdb947a7c4","signature":"c6595f388cd13a3953de18d7fa043404199216753a8dd09f62ff7e23e6252318"},{"version":"564e46288d96bef0f61ba7e11056ca7bd429aa342f640317d49811b2b4a87043","signature":"c02958dbcf88cb888224bf3850bd8bcd7a30638b4e9b92bd22587471cf6c9835"},{"version":"bb991da84034bcb8af11d3f7fb49538acf99a98939d8a49f1122277f21d5a279","signature":"ec6b17ccdaceada5d0ff2bfe58f759e4b79e1d056974fec97791a9475b0657b3"},{"version":"74932696957da2741267db0519933a10f2caae68cce5bea90072e5fc57db23cd","signature":"5c8e01f96eccbc91b7243158ddef84763ac292ad5e914a352f8422f4b374aa6a"},{"version":"673c785030e4a6704c2cae152b92b2d95c88c4d435e342db2d19cf46d6f874b0","signature":"057e3888c2fd6ff7a84ab0ec9ebfa9bb1bc8399836e87973a812b21c9431767b"},{"version":"0e82b1b1053a5de4c429e12a1d21eee1ec4806e458b832e4774186ca0f7e4236","signature":"37f7acbfb476967d35f33541ea813afd46bee4432026b3d709b41a0fa1b0168c"},{"version":"c61d23454613adceecc3e78bdd0a8ee1ecde66cb3d5e1dbfb80cc94b7f1ce0b9","signature":"9236418243d296448ad7e18c18e20ac815efb54804bc843c91355c2a2310a4e5"},{"version":"0ab4ef4fb8ec491e58f1d12de09d1575c6b00ee3e98ed47e80737937f8bdebc3","signature":"1ce23953edc19a9ae5913fecc28971b7598293637ea38553ef00a6689834f294"},{"version":"9c9d30f7cba0c56e2a2afd73b4eeaa7755d0f1b04d68af2a618d0fbe6772d8f8","signature":"19fc3abb4682a127b753ceb3ad5e0e48c03531ea67ba1e7305ca571fb7012ed2"},{"version":"9d845b9d3b5d420766a82189a907b341bd58d687613b5f1d3cd770e93602bf35","signature":"0e92e6224e167b5d58f377e0513c39bb3b513cb2916a2166f87a20d3ac9cc629"},{"version":"24ded7e851a9c446b199bc5eb987b92f47e5f328fb8205ab3bb8d5a2960ccf55","signature":"f8ac07e911fd9a3bff7d0a2cb3b8589e14a3b52a3d26a2fad493348f494edfac"},{"version":"26d49ab867c9c2a00e7abd5c4a9c0553a5194d5a26f318a624deaf5b6db56f63","signature":"24cf0f162a2bcae8f5ae4b678da2ca97a91699cfc30898c606cda9ebec4d9f69"},{"version":"80621ac28c75bf6c664ce92a4dc1984cc4ae39d18163f634edb6f39aca131eed","signature":"7b90b0d17d565ad2bcb84936503634ee9f77cf9f40b2353797af5c1dd26b6161"},{"version":"304214119fe80a20e54711030bd7dcd3a3549dd4fa58e7907dc5aea49314483d","signature":"75bb75b5a48ff82403af76163372c541d80017c3a1bd023912a49315e0b7c857"},{"version":"7e34b3113146b88b7cf3feb9c38d54bb5004494787f9667352b9e68513123c8d","signature":"d4f8ca3691504bb90bd9512be095355b7ffd1c0287f7225cabdb2d95b376c8fd"},{"version":"277701c9b0dd09fbdbea95abeb8320e000af44d141bda7bf32a1021990f0d7c6","signature":"a9a36500eae5c5d23d90dc889ae116ee7afe97061abc80743514a3bd287fc850"},{"version":"9f338a67c935752b2cd34ed25688821bb43bf3993cde21cf9c2b67ed464e5e30","signature":"ac21403a12cbe347469f0e99c204b42c9fea77ab189763d4becbd9edff555e63"},{"version":"50c8a809ce23f5fab81f33ef5a007d93618070b411d470eb37ed016f77fc0ef2","signature":"b4d1585d23ab5fd5c64c34668928c806d69b8de34bf14688a38db25c17c59d39"},{"version":"ec1a7986aef0a3a1a7a5beb851b4f32886e1de8faab13a4c49ced77be116f048","signature":"a78d3790cc5ae1b4930c299095023c07eccff89cca32e2939a4722a716c9cb55"},{"version":"12ec2200ee91a045a93de21c96802413078541664d7b6888bbc79ca8c1488244","signature":"218f98e76e42a83449d9ba009fba0dbb4f53f12dfb7b899befb95df9c2a334b0"},{"version":"bc6927fdd4e4d9474abd768ea77a7d3e8dc11cb857ee427b33ea2f0c9396f78a","signature":"bae6a411b96f00598def766b83e30124448aa32d8826fbea105091099c2d5f68"},{"version":"50788d02f0a3706ec350f199a66f39d727ad92ce55b0f90a34c9532c80c7628d","signature":"3fdf4a05021e1b6beab9f24cb60619a354bdea5b3af5000ddc2d834b1e392a57"},{"version":"206adf107dbc82b1df1b01ebc42dc115e6cf99df68bcfbe2f0ca64436dd5a723","signature":"6729af65023caaaa5e219c29bb52d35e46a47b3128222b404ba24960b97bc907"},{"version":"7ed8567d72959fc070e30d0571356f25c6eb750aed88e5e9dc7a6351c7e23b6b","signature":"ff88fbeb9fc34d6ce2fa3a9ec0526dfb4d9f227e7e48dca9282fadebc1b9b3f5"},{"version":"c2a2136d5164c32d372784cb7684319e8f20a76084e37b068e50303391df06a0","signature":"e7dc47606500af2c3ea9d69e3d9ed293bdbbbf81bfcb0c48df6568c1ffee4b02"},{"version":"cd065d8de27478d8300d9faf20bed3bb099f883e5e6505dd502a5c197989ea10","signature":"55b35c14620fb1583e0fd5bec90694e957840bb65c16ee338580c6961ffe2de5"},{"version":"e241b03dd7c38e74909cdd9fce7d032c6ccdc2acb8d672450faf9f6c0acd3f7a","signature":"71e0ae0ab79469ca19dcf4d5566240e5019b5a59f0aa86a6d1b5afee5edac2b7"},{"version":"3aedcddf0c597216821a1765ced9dcfa4eb87715333a31304f200da7679d6166","signature":"f4453522d7a12c9e68c046016ee99bfc759d7c07df309510c331c750111ed4dc"},{"version":"6f7b0430b79e9ce49429eb824b0b7678ae503aad75ba670b78ea9f1cf1eef138","signature":"9446380d967f3cb34d51b74cb317bf65dbf2a5bf8bf73892553be82a579be921"},{"version":"d720a8f8b13e2cf730a4e1bd145dc6cfd256a098ec9df4277234edb41ca0e186","signature":"490e27c2a455ddcbcccf476c419ece90c92164ef21d4a63aeae2da6a53a96664"},{"version":"c7558d192400db5c38381c4fed22a26f0dbdfadbb134ae736ab3f22dbf85dbf8","signature":"93ff2618e49bfb06e7c63dc70b3670c683bdfe554cac80fc94d4d3d6c4add74b"},{"version":"1667256252c8450f07edfb17f1ce6638f42ad7481de98ebd64ec40113eba3303","signature":"4d1d0af493dd5441d3943b40468eae6331ee0a374977632cc0cedc04668a1979"},{"version":"1c7a9e0199e95951c4b47e6b8d5f7b99ca36855302b6ce1a0a380db7749d3800","signature":"a8981e806c16cf4a988385695a5e55294adfe356e59f54a9c3a13161f0e9edbd"},{"version":"2606bb4d741d90e54b1b94c3c26bbe9199866093edbc0bfd6d7d14c8fc3d1b5b","signature":"bc9ff410757b4a4d670c277e183cca8c92d9133e1c22cfa4920aa3e885e02d96"},{"version":"9e1055fc07da757b71c3e169a5669303e4652c338c06b6ce46a815bf2d99260f","signature":"9e8ba799a6c8fbe2ceb3b358d84bed46f5f7558aca856e3f63f14c444dfcb27e"},{"version":"2122e8c0a1306e026102a20b43b17b0464135c7f8b30f9f67c803c2a5a6e6884","signature":"b44400e11517ceddce8ec70b8163280b1b4ba891a19ebc5b2cc2307f291d3b88"},{"version":"7d8ca54716e502790d05cac18eb1e38b6feca001d51da11985f8deb4bc5615ad","signature":"de507d97c27553e4bf35cb8c2bc772fed8687c5538104274cccc9da99aca21c4"},{"version":"abf64f5c05be5dd41016e87e5969ed28600dc0e61aadaa61a00b0c3ef4381ce1","signature":"99a6ba31404d66459a57b93db84c49b16f0690ff7fd7bb07bd04fc192f4b22dd"},{"version":"7992fdd855c7dcde0b7c358fe4fea31203e8e9b6aa270e86047fe53c0c7009c7","signature":"00a5afb32489ec5937497735f6212357fd2f878a64aa57f4f0c0472d1c2bb8a1"},{"version":"553ffc936e18fd0ad6603c58e205efe897e61dd45eb5c9c190a9dd7b28b6206d","signature":"30e753be12067427fdac00849d0620d9f2cd7bf655a80c698ba3bd5671be8e74"},{"version":"3f443fbbf9924ab11703529cfc20eacd32d049779c198633228ba5ddcc7a1ba8","signature":"dd748d8d9eea57557a55a89f7ae5501835c4529678157498752db303f182b509"},{"version":"c059e8d4ee13412a9d817d1a0a5a3acce021e27ea235817bf8bea3901f9d40a9","signature":"c34b363d2b6cac61ffe29e155fdac051d7caaa197bcd33c1dbebb9632c10dcf0"},{"version":"55a9358103d4d3fc812e7226cbf54ec885ab5d274c3f0fd7c7b89138761420fe","signature":"09ac449d6431aaeea979c72f664e0179ac698d66b9dd695199bcc985f21b10b7"},{"version":"c7b9394525423256ff8e8b894a21af17505ebf3efde2592281d38aa1fa89870e","signature":"57ef2bfa07808447a73e8c64f5fc664184daacd3570c05c1efe56bcfda8a2eee"},{"version":"0c0f21b2b2173e3f325fb91099b32f3e19843b80a92f4ff2ff5a3d4235afa72a","signature":"e5861628b3c26867cdd721803b8fd63ac2568b1a32ece964adb39bcc895a1ce2"},{"version":"b6ab5c4c25d03afcedcaeac2f296db2512b99e67e3eda8109a223fff3483c934","signature":"e21a61ef0088c9d954930ad728286dd23c0dc3790192a6ee38dfe04a0f074140"},{"version":"92f971441944c16a305337d047a6ee4156e819f0e49e4f7d5ab5b87b6d42b6a8","signature":"b1021f4fb12bd15f1062a739a33f8a6bdb8791cf52f45d0babc5b1c0b4ee901a"},{"version":"8474a06d8021e426e1ac10c5bbb8793732d58749ccb4f6ac3c19849f3e39cbd2","signature":"414485e877b73dfb0beb3576aa20b367aca492e350800e76fcce97c7e9db675c"},{"version":"6b2535919d4b0ade8069db0b70c78f7c9976dd902afb594e05e2860324aacf89","signature":"d9a1a7448109c82c9e991536d02c1787bcbdb043dd9009ddf6585f6dafaa613e"},{"version":"532241d3c3502cbf657521d1eae1d1522cd7358d39d71cb58ee1e165774efbdd","signature":"ce3c320aa064afbbdf251b452c532471d0158759a28f4f50bbf3535947492370"},{"version":"0702499aa6384244a89e13df162163ed41949de76518a8580c8044e783876dea","signature":"401f1208590180b74cc9007c8a894d499d48b469bb110c769cb004aff4819b3c"},{"version":"89d9f65ec6270b62ac2297e2b69b0d063b1903f2d7ca02d57492ad83e07cedf8","signature":"dc2305978a758b68bbd20a28ac5a6ba729a6ec2adf9e65998ec0940d397b8e25"},{"version":"5f2da296822afef222b64f2230de59c8b19264e5fbee576bcf923ef127316a59","signature":"32d1aecfc4df9ffbdf41de3abde55daa360fd375bceb4b9f0539b5095efe762e"},{"version":"0e55f17f1022c18e2b88b6fff73f9f4e15121b300a924c4093fe60270803b79e","signature":"b8df9c14d085533e16aaa58dfa061788e5dea6b8d7e3a17ace1562e6d904cd85"},{"version":"18981392c502332d353be793e0eee6b4b71b92c4cc159879c76c0e412b50166f","signature":"85a5f8ec84196d475ea68d0239a7ee678d96c40274f16d0e940937166e2f9fa7"},{"version":"097c88111fa0b1df7962c1c30db8bae5dff4d0e7ac25a177f0fba84461129017","signature":"59b6b492be4b755e74f3abddc5c586cedddccd3d5dd10a4dbeb4316ec43bc7c3"},{"version":"5abe717e11b3a2dcec527571b041a6df92058148ab7e9db05e514860cdbaf785","signature":"6a891a6cb7835fc4aec6da5acfe7e699a7657630100a6eb7670e371e5a4d2ea4"},{"version":"da1eaddc979e537f52a882c74417f1fb3c683d81064f996a9af5dbf1b3b50e88","signature":"e8e8633851ccd9b775c85fbb5a1d901b07801430ab76700f960acac4bd7be2b7"},{"version":"311218105ed0b4673b3e5aa11b36e9de5feaa78ca3d836ed2331afeee09191be","signature":"bc9e14f58deffbbd68bc1bfce57f57b6da9d3251b36290327106cf70252d0c20"},{"version":"c353a00a14f77db4ae0a0e9893f06fe66e0f5cec36e640898d5eee06b82f58fe","signature":"c5bb2e36d8a842199f3de85755758e5b85094e03b7365b58466c7cd88a65bbf7"},{"version":"5c71f9d163d5f0ae5a03fb8a9b6a292a33dc0d9e82c9309ba1557dd2621ad712","signature":"20283420a1a06d38899bfa3f54d16f6902cb186f0907fc3b8977de158d081693"},{"version":"fe03c0b7d0eee5f3ad24a99cbcb4c914a7a262b635ca53120f1e28c374cec37f","signature":"cff7dbefa0c21c5e58f63d4b5f573436d80b8cfff344b555844d967e51d1d7c8"},{"version":"56e88d16d79406e39aa9de20559d941d2e1d779133fb5002633179a66b872d8d","signature":"0774366c811ec1c799b0c0922d3a58dd6e81ab902ff9847ef804b1cda0b16cd4"},{"version":"843877df3ecaeee2ed7425e24f8adda007ccd544467aa93b16344bbeee3ccf86","signature":"d621400249ba8e7421459928f59ca558d53c61417f4d07833f994947592fba99"},{"version":"a51a99c6f12fbd275b7d38f75659f78339793baa8ccaf0dc60a6b3509b307384","signature":"031f80190948b8a395721dbf882796ff5f71390be85d950e6796e851316f59d7"},{"version":"9d6489481686e1d4b12b9063bece5327681251df9aaf6b4815841da91f0c76ff","signature":"4d981d6aa5d8dc5af9b343ecb2c5f4c5a9e1e9890c31158e049df1c31bbf7a72"},{"version":"5da85146f8149cf43a0473f278bda54ec9063f977dacaa43ca157e251399a5ab","signature":"6ff5a08113fb520f023cd78f8c7151bcccc3824aa41cc276419d8f031e790082"},{"version":"50f12f73fb7bc94642aad9f14325af6cafbf17d89598fd518481afa6f4059c04","signature":"dbde942ed04200173975b3aba7a4b95d3d29638118f4ee6cf3c12bc5c6aae7a4"},{"version":"ef89e15381725b2dec9ad150a75b5ac071ed8d2a67429432cdf996bf6b7dcd49","signature":"2d40ed5b22e817c315e2d541bd1583648872728ce3e1cf92636778fcdcbf78db"},{"version":"47dcc1c11566410ba7ff49baf3ee84445d2c552e90371b147a8cf7608f125d7d","signature":"7a62fccc87f6097e7aef8373169218fe17cec1f7de472cf07a7234b4b298fe94"},{"version":"1e90e0336b6a315bd3241c1ccce81216caaf4fb927dd103a45cf395c15d42b57","signature":"8d8a8295107e2834f955762ff110f8f87cec9211e37d5de2be000e4593fc5af7"},{"version":"f15212ead0a0cbdca75bb858d26ef06276f07891d0ef5469f3712de626379b93","signature":"5d504d7753f7c784bb3aa32ee67d6cccf890afa51afe0058d84acc63c7295e11"},{"version":"4037e9d672f86620f30517ab16631866b429c208b0b997f16e10b9d265e0eadc","signature":"79f1f1e9f52a7c07246a9084b3e5bb6af722523a8325ebf02a0daec62b773448"},{"version":"790d61ef88b26fc99e4fdbbd54f1aa54de701a2d2f036c7791fefae21f0a610a","signature":"d809793dd927943844394da81f4a73e4f930288f6ce94d44008c838d422a0db0"},{"version":"7b616cdd7b57e71550aab727941d24acb9add6b75f00f7a569765dbf528aee73","signature":"4c66d74ef56464f8dff370e32297186663f98e047d7b18fe5b797b5d8f37da8b"},{"version":"492f0ee2b81dab625369473c3a11dc3a5eb03d288f868a0c3f60bf693b35a676","signature":"411c558ddfdef650e590b3eee15829ff8efc820b6456dde9bacdaf8a19b9385e"},{"version":"9e9a4eb8b61b2e816448f43ceb9cb812557aaf023a5b3d6314481ac7c3eb7530","signature":"945db498071dcdc8b7c6f3ebe1aa3923f8daf684d84ab5af621f5f4d127ec5ca"},{"version":"44bb029cda827025d0d15cced0419a884118891ed406587d6f546c5353f07d98","signature":"77c82107fe9fa152910d013c7b5a61554b3f8d6919fb29324be04fedc91aa46f"},{"version":"97726ff3fadb4a0b16b6dd1a131c318fa8da9db1dc316fdf5f78d592c953f77c","signature":"2a499f5a9196f0306f744c20a49e4b172c69713ec3234a59f168c686b12d9520"},{"version":"3d7b9603ccdd03dc6cbefa8b324da7dbfee3c9d19590d58231ae3b9e86deaa98","signature":"533c37afc84f4a66e5d320a3d8bd4d8fa4d7756da0712e42abc776962f08ce84"},{"version":"b35c6347adef687c6b3657f0dbbb781304e2c2fc7a48c23443b43282bbe11331","signature":"14b994430a17c83325fae751d73b4b91dc638fb2a13b138935416812efc5b08f"},{"version":"b1c005bf848730a351c5c28c0367ad69e166f4c86a3ecc05dceca8bb6c69cd52","signature":"7a556bdb2f531ed1a37a59882388b506096e10668b8a6aac5e1a43e41cfc06d6"},{"version":"6a3728541166c0134100e44c1c5131c1f5eaa3c96129ba59594a74e3eb76d149","signature":"12848da39546517140b9f4b17b7d0c9a9d91657225c839afe34bf0549aead842"},{"version":"5b53c1225b50f71bd1cb30da590ddb1720588a883d50745df0e878ef224779e5","signature":"02ac97652b56323526d333a584057e5553eb421cafcea8cc6236316990b035a8"},{"version":"f757059586ca80970656ea29588fab3eccbb9912044c6a5799235bf8810bb631","signature":"a75023d9e41a78a7e453afe6a8e21f944e62fabe49a1ba7e4e9b867bbf5d280e"},{"version":"38bcdf7ab9b201edbf3a12b93fa8c7139329330cf6e415c5ec0e0c665a29e2f9","signature":"9633c6dfdba9dbba498db27ef9b4d4dd2afc71db9920cbf4a0452f566a613258"},{"version":"3ef9b9fe1153fd2e3cbf74af49db14a0e17caf398939209852281a2d37a6e039","signature":"401545b2fa7c40a45ec19cf00926addea4987c95c7c1e8943270774c876b68e9"},{"version":"a06c8d4788c8f1b23c6c1cbd28484aacb036d5d15b2248c330c3ec061204edfd","signature":"0fc0c1e35e42c295ddd822600742ad7b6d8469daa236585225e2cb4416abc996"},{"version":"c6121d418ba0340b6ef64f3d39f3083ff6ad1a5aa39df08187427164b9ba4cd8","signature":"6814a0599ce7feb30613525fd5aab4b0edd97beeec5d9be766024892b074883a"},{"version":"be8ab4a80ac239b7deebcbedf5e50b969e1ff49e786289ba9d5f64ee997a218e","signature":"ac64a066fc27b1687ea0777aaf98076ea0dffc4a2a3f6cd5412368dd9cae7562"},{"version":"f1d4563a4b1767dc0eb821a44609484863ac408dd989d73295ce6050c8fcb203","signature":"abe72455f516e18ed06bbb7e01ea1450572ff48cd86d4153c5853474dae5e8c1"},{"version":"416d3fc5e8723520066243cd9e92d881747f642c25d25dfab4f774fb66304e9a","signature":"4ae59f31cbb1d8f65520a2852714b43fa800e651ddf50dc1a3e68c56d6537f9c"},{"version":"68e3ea320ce63c137fc042dbf759f09c3d9ddaa22f4b4dc6793f5217b10933e5","signature":"a0b43246886945a46b382596b870da48d5d5fabfb55e7ac009ff0dca3e48a5c5"},{"version":"65489579df08cbeb406e993746414b828cb583fdada6f410714dc7084b070edb","signature":"722f39b7bf485d28ffb6ac6d2dcdd0985ebdb4fc12f94a14011ba889df86d200"},{"version":"8d0fc74f4806e9c71d0e6587e5d844e93a857e7ef1935fb8f59e9c5bf14e8b3b","signature":"b79d4edff2b414e35a3bd893e38505d7b8fee3cc678f7c8c06d3ded65ec13913"},{"version":"c96a5853a9795dfcc0c3682991924e93dd487c7d88ad5ef26a4fbaf776b780fd","signature":"868c432b61889f1028f7b0d5ac70541268dd34a158b7def3d464c0ebc5a0306b"},{"version":"cfb552f86c7103dc47a7bc3db045049a449c0392b65e04529be0c20a462fa450","signature":"b30c66f8aeae088710859fc3c16836dedd29bdc025e7304fc50dce105b9c04e6"},{"version":"8d0f1cd6f225c29791c453aed0e97cc67d5998dbb5dcf6f9bfc082f8078edf6f","signature":"1d1927e0f32fe7113f0d8d5fabb07d479115d00e67a8a935d5f8f447e81fd876"},{"version":"21c1fd70a85c1449c253eae941998ae919bc66887edc63591832bcd396ce37b3","signature":"60e6043a56300fa24f867fa24168c0ce827d6154625db4aa79ed2d49432f06af"},{"version":"0fc97acce344521fd3a5ee3bf37f90b96d9bed91ed4b6e21baaac3bc47996db1","signature":"0d7b280414b0cb316adfab6c3609f4d3b0c34aa5f942a74f5d0b330aee061cbc"},{"version":"3c30eab34fe65c8d59fba196724080f0ae9ab17f6267d91660771867156b67d0","signature":"aeb705359b2226459d63d6ea83c53f69dd42c24f2ca58136fb06fce7e5306a3e"},{"version":"b1535397a73ca6046ca08957788a4c9a745730c7b2b887e9b9bc784214f3abac","impliedFormat":1},{"version":"1dab12d45a7ab2b167b489150cc7d10043d97eadc4255bfee8d9e07697073c61","impliedFormat":1},{"version":"611c4448eee5289fb486356d96a8049ce8e10e58885608b1d218ab6000c489b3","impliedFormat":1},{"version":"5de017dece7444a2041f5f729fe5035c3e8a94065910fbd235949a25c0c5b035","impliedFormat":1},{"version":"d47961927fe421b16a444286485165f10f18c2ef7b2b32a599c6f22106cd223b","impliedFormat":1},{"version":"341672ca9475e1625c105a6a99f46e8b4f14dff977e53a828deef7b5e932638f","impliedFormat":1},{"version":"d3b5d359e0523d0b9f85016266c9a50ce9cda399aeac1b9eeecb63ba577e4d27","impliedFormat":1},{"version":"5b9f65234e953177fcc9088e69d363706ccd0696a15d254ac5787b28bdfb7cb0","impliedFormat":1},{"version":"510a5373df4110d355b3fb5c72dfd3906782aeacbb44de71ceee0f0dece36352","impliedFormat":1},{"version":"eb76f85d8a8893360da026a53b39152237aaa7f033a267009b8e590139afd7de","impliedFormat":1},{"version":"1c19f268e0f1ed1a6485ca80e0cfd4e21bdc71cb974e2ac7b04b5fce0a91482b","impliedFormat":1},{"version":"84a28d684e49bae482c89c996e8aeaabf44c0355237a3a1303749da2161a90c1","impliedFormat":1},{"version":"89c36d61bae1591a26b3c08db2af6fdd43ffaab0f96646dead5af39ff0cf44d3","impliedFormat":1},{"version":"fcd615891bdf6421c708b42a6006ed8b0cf50ca0ac2b37d66a5777d8222893ce","impliedFormat":1},{"version":"1c87dfe5efcac5c2cd5fc454fe5df66116d7dc284b6e7b70bd30c07375176b36","impliedFormat":1},{"version":"6362fcd24c5b52eb88e9cf33876abd9b066d520fc9d4c24173e58dcddcfe12d5","impliedFormat":1},{"version":"aa064f60b7e64c04a759f5806a0d82a954452300ee27566232b0cf5dad5b6ba6","impliedFormat":1},{"version":"7ffb4e58ca1b9ed5f26bed3dc0287c4abd7a2ba301ca55e2546d01a7f7f73de7","impliedFormat":1},{"version":"65a6307cc74644b8813e553b468ea7cc7a1e5c4b241db255098b35f308bfc4b5","impliedFormat":1},{"version":"bd8e8f02d1b0ebfa518f7d8b5f0db06ae260c192e211a1ef86397f4b49ee198f","impliedFormat":1},{"version":"71b32ccf8c508c2f7445b1b2c144dd7eef9434f7bfa6a92a9ebd0253a75cb54a","impliedFormat":1},{"version":"4fd8e7e446c8379cfb1f165961b1d2f984b40d73f5ad343d93e33962292ec2e0","impliedFormat":1},{"version":"45079ac211d6cfda93dd7d0e7fc1cf2e510dad5610048ef71e47328b765515be","impliedFormat":1},{"version":"7ae8f8b4f56ba486dc9561d873aae5b3ad263ffb9683c8f9ffc18d25a7fd09a4","impliedFormat":1},{"version":"e0ab56e00ef473df66b345c9d64e42823c03e84d9a679020746d23710c2f9fce","impliedFormat":1},{"version":"d99deead63d250c60b647620d1ddaf497779aef1084f85d3d0a353cbc4ea8a60","impliedFormat":1},{"version":"ba64b14db9d08613474dc7c06d8ffbcb22a00a4f9d2641b2dcf97bc91da14275","impliedFormat":1},{"version":"530197974beb0a02c5a9eb7223f03e27651422345c8c35e1a13ddc67e6365af5","impliedFormat":1},{"version":"512c43b21074254148f89bd80ae00f7126db68b4d0bd1583b77b9c8af91cc0d3","impliedFormat":1},{"version":"0bfacd36c923f059779049c6c74c00823c56386397a541fefc8d8672d26e0c42","impliedFormat":1},{"version":"19d04b82ed0dc5ba742521b6da97f22362fe40d6efa5ca5650f08381e5c939b2","impliedFormat":1},{"version":"f02ac71075b54b5c0a384dddbd773c9852dba14b4bf61ca9f1c8ba6b09101d3e","impliedFormat":1},{"version":"bbf0ae18efd0b886897a23141532d9695435c279921c24bcb86090f2466d0727","impliedFormat":1},{"version":"067670de65606b4aa07964b0269b788a7fe48026864326cd3ab5db9fc5e93120","impliedFormat":1},{"version":"7a094146e95764e687120cdb840d7e92fe9960c2168d697639ad51af7230ef5e","impliedFormat":1},{"version":"21290aaea56895f836a0f1da5e1ef89285f8c0e85dc85fd59e2b887255484a6f","impliedFormat":1},{"version":"a07254fded28555a750750f3016aa44ec8b41fbf3664b380829ed8948124bafe","impliedFormat":1},{"version":"f14fbd9ec19692009e5f2727a662f841bbe65ac098e3371eb9a4d9e6ac05bca7","impliedFormat":1},{"version":"46f640a5efe8e5d464ced887797e7855c60581c27575971493998f253931b9a3","impliedFormat":1},{"version":"cdf62cebf884c6fde74f733d7993b7e255e513d6bc1d0e76c5c745ac8df98453","impliedFormat":1},{"version":"e6dd8526d318cce4cb3e83bef3cb4bf3aa08186ddc984c4663cf7dee221d430e","impliedFormat":1},{"version":"bc79e5e54981d32d02e32014b0279f1577055b2ebee12f4d2dc6451efd823a19","impliedFormat":1},{"version":"ce9f76eceb4f35c5ecd9bf7a1a22774c8b4962c2c52e5d56a8d3581a07b392f9","impliedFormat":1},{"version":"7d390f34038ca66aef27575cffb5a25a1034df470a8f7789a9079397a359bf8b","impliedFormat":1},{"version":"18084f07f6e85e59ce11b7118163dff2e452694fffb167d9973617699405fbd1","impliedFormat":1},{"version":"6af607dd78a033679e46c1c69c126313a1485069bdec46036f0fbfe64e393979","impliedFormat":1},{"version":"44c556b0d0ede234f633da4fb95df7d6e9780007003e108e88b4969541373db1","impliedFormat":1},{"version":"ef1491fb98f7a8837af94bfff14351b28485d8b8f490987820695cedac76dc99","impliedFormat":1},{"version":"0d4ba4ad7632e46bab669c1261452a1b35b58c3b1f6a64fb456440488f9008cf","impliedFormat":1},{"version":"74a0fa488591d372a544454d6cd93bbadd09c26474595ea8afed7125692e0859","impliedFormat":1},{"version":"0a9ae72be840cc5be5b0af985997029c74e3f5bcd4237b0055096bb01241d723","impliedFormat":1},{"version":"920004608418d82d0aad39134e275a427255aaf1dafe44dca10cc432ef5ca72a","impliedFormat":1},{"version":"3ac2bd86af2bab352d126ccdde1381cd4db82e3d09a887391c5c1254790727a1","impliedFormat":1},{"version":"2efc9ad74a84d3af0e00c12769a1032b2c349430d49aadebdf710f57857c9647","impliedFormat":1},{"version":"f18cc4e4728203a0282b94fc542523dfd78967a8f160fabc920faa120688151f","impliedFormat":1},{"version":"cc609a30a3dd07d6074290dadfb49b9f0f2c09d0ae7f2fa6b41e2dae2432417b","impliedFormat":1},{"version":"c473f6bd005279b9f3a08c38986f1f0eaf1b0f9d094fec6bc66309e7504b6460","impliedFormat":1},{"version":"0043ff78e9f07cbbbb934dd80d0f5fe190437715446ec9550d1f97b74ec951ac","impliedFormat":1},{"version":"bdc013746db3189a2525e87e2da9a6681f78352ef25ae513aa5f9a75f541e0ae","impliedFormat":1},{"version":"4f567b8360c2be77e609f98efc15de3ffcdbe2a806f34a3eba1ee607c04abab6","impliedFormat":1},{"version":"615bf0ac5606a0e79312d70d4b978ac4a39b3add886b555b1b1a35472327034e","impliedFormat":1},{"version":"818e96d8e24d98dfd8fd6d9d1bbabcac082bcf5fbbe64ca2a32d006209a8ee54","impliedFormat":1},{"version":"18b0b9a38fe92aa95a40431676b2102139c5257e5635fe6a48b197e9dcb660f1","impliedFormat":1},{"version":"86b382f98cb678ff23a74fe1d940cbbf67bcd3162259e8924590ecf8ee24701e","impliedFormat":1},{"version":"aeea2c497f27ce34df29448cbe66adb0f07d3a5d210c24943d38b8026ffa6d3c","impliedFormat":1},{"version":"0fbe1a754e3da007cc2726f61bc8f89b34b466fe205b20c1e316eb240bebe9e8","impliedFormat":1},{"version":"aa2f3c289c7a3403633e411985025b79af473c0bf0fdd980b9712bd6a1705d59","impliedFormat":1},{"version":"e140d9fa025dadc4b098c54278271a032d170d09f85f16f372e4879765277af8","impliedFormat":1},{"version":"70d9e5189fd4dabc81b82cf7691d80e0abf55df5030cc7f12d57df62c72b5076","impliedFormat":1},{"version":"a96be3ed573c2a6d4c7d4e7540f1738a6e90c92f05f684f5ee2533929dd8c6b2","impliedFormat":1},{"version":"2a545aa0bc738bd0080a931ccf8d1d9486c75cbc93e154597d93f46d2f3be3b4","impliedFormat":1},{"version":"137272a656222e83280287c3b6b6d949d38e6c125b48aff9e987cf584ff8eb42","impliedFormat":1},{"version":"5277b2beeb856b348af1c23ffdaccde1ec447abede6f017a0ab0362613309587","impliedFormat":1},{"version":"d4b6804b4c4cb3d65efd5dc8a672825cea7b39db98363d2d9c2608078adce5f8","impliedFormat":1},{"version":"929f67e0e7f3b3a3bcd4e17074e2e60c94b1e27a8135472a7d002a36cd640629","impliedFormat":1},{"version":"0c73536b65135298d43d1ef51dd81a6eba3b69ef0ce005db3de11365fda30a55","impliedFormat":1},{"version":"2a545aa0bc738bd0080a931ccf8d1d9486c75cbc93e154597d93f46d2f3be3b4","impliedFormat":99},{"version":"9a93e0c2d6fa17018d91910a27f16b582765e0fec5fd3aca2166fa4e3317e35d","signature":"0d20b7666ff0034e2c001607718702d79e3c2ffd1f40bcae18da8b101fadd71c"},{"version":"e82591fc62047bf9bd28dec8dc0caab05b1361206c361fd19f8aa0fe829fb415","signature":"3504adfa9605ad21003156ee158e7d62866484e1902196481fa8ea0caa80435d"},{"version":"0e2c0bb4f07bff63736681697439642da0a71ec76139d921354fb4cc15bda15a","signature":"8eb5d3569824aba69bac738c173d655d8fb61b8585dc4417a04c591d8e1b26bb"},{"version":"f4d274c7cc654c02b314c49f6b5a7fbf31094c2a269b2eb1b0b46a9f8db7cfe0","signature":"0b4872603cdab838437f754c0ab373796accb15efe9d82e3d45782ce193369a8"},{"version":"77ed2023c0f06f2a6b4d4f24d0d77382abe6b5cc0d7a388b9578c9220f286be2","signature":"ba619e2fa2bd28274278ab5235a10cad791e8badcc1f64b2b6641e5dfcbf2f61"},{"version":"2b11d4069fa624a51bd209d6e078333ec1cc12629944d1459f45664e0137226e","signature":"73256b8ea6edc26ff0d24122c1db849a3b10757cd78aa5d658daad533529919e"},{"version":"55f57b1b0faadcef5da97aec5d9c3dccc94b9f56e0cedb1a18ce5a8811e7249b","signature":"5689535a15d03e0a240802149a23706b8be75dc050ae0be9de884bc7c7878fa9"},{"version":"91863f2cd7c35c7df86ce5faaae82e1450a99ee40c440ce1826ba7240818c3cf","signature":"4980c890de11b6db5b6c980dab1d996bdcd746a36199849a49a276ba80371339"},{"version":"65417ced218ed4e2159bfecb014f5d7e1cd351f963f6e0c8895cdf0611636aa5","signature":"cf7f4bea29a7e73deddc02ac52ba0c28143c4a54bee3364fd5e209b681ae8981"},{"version":"979df6a8b93827fa32ce5e35c430b989caf4273d164cc364b9e70726d5f42bd8","signature":"780a11c3f58a96e85193d04cb8f474720c37d6db82e64142639e0aeca7c14661"},{"version":"c912d1c80e077b598e0dc3ab978847a260a0b18579193dc8195dab79e09c9b65","signature":"99ffa97c910c30821679211070c64f0cd84154659d100d7bfc383dcb86045bce"},{"version":"884eaf70b02207d6d53d678cd4673fe80d93d110897cabf3c4af69cb6c1603e1","signature":"dad887fa4ed8c7e1be19c2b3529a9ef7905414b5b866c8647668eaf942dd630e"},{"version":"e278385c2205b853625d59bbc9f9cbdcd7bcc30dd1cab918e03455554027e7fc","signature":"7ce2ac19364777e91c04ed2fd74e45348bda0c7a48dd79df0b4a4f00e9be9995"},{"version":"2bd59323b45d43ed60764e4306339bcd9f078207ea769dfdfe99ac59d0ea0b98","signature":"1af3e359f2c3a3b25e6cf0532c9b12b27c8ade0e7eb582007452ce06db289d9b"},{"version":"8a97d6c8b72f9fc66b1281d1ca235736958e8327b9fcd7f2065957218e474f86","signature":"bf3ca96bc59503b3214f0618af79741f5a28de7d7ac663c13578af8a12fdc385"},{"version":"4b1818ea1c348f92ed8efe1c7ae76e2d87ae6ab15057ad011eb97899f8e929a4","signature":"dbd0c498b5edc07924a5f7ebf0ae90efb9436dc2229792eaf175e16c03248f98"},{"version":"5bbf60bf8b06a1e76352363c76743b0e96bc0e917f26bf8540162d32bbc5fc14","signature":"800a7b9dc46ee24c46db2afa893e0bcf0b8c8bb1bd9b8db5361e34fa3c2aad18"},{"version":"ed0c7c8654bd978cdf57d19918154e62b23e5e4b8db2cc68956fe6f2c8ed7bd0","signature":"b7b254f81d9a367bcf98769a957c2f8dfef2267573a862b52ee2748647af39e1"},{"version":"7081278ac3a6e1efea13238ea5ea974f8177fe11d469c85b87f0127f53a2937d","signature":"d949e121e0b71673df6140eda41341316408ac47555ba72b02b4abd3f0b6bc3e"},{"version":"5fbe83c9492d80ec1cf1aa1983a4e341f25a70a8088ffb5e9f559fedd56a5f3c","signature":"7bb19ff78f5dad94999cc2e0debb65a5ef8812b4507a7652b0b3fc455a9e8ddb"},{"version":"46b8af0331df242732a621f51281b06fda610781d324d4cc2862caaea51ee48f","signature":"d96bc2df413362d899c2a26a8be1fbf15d38eb758a9d65112d7eee95611f0bb4"},{"version":"ea5d7d5df768c0b8bb4a9e78f20a0f2b7f04a968623816c7c42796a682c872ff","signature":"ca0d4f9fee10dac6884f9cbd977eeb4e27683344cac0c99bc23e901e7d0babb9"},{"version":"ae4cc51e63b485a94b4ab799c5bcbae2cd76ccb755b26d4ca457c34eeeda981a","signature":"96a7977f7405149cb2a3637eabba9eeec8fea99592f0e5e037efbd74d67ff9d2"},{"version":"084da56a15ec4d2b92d763297a412d2f8efe14d52391019dd42ea405489955d8","signature":"512843e9d917c0a57276d58b2e060897baa591256abbc441228fa24f003b3539"},{"version":"ad98755cebe0206ee29ed0a9954f495df0e632c38d434c5e336ea5f8c314316c","signature":"7d52f0155efb4fbfbeb7a71bb9437c364c94315acf276096ea28168fc24aeb80"},{"version":"9408b29bce1cb25290705d7aa27742932b71c2b1c66c29c60dd0e2bd3e2be368","signature":"58a633a5995b70987959aee4115c9d2c0137c014053d64c92c2e2d03785333e1"},{"version":"1f1d577309b97d2f2f5fd595ce36360c757b7df466eeafbe1bb4b5e32d51f3a4","signature":"d22b2ed965a6ef70592065bc5e129d113648ff38efe84b3393591b802d92726a"},{"version":"4ccf36f3a7d70e9695962f13569f7fe68fee56d862f27e64541d09a238ef8cd8","signature":"9a1fa87e956dd9e945a728ecbf0bcdf59b5bc35bd4ab98618c8f51fe319ae756"},{"version":"2b43b07fe94106f5472a4dc96060e9ec87a1762abfe901479d3e4bbd6a5b54a9","signature":"d3fcd7e5c042241fda26edb5e44b7987838092f2c30b9fcfd5fde8cc5af1b958"},{"version":"d50e1b864afa7dbd6e6483eb9850e156acdec7b39f6759cbc3e048149228080f","signature":"29e0a461350da6f5c7ac57339ad2e541e5492c422f6ecc2f66413b4a741a1e1e"},{"version":"6dd2ce1011ceedc6c5701ce9be652e6081256473012998175e389eea39174884","signature":"66aebe870c5c940805b59e3ed00f2e366eb0633ee94f069f4d3147e4b052693c"},{"version":"af8a2ab913d22ceb1a6c51d29c315941eb6fe950a24eaa871b6af91586b32fca","signature":"4a26aafff5702c778bf7349914063554cabacbf4b74ba530aea9fd2b5c060e1b"},{"version":"30137406242997f2c452f99ccfcc257a339f3deeae31036cb7530e4f6f898ea7","signature":"e5d7539a72d07ef9c3d686776f885111a063fc63c60c975d027b6b643970a358"},{"version":"e0381a2cdab33262ae2e66a732f079a1efd31e47fb9fc501ef1184df6102ea8d","signature":"af8541ad25caf543ae81e642a69d481f7bb2d0b642df88c46a1fe8910626a935"},{"version":"f6c4d5f246f720577217208a1a3f99651fb8bee70ec7a571a8f267b73d25867d","signature":"562105feb1d69fc9516ca37ffc5e5af73d1339f62eaac2693c4856b3a06f1a21"},{"version":"c40cb7b967045f7f46a4572d7412a531be5855400ed04064361294b34afbc42d","signature":"8cc1e098f03dc6645371b775e7cdb7f6eb24ce76942b98c5d2d43919ddead4c4"},{"version":"5123f43e9de341261bebb1e1ffb958f01d75f71fd9c76197313a325fb5b1a159","signature":"56cce191669f569e4f1eadfd36e7a99bc9958d88a7533e25ac26c0b8e6236e6d"},{"version":"5ce3756f05ca0810e17437fb6763cefc74938c8c1b2b25fe51dbf04c24d8ac92","signature":"f7592b9eb1b3d9d1583aec0153fe74f9970f47d18bc1aac8f9d4d9e1783de183"},{"version":"76db92e0ae6ac0021b17af8f167b21094ae6ab30a2398ab40e81c3625239128f","signature":"137de1e22724e42c5f197f61b17ec1264467852dd37b27811c311c97d705c138"},{"version":"546006f39ee42d78b95c643faa574c338775050364e85154d984d129e812df54","signature":"dba53de0cd1e77ba275a61f6203783f10a2b35ff248306fbd6d9689303d50f03"},{"version":"0f59b150e306de736e08d3f5b2e138beecaff95df79f61faf7449f4938f21b06","signature":"f19a0c7e1142fc0502d9e0014961ee6a6fe8b9fc26c4602a72aea8c904f15349"},{"version":"2046fe57062fdada2040da98eb04a0e2af848230bcd1fa055898c45202fd884f","signature":"bc100a6821798c9203c229f4f702ed13caf45a78529be319523cf8101b0e69e2"},{"version":"e486e7352316102cb7ae42084cdcbd25c1592700eb758594ad3c40b7421631a2","signature":"72d589144cb568b0b803e59532ac2df4c04998cc89d175d259815ce6a1acd5ec"},{"version":"ac76a5f85b70d469b19f5ed929c2349d85f36af4cfa38af722cbac5b560f9d54","signature":"06204db393a51c743e3f66ab6d961ff115b2339c936b98c2d7ef7574bf8072c3"},{"version":"41a4b706f190423fb86f0fe568b685dc59c4a76760b506b02c10a3eb70ff880d","impliedFormat":1},{"version":"e26d25ac80157bf4dfaf1b15917520d7d5aa515389c7c0464911ab0129e58835","impliedFormat":1},{"version":"e305ba550c25a1807b5c432b31563f897c82111c8bce166e560029c94df204c3","impliedFormat":1},{"version":"a3e0936d6b795d2fc46850dc9c590152942ee297f3271dbd3d8588c3983592c6","impliedFormat":1},{"version":"2013af14579369e7822fb5f20f01268f3338141d4a382d5969144fe9c6a25ccb","impliedFormat":1},{"version":"6b7fcb23322518af97092aafb777aefa9196a1db215071fe8f96ae6f6101b498","impliedFormat":1},{"version":"a98d81cd9207ec9f1bd54d454809a60b267043dd56b165bdb6912bf9d63d4759","impliedFormat":1},{"version":"cd1aff7bdfa3af19aae3a21c958b932af6941ed89af069bd789f9ba5ad43377c","impliedFormat":1},{"version":"33e9caf10988726e5f7be53d2a4ebaad8db16e51b5f81e5ed4dbb0068f3a88fb","impliedFormat":1},{"version":"cc0d535d54cfec869fe4d28b9acda7e3427b25d8d84fdfe495021ad4cdee301e","impliedFormat":1},{"version":"f3e743e40c9a3b22de28624c8428e1df4cedc2fa3b3d99c546ebb95592ba1978","impliedFormat":1},{"version":"f8dab311b48db5e3b4361d76a629201098efdaa1b785120353f0ad221b263682","impliedFormat":1},{"version":"261b2daf69470a9e9d1ef26aa5e0c23f1611af380eb5e5f031c776ba57367741","impliedFormat":1},{"version":"e181f3ee5845726fa8077b39742a875d4fbaeb35f036765f95a77afcf982f989","impliedFormat":1},{"version":"9227c7d1182bc93c52d39d65472fef3f850a0d0145e1fac7388758d995878ccb","impliedFormat":1},{"version":"d90fecb86a618f308931b2efa45d7beeb73185db71ff184c1066b4e5f5900771","impliedFormat":1},{"version":"69af21e2252e417f1f26d2e6b1ded1f20dba11f066ce0690c53946f4369e00cf","impliedFormat":1},{"version":"fd4ae6100e700ea9e36ab622968f12ec03792f98f7da286cc44649395022453f","impliedFormat":1},{"version":"1adede2d971ad167525195ed811b8e0af2d17c728350689c470b6d5c5a3aa60c","impliedFormat":1},{"version":"4486b61dcb644ea4527e90b5bfbe7600b9ed50371d334a0c01e8fe661a99d952","impliedFormat":1},{"version":"dae0c9cc1c106f7b91a726813cfafe489dec4fb1e3f4f7c684d14e5f99f96b95","impliedFormat":1},{"version":"bf5fe5a9da7069bca0bb83fdc31ba9f624ac778c3ce25a06a3099d83a92a4858","impliedFormat":1},{"version":"7a0958a789740f9ddba6b7d80fbb1ae4d97da910970c1d59965ebc80304ad22c","impliedFormat":1},{"version":"54c9f10d962306a39448b924e73e631bba8221b5797ca4b2dcfb00dca78827b8","impliedFormat":1},{"version":"7125d9240eb556853fdaf7c892b60c630f6d01bd11580a32e9f299b0b91ca4f5","impliedFormat":1},{"version":"b339079a6ce4d7e86dc68ea8af379ee8700dfb205da5546ffdd3cdfe4be43df4","impliedFormat":1},{"version":"a97af3b565a5649c8cd983df40878da9a4bc080cf1a918199574f82a25fa727f","impliedFormat":1},{"version":"6e5996003f6555309d988ceae5b9952f3de47fc6e5110e5d64c612d4f37cb490","impliedFormat":1},{"version":"f07a1939b55120050d3fb95633ac6f8ebdb594e63eaf8f14c123480cc4bec03a","impliedFormat":1},{"version":"325e48e1f58a88830852e00f4fe7c515afef925a1254975a3ba9c54c9156f3fb","impliedFormat":1},{"version":"79caaa6f2db6cd3a1362f50b48ebae937a234b421c66516b8c98282b7d713d12","impliedFormat":1},{"version":"e7f0aa1f6c72a395ef5ac8749c03a31b00ea527abca2db5aea63804fef2dbb72","impliedFormat":1},{"version":"edca8fd9f3960acb4223f2a596d1f944320193afb076cb8b96afc19fb83144ec","impliedFormat":1},{"version":"511a250228acb3131a8c9eabaf45a53c3015c8a5abffc1c2e8b37942b779ef31","impliedFormat":1},{"version":"496927b9d1a6a7bea58453b9749b878ee1040ddc22de6db6e689db11ec111ebb","impliedFormat":1},{"version":"d17c142d0bbf9f20c696ed0224d1740fbbda0b0211225f2e48732809e15f2505","impliedFormat":1},{"version":"67e2a7d5facca25f1df14bc4fa4167337405fdc98b47df49b98aa2dcdaff5696","impliedFormat":1},{"version":"e16f1bf72fcbea703f1b9dba81e6faaf65e29d24128f9e09504c9a862f41c9d1","impliedFormat":1},{"version":"2e47b85f55b2c6606b137b4be24ea386c58bd9621370d2373e95b530c955102e","impliedFormat":1},{"version":"1f9fcdb7e24f1f9b391bb9200e32f6353781a1c604159a6257e9d8f1df7b9bee","impliedFormat":1},{"version":"3c788378c8c8d9525c168ce0578d536607896146b88bb5f6670ab19834be30b3","impliedFormat":1},{"version":"d16aba532f5391ccb1a61f90cf8e904ecabf3105641c398df0f4a79cac27d472","impliedFormat":1},{"version":"8d3a30c921fa2ea91d1bffc7fd9b8c42dc74bc819c503208122bd2e84cbffb5f","impliedFormat":1},{"version":"e4fb93fd2ec72ebaaa1e657435d607ed155da0005e6e30c440257d9fa877d834","impliedFormat":1},{"version":"ad103c0c76b809a8830ca4f9a8c8cb43b53d578bc63dc8c63b1342f1de90f8d8","impliedFormat":1},{"version":"7e15e2f23da806eabcf4bd0f1e48d7a5baeface210f05837171b50eed7d2b894","impliedFormat":1},{"version":"fb5e2afe7c4f988b615085166421f8d88464f6234abcc740ed861eccc0e17ba4","impliedFormat":1},{"version":"652af46b250ee5144ecb0eef7bcfa1647c88fd170cca974bdff9bb315f349f52","impliedFormat":1},{"version":"790daabde36636c46a264b1628f488ae49b2b378810383a1059ee722e82ceab8","impliedFormat":1},{"version":"89a58d0ab59eec78a6b6532e5d748f9942568891619633c890638a2912224ad5","impliedFormat":1},{"version":"9af24ffe92056dea7acff1dee779be364ad35e5f9861ca417d17bfb447a0a230","impliedFormat":1},{"version":"8fab0b106acb9de629cc3f7bf784187cd59d506d734917c4f140d02f0dcd167d","impliedFormat":1},{"version":"d0ba3b6aaef0c96be907938b6fb2a3a04a5db59de34a40f7e426bc7f10bb46d6","impliedFormat":1},{"version":"d91c919538e393ed3c649270a73f239ea7cd9f312dcee7dad037869a6eb0eee0","impliedFormat":1},{"version":"fcf5f4ff294643e6ea5100d09f40668a3a8744b73b8f1c397fac4b17ccecc72f","impliedFormat":1},{"version":"af3bebc2d30fe79abc9a505bc890d16af72f8ea21ec59009e9d57c2d8f6e0b01","impliedFormat":1},{"version":"784c657f85bebb1a7d94ef05e10f1cad4abdf32798203ef8631f7c3aca2390dd","impliedFormat":1},{"version":"e488fdf1efc9112b9ae08aaf2be027c3cc5603b916582c45d73bb3885728543f","impliedFormat":1},{"version":"a43b695758408470608b548841c97ad3827e453fe81ad835e29b9871129785f7","impliedFormat":1},{"version":"96dd9a7f52627f94b64da26c1ce05d2350941487861c8c27a0014c67273c8a40","impliedFormat":1},{"version":"0e754d4ed9a6cd6c131515ba94f3f1095fb10ac3cb0c20c2cbeef9e895f924c9","impliedFormat":1},{"version":"72cac4a4359c6a5e2e5c0ece767455797e46871350324dfe42ab14238f675729","impliedFormat":1},{"version":"cd091878f6b6994d9307156bda8a4419c7c41c524228d9e830f5fa618d70672e","impliedFormat":1},{"version":"bd3bbe444bc7cc28757c7669fb186a9ba326d4b65dbf99e18b2b5b9ca66edeb9","impliedFormat":1},{"version":"b10c73b3d7703d2d870d35631428cdec737d4bbc06706b6fcc6f6e058b8e1594","impliedFormat":1},{"version":"2543b46883befbefe10c2de9f3a0e7809de7baa09e192edda748443fd15d38d3","impliedFormat":1},{"version":"01cdf83ab596024078a6ce08ff990770326ceaf16f9081a8e369b9bc5110cadd","impliedFormat":1},{"version":"bbb9a17f2654caa1d34f49428c0e48ca0fd0d9550f5f82da6f544c924afa17b3","impliedFormat":1},{"version":"a9d8ba2c15cd99f51aa291034a1afff2f67f1f88259d2162eddaa25e3644032a","impliedFormat":1},{"version":"affa88c9484982a9aa35b30480059dadfe98c3bbd92f76513ae2d1d7e68096d2","impliedFormat":1},{"version":"3436c4b40e71c333e253578f6f3176870d4963d5f4ec14862ba5e40794bef8bc","impliedFormat":1},{"version":"a02f786598d002e2e42854d9bb4fc5a4ac03589538055a0eca03c9ec7ad35457","impliedFormat":1},{"version":"348863ce75f819f43557d134e5e7ff11a8ae582ac879349cdf9156bb696012f7","impliedFormat":1},{"version":"235ede03bdeeb87ca21b68fb1398ebc4749a924e2a7219977b71bfc9c51574a7","impliedFormat":1},{"version":"ec83a163716e8a8c2324e3f6b64c907ba7e5247b43df47f52edef954232c0211","impliedFormat":1},{"version":"15a76d1390ae38fe474023a51778887a6e39cc4204f65519a448ed7d1931275f","impliedFormat":1},{"version":"d7a9a81362adcd395f9db48531a89df84461595d189a234796e85ef983399042","impliedFormat":1},{"version":"7ac3584d37571a5ba61326f50860d843072ea95673e53d23b0b684635db9db00","impliedFormat":1},{"version":"96354248b7b7fe792c9545b7b153ef20763677692e9baa9aa6d1afbb17376ba7","impliedFormat":1},{"version":"5dce9f1eea7d40ad9f10295dff47b7de6fbb24b33858c0ff91aa75043e9899d3","impliedFormat":1},{"version":"d324bb068a3a98f3d7ff92eed388935a5ed4bd46b7678c5bf057b5a2ee9416d3","impliedFormat":1},{"version":"fe6e76ab5933ca777c6ce422c7023d44799d4832a8c5ce35e3592c8430867329","impliedFormat":1},{"version":"cc5beca247a7da7286a82c0f3b84686a922d0a402ead5d11b9ddd0dcdef5c762","impliedFormat":1},{"version":"24661a3d44d16268a8ac8260f35651526c54496ed5f29e559c066b7b7e6776c9","impliedFormat":1},{"version":"7c7ddd5cfbbab70612c216ab1d1f982468118fead1d57948ed31b41cd692c2fb","impliedFormat":1},{"version":"461ffe558e162cb3e451654eed59d0090a267818fde655088616be907007d654","impliedFormat":1},{"version":"829df07ac748fd372b8bcace5b46e6ce0420d2fd65c23f64b86e8a099b69a21b","impliedFormat":1},{"version":"162ff76724612621b2623b71139fae21981b276595c5e93a909b464c6eaf6310","impliedFormat":1},{"version":"4ec0abadee52b5287cba7929ce1c34e81a67a046c0299908158497ee85ff20b7","impliedFormat":1},{"version":"3b60b6d30d8be5b573e75d148abd155fb74cbce0795055965ad505afa4b181f9","impliedFormat":1},{"version":"5ea0f9746d216da9d45885462fdad43ed26dc4473ac6d289a94661c9a1e7bbbf","impliedFormat":1},{"version":"96198fa503333a1856039319ad4bc45c6e32afe2ede6a050e23fee2127139a40","impliedFormat":1},{"version":"e9dbaa3c845b96ebd98a7a3c59296fad4f5cbe4c2e471d0c54a248dab6d575f0","impliedFormat":1},{"version":"63c5fe7a04273a69125008737aa3c18212a1276b3a2f3892080c346cb589a716","impliedFormat":1},{"version":"adca096d8a06e8fe1f6f8a1d95dd176e0ec2216f5dce683c9c3656a9bb1e1f10","impliedFormat":1},{"version":"fe5cfccf2b757c44e7251d6ac822f4892d63f0dbbab920da4f24b893e655e836","impliedFormat":1},{"version":"e2a5e2a231048f1b0a8c6c123d524adeb3eda1464ac2413fd039cf5afa57bc54","impliedFormat":1},{"version":"8701130ab14da66b4e908e13c3ece584b420399cc543bafca971c414059ee5e8","impliedFormat":1},{"version":"811e9e98ddaacadbbcc92015fafd5f5ce0dbebc14f3536cbe225094b1d61f885","impliedFormat":1},{"version":"f7c8e5f19d7159bde8f8e9c6561c6e517953457faa86018a7f963b72863380fe","impliedFormat":1},{"version":"c0bc4b28c78bccbb158fb2e8b3e37a86fee5f26b6098a857befd864790da7cd8","impliedFormat":1},{"version":"e7b604762369c8fa5ffafb6e238a2c7af296e5a25bfa25cb33191b525f064cd0","impliedFormat":1},{"version":"dcabfb44bd25183c919819f87428fc589b20c3b9586825ec456f94cdb67bd316","impliedFormat":1},{"version":"41c39405eb8d94777d8b30d2bb295c258391ac4a45deef8d2f569b29bb82938c","impliedFormat":1},{"version":"f5786f9b0a39c790d245b33436e75576990e41e995e1fff1b0919833a57f4357","impliedFormat":1},{"version":"970025f12906d26ea3c1c381199eb6702b9c8cb0bec44edc02e86e004cf95eb1","impliedFormat":1},{"version":"8dafc03ad3ee7acabdb9254c702b80755bdafa7d7548cc6ffc21814e83055abd","impliedFormat":1},{"version":"e0dba6e973edfd7a5d8a7307ad1e6ec014b51fb7dac507ae132d1f9429016252","impliedFormat":1},{"version":"cc2f61e4781ad29f2aa93d4850de1b1d8313f242631f10ca17cc99411eb63022","impliedFormat":1},{"version":"6679676c1dc90d9c371f3de8430bf070ed36d2677d9ce3d2a336b54c5c40c2d7","impliedFormat":1},{"version":"cf2b168364792895c95f8f98f8fb662f07787e518e6d25b0f7c4aca9927a1bb5","impliedFormat":1},{"version":"db115e097d9ccc281414e7cedebfb0435d5bb14022f147331fb1bdad09404885","impliedFormat":1},{"version":"aa78c2b93bce87b73fccd6726cac3cac4f62927460ff3495f2a05553b4c04d3e","impliedFormat":1},{"version":"298104d50f65103c256ad79ff5128141000e4544a6afaa998d3099bee3975b84","impliedFormat":1},{"version":"a99f5ced5c95d7603c94c66a4619dfd0e737351bf20757de516b7f8ca193cce9","impliedFormat":1},{"version":"341b20f291eecbfefa6760a69f7f3f18b2094edcc794f4e78e903a5f0dd86fa6","impliedFormat":1},{"version":"238dd354909fc4a682e0cc4bd0d1eee8ba03197a4efa3cb284e502355eaec8de","impliedFormat":1},{"version":"8a3156a33e38b19f00d543a9f7a96054a0a4b051533449fb04267b4e533f55ef","impliedFormat":1},{"version":"bfd14082a4db87c2847135aab3d617ad7b488b3e65ac82f1620742548ed630f3","impliedFormat":1},{"version":"e2aa5b5cbc067b485de95616efb852886f4a1a43685ed7ea0ee8e08fec961cb2","impliedFormat":1},{"version":"3d6d27c275808a7e8540b1778a5d3808542518acda03f5c1ea4c9c5831058ea0","impliedFormat":1},{"version":"f534c1a02cd756679611ca2b36431b51715a0c59a070d413e292dfa23b9b5c6d","impliedFormat":1},{"version":"e26cccd0ef5654714877908c1674bee29a8e53d60c8c2d82bdffc12cac6b0fb5","impliedFormat":1},{"version":"ccf581fb8928f37fbd6509a7d8fa0d32156fc4eb414f434bf81cf7bd6849f7b8","impliedFormat":1},{"version":"69b227120a5245cddb0805eea82a0bea405872bcf595d2fce9fc03dd16133291","impliedFormat":1},{"version":"d6f495bfd0020102c67a6f80e411b00b913a001c468001b7cbe8c592c748f301","impliedFormat":1},{"version":"eb4463ba66be74eb04aeba3bded1f485a6ee90bed8c28a2c2573f0c983834790","impliedFormat":1},{"version":"a76187885d25a8aed20f71760c116bfb89ed1612d125bc190ab25a1a7a87ed91","impliedFormat":1},{"version":"76d36099aa1a0c7ea690ee1675ac4dd86cc62e2643cd40c097899268f8d2f7a8","impliedFormat":1},{"version":"815d7ee4cb5383f94b88688b8f2d70ce3e5df4de147d3669d225f8bfcffad673","impliedFormat":1},{"version":"afc6a3b94e405b3ae5d5038fe66f3b2412300c93ba1250805ee1a7ad19964ff6","impliedFormat":1},{"version":"b168bf198df3af94f54863a77ca14dcdb67af689d4cd876328f7c70bbb7b985f","impliedFormat":1},{"version":"54a40fe6e389146cb444299ef2d7d6e4ec83b05a9df2e7611fd1d1d862b2743a","impliedFormat":1},{"version":"65ec140c7cde7edee0f611bc84ce505cfa71916571e76f5cc197ba1dcde32f2f","impliedFormat":1},{"version":"969a96cb343d30bd8a28bad66c77049aeb0fabd9f608ad82caee751082685eeb","impliedFormat":1},{"version":"b30e161d3bbbe3f8d15b0bc5d9b47d1935ffffc7ced1099fcd84536c906af711","impliedFormat":1},{"version":"3eaea558e36977f924d85b3207406594568053d7a52950081b7603d112edefce","impliedFormat":1},{"version":"4918bfaa32bc0f63380d84b19bf5adff3188797b17b055417bbfdb09bd531d1d","impliedFormat":1},{"version":"5750305060905aff115cc0a6f295357099856fe72d76a1193204c23b4b9417f9","impliedFormat":1},{"version":"98195f663b1c09572bf794bf2fd7351b05d5895ed471589c0c79ae2e7c7d6b97","impliedFormat":1},{"version":"355e8226f1a83a02c2c5dc22781defbcf171df314f6f3205318851fd4550132f","impliedFormat":1},{"version":"8abeb772b7a7763686fd699980b74d9895863a758f2a6b824edb3d5e5c235078","impliedFormat":1},{"version":"dc1e2e53c9935f62739ca37d9acd484e83fd25bc051e5a330c9be0acbe774253","impliedFormat":1},{"version":"71a542cb540a3c0d4d954d311937a4df56157b0797489ea5cb8a9e57f449aefe","impliedFormat":1},{"version":"49fbd0ae0b53936499ca6293450230273cf299e017ac4a1cc8de936d50d8e696","impliedFormat":1},{"version":"d1a6aec1239a47bbcbbc55324d6ed293f79d4554f6bb0e411e206a9e22c50aa6","impliedFormat":1},{"version":"4e81f6b7048f1022bd8dd7dc18e43b4aca8967c4ffbfc8fe80bd4277936e5be3","impliedFormat":1},{"version":"5ff6328b404fb34d2828b501bd16f75bf17590d2b03a66a469a0359da07a06fa","impliedFormat":1},{"version":"149fbdfda86091cc37779a6eb3f01ac5c73d6e6d34a71e76bb3702a1ebdd8bf6","impliedFormat":1},{"version":"3c1e92d9a7a0c81d02f016c47de63a39706bb0e36231f2e6727a08cbbef6cfa2","impliedFormat":1},{"version":"ebec459a7d4933732cb453254997b9b9e7d17319dd40a29946f985719e297927","impliedFormat":1},{"version":"394dec85f81a33891c71f4e6a1b9a40afdbe93210d5dec749a2791deb57df5e4","impliedFormat":1},{"version":"19d2786de07b0dd973e5515d84182d2734f1c1ecf602929f75b30081fb20fcce","impliedFormat":1},{"version":"728d3db3ffdbd649c96fd64cb5766993cc8cb10b4ef207403fb98304eba04f57","impliedFormat":1},{"version":"8e4ae5371abcf89ca3059e621b666f1a340db0575f0c8635431417528f2d6367","impliedFormat":1},{"version":"de4f5917a7bf2c62cd3ab4c171620fd6e88a4a92902f02c0808ae67793e6baad","impliedFormat":1},{"version":"8f09f0abdd346cdbb1e545a44c85dcefeb047d07080628562a328d3e88160beb","impliedFormat":1},{"version":"4557c1259460570a893f9adb1547b5bdd19948497740c53fbaf654b20ded5855","impliedFormat":1},{"version":"ddf8a6692f74ae9ebdece687ec1cd9ff63811c5af27381b40c7996053a1b0504","impliedFormat":1},{"version":"de735626154dab7ceb24b208728b7461aaa5ad8848152ab0b39192e7bc0aa4be","impliedFormat":1},{"version":"c6296acd69aca14033c815aebc1b4c7fe72b92e44145dfe6432fe855c8c7b463","impliedFormat":1},{"version":"fe7df3fef3d25c0455e7cd4f36fdff7ad7b4d2163e7285a74d6a98a6cb48a282","impliedFormat":1},{"version":"7e9bf7c76b60c4402bd48996bfb0d1fa552a576991f9f73dbb856d15b0346793","impliedFormat":1},{"version":"29199bf01375b374d516e5c8d5d8ce1dac3c07cc52b00eebc0a7ba7b05baeb1c","impliedFormat":1},{"version":"7696d51d4ce2f2f21e9f73214396bfd823bee6c57f65d39e6a2264c40dd021f2","impliedFormat":1},{"version":"4b74f4072dacae8ba4f21abf5d042e5da499d027b0aac8e2d8f42f5f591453a8","impliedFormat":1},{"version":"d3b884fb07719c9457497fa9d5146f7977fed29df303347ff4175f630b903fcf","impliedFormat":1},{"version":"665320db7cce83346ce47ab3baa657b3115d796d28d8f778a98e7f23962b1247","impliedFormat":1},{"version":"3f471b5d1f378519b8276a869cb746d5cfc9b2bf4d7e5cbd0bdec3f9b5f81fe7","impliedFormat":1},{"version":"4afef388856e350489141ad7d90ab0767a02954d53d953f558237e04c89b5d0c","impliedFormat":1},{"version":"7b16f7f12771f8e25117321d1cb607e06be688ed8db002fe2cb13b716d0662b7","impliedFormat":1},{"version":"9412339ec64000a6dce5898fd848f025d31ec3b446872070cc041ade876c0c1f","impliedFormat":1},{"version":"726bc5d2505bf6051e80ede05a576e21d65118c077a8407ef4f9eca8d5445464","impliedFormat":1},{"version":"05f2af853ef135671b9482077d19a2935e3d924debbcf2f4803110bde114f113","impliedFormat":1},{"version":"134078b75b0105535f6164680bd73f88f9ddaa84b7e0126aeddd2af7ea27bcb0","impliedFormat":1},{"version":"264f0b10beaa4141a6bf228d2e22b19ff7baa76b39388da319bffcd1ced741e5","impliedFormat":1},{"version":"98682512d496bb618315de173d7a25ec363676da2457939f42b75a23c5acab87","impliedFormat":1},{"version":"1471ca4439a9dc9787976d1c9ca2b913908f4029465c87372d2efe2069c375f0","impliedFormat":1},{"version":"54b8abfc4713160ce97f91758ee1e8a547ba67c7328177d5e4c40613a3e87f79","impliedFormat":1},{"version":"60894b9993d7e1a7be37933c9bfe96b228ebc206cada93a44bca9d30e12d8d8f","impliedFormat":1},{"version":"236ec9d640fd6438a08dd2be3fb739352f147de529b4aa1953e4d4f74d6638b8","impliedFormat":1},{"version":"e75ea841bf22a156a7ae8f95eb7b1d4479da8c291019148d0a643027356b76c4","impliedFormat":1},{"version":"4111a7444998538da1f6f76378412547281e30cc5a7249b32e7402f66f83a492","impliedFormat":1},{"version":"4b9a4b3456012104409fde7f7631be98068daaafe1c49b627b8d92f033960b67","impliedFormat":1},{"version":"7f7840713032b2ad3bbc379ee2d401489b4e563294f7c87dbaf2424a6682beb2","impliedFormat":1},{"version":"b52b80732805d494ffee704b80f689772e1db9440c1728b907f7b25b3328d5ea","impliedFormat":1},{"version":"a805a58a4c72c7d513295fa7102284dd9cf76c1470e1845a6d7e9afa4bafa609","impliedFormat":1},{"version":"724e0ca25a06f553306e33a45951c368346cf1fc4b26b8bf4bf88b1479131659","impliedFormat":1},{"version":"b0880e598b7256855af6b9ea2aebdf47372444114264b56da9d25ea5f95d064e","impliedFormat":1},{"version":"3ff6607fc3c3a85814ec3d6e05e358d99773ee7c7b5e9deed5e086e39f5372a6","impliedFormat":1},{"version":"62247290540b91ed85258d7e8c67c7786e38bc1111429da9fa42b1b34e4ffdd2","impliedFormat":1},{"version":"656ee3f8184b5dfb87200f73350e57827dba056130d15064406487c0993f0e6b","impliedFormat":1},{"version":"b14c19907984b69ce25e011e6391ff6a150bb31f344d643f2a3d5af9aeb4ba73","impliedFormat":1},{"version":"2b77a0e88653109c708203a50fa23bb50406a9c8c7f61883c92e009f778387d6","impliedFormat":1},{"version":"2aa50966f709107108e7ad733c129c81f9e42731492948a9c23d4f8a0de5ae1e","impliedFormat":1},{"version":"080afc7aa193ecf03c78afe2e3d81cc3b18fa482f1bd955b4dca67bcbf220eb1","impliedFormat":1},{"version":"7282df0d72afd1c283644f5827159ffe0c899850fe121811e6e7e3eb77416868","impliedFormat":1},{"version":"07c70d4602003ffd9f23f8f6fc2693b7cf024a323a6d6146b11659105ae588fa","impliedFormat":1},{"version":"79eb7464a0215c82cf6fdc55b2378dc0a3aed416f03dc647fb6956975d446ec1","impliedFormat":1},{"version":"8cd341d72d1ce25d33dbe1681a9a5f27fecfcf65d426a0d0bb80ce97a1e37d50","impliedFormat":1},{"version":"4f750b488d0d1019bf8b6651e68689debd6106312ca7f4fca22627fc2d0acc04","impliedFormat":1},{"version":"4307994ad4d3a8d842a7d7da76f45f84e5eeaf1580e9b6071dd5fa6b8b21de19","impliedFormat":1},{"version":"87b54711b1c9791dd95b4ff88f814b489089f5b128f29e8a5fb7f6b8123f739e","impliedFormat":1},{"version":"4763437e8a65ec15310aa20a4ec288eae3de1b94b9426336ac423fddd482d70f","impliedFormat":1},{"version":"e2398ace0c73a5da036dbd6cab98008a251c709c56f1b665b6202e99ae3450dd","impliedFormat":1},{"version":"1a59a1ed95ac47cb6d1798a4dee9088b847f41491e57545e788ede35ed1204e5","impliedFormat":1},{"version":"c89ec75e2ebb2f5c2dce2d5f85ab59951cdd217748a49a6e0bd102fe69f5eb75","impliedFormat":1},{"version":"e653e5173f22f243892807fcd85800dfa4efe84be40e0ed1cccd15d62e1c9da4","impliedFormat":1},{"version":"973c290e94835130e51e934d078ab80e975a7bdf5b9563f5fa8de080a06c588c","impliedFormat":1},{"version":"5abc40134600b35d15fcc7305d5bc9e29942c64dbec6413137b55e77b689da1d","impliedFormat":1},{"version":"85c3303460c6339a77ec37ed9b7e06974f6d8e1351181b3f6f0c5180e0e7a76f","impliedFormat":1},{"version":"88d761b9b53ee5aa4ffe94d18e1c05c31ba8778beddcb8418f303c184f4f61f4","impliedFormat":1},{"version":"8ad1059fc2cf09ab5208fdc50a974a1a0c0f3737d81c2aa0718c583716b49f7b","impliedFormat":1},{"version":"ded7cab1c0c297958efedb1c4569674117693e0ebb6e8a1366cf7a629ba490c6","impliedFormat":1},{"version":"997c088bb8c6e1d869a689a3db0157d3efea26fa3fe49ece5f01044321949769","impliedFormat":1},{"version":"aa4d9968e228a15f4f93ba782c05f19de5aab2598ef8acd819ce72d6f4c9953e","impliedFormat":1},{"version":"1c4420d12393decf20734fff3f09f44daea5433869633ed11e9fed9b316523ec","impliedFormat":1},{"version":"25e74c23ca9d123ad4726803694ca249b958270fa3e98eb3de7f339f15241a45","impliedFormat":1},{"version":"87537c5c41597d3355cd28531f715bcfa77b73f0622d49b28b6064b3c0ba0ab3","impliedFormat":1},{"version":"56acbddbfb96d3e9502c73d87f216fd16b9954ce7fc345adaa51a054bbd548cd","impliedFormat":1},{"version":"ea5f8a7e470eec67d9b3a57a311393d9b8146d59e7d3965fc2a07745aabb50d1","impliedFormat":1},{"version":"d75a15490d5dc9b8bdd209200429a4cd31c139b1503e22e1ef743e6d4fd160f2","impliedFormat":1},{"version":"fb238a904625a2a0942f8f0aad2c96d5ba7b684b59890599a10d73c1bfd3f771","impliedFormat":1},{"version":"35c91fef4484772dedb9c253a07ad91912a8e349838bef1e7c85b93b6acd39c8","impliedFormat":1},{"version":"b92d1f42b729031910871b073bbf05b8d68994a91dc43a7fc64f88abff16db54","impliedFormat":1},{"version":"beaeb7cae58c1b074e1e5c4c0cc4e205b1763f0e9f413d7062951e1cf539b450","impliedFormat":1},{"version":"2059b7deab3beb764a2368200ead025358b48fe470bd6850500785d94c8fb5cf","impliedFormat":1},{"version":"da08b48bf74446b6f988e95f501693844d59d445487d89d2364b8bf431c8a21e","impliedFormat":1},{"version":"17c4db14970964450f5bdcd1349e6f3035419db7f7f9f88ee966b3b34cbaa8b3","impliedFormat":1},{"version":"a9e6f34151c8629364892059c1689e2f99775b3642a24854d0330112d6892cff","impliedFormat":1},{"version":"bf5fb8cea51021d395c45a092c1e97534d498c91812b99fdb07c658cf5990585","impliedFormat":1},{"version":"bfca510544abac5e94459e2474b5d4ec143953e0e664f08bd789b6b3f1a2d57d","signature":"e1c4a8043b4cb75b05e3f74a962dca3102808379956299b7a5840dac50afb6fb"},{"version":"fe57f2e07a19f961b80dfbe51d03f907bad09e692d409d240042b38f1b304213","signature":"f7c40dae304c15b5ffb61e0bffc5f48c03fd3440f1d5a4c2d14f7d7ed3e4c862"},{"version":"006a31c6ae90e0a32a43f131e71f40af36fee2ccbe630c67e89a614ea086eb40","signature":"0274669c63081de789d9e45f6bb4f5e424c7ca2a289b7e3093e86c0bddbc08ca"},{"version":"6f604c875f792abf866c9e005868a356bf61dadbbbd9accc5fdf21a2f54a1b6d","signature":"00fbb365a27a2e87ccb013ac9a455f3fed26be397ee2deb7ffcf76d5b4efb79c"},{"version":"bac854177cdf377ed0d3203109c4d2a5dc0e12629e190493ed59310bac59d4e9","signature":"cfcb4b38c0009bacd9680ccd510354dc62935cf64c289dc28715ace710637961"},{"version":"e7c935166444068c3eb09500cb50994cef6a3ba4a22fddcc4a7147c8937d1a2c","signature":"fb891c7c97af75b1638fcbf3b1cf51815a1f7aa9192f202bc4fbe295827537c6"},{"version":"1a9e6cce6edef78b4e432bebb2920ae966eddc8467d953db38d2475e74cc658b","signature":"fc5fd2e7323999c98157bad9e3127b063b17e115c5298bc9d5967a8781eda752"},{"version":"6b5a973456b16b12503638552a525ff80f2473bba42882bb6c54d53fa8044459","signature":"8bccf01cb22376a08d48b284667f4b812ed8d38f2b723d6aeaa8c96d133d2ab8"},{"version":"fe8774e2c39f9c410a7dc1e7ada3a7791952cd4cac2e814e139d8f777ec37cc5","signature":"542542c33ba947f131b795d3630b41a32a460aa588a03aeaa5eecc38f3592041"},{"version":"4ed27bafc67f8446e1e2353c752229032319fd27d8be9defd7f3aa424db1a320","signature":"064b44fe73f0f7a084ed8e01bfb2ccbf0a6203b191fd521fc2f9c76d21e652c2"},{"version":"b69b09c53b81a9a88813efd04a7936a532ef3aba04d4380428ea69be41531277","signature":"f3a1e1f72b8affbcc2c49613db77c9ff271fb02b9db2615fcfc355b194855b08"},{"version":"c1f1feafa76973a9973dd0c044034f7f9561f98513509459c3a7ee1f75b8a92b","signature":"d86e8a694d25e37475962cecce34728a00aad3f3ae57454fb83e80c2e559cf84"},{"version":"3e26dc1de5ae916a995d0104114a2cf3a28a14dafbe2a9e66c80f0ba21ab0f3b","signature":"38d9938720a626eaf80d7c415abdc14d165e67cc0a3a5fcc4f40f0a16d2ce5ff"},{"version":"a3c9238b76558a2b9b60b4280bea5f7ff4d5764b0d3c6668d0c3c814274f15fa","signature":"c5906c1e499e174ca1746e431b4a96cab0a3332912431c59faf460cb69f959c9"},{"version":"619588b8c97f70e90677e76a07fba9013a121d04ad7392e86b265ed2bee758ac","signature":"1c9fbb019e31e325d23b95b4d1712239673b3864a8a53f4ce595f2b97559f1a8"},{"version":"96154050cddbde078fd1e92a76ec6b40225cdf8a4623b8e559032f002e2031cc","signature":"792dd988d9aea0f0008be0a7ed777727ae9ab8cd7b02b0ed18a02c694daecbc9"},{"version":"cc0ea18d60e16798af8212b941b6a4399fd7d1fe7dfc4fee065791842f66b427","signature":"525d97773ff298b2f2fbdf14a791f0587ea0172827b6ac8821f3eb70b20aaf1e"},{"version":"ecb2f83825e00ba07c0c992c869efe61535e66e28dfb3773794612bf53b462f0","signature":"d6f37129cc58c234bb3932756578fd5e922c3533885b1cb2e5b54e11caa29aab"},{"version":"4f8d220deb977c4982127dd4c182f30028c057ff5fb81d60a1f74fec82f810fc","signature":"1be400cacc24702ca3120b0b191189a5f2b54c1ba0968193bd991a2f6b4ae80f"},{"version":"c08bba4bf068aed3e486f29abc6235ab6feed4029f11c22e92d6ff60b5942b8d","signature":"3f8d7da16eda492c2fec969fa98c5df021e3b794883a0eb42f273f61a354145f"},{"version":"140a7665a48036171135a19b4861ecbf0d0c5eb144e0167b97a713a2a3dfaea8","signature":"b4444c70cf4bbb122a1983ec33c297027a05fc1dfc23376d3b140745a58a2ed6"},{"version":"cadcf3eac24d033bd12c3c15337b26cf496ec22600fc0378c806cd1f72f10747","signature":"94e6d27e3abb71e332ba85d07c493fc6e2c61479b04ce1686cd9b81326f1e10c"},{"version":"627661f6539ca706f24e0d5c58b45ce04afa7f5f01e8a4a405db2fec7207898f","signature":"fa281b36685faa9c4a9d379f8a1ebb2f13f7a09f19e184becbc8e58848dc2396"},{"version":"7104eb14aa794ac738589409c6698195df1ed3158e315ac68000bf6fc170c8c5","signature":"aed26c8732502b8a3775846cef9cb70533d2795a9296b8a4d5db4a0a02125b09"},{"version":"18d6c50db31c910e327d8a54324ddb4e012ad91ec0ace13807da4336251924d5","signature":"68597f354e4595d3f1f99cfed58a445a66dd9b5d4ed5d8133e445ee2a3de6cfb"},{"version":"9c512b5500b454a6af1d29d2de97332d7db43f79711138100aba90958ae9ca13","signature":"1aaeb72b56fbfc7fe35e5c5195bc4b102fdcb25a30fa39a8dcf26e4e03afe4f0"},{"version":"92bb24e1d9827331d6b45348cd652939d0083d436ae50eca442f61788dc1ac81","signature":"f91f8a40ca995dab7f060770dd756131d82c90945b36ecd8e762372ec1caef16"},{"version":"632e2305e39b23bbbaafaa15a2e9691170dfd9e45c20eb86ec2a2d25a3e1d32c","signature":"726f24bec8a62db7e198d7a3dd147019c596d151b7d2871e1561f09d4c548805"},{"version":"d173eae1b5d20b52e1da8c777b9b5bdc8d88a7fd32c09f9e0daa57e6acdd35c9","signature":"ff942dba922bb7df478e9c227ad68bd44d19ea8e4a69c357d55c2731c8fc11b9"},{"version":"072e481d4ae6f9c1c735e22094c5b29dfec50c26daafbfc6c9a9db17dd90c9b1","signature":"798599398dd2a36b0ea6e2578b40731ad32062a7b01f966279fb61d84bba5483"},{"version":"74a2dd9a8785060c3859ad6372fb4090838589e07512b4dc2db350a26f83e070","signature":"b204b8dc8a299d4fce994ceac613f46bfffcf4486c9a5b9f457f42f5f518b0fd"},{"version":"4b1a3e9ab633a30ea05f2e2217caccd18cd490723166caa51f24c8b566288913","signature":"1ac20b637cf03512205b7ded982f8cce25dd98349efa920cb299cf0034f1ad48"},{"version":"f3df0bb8f4b3af9128dfd66c33f8f77719170e34e1fb6d71b72ee71e2378a295","signature":"8ce894bcfcdba14681f819fb0450ea391c44fecd82b98df97b25765fb4e7ca84"},{"version":"e5950aec63f876beae5b55824d5d25e641e2207f2eb06071ceadce7101e109a8","signature":"3a0f946578957c30bbaa7f927012973474919f8d43e8daaf94f2996039a2e773"},{"version":"d84aebeca45465e2823c6c06e297df51af977b03d4a479ac9b190bc392f681e6","signature":"8b50aeb59f6987c56aa1d3329396f4e6c4973133fcea93a0257227c1ffd59080"},{"version":"0c56334fb43db4272bf0f5f057764ab88687dccafae627902f51adfbfeb94b88","signature":"700ebb95b8e92fec141e74fe28c89dded7d0d75d17d041b2f33dec147c6dd925"},{"version":"becd89717f17094624e6616f7587fc3c51d18d4c7c6414e30686fc1f61843467","signature":"71c8984f817976f2868e4b97031ff767baa0a3bc31e29a03cdb0f38dabb3c6de"},{"version":"91703616899954c8355ff138b03b40b15b33a19b500f129208d313142af9f5c9","signature":"a53c9821c1526959393efaf002082f8644ab2054f490e9aa6a0d23862d0ecba5"},{"version":"57fdacf4f075934248c9c313009f9d5fa675ac2c48f08d23fa8d2f86c0ad631d","signature":"73be23d9b3917e48d86bc0f8625980f6ef348eb2174f301ada759df5170d66ae"},{"version":"f8a6d14740ceacdf3a78016efc8256beeb2af8fb0e52223e03588dee9cc2c908","signature":"ca270e6e8203c8757397bd31f1233d30d56757a6be9ab66ea2c4e7a53f395187"},{"version":"ab7c6916976916364e364948aaabf628d20afbfa1e5f1a17d14e3db0043b035a","signature":"9a90add49c114832a7fcc7b7f76ba501f3a3f00ae806a85301d65b36f0734e69"},{"version":"fd7930dad4da7394bbd273639e4e39155e952ceba42b8f05766e87c71b5299bf","signature":"20311a8eea15739e7c72b3a2b56389f0944428c7e329b1a91a6083740999fcb6"},{"version":"f4a9ac065b044998f154bb6976a5634cea5e3825b4d5106c7ae5c5153c1c01fb","signature":"1017bb3050be6ce40e4cb8a95d0a6415be5b45becd8ff6c62c6341f2f29a9590"},{"version":"54507e17ab7acf644eaddc241605d626e3482e9c947dd2802a2430301b124af2","signature":"98127978590f8f3ad2496ddc8309e7dfda8e191570e18cbd7a146e0cb9d089cd"},{"version":"6f94549d36277cee1171d19b30c9df4bac5009624ca895f6542fff2abb642c5f","signature":"0d27b4098a7f8d9daca5e7f0304750773f03dd567b4d7d49db9a983be5a2e57e"},{"version":"1a36c533d0507c55735c0b5d1b9ec44e308a7f7333539fc723b35e813e34b392","signature":"a08a57ef55f654a3a70741e50af5bbe47a873ac74a8eaf65b3eb4684136ab742"},{"version":"187b3e36e643d6484ba0286c361eadcf4d3f4174865c8bb2e92ede5cfc0206c7","signature":"742178df5f8a476681d0544713fe87cee7a790042b11b27da9b102e80c318bb7"},{"version":"39d29432fc333562dbe6aaf8e5063bfa3d2163c4e4bf3005f0fd3bdbae770fd4","signature":"b92e4bdcee67fb609851b73ecad31f057ae04215c54a4abd3ba4e2f0e24c629a"},{"version":"a177ea826ba9a97c34fe1a29c6c203fde8f62da08fb6acbe5f3c99087bf88593","signature":"8d866b691c98d47c7eecc6462e0581e3c9e62a4b13b87c6a7f830f0c2b918015"},{"version":"d84c4b97c93d92969674a29b2e490a42b50a9f0e2a6ef840068fbc717827c7d5","signature":"aa36daae0f0633d472253091285064a9ff68e4ba5fa71cbe61cf5bdb1874e8ed"},{"version":"a55ffe3808700a61bbcd9421152916959c9f233420538d7237e4afb31fba97f0","signature":"51497d9e66c36bf79dea9f8202f104c084ac4a93a0a940a6dd1d2e0d30af0f6b"},{"version":"239cc1ba3dbbc6dda4a047f0dd81b6b63f0748ec27937ceddebfc5e8726e5bb8","signature":"aa43218ec3abf932ac6aa3fc859581b8f2fd8c9469e3c919408062e57d232bda"},{"version":"68c08225c1fbd1b4be2e0ecb96316de85597baa9685b6e69b069b8f76dbb3d59","signature":"4910911105559d5ec48fa179743ed357001b66fa8df1227bdb5820ec71c3c5bc"},{"version":"b015aafba6c56e0fe60a6564bb08cc054f9e1c0a71c1640d8d90391f56683609","signature":"4586ac84bca04bf36a1ed0d8b6c0fd542860be317c67a0842311e28165beee5d"},{"version":"6c34dd33d1736839908c075655eafa8acdc5b59b3a8027af2a5db38d3aa29e52","signature":"b5e0ea031f83d837aab204deb17bb3dc7bb49f3bc3d5f5ae00de7055b0957bdb"},{"version":"51edf78bae3b5deffba7d39b64e9cf2864419ad0e8e3d76f3d3e3737ff526b48","signature":"32265e46d9c9f8cbc102ff9a45d3332302eee0adefd7ada5e2330730a4c19b74"},{"version":"2f2e15879914bda1a5c3ce9d4da7ee16a360a777c2da4f68d21ab7ef4023377c","signature":"e605c17826925ef50254a6f1fe1b7615d239bf637b30cdb0df4637d362fe265a"},{"version":"e3e2bcc91d737c58872ff888177660156db15d111824b9724e7a9b4e4a7ba686","signature":"307b1f6b818d93140e0f0a31acaf65d20eb606098df57cc97103fb0d83e79529"},{"version":"878e67dcb9d4e991ea86e7fc18d2fcc9756e01671ab69fa15892c6b823a69a0b","signature":"c44cc8c4930b76d1fbf935484045f099a078a0e56218439da28da5d829065a89"},{"version":"7c14a9fd8befdb7577d9e52242fcb550a8b587e2e08d9af0c3d54c218d628ab4","signature":"0d2eed2f304bc1f3b1d854f9f66a454ab06477ac5e5c4541e10b6111c9b288de"},{"version":"d292f358198e4340cdf3e11f0d33f3f24ea84c70114bf6462c4140fbb921052f","signature":"1ac9fb8cd09e61aaf85cced63abfbebfe7620efd14a30559e8c4197a629212b7"},{"version":"0d3b1051343fdb013414fb6f6c0660838c623dfa38605e26b2fdca99aa594588","signature":"e7ecaac00ca47343ea2a525058f36c7db24fccd91b74ce24bfb05f5057514156"},{"version":"5f1a2dcf661477ecc37fcaff7cbbb4e8c69a8b5f8e25c0c0af9d436c96bac17c","signature":"a406bd45d11ccf4449bed75df91936ec197ad7de0facd342bb0ac55597dd7cb4"},{"version":"5804fdcf000b75546edfb7e502b9342a4b5927ccb9fc142fa5018e2a55663f4b","signature":"41770f47a4610b077aa385f08215f7dd99e8dda8643a10a1bbdb1a386a58b641"},{"version":"5fb5890e01d4926bac82a299a50fdd6c2967306cfaf032e0edd9bf29bfc96c29","signature":"866bc33412b93d1a44a41d1a8ee37e688082c1979b4dafc2ef88edc53a7a999d"},{"version":"f0d1668a2958e336807c33f5a63e9fb7a80eecb21177002901c8b18a0bc7cedc","signature":"f6eb9961bbb1fc5507f52b8081d6e82eb3ef5eab264a5d926518296dc4244127"},{"version":"438172ff2ce4e3f0ff709eafc95fa97108232dad20b179e4afb255aca1be1853","signature":"a0a545639911994ff57683e710206d749c7d92037f30c37ab2cc070178a7fc3d"},{"version":"2fa2289f4a44d6b119747ebdf9dc89780e997d9cf90242390d4bc624913db00a","signature":"35b8792f84ca377922829247470bd076930e1b1d50eb2abcf351fd4cfd3096d2"},{"version":"88d6d2c25739360e2d14ffd1bf391c661b51916b869346f378bd392ea04c3b7f","signature":"1342888987d6078504543599fffb3dd6029c2c0f768b489cd232fb10bedb675a"},{"version":"2d96a60d94607204ca301f60f5967ab2e500205872d93f6a1ee0a8fcbe42cfc9","signature":"58d3355ec6456b6484a31ed45c1aa8360a6f57752ddcef27438e3f145aa488af"},{"version":"6de7c4598aedd55a66a22bd23ea5fa6a79f59160ed0159fbf583ca2ac2650fd6","signature":"b40047380cb999fd7b125f2f890c129d367a15e779eab73d18dac869d34dbe4e"},{"version":"721f9fa7ea09b0eb7bc49997c7b02d9a33778fcb082790e2b3208c07d3b9941f","signature":"98cc84a3bb34e7efcdab6bedb1621ca4d12a94aa55bb5daf20ec8857ce42dd3b"},{"version":"5a9718c55449587edd2121093e1ed79cc25413c42051e3b8e430f6fd318b5213","signature":"5146399dfd1697da344345f55d124ec0bd1360bc0e90262396b38f50d1cc4dd9"},{"version":"648e7baf4ca6388a770c21407dbaa46adc09e4d2dfd20287611602cf9f763224","signature":"155a0ce1ae5dea70b7c62b88bad1d252f860edbe6973cfb381851ae84fbc57b0"},{"version":"655941cbb10c64aba85eee5a627525868969c4ecfd634480da3008dae67d4b1a","signature":"8c40edee0d9d2d04dee8654ba2f8f239f1662ba5d78ee296068d1e17dece3391"},{"version":"a9227f585fa40e22f451e3ee590661f7826a17a45f4d9a7a0a1b198afa418268","signature":"7bef485675c2b5c8a43e4679d81f41a178e796dc67ba039c0e736b06462eb1d9"},{"version":"7864b6a03754872d9409a01801c07b3d01bca5b062b4e3b8fe8056cb092b5223","signature":"eeafa09209ef552b40702ff99dd64b72dcb5cf47bf6d7d220352e0b99def0f2a"},{"version":"d37df1a697bae4625bf7aa3035ada9e09c59000b7730d4bb5211a501adcaf2ab","signature":"c0eb3acffe92a379e6956e9348b07760f3996f9bd7882732a520cbb7e225251e"},{"version":"89121c1bf2990f5219bfd802a3e7fc557de447c62058d6af68d6b6348d64499a","impliedFormat":1},{"version":"79b4369233a12c6fa4a07301ecb7085802c98f3a77cf9ab97eee27e1656f82e6","impliedFormat":1},{"version":"2b37ba54ec067598bf912d56fcb81f6d8ad86a045c757e79440bdef97b52fe1b","impliedFormat":99},{"version":"1bc9dd465634109668661f998485a32da369755d9f32b5a55ed64a525566c94b","impliedFormat":99},{"version":"5702b3c2f5d248290ed99419d77ca1cc3e6c29db5847172377659c50e6303768","impliedFormat":99},{"version":"9764b2eb5b4fc0b8951468fb3dbd6cd922d7752343ef5fbf1a7cd3dfcd54a75e","impliedFormat":99},{"version":"1fc2d3fe8f31c52c802c4dee6c0157c5a1d1f6be44ece83c49174e316cf931ad","impliedFormat":99},{"version":"dc4aae103a0c812121d9db1f7a5ea98231801ed405bf577d1c9c46a893177e36","impliedFormat":99},{"version":"106d3f40907ba68d2ad8ce143a68358bad476e1cc4a5c710c11c7dbaac878308","impliedFormat":99},{"version":"42ad582d92b058b88570d5be95393cf0a6c09a29ba9aa44609465b41d39d2534","impliedFormat":99},{"version":"36e051a1e0d2f2a808dbb164d846be09b5d98e8b782b37922a3b75f57ee66698","impliedFormat":99},{"version":"d4a22007b481fe2a2e6bfd3a42c00cd62d41edb36d30fc4697df2692e9891fc8","impliedFormat":1},{"version":"9d62e577adb05f5aafed137e747b3a1b26f8dce7b20f350d22f6fb3255a3c0ed","impliedFormat":99},{"version":"7ed92bcef308af6e3925b3b61c83ad6157a03ff15c7412cf325f24042fe5d363","impliedFormat":99},{"version":"3da9062d0c762c002b7ab88187d72e1978c0224db61832221edc8f4eb0b54414","impliedFormat":99},{"version":"84dbf6af43b0b5ad42c01e332fddf4c690038248140d7c4ccb74a424e9226d4d","impliedFormat":99},{"version":"00884fc0ea3731a9ffecffcde8b32e181b20e1039977a8ae93ae5bce3ab3d245","impliedFormat":99},{"version":"0bd8b6493d9bf244afe133ccb52d32d293de8d08d15437cca2089beed5f5a6b5","impliedFormat":99},{"version":"7fc3099c95752c6e7b0ea215915464c7203e835fcd6878210f2ce4f0dcbbfe67","impliedFormat":99},{"version":"83b5499dbc74ee1add93aef162f7d44b769dcef3a74afb5f80c70f9a5ce77cc0","impliedFormat":99},{"version":"8bf8b772b38fc4da471248320f49a2219c363a9669938c720e0e0a5a2531eabf","impliedFormat":99},{"version":"7da6e8c98eacf084c961e039255f7ebb9d97a43377e7eee2695cb77fec640c66","impliedFormat":99},{"version":"0b5b064c5145a48cd3e2a5d9528c63f49bac55aa4bc5f5b4e68a160066401375","impliedFormat":99},{"version":"702ff40d28906c05d9d60b23e646c2577ad1cc7cd177d5c0791255a2eab13c07","impliedFormat":99},{"version":"49ff0f30d6e757d865ae0b422103f42737234e624815eee2b7f523240aa0c8f8","impliedFormat":99},{"version":"0389aacf0ffd49a877a46814a21a4770f33fc33e99951a1584de866c8e971993","impliedFormat":99},{"version":"5cb7a51cf151c1056b61f078cf80b811e19787d1f29a33a2a6e4bf00334bbc10","impliedFormat":99},{"version":"215aa8915d707f97ad511b7abbf7eda51d3a7048e9a656955cf0dda767ae7db0","impliedFormat":99},{"version":"0d689a717fbef83da07ab4de33f83db5cbcec9bc4e3b04edb106c538a50a0210","impliedFormat":99},{"version":"d00bc73e8d1f4137f2f6238bb3aa2bbdad8573658cc95920e2cdfa7ad491a8d8","impliedFormat":99},{"version":"e3667aa9f5245d1a99fb4a2a1ac48daf1429040c29cc0d262e3843f9ae3b9d65","impliedFormat":99},{"version":"08c0f3222b50ec2b534be1a59392660102549129246425d33ec43f35aa051dc6","impliedFormat":99},{"version":"612fb780f312e6bb3c40f3cb2b827ea7455b922198f651c799d844fdd44cf2e9","impliedFormat":99},{"version":"bcd98e8f44bc76e4fcb41e4b1a8bab648161a942653a3d1f261775a891d258de","impliedFormat":99},{"version":"5abaa19aa91bb4f63ea58154ada5d021e33b1f39aa026ca56eb95f13b12c497a","impliedFormat":99},{"version":"356a18b0c50f297fee148f4a2c64b0affd352cbd6f21c7b6bfa569d30622c693","impliedFormat":99},{"version":"5876027679fd5257b92eb55d62efee634358012b9f25c5711ad02b918e52c837","impliedFormat":99},{"version":"f5622423ee5642dcf2b92d71b37967b458e8df3cf90b468675ff9fddaa532a0f","impliedFormat":99},{"version":"70265bc75baf24ec0d61f12517b91ea711732b9c349fceef71a446c4ff4a247a","impliedFormat":99},{"version":"41a4b2454b2d3a13b4fc4ec57d6a0a639127369f87da8f28037943019705d619","impliedFormat":99},{"version":"e9b82ac7186490d18dffaafda695f5d975dfee549096c0bf883387a8b6c3ab5a","impliedFormat":99},{"version":"eed9b5f5a6998abe0b408db4b8847a46eb401c9924ddc5b24b1cede3ebf4ee8c","impliedFormat":99},{"version":"dc61004e63576b5e75a20c5511be2cdbddfdbcdff51412a4e7ffe03f04d17319","impliedFormat":99},{"version":"323b34e5a8d37116883230d26bc7bc09d42417038fc35244660d3b008292577b","impliedFormat":99},{"version":"6ad97a2d83bef09479d7e64f3b27ffc70ab1ae57e4049944892ef2958bd010d4","signature":"c55b5dd40b4e4244911fb70bec24eab327488b4b6414513f29d5c0d4669d8399"},{"version":"2de98495fbcde1ee42220fc159801b681f7eb58bacf08f54afbd9980acb5623d","signature":"443618ff6091ad5b52a77dfd029420299db2fc31735f4405482ccc63a6044c0c"},{"version":"a006817d449a5bdd64d7f5ba879fcad1ce2d9a36d457581e38812d6cb800c413","signature":"b9e0a6bfd2e8a789e396e72b3816b415e0d9d0088411d28132e67c2a3d447fd7"},{"version":"a12d6c59bd1e8a28df09134739123c5046238e35e45b59adaffdbaae9bdef401","signature":"f1223da6f0fc1fca4ad9ae12e3c41227b7da0de5f55b38d9b871f3c651464039"},{"version":"fae2076383068d42680208d9e2ae564dd4077e0d3d1477e2915fedcb14b6a849","signature":"651a00540b7a7805a8de79cc6972dede798970c3b718c8374be90813037437cb"},{"version":"11b9c3d93d309e1f5b4db0aadfb647e759ea287aa2c988216c659c9bf8921897","signature":"34da1e99fefcdf0c678bb9084bde33530c33109b7b55fea44d43d2bf30b991e1"},{"version":"d47894b8c5094a562dcd507c1356f2ba4a949899f3f3996e105577189cffe4aa","signature":"b1ce28d2db05720e15621088b8b3542d45d2af78ff200e098ffa1e04f98e34be"},{"version":"bb5b5c94e919115cb8e02fbd379712799d21fb7134cce7dddd0f0e773b172173","signature":"0717bfe8ecec022eb7f964ce697b1b0d749e217864e7a11b438c462ca1e55412"},{"version":"db9c9aabd4720b18cfb7a161cf40552bb8fd2a39b307b3454834673792c8d026","signature":"99173fef2fa963dad3c3a06cef6bff8e4a9e4fea656ec6414aea2f84126ffc23"},{"version":"a9f19e9fec49f5abee045aa42e49b3ff0f3ba2906b0b0e71af07dd04df4ed24d","signature":"08e14aa9343103c4781efad6b4d287b2d577a8266f51f389b2ed4db8957cb5ed"},{"version":"03b72907cecbd439aea347e2608bb94e382d8eaf100c2b4f187b4685f5a9b0bf","signature":"bc1cfb9f737c4d343b2fe2a3709bb926857aadc6fd235554ef991b823967203b"},{"version":"1635e208cb05025f043d158475e9e950243d16a0358539330737208eae16e3ab","signature":"abed2e07ab4d9ae16437a73aaa4382973966df349afdf5eaa654b3f766c30625"},{"version":"2afe38701c15b5aa11b8b4a3b0c09725937df20f5810dcda88f20863969c8679","signature":"ad2b679c1fa38275a64a7016f20f431556e89feda74d3fb88e35ff3994ee4379"},{"version":"32c2851b8a07afe131d392b5474abe76bfbaae077a78353b3d76012d85b04ad6","signature":"1c673b5e90e9c0d4d79cf9a20d521cf4a0c1599948fc08375c144664a961389f"},{"version":"62003e086d795b4ae89af940b9b508ade008a250e2865192a44d8e9333ed5dc7","signature":"73dcef7405b59cce04dfcb6f53f903273fcd42fd9a7bd2fe68189dffd5ffedb3"},{"version":"caf1f4496cf4450cf4c77f0934bc1d050bd9cfbf469789af029f28c958050782","signature":"78fd9f116c4a198c60620ad7374fbf67fdc41baa66b8da57db3b80cb6b23098a"},{"version":"99b334fb65c4110f08413a6f449770168cee0c4606d4ebda5ba449786ccf1808","signature":"8483914db284e07599e4fe920f9b5ae7450f8ea5617bae006eb4e201bfacbba5"},{"version":"20e9242a8355dd2a026704688dfa4ebc74b6c836b58f60d8aec29168a33aef2c","signature":"d406c57bf0c60a88a4cddaf0944ed69a7554658e068fb62559e2d823f9255236"},{"version":"c7bb3e941dee1f091255c139b48c18327688f2086f9f2f4d34b2a8b024c5f0bc","signature":"109a47009ff1ea87255fbb9bd75f5f3a918ac7ab44ed123b521028f580aff53b"},{"version":"6382a96f2d9e5794b88d138893354d914563d4e798fdc6c5102bc5f02f5e78de","signature":"8146af3e7bf09f628b043910cfa6b72b86f8109aa7b06167b9eb51a3f0a75da4"},{"version":"e576c98983f3adc0efb794eaf40c971ea63118585bef5b40b10440f6d9dadf70","signature":"5e25c87cc967b7bcd7949f75916a6757b59aada3685fddd966093696c85163b1"},{"version":"075eac8f39596e5f04ee1c5321c05035849f6f64db29d0d9f2490eb0f9c4501b","signature":"ed09ce0bd7cf961caa2bbaa0265743b1a22acb59fe82fc227698550a7f0b1e14"},{"version":"9e88cce5d6bf0684bb5ba947846bb3e7cff493d65773e2d45d3534b480a9f738","signature":"7afb481364c9e976ea5c55b9b02006f2496e68cc009eedc57f38264121a77836"},{"version":"98e9a4c0f2e11973753af34fca47091e102656cb4603fc97a76be56aa14fcb61","signature":"91ee1b220ead097d3cc5b596db9be622f0dccd9c05e4f4cf069f2e1db077511a"},{"version":"483d3ab13838dcf5edeb0719a8ef1ecce6fd3c5f9bcfc1eb45b9d2917b6de41f","signature":"44248c8a13f35779d07d3168c64fe9a1040ea2e66bfc4ff92567095c5b243e55"},{"version":"e6cce19f4311e741a2b958a7a2eb4e1ef2ba3b4316ea2a9d1c06037c8762d241","signature":"78361a8f013fc8aea9c04034475febbc49998b63c3fe09f56dacba1e1c73f8fe"},{"version":"f1e45d3999270d9468cca90d6c95a74367296c9ece50a08f243225c97eaba62a","signature":"11fa086538a611fe1a99a34d1378e2579a4de6eac405ad7fb9eeaa51836977c0"},{"version":"2cca64bfd67530815b5679d2eeeacc131770c14a7bf54082e63138519d4a03ec","signature":"6840721f787baca46b15289facff041cf00967e6d746b6e2fcf657881b6e6c5d"},{"version":"fee5fb28703c416840f9cbd5a51aea0792f6671934a0298462532d6d9f0a98c5","signature":"1d8429a365d644633813437c38052b178d2177b4fa150670d7f9cba6cabae8c7"},{"version":"95445c3662a17b1ca8988d1e5fe59e03e86578cb7dabbbe119ecb47ec6bde73d","signature":"4929cd61e267755bf505ff0a66adda55af5d318b84798ab1b46ead808203bc59"},{"version":"45ecfa89ed0519d9dc239eb9e92eaf4f36668b8cfeb4afb2866a195b5824b325","signature":"d21e563fc29f32dab8756bf5797d4c39b98ff0828fe02f8b766a8cd0f2130729"},{"version":"245dcee5a8758e766645edea2f590acadb483db2bf91495dbc797b77ba7f6030","signature":"b524e9c8c9572a85e539e60885e7cd27a4a3734d72040582434a25e486702df4"},{"version":"b34064e4b8e3dcb7fb647344f7af0c14d563092bf789c7c78b4e40592758162b","signature":"bc3dc9e5cb7a7493571d35b9b2fa5a1f39cc7ad76f998ad62e7a98b56fb8df6c"},{"version":"88ea57045aef28c44eaa1c980fdb42bda1b9824d738fc773dcecd7add4eef207","signature":"063c721b1237aa52f454a374210ba793cc38a5267af12e5f937c7f36bb33b6c6"},{"version":"1ab3eeb19128b6995e67b9c655dfc84ec972c6f694db5998564a1d8d222ac720","signature":"32bf8abd00a1484e9046f8e3e7ccdfc121ac97b7238e5c8ad7d5c8b3624d26b2"},{"version":"cb17ae63c380b3813ee1c216ae9f4190cd4dc47f0e4ac344e04f98e50658cdb2","signature":"de0b7ec69d1de3d88340668f43c9b8ef7086f96671d741ffb16d82ef844fc18e"},{"version":"db4c881c4d0036d8676e76f60ff17c6fcf240dbea3e48b5961e17d9a3b73831c","signature":"a2885e55e65c47dde0e39e7dbe3f9d931149d439b3b3c2a4480ae20b609bdc83"},{"version":"62d8a627ade9c9e93d0d2f3597716c1da81ec33405bc50455eddf5fd6889967a","signature":"3d577b57ecd8ee26a71f8dcaa01d354301d4155aa8fc228210f7980278d5a40e"},{"version":"d1db0d711df2d51db85ff9dd6ee9dca3cd624f008fa58d20713c857f269a8aa9","signature":"a257a955f81d30464899ba91ac6e7caa9c165d10f49b0e06bc9cae4cdad3bafd"},{"version":"9bb62f791ca6807f95f797b1d0dde629861242b2dcfa33bb0ae97c47048a9f89","signature":"911dcb2bdcd90baf815f68fe90307bbc7dca6f52bbfa3360211741e1ef3898dd"},{"version":"b5bbd36607a8138229849f395407a9f2efa9b77233c48cc7e8816cd2d9839e94","signature":"81142ee61fe760d78d04ded56e1aacadc0596742f5c13fcff335b1f462cf54ea"},{"version":"6fae6451c80fcffbdec0d1660f9ecc3cd640b44270f6a464599a4239acca97fa","signature":"956b5043e6b257ef9a756ef3a4ded1cb6e6d17ec9a6ae475894956d271bc2296"},{"version":"e784654e1df8ff79e93f9f3cd2551f5d64f0ecc2e54cb4f6c7ae0d8702d69fe0","signature":"619b2bf107c7c61e145876687ac47175b223d7cbbe8414b8d1ab5186064bd02f"},{"version":"536461e3c082c670e05328f21c90473eaef75a7c151791f3b5684801908e8ce4","signature":"0e81f3c44d6d754411d9b3fda7802a8c6c9567fcc7298542fafd23d879519d12"},{"version":"aa6d99a31a956be40f33fdbc441c2aff31aa8cefb57e8762edbbb52158ad89a4","signature":"ba31eeb48994cf91f0acacad869ecb708d6840d7a8df864ecb86522256949502"},"6a32401e8969f350818b73bad385f0fe6fab1edbac7eef198ce13e790135198d",{"version":"2644cbea24510f37d9308835e9b1f2eb8ca4addefaf31dee0de6e8a60ff911d2","signature":"47aeb932730902d4d8c41ec941269a416311f709495b5d20f2ddeb6f8b483073"},{"version":"12676421ebaf6b12fbf551c215db5748586263e9daf69202abcdc4ece994d952","signature":"1416dec78fa5f6be6be2e406d0cc50d6f98ce0c77dad79080d5e5a80be18084e"},{"version":"600b9ca4be5dcaabcd7029783013a337f2d27a5c9e801f6df5dc95bb70c741b4","signature":"e28422e9a6af42ba47f7aef0833e002816fa287c438cdfb33754716547da6bfb"},{"version":"3f9862ce2a75340e7afca185dc81c6847e7fb9f759db6bc78d2a6b519bb0e49d","signature":"45e9bfbb6ba5a5dc06a8f9f080c53ace1285ff4e0b04a225448468ee532eb0f2"},{"version":"abdcc36c68ccca6c43c1ae78ad4336a873efd3d78378412ec013dec3f7995df6","signature":"83805f53c80fc8b715af907cad4ed7b70cd140e54f4525ed14fe8f37b6b3a738"},{"version":"a1f98c853bf18810d8b229083066aa710eca359edac6c210472089f7ceb2bca4","signature":"773e3d098838e2ff00d61a55ef560fbe2771df55d900d6849bc3de3eef5c9ae8"},{"version":"c471d37642776e92f753bcdbc7d5ddc08aa0f7f04631b6276b503b7ae94ef3ef","signature":"52fef2cc3ace541aa2f5f9c96b79dcc527785774b2925604d3c84955e01a0cd6"},{"version":"8624f92f1bbbab3e714feb09bef38fd335876434a3874fcebcb9ae046ac473f5","signature":"a6403bea9d1a1d1d408265797c6632760858edbd1165b47dbd18ae9a55360e94"},{"version":"98d4729491177cdc579518f1e8040191d0463eea3e6207ede9b855bc9d04bebb","signature":"425e9fab16d185ef0882c34b2665df5eda3ec844a9b3b3d5df06ceec263dc7cb"},{"version":"53cbffb82c8a37debadade9a0c482bfba161c4f370e6629c2a55898d3c7a6130","signature":"6b1710221e0def096b2d020e5e5b74f1c1f574ebf9f0488badad59229bddf20e"},{"version":"45873bda98f9ce75c75e54f1920eb2fade6313040740a0e6a47f1838e8f76188","signature":"c230448efae3c07fb05bd06ace783910db363b2ff8652783f4edb480eb6802d0"},{"version":"fb7a7051d084cc52ab9d5826a831a82a530a3b45657cee4496368fd0e3c7c911","signature":"aae24b4c2f671dafbb715f690342ad88c3c1e1fed0276bd2b50f04aa409b67dd"},{"version":"8211d37643b80ad8a361aaadcc9eb634afea2222b95891607416d6607e21446e","signature":"b130d48ccd8185fa5c192d5f8a049a409ffc9eef46ad5eb86f92b7df2d8dbb9e"},{"version":"08ac03624d51130f9697a5564816ea71de32c79b945a52bb1149f69d81827421","signature":"c509393f91324bfa56f20aa80aa7b2568560f376f6e035c1cdc3dc2847487de7"},{"version":"466d34c304b37bfad5ae2a5b86de942255e829d8774f86a5c0ddf0288d0ca102","signature":"43931128cb1dfcc47ed68efc29e74a2670b66f10bf57008046816e07e5a339e2"},"3734ab2b6d10352e159e69f1abdaf1d0681f86925686c797b9af59a3fb3f696c",{"version":"8eff1dcc176044fcbc60a0c05fd9375174be5e4a9b2ab9696a6d0ae1598fe262","signature":"4a68e8778087ddeb83520b7ed367b8e7a5413545211f1c12181b036b98b46972"},{"version":"402d7dc3e5c84bc724bb4e93cae19e6c47dab840eca709ca6f1ab5a120db8cb5","signature":"c6e9794ab00dabf9766d12dea53be438bfc18291e390e4f52846c7bd4a76ef91"},{"version":"0f4cd2ebe07e4bba58d08b8b333c8d52f83d49418d00b90bbbf54824eb5c4b1c","signature":"f10c4d9ebd838c5dee36c49d6327228d9c1de2375fccafff86909a1abb8f9a31"},{"version":"5c09f6060c2da66befed1f0d85974de41a9745599033049c5fe5468cd38864eb","signature":"8f0537d31f337710a710ca9da779d9fbe37da09f82a2bccb3159ce054a303771"},{"version":"9cdac0173b2fbcf4f0acc5b8eb154e2275b4e6199b1ccb654566421313c9dbc2","signature":"3bc6339203e14955ed7d1f9bed916418c12a6a692d269a609b3a33dc4a863951"},{"version":"07efecd22d47696ad2e459df9c3e28d75550ac8acece69caa9263625af447e4b","signature":"cb9d2bede0762fcbf1f1f6e59445d5671d08ddfd920f8ea01612d81b3a4384e1"},{"version":"6943db60489e17ed68912aec658d0f893d499ac7053aeb7ed161017151897991","signature":"fdb9f15b09c3f33cbb6b0112e1c7a25d797f32511b1fd8406824a1932be39e6c"},{"version":"2fbb44a0b7b3008a7d77e6e27b803448af81671c11e58a1d48b63811f13a7158","signature":"54b1178ff1aaca40dcedcc4a7553d2c30a2ed187105f013526056f721b816f05"},{"version":"835dc5372073132e66588d9e38e54d65e9a86d191eb850f5cc92a7beb5d1b877","signature":"fbcf0ebeb72ab3ef2c5e91fdb048894dbe46d68c62fd7bc35a8c6476f6f7d6b1"},{"version":"50a97612fd2557f947fff7bc53118552d9eeae0f349814c410151cc6dc305f81","signature":"990ed5af4440089a54368245a9fb7777e60d433d416d7b6d6a2035c4225b4eb4"},{"version":"81f5e70b2efef928bafed1f1ed517b5f3d7dbe1bbae734620b1680b8cdd0bc66","signature":"56311c20d6b70677a8c70f2f96ec4fd60c25decbbc91a3eabbe2a97f70857c49"},{"version":"fe78e873ceb3512acd13e34a07f84ac9164174fd0f49b2c288b604d1b8658fcd","signature":"ac55b99427e8f93d864f62023f10171b091089b07e9b94cb244b45bc926ac00a"},{"version":"a44d98f459aa1dd5e9b24e7a4b1903d3b5b7e3b0e1d4000acc9edda4b4a4111f","signature":"43a141930d57efa165ddc6eb216ac4eb9a04c71becf90bccad4e829e884fc505"},{"version":"c3791f64ba0e4efaee48addc1b5d998b38402723e22180ab0c197b2ff114bc10","signature":"85883d40c8cd5c7d4005a595a9c34a0ee96adc71774bbf889486cf1e03e24ee3"},{"version":"88b18ed107ee8f4ef8d9b3fb34031ca6b849d997a944205fce8e0f5375d61bbd","signature":"29e1b79ff0f8662cff1124e3f9c5b2d1647b12f67c929545279f8dc35c44aac7"},{"version":"89d4719af42fa1beffb1ebfc5fc8d8d27ff11255b43e90f94f8be9f434be4196","signature":"ecacb7a344532575d0bcd497fdd22d7e66c42117aadfa1fd211a47dbde3b364f"},{"version":"190ea4f3303d22f9874483ced69d02cb84512c2f7d78a8e5317273d678e47115","signature":"3ac75bb555870b81c6df3ea2f485a805cc92ed4f0101147eb49b03abb8af0d71"},{"version":"978bf5112ea8edbf3f064c9bc525414ce71d3bb3c2b8f247240c6622e58bcfd0","signature":"ac8a2f4d1f18ae09215f2c3b7a9be5623890d20dba85055f0baa55057e0c60b9"},{"version":"54539ce7c00156f6364f3e172820e3fee871a1439a7e82e6b8082e41be915d61","signature":"ea0eeaa20eb610a89ca03507e6880aa8b4e3625665ca47beab4a9b2057bd1f3e"},{"version":"5222164ea7c65c649b96549043a58fe5bcb14792209e1339fe7e26028f52063b","signature":"cf65707352be96547e90a932227cfc57bb9a3a71bccc6659328bd161ebffc36a"},{"version":"27da71c601d567bd84d0b2c165f82e74ed70a17c0f897ce2010f5aabc129830f","signature":"a476746c1a3430e74dc7d6764252eb4efaab3f931bb8ed285d82185b2efb30ba"},{"version":"4177242d6d41f28070ea789d82bf925673b978912038f5dc32c01c841e6e0f9f","signature":"19711303e7061f14777d29f11b17b98a16e80b3f9b71e4f1753c378b0567bf5f"},{"version":"504fcefff7a5316397d1745a08cb7a462a5ab610ca811427d9680f31032bcb71","signature":"a9303ed10475470183bdae2cd301c10b7d40c0ae06050733e5a022a311c4305f"},{"version":"57b9b0dd66e760e67d35fa3679efeffb1dab93870b8bad0d640182e359bcae17","signature":"2a346de3340c2b612e54eaf8f283d10e06620b02d5ad511a26faa5178f473b06"},{"version":"67f06c07ea4caea29065886bd3b963d9414a2ddf283ae8da02dbdecae026ac9b","signature":"9e393f86592057353b72a95bc607d014297b1fa0a3912ac955df2dafd500f408"},{"version":"2a65450bbb5587e3b986046fcc48b542324d787b668b152924d2df04db91f21f","signature":"8ff5a0f7789ee8905d1a2adf9d42d40fe416c23bbc81957875906d660485a78a"},{"version":"ef99eb1c01d181055cf19267e1e77060bd68afc68f11d3df1c7c0e6264ef507e","signature":"bb8324ccfbe6b8c5d014d251e08005c6edbf78874e9143a9b0502ea7d55fd604"},{"version":"c25779d168392a6db6f585d7b1e2c7f9c3b004a5f6e628f7aec2cc89cddd9cdc","signature":"200b6769b0036e06c05756ef6b1a155067c82ac37bd83adfc07dde3df9733dfb"},{"version":"818f95db7caeb67a227f203f56b69fbfb132dec8d34cbae737640faa18ce2a67","signature":"e70d22c4992d706b4da004112f80e350fdb7f5baa47029298ef17ec1d9b0d5d1"},{"version":"a2b88a21a2be1413f9e83aa904c1bf000d4d2317c7ad2fda21072aeef01502e2","signature":"57334c942f8bb1e6d4f71112e6b6ef09ecb4b823462f2008c461b70261a4cf95"},{"version":"bc9a8fc048970b33fdb91c76d7bf4b82dc13c5f3b16ac70930282e7e08b3dc39","signature":"d2302a127467bb97736b87008f4ea68dc226b43942cb2fcfaeb7d6a2eeebfb27"},{"version":"6f5fb4778d48c9edf4e073194ef8be32ee8ed6c526b27b081be2fb2699332f16","signature":"2f0415ffbf291a21f7b8e32657ea9757e9b49bcd4b1dad5524ee463a1927916d"},"53ab4f12c18cb78a9946e6ae696e1924a0c70c933d40f925f6450d05ac461441",{"version":"2d2ac4c473c27f104f7555d2d09069a1211f6096e999792013412163fa2f43c4","signature":"80e816ce6ab347f332104a3b4295fcd234484a8c0f78af931f6f679bc819854c"},{"version":"d3ea50145fc69d7d5042158bda70084edecc82ea9a17637e65228279ad44d8c3","signature":"32cc3d5c511365c3ad87c4fab6bf9cbad8fe64cda8138fa4a3e6d1ad7b501a60"},{"version":"1de0b2a321fedc8312bb012d2e7d6c2a4f3c1f244f57f3b4c90024ce697b89bd","signature":"3e4a13fa3a82198765067d7dc9ebfc78779046ee148aac9b06da6357be695006"},{"version":"dac6f666390294efe2066653f020dadb002c135ed96ec78650068e85b2927bf4","signature":"927eef31f2603821c449226b230c4dfd543e2187e894e4822d46f98ee1e1b060"},{"version":"9dda1d1b1c74d2a7dea69cd86a6d48afb99d5c1e7aabbac1ba5dd05ecf40de56","signature":"80ab17aaf1a46b0bc2e8c68d09df9be18b9e3f8e5e9e17b7ca81797e486b2c47"},{"version":"dc443d2e942b8e4028cd6a26004e8e99e1f831c2aa62d54f403996b68d3c32a4","signature":"35e156f4b41ca83eff7e0cf178b4d0e03fba4e6f81a28e8239c37a8176d92bdb"},{"version":"d54c70ab4f4cc1565747f39afae622bb48bb51fe0e57fe68a3e44d3231c1d443","signature":"ef7f32b7e352746ebe4f8f904e4596f8605bc129bd6a3482cb774b5de494b62d"},{"version":"a6bc0506fd785d58fc01916eed093992884c77047de6ef24a1984e870958f3b9","signature":"6c67fa30e0db9490403ce70c9bd112dc16256f74e793f48501b0af56cf31c2f5"},{"version":"127ab4010cf97a1a249b99f51aa64ce0ec78ac00dd243065e3e40fbbd849e3b7","signature":"564acc0cef9c387a11d6089cd755304c81641036d46e7fe3810c00c2ccdcea22"},{"version":"f1f027e91fd56eaa46196166b089bff9059b765e012b7c6e5fb5d1ec608d31f2","signature":"8f9a45904777bce21a37d6a2f2fc0c16443222e113877e21d8d76f038bf0c896"},{"version":"10191050b15f215ad4735440cebdf0f3bbef5d765ee7a0ef9358ac0efc7803a5","signature":"c4972937fdd1931aa30ce28bb6b8ce30ad6f93041011d50722ed065af37ae3fd"},{"version":"aaf4a4c08d6f7e100958a7cb8859f1e11a29136cbcbf3a8881cc2d7ccc3b73e7","signature":"bb8f5c8174b21b9b1a9d318205301ddeec2d0cf85ba3a7cd68ca9bfa0517f36a"},{"version":"ffed34d5497fb7e29926bfab5a1ca053ee6c870bd626372548f0a0550e5dad49","signature":"de3471094714e2f22ec35ff92df78f1f6fe7d1ca8fab53917a1d90792f4f3296"},{"version":"242738e9e8bd4f9e23c07949f3e87e278284bdffb97bc84d65d555147929c7b9","signature":"6dba4b891a0a8dcf8169b5036d8c89887af23a77aab0eeb92a6435c672c0544b"},{"version":"2a401894795de5633f893cd2fd23893487b287161e3e487bd1d2c89e1435a203","signature":"0d7c827ee785160646253443c92d7b9896e019230026d8bec21c004b92f2b84f"},{"version":"dbd9ffc610462dc033237621b9e52814c03ba7720a009c962850e670536cd54d","signature":"c8d9f0716eac76f852bcef67e50d4b44ef444df32cdd6cdb9e18ec408043aeb7"},{"version":"33b4b09706a6caf693868472f9125dd95f4978ad0a9e19f5a7cd6f9db97602b6","signature":"30caabfba6aefe5b904785c4445f95d3cf6621720040a056fe0c775e09856271"},{"version":"9500b87b59ca8ddfff4c8cf55d088a31665dc1fccff42563dd4c90f9c62a028a","signature":"00607710ad576671fbafcfddcdc1e12dc169be5eec865db96bc24a3a720ced67"},{"version":"25d0ef8694fd8a78968d96fce238c46d48d134a01d143ccf0a2a7cd3727730c4","signature":"0cdee9fa8afd67592beafd0b7c16e5dfbf1dbc95ac37bcd40a25f67fd4283fd4"},{"version":"74458c6cb8c657a8c32e3fab9e230f71ad697a589b19d78a80941db515bc0af9","signature":"6f56b672249984c6df614b88092538c1086584d913c0b2dae14829f11d7d18a8"},{"version":"0f9d01b7aa3564db8b504e2fbd1fa90e831701417e40f05acf224cca740b3df8","signature":"c5ac587c457088e29a96e148770f8bb6b55738c7bf678956a922877b2f80c226"},{"version":"4afcf731b783a79a27a8d8882d81c4803eccf82c3da968d7d6bd000dec1bb788","signature":"178945bd938cd23e41a3bc633a3c4646f8f5d4891baf31ef66c66ba89aab7aee"},{"version":"69f99450ba121fedaee449ecb21eb11d3ca81b2cc409342ff3acb71521474705","signature":"e41be35477d7ffa9f719088ab8bea2bc4bfc86cadc12033d1651903315793c97"},{"version":"c9e97ab16e1b931d6dafb5334e09d0b8f0687df5951b9b5e6ebcb7b959340c5c","signature":"357ff7fa54786ee278632faa1700abe9d222ccdf4a96507de70da3638f97889e"},{"version":"89faf332a5c1dd70c3f6f551c124cca9b66704d83c18763c307726045089ec7d","signature":"b3441ea2d656463bf47dd1981ee9964b8b76f7afb7a1a19b4c071902ce6b2074"},{"version":"da0f84fcd93700b4a5fbf9c6f166a6cc19fc798231bff56dd1e3875bfc6966eb","impliedFormat":1},{"version":"634ff08e0143bec98401c737de7bfc6883bfec09200bd3806d2a4cfc79c62aaa","impliedFormat":1},{"version":"90a86863e3a57143c50fec5129d844ec12cef8fe44d120e56650ed51a6ce9867","impliedFormat":1},{"version":"472c0a98c5de98b8f5206132c941b052f5cc1ae78860cb8712ac4f1ebf4550ca","impliedFormat":1},{"version":"538c4903ef9f8df7d84c6cf2e065d589a2532d152fa44105c7093a606393b814","impliedFormat":1},{"version":"cfcb6acbb793a78b20899e6537c010bfbbf939c77471abcdc2a41faf9682ca1a","impliedFormat":1},{"version":"a7798e86de8e76844f774f8e0e338149893789cdc08970381f0ae78c86e8667f","impliedFormat":1},{"version":"eebc21bb922816f92302a1f9dcefc938e74d4af8c0a111b2a52519d7e25d4868","impliedFormat":1},{"version":"6b359d3c3138a9f4d3a9c9a8fda24be6fd15bd789e692252b53e68ce99db8edc","impliedFormat":1},{"version":"9488b648a6a4146b26c0fd4e85984f617056293092a89861f5259a69be16ca5c","impliedFormat":1},{"version":"e156513655462b5811a8f980e32ccd204c19042f8c9756430fe4e8d6f7c1326e","impliedFormat":1},{"version":"5679b694d138b8c4b3d56c9b1210f903c6b0ca2b5e7f1682a2dd41a6c955f094","impliedFormat":1},{"version":"ca8da035b76fb0136d2c1390dda650b7979202dbe0f5dc7eaefcde1c76dee4f4","impliedFormat":1},{"version":"4b1022a607444684abeee6537e4cace97263d1ef047c31b012c41fdc15838a79","impliedFormat":1},{"version":"dd0271250f1e4314e52d7e0da9f3b25a708827f8a43ceff847a2a5e3fd3283e8","affectsGlobalScope":true,"impliedFormat":1},{"version":"47971d8a8639a2a2dd684091c6e7660ec5909fed540c4479ca24e22ac237194e","affectsGlobalScope":true,"impliedFormat":1},{"version":"e1075312b07671ef1cbf46409a0fa2eb2b90bb59c6215c94f0e530113013eeda","impliedFormat":1},{"version":"1bfd63c3f3749c5dc925bb0c05f229f9a376b8d3f8173d0e01901c08202caf6f","impliedFormat":1},{"version":"da850b4fdbabdd528f8b9c2784c5ba3b3bedc4e2e1e34dcd08b6407f9ec61a25","impliedFormat":1},{"version":"e61c918bb5f4a39b795a06e22bc4d44befcefd22f6a5c8a732c9ed0b565a6128","impliedFormat":1},{"version":"ee56351989b0e6f31fd35c9048e222146ced0aac68c64ce2e034f7c881327d6d","impliedFormat":1},{"version":"f58b2f1c8f4bcf519377d39f9555631b6507977ad2f4d8b73ac04622716dc925","impliedFormat":1},{"version":"4c805d3d1228c73877e7550afd8b881d89d9bc0c6b73c88940cffcdd2931b1f6","impliedFormat":1},{"version":"4aa74b4bc57c535815ae004550c59a953c8f8c3c61418ac47a7dcfefba76d1ba","impliedFormat":1},{"version":"78b17ceb133d95df989a1e073891259b54c968f71f416cd76185308af4f9a185","impliedFormat":1},{"version":"d76e5d04d111581b97e0aa35de3063022d20d572f22f388d3846a73f6ce0b788","impliedFormat":1},{"version":"0a53bb48eba6e9f5a56e3b85529fbbe786d96e84871579d10593d4f3ae0f9dba","impliedFormat":1},{"version":"d34fb8b0a66f0a406c7ce63a36f16dda7ff4500b11b0bd30a491aa0d59336d1f","impliedFormat":1},{"version":"282b31893b18a06114e5173f775dd085597ca220d183b8bd474d21846c048334","impliedFormat":1},{"version":"ed27d5ce258f069acf0036471d1fbb56b4cb3c16d7401b52a51297eca651db62","impliedFormat":1},{"version":"ec203a515afd88589bf1d384535024f5b90ebe6b5c416fb3dcca0abd428a8ba4","impliedFormat":1},{"version":"32a2a1374b57f0744d284ca93b477bd97825922513a24dfe262cbf3497377d96","impliedFormat":1},{"version":"a8b60d24dc1eb26c0e987f9461c893744339a7f48e4496f8077f258a644cffab","impliedFormat":1},{"version":"3f9df27a77a23d69088e369b42af5f95bcb3e605e6b5c2395f0bfcd82045e051","affectsGlobalScope":true,"impliedFormat":1},{"version":"9fd080a9458c6d6f3eb6d4e2b12a3ec498d7d219863e9dca0646bdee9acce875","impliedFormat":1},{"version":"e5d31928bee2ba0e72aeb858881891f8948326e4f91823028d0aea5c6f9e7564","affectsGlobalScope":true,"impliedFormat":1},{"version":"9a9ba9f6fd097bb2f57d68da8a39403bbe4dc818b8ccd155a780e4e23fa556f2","impliedFormat":1},{"version":"e50c4cd1f5cbce3e74c19a5bbf503c460e6ae86597e6d648a98c7f6c90b596dd","impliedFormat":1},{"version":"fa140f881e20591ce163039a7968b54c5e51c11228708b4f9147473d06471cf5","affectsGlobalScope":true,"impliedFormat":1},{"version":"295eca0c47be1191690fd2fe588195fff9d4dc43852aceb8b4cab2aa634579f0","impliedFormat":1},{"version":"59ee7346e19b0050508a592702871dc943083c6dcb69a47d52e888115d840781","impliedFormat":1},{"version":"067712491fb2094c212c733dd8e2d56e74c309a9ce9dac9e919286b7245a1eb4","impliedFormat":1},{"version":"a5eae58ac55bd30c42359e4b01fb2be5eddac336869d3f04ffb4daa54b58f009","impliedFormat":1},{"version":"d12d691ef8933e8db39f2ca81d6973940ff5e37bb421752f5b6e7bc15dea3abf","impliedFormat":1},{"version":"4c5f8bd9b3a1aae4e4fddfee41667e495a045f73ed603993038fa6a8ba92fa14","impliedFormat":1},{"version":"dfb274ab0f319cf18ce7152067c25f984c7fd1924fc72b3f66734588444c934a","impliedFormat":1},{"version":"108c8c05cbc3fbbbd4ff4fc0779c9bef55655c28528eb0f77829795dc9f0b484","impliedFormat":1},{"version":"a7e5444d24cdec45f113f4fb8a687e1c83a5d30c55d2da19a04be71108ad77bd","impliedFormat":1},{"version":"41ec17e218b7358fcff25c719bc419fec8ec98f13e561b9a33b07392d4fec24c","impliedFormat":1},{"version":"23c204326746e981e02d7f0a15ab6f8015f9035998cb3766c9ddbf8ea247aea2","impliedFormat":1},{"version":"25f994b5d76ce6a3186a3319555bbba79706dac2174019915c39ac6080e98c7e","impliedFormat":1},{"version":"dfa4e2c6a612d43851ccbc499598cb006a3a78bc8c7f972c52078f862fa84e47","impliedFormat":1},{"version":"02c1705fa902f172be6e9020d74bcd92ce5db8d2ef3e1b03aabc2ac8eb46c3db","impliedFormat":1},{"version":"99d2d8a0c7bb3dd77459552269a7b5865fa912cedab69db686d40d2586b551f7","impliedFormat":1},{"version":"b47abe58626d76d258472b1d5f76752dd29efe681545f32698db84e7f83517df","impliedFormat":1},{"version":"3a99bbbbbf42e45c3d203e7c74f1319b79f9821c5e5f3cdd03249184d3e003ce","impliedFormat":1},{"version":"aaacc0e12ab4de27bdf131f666e315d8e60abec26c7f87501e0a7806fc824ae6","impliedFormat":1},{"version":"3b4195afd41a9215afc7be0820f8083f6bd2e85e5e0b45bb0061fb041944711e","impliedFormat":1},{"version":"108df8095f5e25d7189dd0d1433ac2df75ec40c779d8faf7d2670f1485beb643","impliedFormat":1},{"version":"ddd3c1d3c9ff67140191a3cf49b09875e20f28f2fc5535ae5ea16e14293a989b","impliedFormat":1},{"version":"7b496e53d5f7e1737adcb5610516476ee055bf547918797348f245c68e7418fe","impliedFormat":1},{"version":"577f44389d7faedd7fc9c0330caf73140e5d0d5f6c968210bff78be569f398a7","impliedFormat":1},{"version":"3046c57724587a59bceefadd30040d418e9df81b9f3cfd680618a3511302ed7a","impliedFormat":1},{"version":"15ccc911ed15397e838471bfe6d476c28deffe976c05cb057e6b1ea7491242c2","impliedFormat":1},{"version":"64b5a5ebdaead77a9a564aa938f4fb7a45e27cda7441d3bee8c9de8a4df5a04f","impliedFormat":1},{"version":"a48037f7af5f80df8973db5e562e17566407541de284b8dadf1879ea3aed8a2f","impliedFormat":1},{"version":"dab97d96ce986857150db03f0d435b44c060d126b4a387c7807f4e9f6c92e531","impliedFormat":1},{"version":"85f39366ea7bc5e34b596fc97de18a7e377856755e789d8e931054f2191d9b8b","impliedFormat":1},{"version":"daf3ea3d49f6e8a2fa70b7ca1f21bd97f1b65021b31fbfccb73dd55f86abb792","impliedFormat":1},{"version":"b15bd260805f9dd06cd4b2b741057209994823942c5696fd835e8a04fb4aab6b","impliedFormat":1},{"version":"6635a824edf99ed52dbd3502d5bce35990c3ed5e2ec5cef88229df8ac0c52b06","impliedFormat":1},{"version":"d6577effa37aae713c34363b7cc4c84851cbabe399882c60e2b70bcbb02bfa01","impliedFormat":1},{"version":"8eaf80ad438890fe5880c39a7bbf2c998ce7d29d4c14dd56d82db63bd871eefb","impliedFormat":1},{"version":"9b3e7f776f312c76ac67e1060e5398d7ac2c69d6a3a928a9daaae2eb05b15f56","impliedFormat":1},{"version":"202042eccb4789b7dee51ba9ecab0b854834ea5c1d6a3946504bfc733d4468c3","impliedFormat":1},{"version":"2b2ef76a9f36094b07ee6f76a5ac6903f2f65c0a20283201814a8d1e752cb592","impliedFormat":1},{"version":"8882e4e087d0bc8cc713cb3d8090c45d33e373e6f5c83e0f8d00fe6a950ef875","impliedFormat":1},{"version":"239bf5ecab7a3e2b5aada92cd7ddbde7f5203668df4a6be6370de467673c0afe","signature":"e3318f4fb1fffb76d06e2760eef2a35c394a3bcf63ee416a72948f06c3e4924e"},{"version":"4658529914e4785a4ce0213e8cc9af4f2cc86adbc3bef7cb6e9836ca17d8f0fb","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6500eb6b035d7cf336521bfbfd879f50e35037c06d98b1a19cbe5b2c0da63382","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"89edd51dd80dd516fe2106d109787ccd8f266848ee4dc9e5d9bf58f64ceeb6dd","signature":"a3bbd087770ec8da617bd5aff121de0c9cf9d0349332fe5f7745514c9c493ec6"},{"version":"caa19c07e2136777d963a0a9524abc2cf22af3a0e8da9dd939ddfc3fbdc2ffe3","signature":"be1b859329d61b0f74fe1393fd56c1542d1807ddf5a035b27a7153c471f53e32"},{"version":"7735162c45b2819ac4b735b8e2326caf71177e785dd2cc25c4984b3a904145d9","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3b8dd79475090176f6d87ad62e74e00dc5f1841480f05924cbfd5fd2bd962ce7","signature":"da2c86818b2628998aaeb7093e18386535412b90d6482e6e55e57bb9826f067c"},{"version":"935b99a46413ad7a34b25f98210295d6747ba58fb9f3347f0723aa4cabec7e38","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"80293569ac80d5bd82ee00a3fdfed54495561016dcf201384e527a89daf5d6e3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d519d73d2e115370efa0ce4ace26f38f34215063c7e0810f3994a1390078ea8b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5d0bc2306b8f111545fc6b3dd819a10e6ed1142c1454313781df8359ba7721d0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"aa481d298a08f3f648a340ce569d15e81813f64b3784682bcaadd0106854df93","signature":"5272a45a3368fd3d5b08c29dd0afb26098a2ece5834819bf5e3de14c9c4ad41c"},{"version":"f75a1566dfa5db69e27a299b2dea7089f74267150812ae659de9fd1fac82f07d","signature":"3dd356c08322fb7c79a49f242d2b9c1cf64a54a7cdbee83a902f23e9cd8503d2"},{"version":"6ada175c0c585e89569e8feb8ff6fc9fc443d7f9ca6340b456e0f94cbef559bf","impliedFormat":99},{"version":"e56e4d95fad615c97eb0ae39c329a4cda9c0af178273a9173676cc9b14b58520","impliedFormat":99},{"version":"bd821b87e2c0fb5f509cedf47da465c447451835ce0fe2a752c4fc53a9f95a5b","impliedFormat":99},{"version":"f1d7352c0f7041abb43e1054abb14fb8c53a13dd54bcc1d67b97d2c02bb5028c","impliedFormat":99},{"version":"fc820b2f0c21501f51f79b58a21d3fa7ae5659fc1812784dbfbb72af147659ee","impliedFormat":99},{"version":"08f88f75fc2f516413477606a4122b6d3f6eb6680e8eb79f3fda5a5d2ed306df","impliedFormat":99},{"version":"6ab9821afd2a06879620eb4e041b9492a90f294e9b733ae5eb022edaa3964a45","impliedFormat":99},{"version":"003533cc3fa10cc457668d4256d21a65706a67a04251962cfe85d240502f8d67","impliedFormat":99},{"version":"6c00cb8a4b187505dfe21aff242b07f69f84f5c832e8ab4357af69daaee1b0df","impliedFormat":99},{"version":"de14ddf9d780367c6a117bd8a1718d491aff66094186523b3eea680ea7035a7c","impliedFormat":99},{"version":"ee06b94d0521cfaf91e4b003518eeefc45bbd594b0c22955fe35be282958252b","impliedFormat":99},{"version":"9e5f8fdaeb03f1699392b4724a58ca7b47c5cbb6762920d2bfc722c265495ede","impliedFormat":99},{"version":"d05bd4d28c12545827349b0ac3a79c50658d68147dad38d13e97e22353544496","impliedFormat":99},{"version":"a1597b0039f39e9f3eeaf120f02d0c94a826fad30b027a2abfdb8d580c89be70","impliedFormat":99},{"version":"04ace6bedd6f59c30ea6df1f0f8d432c728c8bc5c5fd0c5c1c80242d3ab51977","impliedFormat":99},{"version":"57a8a7772769c35ba7b4b1ba125f0812deec5c7102a0d04d9e15b1d22880c9e8","impliedFormat":99},{"version":"1de82ba3718b2b3bc5333c5bc35da5cfc46d1b654edc012de46bbce48126fcce","impliedFormat":99},{"version":"8bdbb5e0426b40c11dbb4b86045f008c619ba02050126ede6501f7c59376d1b1","signature":"d6ab9d3f4bfde85a62fea7182d4e68ba2104946541f47eab58799009a38ba2db"},{"version":"b0900110d5c7baa5c3bb7c230dd6c9bafa906eca5fc63c1f3f59460faa717da4","signature":"2eefd9c7b8dddc8d713b7ec2f408480a4c1ff14f1975c85f91834b8886963f8d"},{"version":"a44d9bdda85e27849447b4b58bbd061c74e7c5ddb91c31e489e926530785cdd8","signature":"5e5a13138a956d69dc4e30dcc820b816b253b6907b02e168e1067a6f026bd4b3"},{"version":"86d4ff8ba66b5ea1df375fe6092d2b167682ccd5dd0d9b003a7d30d95a0cda32","impliedFormat":99},{"version":"736097ddbb2903bef918bb3b5811ef1c9c5656f2a73bd39b22a91b9cc2525e50","impliedFormat":1},{"version":"4340936f4e937c452ae783514e7c7bbb7fc06d0c97993ff4865370d0962bb9cf","impliedFormat":1},{"version":"b70c7ea83a7d0de17a791d9b5283f664033a96362c42cc4d2b2e0bdaa65ef7d1","impliedFormat":1},{"version":"eb541103f61ea69ee1795085a473b727d46fe49ebc7091c721623b5ecd87c0f7","impliedFormat":99},{"version":"014ba72e2add59d6d2d2e82166647982c824639e2902ccd7b3103cf720a0cb65","impliedFormat":99},{"version":"e22273698b7aad4352f0eb3c981d510b5cf6b17fde2eeaa5c018bb065d15558f","impliedFormat":99},{"version":"b78c801c3c21015ee487f6494448bcff55bb6b61f41172dfc2c26f2218d99138","impliedFormat":99},{"version":"de97e016d8dd4869febd5bccce02eb96957089d04b74ea5d1dc0e66112493b64","impliedFormat":99},{"version":"671ccab2e6a253d2516c0e4699b3077fc30cdb70b4436d8c79d76c91266a1a94","impliedFormat":99},{"version":"a11fbd8ffbee6e5a7fe4c7c23e6a391be615de2e710a6946d7d1f947a85a1374","impliedFormat":99},{"version":"2d383c515b9b606aefcde23da9c312a69bc7976b75abb85c02592f7a8589a343","impliedFormat":99},{"version":"e760f7860d08e9d42b6ecd7dd341602fbc0c13d60eb30beaf1153f1c7c44d66d","impliedFormat":99},{"version":"fb04e1ca667399e7302c033656cc285e6c1cff9c29f264cf229dd25e3962a762","impliedFormat":99},{"version":"ca6fb77e3480af8f2287ccb756ac88d047ba8a8bcc0512f6720ac1216e274ea2","impliedFormat":99},{"version":"c0cc44b0ad2fd65c933d187c4faad6157efbed33c3c21023802aa6a89d9b9d13","impliedFormat":99},{"version":"ddaf5d3ddc45282b19fb0fecec91c87fc9b4d1f45c2ee611677345c81383c5c5","impliedFormat":99},{"version":"5668033966c8247576fc316629df131d6175d24ccf22940324c19c159671e1c1","impliedFormat":99},{"version":"d76df1670eeb97afbab6c87b8cd31bbd09dbf9026ff0ca533b5d7d3fc0291f79","impliedFormat":99},{"version":"e9b947b944887cf2cdea2d6188f412a25125eade82f2fe2334658af86f14cded","impliedFormat":99},{"version":"bc05fb9d657d30e61d50d690615f379b0d0415b8f29e69196e1dc6bfc664dc57","impliedFormat":99},{"version":"e315bab2f28d53f9ab473d9de610c455b6c414757bb19589b31ec8f490cebd4d","impliedFormat":99},{"version":"d999dd5abf4befbdab5f1248193cbea69b323b71131a02bb120f9462807fcd5a","impliedFormat":99},{"version":"031f1805f87171e8a9125cd99105bea4a869018ab2356c2e29dca7c86925510c","impliedFormat":99},{"version":"bdcb070ed484b40b84dad668b58e4861f7c3d36f38632072dad5f905bd8cc0cb","impliedFormat":99},{"version":"54a97fd1e2f33041d7b4cbbe8fe3235f97fdf1a05e9fa41e78417851bb8f1c78","impliedFormat":99},{"version":"f63e0743e2ac9f7eb4d3b8bc110834465f164eb9e75e4f531046e4ff9822f3a4","impliedFormat":99},{"version":"0372d6d6a41ae89f9aa86eef998b44bc0ba035d9d09c9226e0a150ef4578fc3d","impliedFormat":99},{"version":"cd8a4297d0ab56dc571dadd2845e558c9d979fe1e120a0dec537935bc8a36dd2","impliedFormat":99},{"version":"079a12cb0e0c42655d77da5185e882b4cc94bd5c6c2131171a9289fc1f4287fc","impliedFormat":99},{"version":"50cf14b8f0fc2722c11794ca2a06565b1f29e266491da75c745894960ebbce06","impliedFormat":99},{"version":"d4ce52c42c23981d958206037138e05f7b48d41faa1cfaba7e9eecce8c2e5489","impliedFormat":99},{"version":"8a3be5afe0275ce84a6a6298010e66d54d2d2f8e927df6bcde0ac326b5e81792","impliedFormat":99},{"version":"167edfac7664bec77aa2efb2ce9d515c41b5cc4269091a946b3fa6ec4e7e8738","impliedFormat":99},{"version":"218997a627f0efc4b8be5e6bc0b58e0c9edf250baa3674b661d3f7e6a7ef21e8","impliedFormat":99},{"version":"c3f1acbd39f587a7539d435d6c78ce8647b3bfdc5435df153a70cb2656b52b80","impliedFormat":99},{"version":"a1f568855887e2b5121e8b5c4cacd66dc5428259981c47026d95be1d844adf71","impliedFormat":99},{"version":"1db6f2cf99c83598669df0c7166405b0e83c153a03b59115000df3cb1afba79c","impliedFormat":99},{"version":"49af73d71b88a99b1a211ec02bacd321c21397062d253c605bb8d140082eb7b0","impliedFormat":99},{"version":"a50de7f1a7eadab7732d80dcf9c8a0c0d7d00e33315425316757006b5bec6e46","impliedFormat":99},{"version":"4c6fe268c2a984b3f14031a4b09ae7b2d9e51673258f4b4352e48d0c6ebed679","impliedFormat":99},{"version":"3bc3d81dbfb842bcf15454aacbe37cfeac57f1d15e829812ad02a05cc42be873","impliedFormat":99},{"version":"016952415e1ad35f9070b4d454946e79cd3881f3c76c0978d759b168fe033018","impliedFormat":99},{"version":"6bf5dc0c9f6b6c79fce77b56c985dadca4d4d474c9abf9139ae0785cb5c01992","impliedFormat":99},{"version":"d507276467f554e383b4fa058f42b8fdf5c15f1d1db84a2372bb569ce9d57a66","impliedFormat":99},{"version":"b3af5d182c9ef267d58cf43f3e51ab73986862436790a4dbf076b5994758d53f","impliedFormat":99},{"version":"1c7f26a88f861afdccd8f9d9e793f1affef4635d16d2609c487480c41c42f253","impliedFormat":99},{"version":"4d4551dcb3fd19a4f22aaa63c6c391d42ce44a15602a6f6a19d582709edb24d9","impliedFormat":99},{"version":"a76075b5aba8187b1fc5c8f565745daed6e4341e64b44e6ec41412a16d575d62","impliedFormat":99},{"version":"f836bf3653e31c3bba120071196c95d416b83c5d860ce27549975f8785cd670a","impliedFormat":99},{"version":"058d970583137cface729371715449aac0c1388bf7a5ba15e0be952677485fe3","impliedFormat":99},{"version":"31c45c074a9acb94dbe340d9336d3c915635eac2df3308916fcf41f2ba6ab84c","impliedFormat":99},{"version":"774256d456ca1d8266f6e2170a51bad2659cb7116334d1e7977595999533a5d0","impliedFormat":99},{"version":"07916d3ee50b94b3217c0bac71fa9d70d48f51586481c7cf61af2a8ebc2a9db5","impliedFormat":99},{"version":"f687f35c2206a319dc7d8f0b751e182638c912838ff54034fb782beae50f7cac","impliedFormat":99},{"version":"1ea2d362005804d980325c2fe6ca0abbb145197b856a64d80016554129966c97","impliedFormat":99},{"version":"7a81f15892b1c8d0cbfb35605038ce5c6d0cf93542946aa0b8c415dbefdea1cd","impliedFormat":99},{"version":"22ef1a1604bed6e226888a2414676ef477a7ad5d6ed907a62d6e40c831797366","impliedFormat":99},{"version":"2ab500573da35083b48fa8f4fe719860099d1502df3384f977eb22ab6b14caf7","impliedFormat":99},{"version":"9fd7d60e314f01c950ba31932c150dcec5db2c82de3c7fe0d0d24ee8b54f1fca","impliedFormat":99},{"version":"841d1f32f66723468772802e1558eb36ae0226c95d0527a3ebfcfa2c75b6f6d0","impliedFormat":99},{"version":"d3327c9f7dce1e11f5ce85b8ca921668a13068980f7f4ea4daedbc2c81589e9b","impliedFormat":99},{"version":"a698ef4e27ad6053ad8c189b53c468f857501046aacb554eb52be0403ba7f262","impliedFormat":99},{"version":"94b576c860480aac3eafdf904cd81755f5c9b16c3e0ef3253953a8f4fd8cecce","impliedFormat":99},{"version":"8dc7c54b72cb2a49a7639dccd99a559c243667a74abfb09545cf8afaecc58056","impliedFormat":99},{"version":"d2166d3793936235216ee5d014bcf0d8695f3a954ad54c01b1976c05f544ceea","impliedFormat":99},{"version":"41bf8c3193b575946682ca243de53370f61917035c3ff3fb747067bc680f2509","impliedFormat":99},{"version":"aa22166e170cf5fae797f7638bc1331aeb752f267c746faf459979540aa6fc4c","signature":"cc4378fa4ecfd466c3ae4491a7d8595f61fec07131d6b961583109bb9aecf551"},{"version":"2f9abb7927e6b1751e8adb9d20e7fb5fd27e84150f21b625c286f99ef7bd39e7","signature":"24644a17b266badb345ca337c9d9c80300473c40b2c87a28e5a3ddc011551909"},{"version":"c4af0a769b947a766b1f41d9b09d4258c8f2054d87dc9f0c861396a5ff295fe8","signature":"71e597ff732221dcbf043d2de4000ccc5326c9ac63b12f2a27f89b5adf18e609"},{"version":"3c22ea48384e01f1e7cd7c50ba24a4e4b151392a3ffc002e4fbf5e488457efe3","signature":"22c51e70701555882fd248a93bda5c759c024c0b88a58ce37a54ef186729e795"},{"version":"3c004100e0c0228a4f538f445e6d4c7f1176e24cf0bd0126ace46e6c9d276967","signature":"212577e3f6db3f7bfb26e82ef9385a9c0c241b3906ccb6d80e4ae6bdd657e00d"},{"version":"98cd335ec2890aaa6856e59ccf3f4a5b2362c4ac9bb9126414da0f6ff0d75f88","signature":"0270d8376c084b2e07697ef2de94f943eef66b6de4b77fb20147d306f645e990"},{"version":"42fe92d759016a113417544e75012dd0346ce50780a2ac89ee9dc93fcd0f03e2","signature":"8981293638dcfc12c0e02bfdc33353c92e1a9821e73ebe269ad367434fb5510c"},{"version":"16cea904e8ce45cebaa9ea04eb9503468cebbefb5d8bc01309eb4adcb3cf9c1b","signature":"dfb01e57f4a98a678b16d78007abe78b8600ec7545e63331d72a0daa6ce961ad"},{"version":"7d2619408fabf7a9e1994d5e983e19f6d349fc93dbe98553dbb6467cdfbf624f","signature":"54d8ac0a02cacde5162ddc4bf4a5e973fb1f76eaabaca37782bb822ecb91f058"},{"version":"0b943d8397fc7d8ac1a23a0de3bb23e68e75092436292b3db709affd6bbc6484","signature":"a9dadd65d2aa2cf96d962c488059826f5484b70093ef76d1f871c961fa912eff"},{"version":"dc1d17e168090dd4df773ba839410a2e106ff0f43163a994f681a0044b4de578","signature":"6113e636ad4fffa72648f2a41eb42ef83262089973d54e080d130cb4219dab4c"},{"version":"17ae4d2c0c3487e077a7f6db647df1ebc56194a135262b8af15b19a3a1072452","signature":"7d5919c7ef0b53f308a78754ac25e352473a2de6bf6e25e109cd03dd493aab54"},{"version":"cc3d3c96230c0c688857be4ec21101eebd384e427521e3b77f6c87507ca36c24","signature":"9adbd23317b76a09a9364daa02f7ad358ef30c277dca1b05e8df356f7751702e"},{"version":"0083cf5a71517844e6e3f71b504f0c921141068c42936b9208ce2754c4aa8086","signature":"2e717c399a5cc34076335b2718b56c3cb2263caae14384b8b971f4af16103d3f"},{"version":"3ca5a20c56112eb875ae0f86af92e3504a07f5d791da9e024e2cf1b871d6dfad","signature":"098fb9262c019dd7c7d2bc1efe85f61d7fef30a8c6ea0267398aee95321cf1f5"},{"version":"3bbc3df9db389a183af7f9c49942e65af981ab975e88fd50e714a269500c16cf","signature":"7e2734061c31bb7fcc162aa37af53a181cb8db1bc2ea1168ecf1c816cf52e045"},{"version":"04e564b1244256a78028b4c640a0c063ebef8304b5744d6a8f1c09f34f7c1587","signature":"d23b1f070ca79bf4cececb66c23b78eb3f35e10b3ef7d0119549521c9fd2ccbf"},{"version":"822d455d9ad873f074745ef689e696456d9cc046009453a57b7f34b41465a5bc","signature":"2fe07fd890f914dbdf16fa8e6270c867ccf9999973599c1f75da3177ed5c0278"},{"version":"6ffba050345117f0fdf875d60f54a37529ae042cb113ec8dff23680f794ef3ab","signature":"03f8247a05f82c8f96aac14f06944d2dab5061d08fe71d98e429251144b7d9d8"},{"version":"2fef2f55e3ccd796b7b96dfff12c034153403c6d3075a0f690fec9a582c00f81","signature":"1b758ade259220a7723152591ae4997ead9ed62664cd36c08289f94fbaaa5511"},{"version":"574fede341569986c736fd5468e28194913dfe16685e804a5d5fb0a24e71384c","signature":"cb0d718f6419c7cf0ae1316734875540e46cc33e10e9aeecdf79e2f29f6eab4f"},{"version":"ae017e8bd48f178b0da6ce83bb8c4281797fc3446be5d94105c534266a085ce7","signature":"291d5759f6ff0d7180456b40f53ac4ec52d6fb91b6688aa2748e70feabcc8124"},{"version":"26f5a631c99beb2e710e9f780d38f5296d8e7eda404654bb47c5425b90d45866","signature":"4bd61fa62afcedd4e842ca0d3de983b761f6729d267e9a0d1aaf4c15a998c4e9"},{"version":"886ffde80b9f55cfa8f92f32690f77bc7892ece2d1315ca3a8856bf7e878a97d","signature":"50d64ee04b0476a1348ef61e8f7e8d49883be123bbb3bf18eb9870d3febd73db"},{"version":"48193d602f5f2727f1f0dba57b9f8f198c8dede37b0d4a023fc7b6b22208f67b","signature":"74b2ceb70d6eaae4dac30827745318df518df3e547528f5ddf8a93bf0ac289c6"},{"version":"e3c0da528cb833d8c12ff075f0a722c0423d35c43c1826547283763cdd5d1868","signature":"6fb16d7f85050f01ba2e8248d33306db324277a87040aed2ac58e20343a0c2ac"},{"version":"c56099230a4d6b6479db912f210ac0a705b650309457073814dba6264e656a83","signature":"c404855e249e727a187122c5a1809d1e93cf3bb3af6d64d68dabae61754a2da7"},{"version":"2a53285eb8eccb748b182a46e1c2418d0001384d6464dab4fd106489a5db0a68","signature":"37fa56790fd8a57b9e8e21bd7f2aa4cdd33b7a833ae9626d8bcb9eb41e0288e2"},{"version":"d7c40d27ccfd7caf8a452594693f996936b57e48008e5467ee2683d7286d214c","signature":"a377867f70cb021f6b57a076f3124d8e5c9e207ec1152e0fa5e6db763ef1b409"},{"version":"d39acbf8428f8d65c9c04c27d2414d9d9751256b1a2a95ae39c33e9bc3a8bdba","signature":"de82dd11ce4b81aee57b38fa6794ddaff9dd8421844abc4ed6573582ac675157"},{"version":"302d3cbdd32beffc04087cfc12cb46c63d8b97d5d5e1ff7be05bf5cd0a86aea0","signature":"ed02f8c6d224e08e9458832a973a1347b7aca09e9b028509067f3a6eea456e9b"},{"version":"d45072001d72ccd5a50f0b8f870b0c543b3ee2aa86a9373629386e73260fc8b0","signature":"9c9221954c7e4354f0499f4aabb84a43506be7e4686dcac7eb43455863c65130"},{"version":"918369b8524d16bec17184784c9910a16d920d905fab7e2c4d15ca3c70e2de42","signature":"e22176f88be4840e38913cd8d2ecd30bbf400a00b25024f700ee2edd7b173c02"},{"version":"e3231c9f3b6499b7af7d75f365d8e5e577f38a605a9be03c51797e06ad1b32c2","signature":"929656ff244aa687d3287dfb03d592c39043a9cc57bb4cbdd35712230b43b96b"},{"version":"b2ffcadf25a7be173af55c18206ed7b81f553d08918a87d65625c310eaf56deb","signature":"5a751a7dc15220b5b24e11d4a5d8728ab4a83e6787e59499e86dc41f8acf5cc4"},{"version":"8cc036453f78f58f2657e5ff52bb5af95e3efd5eab523ed42252bc5449fa0315","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8ab61002066e314d8b266725a4ed3db857e41c7aa11927aa4a38386370204af6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ca973ee48e138941af5747d17cc31073d1d08f57241a81b8a6370fba035c57c8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"883302f9d5d8a7800deab84b6a25a3120dd0877748c8f83f651e30b069f0ca2c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2f0277d622e090d744ad739acf1113c61644176a78de414f05093fe587766d1c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"afcf004ee208d0c1630059de2791c1641d806d3788e2801e05b471fe6a72b6a2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"98a392f5ddf126f90210fb87cd4988042afb5e0557fc03ba32911bf5bcc0dd0b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2701c611cada4d7d6a354fbc73754848950ffc53032fb560d34a93914dbecc11","signature":"2b9aaf389c15fa7ad7278aba64edae7db672fab3a6e44b95ad28a37b252a48d6"},{"version":"b4b9ac3c096a51a1a127bf2282b347c87db05dd1da22f33d140afd75bcdc8f77","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"31cf5e25aaf868c1ba0e195b7b62f5cb516b70a5bd6e2ffd8eebad1bb011c24b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c8fa10463a099bc87dfa145b710752192ece654ed08157d6e8bc1ca6fd83b73c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0253ca94846bb56a34746b1477fb3056f68fd66868eaf5882bed6c8d8eef7bc1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9b80069148c23348d866893c51792242c00832e28ee92964dff0c305ad1ba8b6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2ce5c938b33684398ed23f32a911b5ac8433e3c85ef84e75e1eac96da7ef3bd4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"65b9594e69ea0e93b7f2d12c18a3c17c5a8a4f13f092d7e7701605bdfedf187c","signature":"7a5c0dbf3696c0ba77a7a119a5ad131c1fa6a959fa284527d8a46b390bebc0a9"},{"version":"294fec2ff7cf14219715ef178115c68c54c304d984b7fe4409728cfdcf910331","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"eeca00e97ce1c893d0b328211da89a8fd39bdab347da2855b8c99b8d1f433727","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"eddd32b79454e4df90e6e3bd8d43a997c8813aa61e2be0c79875c87a5d9f7b2a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2c217dd4af49f75ea5671d76d7129d3d3154589fd0193c323ec2c687ed13ca62","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"70879fcfe03c15515033be18baa3afa57f4f4a6d6bce8801e87050b02e04df55","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"96215e8d738ab2aa6743287a85f309ca453131d604ab38e00469de31858579fd","signature":"da19036047eb5653fa5c982df7cd191f9329637e42372cedba82c9c9c75061f7"},{"version":"4209442ecd03b6cf5a4fb37f4ab23bf40b387dfef729f6556556cd3a1ae15dd4","signature":"2a718b26b22619bc0eaed2d9a958dedb8d9e52e68294bce22da1de23b74bb8dd"},{"version":"7d8570e9fc6b57c35e87a5fe6a633265959e868224a630da83c864acf5b90d25","signature":"bf42ca6a76956be05c82f152a8a702c561b5d68da2751c079f316c06de4c9632"},{"version":"e228c74df7c567b51085b10cd495ab8f543c5e18dddc533017b877dffa013322","signature":"b5078f3d864b9faa6b707bbeedc88cf66ed76ea68cc6abbb6657674ca9aad8c9"},{"version":"b0e7848e24be2154080c93c6129af32a91d941c10d2075630af61f3aadfb0923","signature":"a9f87e788da2428d806e863b53b1884812d37787c2b08d6c819253768d7300d5"},{"version":"9a5b98a9faf5ac222da8dd0d26128ef53ff801a72345ae293913f62399a45009","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"03b9d2859d208ff11d4e9101a6e2c6a956cc85de3bba967cf2f48c9c3edaf8cb","signature":"588d1b028b2f8f84644f372a7c04868b63de7e7f82d84a558b485007f7c17555"},{"version":"36c18e5dabc73dbeb2c7f65db1f14182c73a34eb9cfbc261225ada0bb9018bdb","signature":"a9ae5baa5573b6c8b87d3962c500f926c7498936182231186650c40b83fc39b3"},{"version":"e1899e2a3c2f53d0b43d87765006a74ca9e879de4dfda896ac62ebe7b59b94a1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d614c17220e0d2e13b6d10471479a1515c92be6808d889304702fbc097d15366","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7cdc3094419939dcd0a97549f716f79446ab5898332f8acd5164987b71032dd6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"048955e8ea4fff7090afeacc7a8d9a0d843199be15fb1ab402679f63d2a18a54","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0cfcd2156217783339ab722166f27ff9da99ec9194d22e9248791d26623dc36d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4d9d666d380658af5bacf49ab6844cd56749720cc33cd076ebc640d6b95712b1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a5dbd4c9941b614526619bad31047ddd5f504ec4cdad88d6117b549faef34dd3","impliedFormat":99},{"version":"e87873f06fa094e76ac439c7756b264f3c76a41deb8bc7d39c1d30e0f03ef547","impliedFormat":99},{"version":"488861dc4f870c77c2f2f72c1f27a63fa2e81106f308e3fc345581938928f925","impliedFormat":99},{"version":"eff73acfacda1d3e62bb3cb5bc7200bb0257ea0c8857ce45b3fee5bfec38ad12","impliedFormat":99},{"version":"aff4ac6e11917a051b91edbb9a18735fe56bcfd8b1802ea9dbfb394ad8f6ce8e","impliedFormat":99},{"version":"1f68aed2648740ac69c6634c112fcaae4252fbae11379d6eabee09c0fbf00286","impliedFormat":99},{"version":"5e7c2eff249b4a86fb31e6b15e4353c3ddd5c8aefc253f4c3e4d9caeb4a739d4","impliedFormat":99},{"version":"14c8d1819e24a0ccb0aa64f85c61a6436c403eaf44c0e733cdaf1780fed5ec9f","impliedFormat":99},{"version":"d36518bd617ff673c7d9f372706f241932a43f27673187f2a8472e93c40041c6","impliedFormat":99},{"version":"f8eb2909590ec619643841ead2fc4b4b183fbd859848ef051295d35fef9d8469","impliedFormat":99},{"version":"fe784567dd721417e2c4c7c1d7306f4b8611a4f232f5b7ce734382cf34b417d2","impliedFormat":99},{"version":"45d1e8fb4fd3e265b15f5a77866a8e21870eae4c69c473c33289a4b971e93704","impliedFormat":99},{"version":"cd40919f70c875ca07ecc5431cc740e366c008bcbe08ba14b8c78353fb4680df","impliedFormat":99},{"version":"ddfd9196f1f83997873bbe958ce99123f11b062f8309fc09d9c9667b2c284391","impliedFormat":99},{"version":"2999ba314a310f6a333199848166d008d088c6e36d090cbdcc69db67d8ae3154","impliedFormat":99},{"version":"62c1e573cd595d3204dfc02b96eba623020b181d2aa3ce6a33e030bc83bebb41","impliedFormat":99},{"version":"ca1616999d6ded0160fea978088a57df492b6c3f8c457a5879837a7e68d69033","impliedFormat":99},{"version":"835e3d95251bbc48918bb874768c13b8986b87ea60471ad8eceb6e38ddd8845e","impliedFormat":99},{"version":"de54e18f04dbcc892a4b4241b9e4c233cfce9be02ac5f43a631bbc25f479cd84","impliedFormat":99},{"version":"453fb9934e71eb8b52347e581b36c01d7751121a75a5cd1a96e3237e3fd9fc7e","impliedFormat":99},{"version":"bc1a1d0eba489e3eb5c2a4aa8cd986c700692b07a76a60b73a3c31e52c7ef983","impliedFormat":99},{"version":"4098e612efd242b5e203c5c0b9afbf7473209905ab2830598be5c7b3942643d0","impliedFormat":99},{"version":"28410cfb9a798bd7d0327fbf0afd4c4038799b1d6a3f86116dc972e31156b6d2","impliedFormat":99},{"version":"514ae9be6724e2164eb38f2a903ef56cf1d0e6ddb62d0d40f155f32d1317c116","impliedFormat":99},{"version":"970e5e94a9071fd5b5c41e2710c0ef7d73e7f7732911681592669e3f7bd06308","impliedFormat":99},{"version":"491fb8b0e0aef777cec1339cb8f5a1a599ed4973ee22a2f02812dd0f48bd78c1","impliedFormat":99},{"version":"6acf0b3018881977d2cfe4382ac3e3db7e103904c4b634be908f1ade06eb302d","impliedFormat":99},{"version":"2dbb2e03b4b7f6524ad5683e7b5aa2e6aef9c83cab1678afd8467fde6d5a3a92","impliedFormat":99},{"version":"135b12824cd5e495ea0a8f7e29aba52e1adb4581bb1e279fb179304ba60c0a44","impliedFormat":99},{"version":"e4c784392051f4bbb80304d3a909da18c98bc58b093456a09b3e3a1b7b10937f","impliedFormat":99},{"version":"2e87c3480512f057f2e7f44f6498b7e3677196e84e0884618fc9e8b6d6228bed","impliedFormat":99},{"version":"66984309d771b6b085e3369227077da237b40e798570f0a2ddbfea383db39812","impliedFormat":99},{"version":"e41be8943835ad083a4f8a558bd2a89b7fe39619ed99f1880187c75e231d033e","impliedFormat":99},{"version":"260558fff7344e4985cfc78472ae58cbc2487e406d23c1ddaf4d484618ce4cfd","impliedFormat":99},{"version":"413d50bc66826f899c842524e5f50f42d45c8cb3b26fd478a62f26ac8da3d90e","impliedFormat":99},{"version":"d9083e10a491b6f8291c7265555ba0e9d599d1f76282812c399ab7639019f365","impliedFormat":99},{"version":"09de774ebab62974edad71cb3c7c6fa786a3fda2644e6473392bd4b600a9c79c","impliedFormat":99},{"version":"e8bcc823792be321f581fcdd8d0f2639d417894e67604d884c38b699284a1a2a","impliedFormat":99},{"version":"7c99839c518dcf5ab8a741a97c190f0703c0a71e30c6d44f0b7921b0deec9f67","impliedFormat":99},{"version":"44c14e4da99cd71f9fe4e415756585cec74b9e7dc47478a837d5bedfb7db1e04","impliedFormat":99},{"version":"1f46ee2b76d9ae1159deb43d14279d04bcebcb9b75de4012b14b1f7486e36f82","impliedFormat":99},{"version":"2838028b54b421306639f4419606306b940a5c5fcc5bc485954cbb0ab84d90f4","impliedFormat":99},{"version":"7116e0399952e03afe9749a77ceaca29b0e1950989375066a9ddc9cb0b7dd252","impliedFormat":99},{"version":"295fdeb3cd2d323564a3e15f496b4087e2e42ad602918fa6d1b1890e08695190","signature":"63a3f8fb69f1775085400e6f0936503543439dab1e793bc8a50d2d7cb27c94bf"},{"version":"65e9d555313c408e62471e4925876c8b7e4b51f713a76d04d8e4a3a39856aafb","signature":"d80742a6a41d9f569db06f2a6f1ce341e38a8023e1f172dfc596ebf59b320d89"},{"version":"8c2f6d091e1d18906638c28e7bcfff5bfbf2f44a7ecc26c61d52d289e4774f81","signature":"c8cea0f80c3f03c528c1ee5bcd4584ac807b414b573259bc90cd3787ca4645b5"},{"version":"598a326a7290270365d9db1f14e1a3c1368964f1a955216ed2fc0f682ff9513f","signature":"9ff47fb4c4e952dc70a99e1fb04787148bd3c14f15d212f1fe1512c39d3ba531"},{"version":"8199d112320cdd771ebbaf8e86e4de2c356b72ebf94e7852edfb123f0397e4af","signature":"9a551fa69928afbb4ed81eea945c5006a128658c108a4a776ed6d71535b15321"},{"version":"ac242850f9a392821b038eae068b80c5264d9a06c6bd3c982b1aae573471db16","signature":"aad4178cf633bd1bb2664557b29427113f85e3a7208c0373d7f7e74b59ca1725"},{"version":"7af3f0902fe8c17b796537172fc075d65b837d250160d1e098bbab0d3883e384","signature":"f4a2ee4468b59e3d890b04fddcd71b3e632da62deae0ca8e338c31e831445cbb"},{"version":"d329c87c1f321be0cecfb6516c40ec3bde0d558cb51041205630e386f92631b4","signature":"26ba71f79eb1ccc526399c703ceb9595712a793b7d939584ca92df4a3ce4fea8"},{"version":"7ecfb0fe515e7462466e1fffddf551ab3cbbb0120b9c60d8b0882da1184d719b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2d6e05af866b9a2fba0ca03edd8461339787f298085067eac7179ff66826fbf6","signature":"845a8c55efa3e6c366c3d4fe6aba5af60a822523d537e8a3930466b565b738a4"},{"version":"c2c3162ced58953283cd7a5bdbcbe0a77515186f6aa81218c77655fb0193e2e5","signature":"df7ee96f49527b1acea7ff54bce98f57bbc2045e7d4dd94382078e5a17c1c703"},{"version":"fa4fd1a6c106daad4d2048e50518a1707039d574d96f2282addc0157b2143b29","signature":"fddaa084c125913ec394f657d67da4f30ebaedd92123e4fb8cc1238a6803bc3a"},{"version":"0c6f69b7945efa70dfea28588e4b15a3fc323295dfb77300dac1406dab3024b1","signature":"2650442ef418219533ac780e02ab13d230f9fa3e26c197c10381bbe73798d111"},{"version":"e5769cdc04a37f831d1f4674a80962dcdcfcd962379bf2bd5e70e6d37d7fdb98","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"93b01da13427e2d00a8d73767869b61102d6ced5b8e6ef297006047e7a36b63a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1ef8ba1adc4b3ac94a3aaae93f7d3551054e12c6aea1d0a934a767ad06304022","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9f53838fcfa25e477da1c8aa9dd33dd3b909172577f8344ef9fe9eac41bf9e75","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"327fad4419515282efd2774fc49d6e072e42913fe21d9191426abe9179e079c7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"32bbde0d15258e54a59a1d14567a934ff8eaddba0be6c542ba87ec1e815a9e40","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9b4f7f2d993b46d9044fecff29a83efbd9ebcc84f049274014c0b239f4b54f7f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"23a5597b552c4fa9218e4ff75c6fde15c735a495a7eba796b21a28102ab16307","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7f0d62361ad8150ac80a9b386146dc76dd0a98fdfb099f780e77eaa737f8f1a1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"efedfed6289043e78d06720efe8eaef631d5b68d527707965021ff334e844855","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"427da98197c2e555e30f5359d020807eb691672ef20210e522a88ef32e699869","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e0b6e509dc7206211b236c554688d9c08c860747896a5ca433271c9acbd54c50","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e9c4df328547ccb9f37f1ad14a92f8198d473d3e880fcb46f2183771f684e526","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7617e24f1ef9fffb0a6d0e8a9f02b8a4bf3020c98ad70a44b2af1a194afc265c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7361e20f9c2b294daa8a369dbbd81e4c976a9b27de8aaee675f10e55782ef6fa","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7814b46afd5f860d40c236a7e5933460f59d659d0e4205190dfd8d2b2f01424e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5b46123ad07cfb12175246764494642b5389d84d3974d63e960ab30ecc650c6d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ac1132c59e81bd0b70f42635e2bd2fe8bd13a4cefa136c759712f75d1675ddc7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6d8bb2bef17669472b95eefc0d599cc39e71c58fd924469b50400f59d0ffaada","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4259ecfb83a44d7a8fa2d4733dd53a8adcd97144d4c630d679185904e86ff631","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8f33490c1ff132d5483cf4e1eb80e6e6495eeee76803ad9e0bf039c16f6214f0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2b3643bf3c8ba4b485627d9f3c0b006e90bb412a063094b5051dd81000975601","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"991e3240184e8930f3d1107ef1f34bbe736c37c3bcdd9f561ccaf0651f64ff19","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"54ce27db9cefd311fa33b93578f36d65af5eedc8504812511f846b5b1e370d66","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"665f7019c5a7cc891091e6cf49d863a02485fe6e340ae4fad754d109feb8fc60","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cf4ae193e287a1551a6c0790edc94cd0d8cf293991b19a4da912e80ca05d7e40","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f49f5f487bb117b152efe502e3737f69c1f067c72eb3a96ad12f4636bff63c81","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ce37ce0f5ffb955703019abd7097f0d168520f6246dc8c6b5476ded5106ab637","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ef6356b0213080ec7b7fdc383a20df947e5036d89cc0584a9483718711ee5aa8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"065eca68728f40383474c3d422ba0212d549ed1941a0b0f0fd518dbb83c64a4b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4b3d74d51301f1c6b53f3ad11d89812f4fc1bac5726bbef9ade3692010154446","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4840466537be226517e071a9d08f1c4fa8d81e50001e380db57847292d894a6d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6720624b133b9619b09af5b9c64f6e85d27ea62f4936e9b792c679f981a75ecd","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1b6ebf8c916316dc7fac4cad4cb9ff0c54477bed67853a2685c250a8c07ad6d5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b8b23471ce6df155d4a670e836a15d6f45c126a2fdaba54497b7be0ef6c11cb0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c1fa2dffaed2554b03b50c42d29bf0b4bc799f42f7339c697eaef6699caf2f90","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"486b0156ecdfb26dc385a71c3d676290947df506fb32ddfdd12af357eda9252a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ef998ac6f1f8b50f0bd69150d4ff0732a86f41d54d4d2158d0be8981fabf04b9","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"34bd75b6379933f0a0371170d95905d43f72c8a3a2ee431fba5129470947bc84","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"50867469b61b6d4bf22fef913b1324b3470db44ae7d2d560638e355a7eac0a2b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"bd6074dbcef6177b94d63a539d60447a71cc249f93982528095f888e24d1fd9f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cb237a7bd94fff6341f661acef3e225e7b00f795af6c8c1578c4ffeffe4e6728","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a9fedd6778da350c16e2af28370c956bbd36b784b05573c79c829673742526b0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c32efa74b6325ec1f3343c739f22c38170c41d20e22ffdb8457119a06d9f2a2b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f78f023abaa102c4788b4068fae63e7fd68917fa41c5d416f1e61a2a2735fa5c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c97e953cbcdb4de6df9f2106c611004790e0d01de19a60d09eb258e2e1584b35","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f893fda6b96afe3d06750052dc827d203ed9262862b906cb42bed6f7d8f8ef9a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"260b42e73e30b8315e28e6b06c750b213631e4fec6609fe89f9a4bd81d4806ad","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"08f0989ec7c66dee021fb2df489a2fb5f57e116e2d120f580b60cb01093e9cfa","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7ce2fcd8bdc677fceb74415fd8cb2b804df004b2f0b25a3de6a37a961dcc85b6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0ce2aa0f503c493a87462f345e09f5398720f372d9cd7242347f733c8eed5599","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"49ade3f88b95fef99f1e6ce5591932de15009c488906c66153104ba58999dcf3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e735f471370fd237f20bc27e9804763a94dfb5ed12de1531190a2703048a70a5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"15dc13db52a86d7a4c6afa8343701c747584d26e79eb2f706d21e9aa3574d6cf","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b40be88c76a5ffd4e33a87c1c88d2d0f4f06f92715f5c48c311c9051044a7127","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e7f23f82b8ba9390be05b42e924ced743fbc9d860e53fd9bb2edb65189024052","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"55fe9b8705c6a60649022dff468ba1f6e0d396eb63edce5a3071c1ec073e274c","signature":"407c70ddc24d5c90bc55d198041d27c6ce2cb0f42fe30a091ef7533f5ac3686f"},{"version":"ad3602cf898d906862e748a847deed9392213387659382a47bda93090c24d2e7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"47beeb485aff1ad4e1fbd8ac6e8d36a24e150de10a30b6899840faa301655414","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"487344aba34994c6bea6db3099ad923d847a27e7c900106a17d5273f8cccaa06","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cb5982a6af0b3d46b79ae5df2d6a483ba51687f950580ca035889641a4e0b99c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6b66ef1ea3dcace743c3158d7bdd0bfdb736a8e0903ee59ce34b614d25e19b14","signature":"7a27ad47dd1e1399758aba0f970f1f9254f107ef9e1397617249f166f57fa7e7"},{"version":"a741e402812a85f7f6cdbd1e027e46f9e85720c8c94d9c03a3d451b188416869","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4b7d5a30863faa4eb7abc1900236b9004ee2405919100e66108752907d9253f2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0568baac3cdb203dd85282b137b649bbf8171ae2f1a61264cfdfdc1dd5f6c1ab","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3f1cfae46f629bd15dd70576ba801076e10138931a97394e636da4274b05fd0d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e5373c23f9ed849a1e6aa414293d7f1d1de18be48a395129a657fcbcdd7a79ce","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"722efeab5c97ae89cbd4c34579f21583772ba4364870931b0961cd592b4f1b69","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f94f20f484a8c07b6c7b24334c8c16f13a4bc1b158f0830fde6a0aa3f5df39ba","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"05fe7fc1d2436468dbd4fff9af37fe4354a41f84c943d1f2aabe4ecd645419de","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a5f1a3661ab26f668a8195833f02b4085991113b44a0bc7f790e9c9af257f07a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"979e031806f5e09fcc4ef162915496375462215f7667dc184c25f4dff7a7820d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f475d6f2f778630ee452181493fbd495b9e91751ba5c97fb3368e00452256508","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"06210c01d4afe0f05c3bcb4257cf6c9c8bb4dfba43640532421bbea7336dfc9a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a88fc4192834b6b0c40556bb5175721a57e12c4fbe0474af636c81bcbaca8f1b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8aa0a852f50e208589ed241e4febb9212b8e6389041d5473fe87a7ee05abf35c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"735ce13a88679c9bc9b33ffb5f96f6aefdeede31373c69f217f402b29d8afdfe","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"32552e3b687bed279decc2ff81ba12b6d194998a102b5592537ef0a7bc246ed0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"861773fd465477aa9fa65ffd89bb04aa9703d8fd4721893ed45c5867b661b5ae","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"15d2ade2ff1496cb0867c5d1c235daccb1d08d0d83922ef1ea9e938477a59d82","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"917670659e8f5e82eba25b20720fa7669d2c82982a1e471f5ee0c2a9af397d5a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e0af4861eb98aaf719edf37c8ee96a3b7dd5ec7d1d92e9dac3d7c447e54e162b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"86727e341ac0b578c884d6a23e8f71ee339ee5908d68eea1d3f06b206ceab13c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"42ede7aa3739a3121163b6956bf56d5894a0b93302635990c79cb6ef222b9e2d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7f21b2e5f827fd2bfa35959f943d5f7c38bd76195247f63a7e00c35c869583c4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cfbb7a79b2aa6358fc674159b086c24e181c16d1ac93590b0b74fe527f66fa47","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4b4b45b5cefad21565fa5e4af782b523c5a39a7f4059988e39bca972d55b9061","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d9df7cbe639e6f1e809ba979e619017e5b1814eb6b6747328273dccc67cd1068","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"71cf715fe7ac9cbda3398c62715e9e41e205bb9f66c14db522c05d33d9bed871","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f6f0c2a313ddfde4cb9a17f94cbbca58e5a8bb25f222a42fbcd19c3416e31764","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"230da823cfd1db9e7f1420e97899558fe51a540913f53f112589f4145b5afbfe","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9c59991578b1c1a6fb0c76e7e5e10e92c68491de73b522f20835c46f6a1c7bf2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b484a38c9af5f5ec8277d1af11b65fc6d3e33520ef4e740f0546aba88f84b074","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b31f35a308e26516e3591787664eba7b7b4bb363bbb9f9ec483f506eea0372a0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"fcf4e15889b48532a4fab0550d27792e595ac7e53b656745561bbf0499175c37","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"bd4cc633b78ea5197833520871416deb53dddd1b78bd69451928da323d60ad1a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ed9c69ecc1f42be1cb4ab8f6f7aced8be8dbd87f68bb7a4c23f887b2ca047239","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"371a8f3dbb785198863556065633f99806e0b8d4fb21cc7368d0649a623f4afb","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1ea8f433cce46067db7b344864ccf0cdd8cab2c887ca2afda8a0077332196f2d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7be41f5460ff85e60093e7bae93ee5d31aacb60c2e6a9f1410c17f633a8b096a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"36e3142e0ca6b104455d191002153bf35a7647fc36aab983261911937acbcf9d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"963e29c03860a04f42f2ca7723bf2f6c8aabcce3c2aed54a011716e20c1d65df","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3690127a28f2bf897229496caf89706414e6463e83e7458af2208affcd82c1bb","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f4f21c1fd681856856de07956c2919756374a07cca623df98b4a34fe75a7cfc8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e6d9a4831d10ff7ea1ff521b5820c35069a8d055a3cc2094a51071f6e705cf33","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"da056220efb35fe8dfa15ce790be1d98da0628396f5fee394ce5a74fccf61b94","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6817fac9afb1da6e43271471fccf52f2f33608d4c27a36b3986dcfbb50028dde","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"59e9816e5edb0a209b423850444c205a9d7f278301c59c04d42e55d6c067071d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8a8d39d70c699f5cf9372096a662445f7a50038dab08dadaf8207db793a020bf","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b06ce1aa92f3ddea6d0ee51a3445087bbfd7fce5eb4945579e4641c701cc88de","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5b46acaf48009c85c38503ce388264e0096def8d436d79c08f6e0445ebd2720f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"07d067492e43fc44e34638ea4e5e03e8e21d04e083fc3be2cdcbfef90f0b4798","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4ca952c5213daed4a36d32cf0d006d1437dfda9536332c67ae67823e352392ee","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9a55a93de53f4dcc5811eb28b868ace3a794867ae5c5249d8744225455564c29","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"75025c0fb0474f942e5b65d41af32ce4e22f47842dfb3a065c7104ac824789ed","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"27811c361c44cc1f41b7fe8a0838d1037a20cccf4c6ba9a15abb9d09d37f01ae","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3a9bea559d82c1df383cd1151b369e6c02bc0ac02232c05de78a5c872ecc2dc7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"95be543b6b8a868938af0b9905a3665e9d50dbeef1729861f73b91153b381cf3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"04c8d72089f7cd6ccae20f8e3459677ceb5bbd29ff42711e9b26e068073c709d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"18d37586db0cfda4e9684acd3e46f2a7a0aa00af5a66c8ef3ec7be9ca4d817cb","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a9795eaf6bf5f3459c8006761f4d5ab32fb036e2fb7c9fb32744d2d1f62cecef","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7bd5242370b7db1c9ead50ce1bdc86c52cff823aa81a591883558fbd64dd6ba1","signature":"124d83ff9e2f42084bd7cfd64be70c1208919b1bf0ea6b55bfbd5eef7c20b60e"},{"version":"830342709611db71af21798d33411968152e0f80619a06a8fae1f25bb30a69c1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3dc9d62cddce1baf59b2b2a35d8ac2b22c6568863b361165e6e9392c3bd4108e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"facf02927de777e8a67a43db92471fabdbef7dfc8850dab508b91f1849b138ea","signature":"ee44ad828722309d73fd428d32c40bbcacd079df09823452f593c38fc1851d01"},{"version":"2c56a86789695bf57ea82613f10020e506e849120b31b6c6a61826cf93f4ab17","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"eded0d3c7d645529545b8faf7178a799a89831855f782699bbf4f6d7ffb53ce4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d263f4d83c384654db3c189d9c53513fb69058748e0d7f5d3f0433ddd80b45c1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6a9fa66ec3ee94ae0670a25bb7b2e2c7fc22f3e2917b0bc253b7d18c2ea1c60e","signature":"83e605e4a0c89b6373d0c0727a935b7d195e418253ad0445327e29b7cdca9d3e"},{"version":"edc2ba438969866bb281b99767206b116f8523beaed8904aa7e98eb458658bd4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b8c9e3a4715bef9d9b4434e3eae730cdb4be42abe397564e96d3069b6d10a0db","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"578015da0ecf6fb49aaf4d86e90e8ce9f46a7b6ac293ca9d810a291d42501841","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cdd21ddbcdf8e5073e31fe7f730fe3c4023c66309625b87b5b9785fe140190db","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d0c4c9377e0649c856ed8a1b47a27272b59acbe547ae03945da48771af67536b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"db9074b978b58ab1dbce3c1d415969ffabbee1ba08ebbab5d79b6259b1b24ad5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6edf5d7e4a6c1e53c71f59d7b824273284f7f86df6e96d4a0345f335a7790780","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"86628d7e65d5c767e9e7125614f302f449165b8a5619beb8d360488548058556","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"02098a352a967d5bfa079b974b367eaf89f234bfcb48e0ea9d4fd3a958964cd4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b25d0dd5f71a90ac4c975ced9738cc77ff40e10923d0774568017691e150c527","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"abd6d932a4f5ffeb10ea89ddc43d53467aabd67f1424f03634d2bdb9e91bb39f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"29e3e70324dde5b5d43a0efa781f696e4af198263b054fdbc06f683eca0fe26e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7f53bec43be335cd9a6d0f8894d58f2151919e3239482d4b7cdf73c85a1ad6b4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b17e5a4e393e12a682e3c0a3f64eee7b33aec3959eb48acd59e0588836d38a43","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"77caa9a483c4e9e912297bfdd899ea973c57ad4b0f149749bca6174d7a730595","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7ea58b235be0c0704cf916c58b0f8fd947573073f348d1d69e6abae80e922f51","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b443d9c86ce1fc6c6108b95dc7cee0f6a398839c35997f812fd1d02b895d4632","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4fab0835f3f0569611b4185f74019264c8ac4338acdae9786c36fc5e73165f72","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"095cc9d0709e52c5869e31a60f719773a43b60940f726480b689e301eb661d9c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7766f347d618e8f747f17629494a89905aa35b4d924e4495f078865a56b08ddb","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"fa48fcd01f34642798ffbaa1931c701ec1959745c58297d51eff9914542a67de","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5ae00f9eb7202bad96cf17277b37f8eb7ea8dea3e1d29766ca904c2b81f54043","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"177f040db68f89cb309e1a040bbaca4316b5933ae80535473e3bfbee11cd42c2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0fe9f16ec294c6d5b89e1e6a6104e974eb718609032190f3fc4fca38baf023b4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e0875a51dcc08220ab37ee44e0d604789fb0c24c6435826eff6522f9af79faf6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"731b1e71ccf858619a73df7c6906ec005db94b254d41e40622266b18649543d3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f4d9742c42ea0b6f70687b6d12392f5c8bf944d61af1db87ad03c48362ce687d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"50f79c93edc75a5399ca0e9995c8c9469cfe19748125122e2a915f9111ab701b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"58386affbbecfa8a769897175dd10911e3726c2e9d2aefc1b98cdac43273bc0c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"79f59bc9b7e7539e21e9afef10cdbe7072540ba621c8c1c782384db467b5889b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"248dcfdd5ef7f53d445bb8e05b80fd4abc799d0a61222d13b80a1330f93ea6c5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"35707dbd962f597cc72a0ccefdcbda1c0cbbddaa11bf9a072fa9dc2f2b40eac5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d4605c8c58bc7e8bed8afe8d69f4746ecdd4e0c088214be14705d46dbbbfd135","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ff24bec700f0c92265e9064c7ca0405e03bef54639ef75bb9c92899ec3ee2761","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"79455fe5803e368c08c032e6af6e7366c8cbce2750702f9553091732e738beb2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"98021cc3dddc6c8ce5498e301424f2314a9f17deb130c609269c900716a6c129","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6eaea317fe5b6bbceeddd6440306eb6dfe56c86796b5e90b0c86f03d98fff955","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7e73bab167bd1cbfce2e0cb9f79979666e154650c0e592edaac791700c858791","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"bfba3f8e0cc98428a9f110ef67eac45fea19e55d73d70930a4b236383c4d39b5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"960d12344ca062e11aa8e328a45d997c78f80be9e2061371a13ffa3f92b17866","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4f5e84de8e08e963712fe5c7ec6316457b9b7e2558034de5c347e637bcdd7688","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"247f6e8e3baaf11711d02bf7cd26640cb952e84df97baececa364996b7d98832","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"490941e1dc98b5aa4e528adc4a574af3f1beeab09fb7a0454315eb2dd1290a84","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1d9812662b25cf3d67aff1a04b33d22e0ea0c441df0621963d761b4c4f718750","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1be01e6430cd9d8716f8e280d35cbf8e1f902876751cf019c2c06ff4bc37ec0f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a8d6ed3f24a6c0d44618d20937b1184e8f731720e7f47033697837d7f1361778","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1dec6d06a9e91a77333932a96e0dfb5a3d64b1ad12b9cd4ea4f94533e6068646","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6ec746ad3b256d7c368d07680d611648cf7e3ed700172df18f58b6bc8aae6370","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7d3564213f76903a4f505161e903844046a05a1113aa9ba34963a3492a4bf2e8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ce59761534d08b543a8258092baf9cfa2766e21c7f31f46b9d381a5c1952de04","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"38d52352dfce13574619c8b5e4b4ae7ed7ce23571a041efd17c31edd1f99cf05","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"55cde79a8c45f86254be594e459b2c2deab955cc4c8893a888773e9135e017a1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2bf9ac573ca3b13d59954ab7636ef366dc7a80f327b7e06ed25e63091e4fd1ba","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4f1d5c1ba4221ae2ed15ecb860eaa7ddabc9e9f073c6684d5f40fd584b53e53d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d6d1dc2bd87e66c5a42db5939ed437b0fd47462d0d773a80e19af531775c6259","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"330f19e202a378b129f9ed576514b89bbaa5e86e53743b56121c8484b52f62c4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9d9dfba9ce8ef128d97227c80833f6b38d0d22a6edf3bd8af547a2e14be2ef0e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1f0d923fc674598c42ed2e04274ee62d3c8935949ff261c218c4765e66e298ca","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8db6cea0ac2a3c56e96660ad1a5b349a17a28413c22411cf771c7c09ab741236","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c0b535109a6c191a6da5949aa506758e20b531b7cfc4fac70482a6c4cac60fae","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"40cb304a657165257bbeddf8d6768a0e1d66dda96568fee914466b785488c848","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ca5a2320c781052b195b38bd95a7424e01468dc5e78ef946d8eae4d218437eb8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4ee066f9213bb1b739a0b708b2cf93b43d82cbcc0d4de9bcf50f3c751b94eba6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2b20cd7cbed9a1c22a77b3198be96416d8a553107ba0d59dd026a99b9cb8bee8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"831e5cdeeceac168c25e4a6d1447d92c79eec9ca78506c2b13940459b2f59ff6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1583aa9c5c34c4ac177ab57d5fc7faa7418f50ec6d36664ec59c37a816b0f681","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8ca104a5938a7cfd1692f2ede284605a212c7326db9c2ed059cd7d6bbe12a434","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0ac122db96c399714ce9b034fb1e5c31de8245c89206a77c92d5ee0e61a31035","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e354df2d2d8fb78ad3493e5b1ab6758dedcf04cd6439642d7d92e5c7a4989341","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5665af2d1f1340232629ff097cdba41501407e3462d24f8a7898e1263421cd4c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b6c06376582cf390169268d7bf8d66caf827189962f669703d0311129809f2ad","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3aeb51b34ad2c8ff6d48a6d35fbd8e6bfe18f2f9d7f11081f4a0bd8a98ab7486","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"77f9d5a53ce5884498db4d6706a39c24e8314fb74a506cf0e1b3ad4dd8837766","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"36fa81ee20a15d8b1c88986eb734c35e4b21f8cafaadc96b57c56917d512f33c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"36ebcc137df00eab82e1386148d32f8b1b296bfbd32d5523bcccf2f61303dedc","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"32c17dbfe6a87a1ea5e5635ff90c89a5520a8bc0ef492e692b78719a56fd3781","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cb926f3112ff0b26f201d824575a42de3d34997b70cdad37d4b7d5ff1d71c749","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5067adfe92f48ccc3efe80a230501fbdf4133c523f3382315bd276f638e5f3e5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"094bc94c8be25eeac8e27ce7dbb6c4acacc3b6de374c7b5ff8ac1554cbe55442","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ebd54d09bcb969873eff5f50835348d41ed084785a1a53110b155cfe0875b7f1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c340b8daa8f22cfa0c98f254e905f505895e595da1be772952d31fca6baa32a5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d2725bce76e4f685e5c3ff860a876ec62f90e7a6deb5a4b12f50682849d13b92","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ea949e13a70371d1fcda1f9d942431331f9b97d1f163934d459d7618cade7a0a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"229dc03fc2cf8b704df4b11c92185ec6b8cfee49f84e12e469a594cc4282f7b8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"035a00e9d1cbb71b33f8faf7750d8cfcfee82e090bb4f646a8b714a74ef7fdb7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0db0a903134f8c811660031caee19cd55718801d8df691ad0183df5881683c25","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"638b8bab7c7cacf253f36fa58f89199863581071f42b361feacc6093376d9d51","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"57ebd17c1bf0e09d222252bb67deb0cef31476b2476c680ea5ceeca29cfa3efe","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"314e47c324fe876e2d9f98673d3aaf59a99488c30f6ac781639fd05ae6a8c2b8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f1ed42bef89c47d9703b84f6529c2fe5211578ca30373e6c6855bdcbef3e4abd","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"bf0dcf3032c0200a3532b0c293383a9ee83e700bea892557efd0cefbfa67ef60","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"bd80640c633c51e82d0c5613a93914afcacc21a5e694341443a7facf05c943a7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6defda40c20eb5beaa9b3b8b3af6438d2a50960a9113017c8d441faebe899331","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"09b4d72988c4682aa5713bc6a6df7892a7b8e2f10e1af3dc02985c6d4aef84ad","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c1ead1b6660d8946abad77f5713f19f5166434d37349269042f6852907bd15c7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e5885dd0d9cc2b4c5f949425b53146d0ccba82a9ecd83ee8b2581bc8263adcc9","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1e1d5c76c59c49b2b6f32b7065d0a95bfd229908da2db72526ebdb9312ed2cdd","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"74d912b4ea13dccf1b9fc0df5f3ce8f463e394d64a230f672b027e72ae0e860c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"334a5e09d34fdf02396fa9f55485fd1044980aa9ceb37c80753f7512d9ea2eb5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"337727f763bfbc5e1df652443773de1939a76dedad4832d4cc708edca7bbfec0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cde7715fe8ab4fa90ab5e5b8b7505810395eb5d45a55156900782f7f2770d9e7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d8e7169e311463a1404f687796203159a89b24d2cc524869db8b8cd97ba1c993","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"860d9d37bb3309553cb0b777bc4669534a5bb0dcbb3892f8866f3b2265abd596","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"945b34137fa3efb0ae22928275c1c42a722dbb81a26c57fb02f64f71e5daa3ce","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"79284e228f99ba638a0369e996a03ba4396499e8ef4696b6e70b2585252701a7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a6a858ff94519de054636ea0a584826c917d7a0d3fe40bf8af0a52ccebd562ad","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"aedbf4a8bd5d408b6eb1ae6bd910487325225a3f72ad9b08fa512f40e84c0d49","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ab1b9f3957784c6d8354f21225a0909e68b445ace8a3257026aee8bd2fd6452a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1e4271d1a149f7e95e42ca69604a531721ca2ad8f971c507e7fefb9d1dfc3faa","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"76d196f751bcbf33a2a8e39799e99ee2137e8ca331cef0d5a39a2e46494c1c67","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"69a8edc242108dee4cc4fc982fb72e8e179c63469e92fc510ec6d8c25759637a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"adae966a8f12db9e20fa63e474d7e907a53c488329c7bd1f0daa706179b3abb7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"420271b2700242703d5feeee719f0a7524c7c999f20a3c90c0c1ee66f228e02b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"bc705423f5e581231d5ab8472659fb37060aa3ade1a052f301ccfaa5eb836748","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"427d2c82634aca3c84b6711a7e0eec282b9cf71c1630596d094011e0ad5c40ab","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"bcdcf4b25ad274742c966538247cb4bf97a15ef23ca57bab40c360bbd8c171ee","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"42f20b516f6c99f5f5fe2670d3bcc38e07a56f3aded38d59e8db2ee0e8789ba6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"564f677fd8e9b2b73657415fb3e95068870e85cf214a64ddd77af57276d93c2d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1d19897a3a3f53ba616971dc855f33691d64fbe5db24ccdabc2eebbc2931ee05","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ceb20d5a77f9707eb639fac7d7a9d2a6167c4f64b7ff2d3f46bf2a60fe230d51","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b4cdf741442d5012bbd6fdb84cee961b862581bfd9624a929451cd70ba3cd6ec","signature":"5461ba0c7866ae82e9bb9bbee6a4e2e50914122566d037ba6a677f6a29721353"},{"version":"02896fff6bbbaed3092f96ea3c25fe1ce4a724b423d3682266779dd4a49b5dfa","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"36e8dfa7f5ea1b57e7e638ea16170867e13a797e3405d09ea6cd8dea0de1d220","signature":"21bb3f3e514cad99c1a18c4f4f0c3aabf5f8c5978739d08650254e5d98f1d8cf"},{"version":"5691ad6b5c4fdaf0da3f6de5d32bdbeb4e59832077d3c1b5d60fa381b603d8ae","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"35bd7fa89a59ca3b136c221ef604ba3a8e30cc88bb03cbc4d26fee0659d02ce7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9ce1dbafdc6b8218467ef36a142255e32db446f6a507fcc6354655bb5848e2e3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e1e7ab8405eb11b2a3ad0c2e698843b77699ff100d895d4590e56070c08d3628","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a85610ba8978234a59d37629fafe05c27fb2154cb62b6b775eb8119802e0a38e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cbb2d8f2651ea33a9096e54800a3874fe7cfc3106289fdff4a8e0da7384e0f60","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f328b3203d26b5f709e5a082bc956c2e95fbd9fbdca6abee0802b59b633e0dc0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"34fc137aa6085dd4f915e8b4a8c0d48d6b07d4fda84716ba91619d5ba39566bb","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d54083855fcc2ed66dedb389bf2efc33b892dcf572829e43758309fe7bcaabb3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"611b7b131854b5340278d13b87af6d01206dd496e3d27d78a430718a110ed929","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"491a1d80866fa9775bf4da9a612d5b599eca6e632411f83ed07fcc0d84910f8a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"53ea8ad75643aa52476ff744c0f5aa02c4aeb9d7b6ce79d04068908509034387","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"92b4e74da47abeb6adad273237300b333215fc08b1c2c539457f0d6bb09ae289","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"489193e0e98c7911e4d55515469734cb7c5b157cdeeb542b60669d37f87709f1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f388816ca0e562c960d5c9b55e0a32cd53b015f32dbc50127af6a223010b683c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"76e4ef5941cdf8f18a821b1c056f5b09e6e286bc05afde2c3e2e98090cf40aab","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9d3a716a1836a0ee669e9f5fcfb592ecd4252a28465e46b08186f354e6ad3485","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"dad6373c8a9550584d688ba58d25f86292e7056d260434d9fa253fbf56ad7614","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3414d416e51f2846b21f4aa1492fd44bf9ffeaece26743ae1527154e3da6f7fe","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a55e617b397760261401e44eca2fbb5d5a3d6ad079b5c05d7dff6e27d4b4c0fa","signature":"4c372df16f354b44e6e653a4442eb9f26b95f2d43efcbaa75b59506276b92df7"},{"version":"5e678b499283c6283c842f9077f08b6441b67566aa312f6ff725d16318e58441","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2e86f202899852254bbf5afe2cfc98462006acde2fac5de75aa0e15db0d82afb","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a1603914e191a7725cdfa1f949d0b563d094ce8653cee42c2fdcf24c04f93f4b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b5072b60226b52cd54b64f9cfc412a8ff9834d1f74cbcea0b003821b1e23d03c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"62f6c502c7099df9a8e97760fdf0d94d3ef30b9f668fc2da7441216b6fc95f5c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b247732a1ae37a5e0307d4333ef15e3d5393951e3236be0287adeaa48d553b35","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"d1986184a09a52db8228cb2bb2a61a8c05c9354e5b93cec8e2628d8579c892d7",{"version":"4370a89cb6c2c6632b37bb0891f804e043b26059a7e375089b5ea32846d77772","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"556ccd493ec36c7d7cb130d51be66e147b91cc1415be383d71da0f1e49f742a9","impliedFormat":1},{"version":"13b77ab19ef7aadd86a1e54f2f08ea23a6d74e102909e3c00d31f231ed040f62","impliedFormat":1},{"version":"b1538a92b9bae8d230267210c5db38c2eb6bdb352128a3ce3aa8c6acf9fc9622","impliedFormat":1},{"version":"6fc1a4f64372593767a9b7b774e9b3b92bf04e8785c3f9ea98973aa9f4bbe490","impliedFormat":1},{"version":"ff09b6fbdcf74d8af4e131b8866925c5e18d225540b9b19ce9485ca93e574d84","impliedFormat":1},{"version":"d5895252efa27a50f134a9b580aa61f7def5ab73d0a8071f9b5bf9a317c01c2d","impliedFormat":1},{"version":"2c378d9368abcd2eba8c29b294d40909845f68557bc0b38117e4f04fc56e5f9c","impliedFormat":1},{"version":"56208c500dcb5f42be7e18e8cb578f257a1a89b94b3280c506818fed06391805","impliedFormat":1},{"version":"0c94c2e497e1b9bcfda66aea239d5d36cd980d12a6d9d59e66f4be1fa3da5d5a","impliedFormat":1},{"version":"9b048390bcffe88c023a4cd742a720b41d4cd7df83bc9270e6f2339bf38de278","affectsGlobalScope":true,"impliedFormat":1},{"version":"1f366bde16e0513fa7b64f87f86689c4d36efd85afce7eb24753e9c99b91c319","impliedFormat":1},{"version":"fb893a0dfc3c9fb0f9ca93d0648694dd95f33cbad2c0f2c629f842981dfd4e2e","impliedFormat":1},{"version":"89e326922cadcc2331d7e851011cf9f0456a681aaf3c95b48b81f8d80e8cdfba","impliedFormat":1},{"version":"5d08a179b846f5ee674624b349ebebe2121c455e3a265dc93da4e8d9e89722b4","impliedFormat":1},{"version":"96d14f21b7652903852eef49379d04dbda28c16ed36468f8c9fa08f7c14c9538","impliedFormat":1},{"version":"4ef960df4f672e93b479f88211ed8b5cfa8a598b97aafa3396cacdc3341e3504","impliedFormat":1},{"version":"f874ea4d0091b0a44362a5f74d26caab2e66dec306c2bf7e8965f5106e784c3b","impliedFormat":1}],"root":[[529,531],609,610,[1191,1197],[2134,2143],[2178,2204],2206,[2440,2547],[2562,2579],[2581,2585],[2593,2595],[2598,2717],2719,[2756,2777],[2780,2824],[3079,3095],3100,3104,[3108,3235],[3313,3356],[3588,3665],[3710,3829],[3907,3919],[3937,3939],[4007,4075],[4119,4411]],"options":{"allowJs":true,"esModuleInterop":true,"jsx":4,"module":99,"skipLibCheck":true,"strict":true,"target":4},"referencedMap":[[4410,1],[529,2],[4411,3],[530,4],[726,2],[727,2],[728,5],[734,6],[723,7],[724,8],[725,2],[730,9],[732,10],[731,9],[729,11],[733,12],[684,2],[687,13],[690,14],[691,15],[685,16],[703,17],[714,18],[692,19],[694,20],[695,20],[700,21],[693,2],[696,20],[697,20],[698,20],[699,7],[702,22],[704,2],[705,23],[707,24],[706,23],[708,25],[710,26],[688,2],[689,27],[709,25],[701,7],[711,28],[712,28],[686,2],[713,2],[1077,29],[1078,30],[1076,2],[1137,2],[1140,31],[2132,32],[1138,32],[2131,33],[1139,2],[1299,34],[1300,34],[1301,34],[1302,34],[1303,34],[1304,34],[1305,34],[1306,34],[1307,34],[1308,34],[1309,34],[1310,34],[1311,34],[1312,34],[1313,34],[1314,34],[1315,34],[1316,34],[1317,34],[1318,34],[1319,34],[1320,34],[1321,34],[1322,34],[1323,34],[1324,34],[1325,34],[1326,34],[1327,34],[1328,34],[1329,34],[1330,34],[1331,34],[1332,34],[1333,34],[1334,34],[1335,34],[1336,34],[1337,34],[1339,34],[1338,34],[1340,34],[1341,34],[1342,34],[1343,34],[1344,34],[1345,34],[1346,34],[1347,34],[1348,34],[1349,34],[1350,34],[1351,34],[1352,34],[1353,34],[1354,34],[1355,34],[1356,34],[1357,34],[1358,34],[1359,34],[1360,34],[1361,34],[1362,34],[1363,34],[1364,34],[1365,34],[1366,34],[1367,34],[1368,34],[1369,34],[1370,34],[1371,34],[1372,34],[1378,34],[1373,34],[1374,34],[1375,34],[1376,34],[1377,34],[1379,34],[1380,34],[1381,34],[1382,34],[1383,34],[1384,34],[1385,34],[1386,34],[1387,34],[1388,34],[1389,34],[1390,34],[1391,34],[1392,34],[1393,34],[1394,34],[1395,34],[1396,34],[1397,34],[1398,34],[1399,34],[1400,34],[1404,34],[1405,34],[1406,34],[1407,34],[1408,34],[1409,34],[1410,34],[1411,34],[1401,34],[1402,34],[1412,34],[1413,34],[1414,34],[1403,34],[1415,34],[1416,34],[1417,34],[1418,34],[1419,34],[1420,34],[1421,34],[1422,34],[1423,34],[1424,34],[1425,34],[1426,34],[1427,34],[1428,34],[1429,34],[1430,34],[1431,34],[1432,34],[1433,34],[1434,34],[1435,34],[1436,34],[1437,34],[1438,34],[1439,34],[1440,34],[1441,34],[1442,34],[1443,34],[1444,34],[1445,34],[1446,34],[1447,34],[1448,34],[1449,34],[1454,34],[1455,34],[1456,34],[1457,34],[1450,34],[1451,34],[1452,34],[1453,34],[1458,34],[1459,34],[1460,34],[1461,34],[1462,34],[1463,34],[1464,34],[1465,34],[1466,34],[1467,34],[1468,34],[1469,34],[1470,34],[1471,34],[1472,34],[1473,34],[1474,34],[1475,34],[1476,34],[1477,34],[1479,34],[1480,34],[1481,34],[1482,34],[1483,34],[1478,34],[1484,34],[1485,34],[1486,34],[1487,34],[1488,34],[1489,34],[1490,34],[1491,34],[1492,34],[1494,34],[1495,34],[1496,34],[1493,34],[1497,34],[1498,34],[1499,34],[1500,34],[1501,34],[1502,34],[1503,34],[1504,34],[1505,34],[1506,34],[1507,34],[1508,34],[1509,34],[1510,34],[1511,34],[1512,34],[1513,34],[1514,34],[1515,34],[1516,34],[1517,34],[1518,34],[1519,34],[1520,34],[1521,34],[1522,34],[1523,34],[1524,34],[1525,34],[1526,34],[1527,34],[1528,34],[1529,34],[1530,34],[1531,34],[1532,34],[1533,34],[1538,34],[1534,34],[1535,34],[1536,34],[1537,34],[1539,34],[1540,34],[1541,34],[1542,34],[1543,34],[1544,34],[1545,34],[1546,34],[1547,34],[1548,34],[1549,34],[1550,34],[1551,34],[1552,34],[1553,34],[1554,34],[1555,34],[1556,34],[1557,34],[1558,34],[1559,34],[1560,34],[1561,34],[1562,34],[1563,34],[1564,34],[1565,34],[1566,34],[1567,34],[1568,34],[1569,34],[1570,34],[1571,34],[1572,34],[1573,34],[1574,34],[1575,34],[1576,34],[1577,34],[1578,34],[1579,34],[1580,34],[1581,34],[1582,34],[1583,34],[1584,34],[1585,34],[1586,34],[1587,34],[1588,34],[1589,34],[1590,34],[1591,34],[1592,34],[1593,34],[1594,34],[1595,34],[1596,34],[1597,34],[1598,34],[1599,34],[1600,34],[1601,34],[1602,34],[1603,34],[1604,34],[1605,34],[1606,34],[1607,34],[1608,34],[1609,34],[1610,34],[1611,34],[1612,34],[1613,34],[1614,34],[1615,34],[1616,34],[1617,34],[1618,34],[1619,34],[1620,34],[1621,34],[1622,34],[1623,34],[1624,34],[1625,34],[1626,34],[1627,34],[1628,34],[1629,34],[1630,34],[1631,34],[1632,34],[1633,34],[1634,34],[1635,34],[1636,34],[1637,34],[1638,34],[1639,34],[1640,34],[1641,34],[1642,34],[1643,34],[1644,34],[1645,34],[1646,34],[1647,34],[1648,34],[1649,34],[1650,34],[1651,34],[1653,34],[1654,34],[1652,34],[1655,34],[1656,34],[1657,34],[1658,34],[1659,34],[1660,34],[1661,34],[1662,34],[1663,34],[1664,34],[1665,34],[1666,34],[1667,34],[1668,34],[1669,34],[1670,34],[1671,34],[1672,34],[1673,34],[1674,34],[1675,34],[1676,34],[1677,34],[1678,34],[1679,34],[1680,34],[1684,34],[1681,34],[1682,34],[1683,34],[1685,34],[1686,34],[1687,34],[1688,34],[1689,34],[1690,34],[1691,34],[1692,34],[1693,34],[1694,34],[1695,34],[1696,34],[1697,34],[1698,34],[1699,34],[1700,34],[1701,34],[1702,34],[1703,34],[1704,34],[1705,34],[1706,34],[1707,34],[1708,34],[1709,34],[1710,34],[1711,34],[1712,34],[1713,34],[1714,34],[1715,34],[1716,34],[1717,34],[1718,34],[1719,34],[1720,34],[1721,34],[2130,35],[1722,34],[1723,34],[1724,34],[1725,34],[1726,34],[1727,34],[1728,34],[1729,34],[1730,34],[1731,34],[1732,34],[1733,34],[1734,34],[1735,34],[1736,34],[1737,34],[1738,34],[1739,34],[1740,34],[1741,34],[1742,34],[1743,34],[1744,34],[1745,34],[1746,34],[1747,34],[1748,34],[1749,34],[1750,34],[1751,34],[1752,34],[1753,34],[1754,34],[1755,34],[1756,34],[1757,34],[1758,34],[1759,34],[1760,34],[1762,34],[1763,34],[1761,34],[1764,34],[1765,34],[1766,34],[1767,34],[1768,34],[1769,34],[1770,34],[1771,34],[1772,34],[1773,34],[1774,34],[1775,34],[1776,34],[1777,34],[1778,34],[1779,34],[1780,34],[1781,34],[1782,34],[1783,34],[1784,34],[1785,34],[1786,34],[1787,34],[1788,34],[1789,34],[1790,34],[1791,34],[1792,34],[1793,34],[1794,34],[1795,34],[1796,34],[1797,34],[1798,34],[1799,34],[1800,34],[1801,34],[1802,34],[1803,34],[1804,34],[1805,34],[1806,34],[1807,34],[1808,34],[1809,34],[1810,34],[1811,34],[1812,34],[1813,34],[1814,34],[1815,34],[1816,34],[1817,34],[1818,34],[1819,34],[1820,34],[1821,34],[1822,34],[1823,34],[1824,34],[1825,34],[1826,34],[1827,34],[1828,34],[1829,34],[1830,34],[1831,34],[1832,34],[1833,34],[1834,34],[1835,34],[1836,34],[1837,34],[1838,34],[1839,34],[1840,34],[1841,34],[1842,34],[1843,34],[1844,34],[1845,34],[1846,34],[1847,34],[1848,34],[1849,34],[1850,34],[1851,34],[1852,34],[1853,34],[1854,34],[1855,34],[1856,34],[1857,34],[1858,34],[1859,34],[1860,34],[1861,34],[1862,34],[1863,34],[1864,34],[1865,34],[1866,34],[1867,34],[1868,34],[1869,34],[1870,34],[1871,34],[1872,34],[1873,34],[1874,34],[1875,34],[1876,34],[1877,34],[1878,34],[1879,34],[1880,34],[1881,34],[1882,34],[1883,34],[1884,34],[1885,34],[1886,34],[1887,34],[1888,34],[1889,34],[1890,34],[1891,34],[1892,34],[1893,34],[1894,34],[1895,34],[1896,34],[1897,34],[1898,34],[1899,34],[1900,34],[1901,34],[1902,34],[1903,34],[1904,34],[1905,34],[1909,34],[1910,34],[1911,34],[1906,34],[1907,34],[1908,34],[1912,34],[1913,34],[1914,34],[1915,34],[1916,34],[1917,34],[1918,34],[1919,34],[1920,34],[1921,34],[1922,34],[1923,34],[1924,34],[1925,34],[1926,34],[1927,34],[1928,34],[1929,34],[1930,34],[1931,34],[1932,34],[1933,34],[1934,34],[1935,34],[1936,34],[1937,34],[1938,34],[1939,34],[1940,34],[1941,34],[1942,34],[1943,34],[1944,34],[1945,34],[1946,34],[1947,34],[1948,34],[1949,34],[1950,34],[1951,34],[1952,34],[1953,34],[1954,34],[1955,34],[1956,34],[1957,34],[1958,34],[1959,34],[1961,34],[1962,34],[1963,34],[1964,34],[1960,34],[1965,34],[1966,34],[1967,34],[1968,34],[1969,34],[1970,34],[1971,34],[1972,34],[1973,34],[1974,34],[1975,34],[1976,34],[1977,34],[1978,34],[1979,34],[1980,34],[1981,34],[1982,34],[1983,34],[1984,34],[1985,34],[1986,34],[1987,34],[1988,34],[1989,34],[1990,34],[1991,34],[1992,34],[1993,34],[1994,34],[1995,34],[1996,34],[1997,34],[1998,34],[1999,34],[2000,34],[2001,34],[2002,34],[2003,34],[2004,34],[2005,34],[2006,34],[2007,34],[2008,34],[2009,34],[2010,34],[2011,34],[2012,34],[2013,34],[2014,34],[2015,34],[2016,34],[2017,34],[2018,34],[2019,34],[2020,34],[2021,34],[2022,34],[2023,34],[2024,34],[2025,34],[2026,34],[2027,34],[2028,34],[2030,34],[2031,34],[2032,34],[2029,34],[2033,34],[2034,34],[2035,34],[2036,34],[2037,34],[2038,34],[2039,34],[2040,34],[2041,34],[2042,34],[2044,34],[2045,34],[2046,34],[2043,34],[2047,34],[2048,34],[2049,34],[2050,34],[2051,34],[2052,34],[2053,34],[2054,34],[2055,34],[2056,34],[2057,34],[2058,34],[2059,34],[2060,34],[2061,34],[2062,34],[2063,34],[2064,34],[2065,34],[2066,34],[2067,34],[2068,34],[2069,34],[2070,34],[2071,34],[2072,34],[2077,34],[2073,34],[2074,34],[2075,34],[2076,34],[2078,34],[2079,34],[2080,34],[2081,34],[2082,34],[2085,34],[2086,34],[2083,34],[2084,34],[2087,34],[2088,34],[2089,34],[2090,34],[2091,34],[2092,34],[2093,34],[2094,34],[2095,34],[2096,34],[2097,34],[2098,34],[2099,34],[2100,34],[2101,34],[2102,34],[2103,34],[2104,34],[2105,34],[2106,34],[2107,34],[2108,34],[2109,34],[2110,34],[2111,34],[2112,34],[2113,34],[2114,34],[2115,34],[2116,34],[2117,34],[2118,34],[2119,34],[2120,34],[2121,34],[2122,34],[2123,34],[2124,34],[2125,34],[2126,34],[2127,34],[2128,34],[2129,34],[2133,36],[1073,32],[4005,37],[3953,38],[3951,39],[3954,40],[3958,41],[3947,42],[3957,43],[3970,44],[4006,45],[3940,2],[3969,46],[3968,2],[3945,2],[3952,47],[3948,48],[3946,49],[3956,50],[3944,51],[3955,52],[3949,53],[3978,54],[3979,55],[3975,56],[3974,57],[3995,58],[3998,59],[3997,60],[3999,58],[3996,61],[3994,62],[3964,63],[3980,64],[3963,65],[4001,66],[3959,67],[3960,68],[3993,69],[3981,70],[3965,67],[3967,71],[3966,72],[3977,73],[3982,74],[4000,75],[3961,67],[3983,76],[3986,77],[3985,78],[3984,79],[3989,80],[3988,81],[3987,68],[3962,67],[3990,67],[3992,82],[3991,83],[4002,84],[4004,85],[3973,86],[3971,87],[3972,88],[3976,89],[4003,67],[3950,2],[4412,2],[3097,90],[2207,32],[2208,32],[2209,32],[2210,32],[2211,32],[2212,32],[2213,32],[2214,32],[2215,32],[2216,32],[2217,32],[2218,32],[2219,32],[2220,32],[2221,32],[2227,32],[2222,32],[2223,32],[2224,32],[2225,32],[2226,32],[2228,32],[2229,32],[2230,32],[2231,32],[2232,32],[2233,32],[2235,32],[2236,32],[2234,32],[2237,32],[2238,32],[2239,32],[2240,32],[2241,32],[2242,32],[2243,32],[2244,32],[2245,32],[2246,32],[2247,32],[2248,32],[2249,32],[2250,32],[2251,32],[2252,32],[2253,32],[2254,32],[2255,32],[2256,32],[2257,32],[2258,32],[2259,32],[2260,32],[2261,32],[2263,32],[2262,32],[2264,32],[2265,32],[2267,32],[2266,32],[2268,32],[2269,32],[2270,32],[2271,32],[2272,32],[2274,32],[2273,32],[2275,32],[2276,32],[2277,32],[2278,32],[2279,32],[2280,32],[2281,32],[2282,32],[2283,32],[2284,32],[2285,32],[2286,32],[2287,32],[2288,32],[2293,32],[2289,32],[2290,32],[2291,32],[2292,32],[2294,32],[2295,32],[2296,32],[2297,32],[2298,32],[2299,32],[2300,32],[2301,32],[2302,32],[2303,32],[2305,32],[2304,32],[2306,32],[2307,32],[2308,32],[2309,32],[2310,32],[2311,32],[2312,32],[2313,32],[2316,32],[2314,32],[2315,32],[2317,32],[2318,32],[2319,32],[2320,32],[2321,32],[2322,32],[2323,32],[2324,32],[2326,32],[2325,32],[2437,91],[2327,32],[2328,32],[2329,32],[2330,32],[2331,32],[2332,32],[2333,32],[2334,32],[2335,32],[2336,32],[2337,32],[2339,32],[2338,32],[2340,32],[2341,32],[2342,32],[2343,32],[2344,32],[2345,32],[2346,32],[2347,32],[2349,32],[2348,32],[2350,32],[2351,32],[2352,32],[2353,32],[2354,32],[2355,32],[2356,32],[2357,32],[2358,32],[2362,32],[2359,32],[2360,32],[2361,32],[2363,32],[2364,32],[2365,32],[2367,32],[2366,32],[2368,32],[2369,32],[2370,32],[2371,32],[2372,32],[2373,32],[2374,32],[2375,32],[2376,32],[2377,32],[2378,32],[2379,32],[2380,32],[2381,32],[2382,32],[2383,32],[2384,32],[2385,32],[2386,32],[2387,32],[2388,32],[2389,32],[2390,32],[2391,32],[2392,32],[2393,32],[2394,32],[2395,32],[2396,32],[2397,32],[2398,32],[2399,32],[2400,32],[2401,32],[2402,32],[2403,32],[2404,32],[2405,32],[2406,32],[2407,32],[2408,32],[2409,32],[2410,32],[2411,32],[2412,32],[2413,32],[2414,32],[2415,32],[2416,32],[2417,32],[2418,32],[2419,32],[2420,32],[2422,32],[2421,32],[2423,32],[2424,32],[2425,32],[2426,32],[2427,32],[2428,32],[2429,32],[2430,32],[2431,32],[2432,32],[2433,32],[2434,32],[2435,32],[2436,32],[3357,32],[3358,32],[3359,32],[3360,32],[3361,32],[3362,32],[3363,32],[3364,32],[3365,32],[3366,32],[3367,32],[3368,32],[3369,32],[3370,32],[3371,32],[3377,32],[3372,32],[3373,32],[3374,32],[3375,32],[3376,32],[3378,32],[3379,32],[3380,32],[3381,32],[3382,32],[3383,32],[3385,32],[3386,32],[3384,32],[3387,32],[3388,32],[3389,32],[3390,32],[3391,32],[3392,32],[3393,32],[3394,32],[3395,32],[3396,32],[3397,32],[3398,32],[3399,32],[3400,32],[3401,32],[3402,32],[3403,32],[3404,32],[3405,32],[3406,32],[3407,32],[3408,32],[3409,32],[3410,32],[3411,32],[3413,32],[3412,32],[3414,32],[3415,32],[3417,32],[3416,32],[3418,32],[3419,32],[3420,32],[3421,32],[3422,32],[3424,32],[3423,32],[3425,32],[3426,32],[3427,32],[3428,32],[3429,32],[3430,32],[3431,32],[3432,32],[3433,32],[3434,32],[3435,32],[3436,32],[3437,32],[3438,32],[3443,32],[3439,32],[3440,32],[3441,32],[3442,32],[3444,32],[3445,32],[3446,32],[3447,32],[3448,32],[3449,32],[3450,32],[3451,32],[3452,32],[3453,32],[3455,32],[3454,32],[3456,32],[3457,32],[3458,32],[3459,32],[3460,32],[3461,32],[3462,32],[3463,32],[3466,32],[3464,32],[3465,32],[3467,32],[3468,32],[3469,32],[3470,32],[3471,32],[3472,32],[3473,32],[3474,32],[3476,32],[3475,32],[3587,92],[3477,32],[3478,32],[3479,32],[3480,32],[3481,32],[3482,32],[3483,32],[3484,32],[3485,32],[3486,32],[3487,32],[3489,32],[3488,32],[3490,32],[3491,32],[3492,32],[3493,32],[3494,32],[3495,32],[3496,32],[3497,32],[3499,32],[3498,32],[3500,32],[3501,32],[3502,32],[3503,32],[3504,32],[3505,32],[3506,32],[3507,32],[3508,32],[3512,32],[3509,32],[3510,32],[3511,32],[3513,32],[3514,32],[3515,32],[3517,32],[3516,32],[3518,32],[3519,32],[3520,32],[3521,32],[3522,32],[3523,32],[3524,32],[3525,32],[3526,32],[3527,32],[3528,32],[3529,32],[3530,32],[3531,32],[3532,32],[3533,32],[3534,32],[3535,32],[3536,32],[3537,32],[3538,32],[3539,32],[3540,32],[3541,32],[3542,32],[3543,32],[3544,32],[3545,32],[3546,32],[3547,32],[3548,32],[3549,32],[3550,32],[3551,32],[3552,32],[3553,32],[3554,32],[3555,32],[3556,32],[3557,32],[3558,32],[3559,32],[3560,32],[3561,32],[3562,32],[3563,32],[3564,32],[3565,32],[3566,32],[3567,32],[3568,32],[3569,32],[3570,32],[3572,32],[3571,32],[3573,32],[3574,32],[3575,32],[3576,32],[3577,32],[3578,32],[3579,32],[3580,32],[3581,32],[3582,32],[3583,32],[3584,32],[3585,32],[3586,32],[375,2],[1079,93],[1083,94],[1084,32],[1081,95],[1082,96],[1085,97],[1080,98],[868,32],[985,99],[989,100],[984,2],[987,101],[986,99],[988,99],[957,102],[956,2],[955,32],[1126,103],[1122,104],[1121,2],[1124,105],[1125,105],[1123,106],[903,107],[907,108],[905,109],[902,110],[906,111],[904,111],[655,112],[654,113],[2587,114],[2586,2],[2173,2],[2174,115],[2592,116],[2588,117],[2589,118],[2590,118],[2591,117],[2175,119],[2176,120],[2755,121],[2734,122],[2744,123],[2741,123],[2742,124],[2726,124],[2740,124],[2721,123],[2727,125],[2730,126],[2735,127],[2723,125],[2724,124],[2737,128],[2722,125],[2728,125],[2731,125],[2736,125],[2738,124],[2725,124],[2739,124],[2733,129],[2729,130],[2754,131],[2732,132],[2743,133],[2720,124],[2745,124],[2746,124],[2747,124],[2748,124],[2749,124],[2750,124],[2751,124],[2752,124],[2753,124],[2158,2],[2155,2],[2154,2],[2149,134],[2160,135],[2145,136],[2156,137],[2148,138],[2147,139],[2157,2],[2152,140],[2159,2],[2153,141],[2146,2],[3103,142],[3102,143],[3101,136],[2162,144],[3892,145],[3893,145],[3895,146],[3894,145],[3887,145],[3888,145],[3890,147],[3889,145],[3867,2],[3866,2],[3869,148],[3868,2],[3865,2],[3832,149],[3830,150],[3833,2],[3880,151],[3834,145],[3870,152],[3879,153],[3871,2],[3874,154],[3872,2],[3875,2],[3877,2],[3873,154],[3876,2],[3878,2],[3831,155],[3906,156],[3891,145],[3886,157],[3896,158],[3902,159],[3903,160],[3905,161],[3904,162],[3884,157],[3885,163],[3881,164],[3883,165],[3882,166],[3897,145],[3901,167],[3898,145],[3899,168],[3900,145],[3835,2],[3836,2],[3839,2],[3837,2],[3838,2],[3841,2],[3842,169],[3843,2],[3844,2],[3840,2],[3845,2],[3846,2],[3847,2],[3848,2],[3849,170],[3850,2],[3864,171],[3851,2],[3852,2],[3853,2],[3854,2],[3855,2],[3856,2],[3857,2],[3860,2],[3858,2],[3859,2],[3861,145],[3862,145],[3863,172],[1298,173],[2144,2],[4413,174],[598,175],[4414,2],[4415,2],[4416,2],[4417,176],[4418,2],[4420,177],[4421,178],[4419,2],[4422,2],[4424,179],[596,2],[4425,180],[545,2],[3667,181],[3096,2],[4426,2],[2549,182],[2550,183],[2548,184],[2551,185],[2552,186],[2553,187],[2554,188],[2555,189],[2556,190],[2557,191],[2558,192],[2559,193],[2561,194],[2560,195],[3677,181],[4423,2],[3942,2],[3943,196],[141,197],[142,197],[143,198],[98,199],[144,200],[145,201],[146,202],[93,2],[96,203],[94,2],[95,2],[147,204],[148,205],[149,206],[150,207],[151,208],[152,209],[153,209],[154,210],[155,211],[156,212],[157,213],[99,2],[97,2],[158,214],[159,215],[160,216],[192,217],[161,218],[162,219],[163,220],[164,221],[165,222],[166,223],[167,224],[168,225],[169,226],[170,227],[171,227],[172,228],[173,2],[174,229],[176,230],[175,231],[177,49],[178,232],[179,233],[180,234],[181,235],[182,236],[183,237],[184,238],[185,239],[186,240],[187,241],[188,242],[189,243],[100,2],[101,2],[102,2],[140,244],[190,245],[191,246],[2596,247],[85,2],[2597,32],[196,248],[459,32],[197,249],[195,32],[460,250],[2161,251],[2205,252],[193,253],[194,254],[83,2],[86,255],[457,32],[227,32],[4427,2],[3666,2],[4428,2],[541,256],[585,257],[583,2],[584,2],[533,2],[580,258],[577,259],[578,260],[599,261],[590,2],[593,262],[592,263],[604,263],[591,264],[532,2],[540,265],[579,265],[535,266],[538,267],[586,266],[539,268],[534,2],[622,32],[820,269],[821,32],[631,270],[623,271],[624,32],[625,272],[626,32],[627,32],[628,32],[629,2],[630,2],[854,273],[822,274],[611,2],[828,275],[613,2],[612,32],[643,32],[921,276],[743,277],[614,278],[744,276],[632,279],[633,32],[634,280],[745,281],[636,282],[635,32],[637,283],[746,276],[1056,284],[1055,285],[1058,286],[747,276],[1057,287],[1059,288],[1060,289],[1062,290],[1061,291],[1063,292],[1064,293],[748,276],[1065,32],[749,276],[924,294],[922,295],[923,32],[750,276],[1067,296],[1066,297],[1068,298],[751,276],[640,299],[642,300],[641,301],[834,302],[753,303],[752,281],[1071,304],[1072,305],[1070,306],[760,307],[935,308],[936,32],[938,309],[937,32],[761,276],[1074,310],[762,276],[944,311],[943,312],[763,281],[874,313],[876,314],[875,315],[877,316],[764,317],[1075,318],[949,319],[948,32],[950,320],[765,281],[1086,321],[1088,322],[1089,323],[1087,324],[766,276],[1049,325],[1048,32],[1050,326],[1051,327],[639,32],[1189,32],[835,328],[833,329],[951,330],[1069,331],[759,332],[758,333],[757,334],[952,32],[954,335],[953,291],[767,276],[1090,299],[768,281],[963,336],[964,337],[769,276],[895,338],[894,339],[896,340],[771,341],[836,32],[772,2],[1091,342],[965,343],[773,276],[1092,344],[1095,345],[1093,344],[1096,346],[966,347],[1094,344],[774,276],[1098,348],[1099,349],[680,350],[827,351],[681,352],[825,353],[1100,354],[679,355],[1101,356],[826,349],[1102,357],[678,358],[775,281],[675,359],[994,360],[993,291],[776,276],[1110,361],[1109,362],[777,317],[1190,363],[992,364],[779,365],[778,366],[967,32],[983,367],[974,368],[975,369],[976,370],[977,370],[780,371],[754,276],[982,372],[1112,373],[1111,32],[887,32],[781,281],[996,374],[997,375],[995,32],[782,281],[920,376],[919,377],[1001,378],[783,366],[893,379],[886,380],[889,381],[888,382],[890,32],[891,383],[784,281],[892,384],[1117,385],[638,32],[1115,386],[785,281],[1116,387],[1053,388],[1004,389],[1052,390],[1002,391],[1003,392],[786,281],[1054,393],[1120,394],[1005,279],[1118,395],[787,317],[1119,396],[897,397],[856,398],[788,366],[857,399],[858,400],[789,276],[1007,401],[1006,402],[790,403],[917,404],[916,32],[791,276],[1128,405],[1127,406],[792,276],[1130,407],[1133,408],[1129,409],[1131,407],[1132,410],[793,276],[1136,411],[794,317],[1141,34],[795,281],[1142,318],[1144,412],[796,276],[855,413],[797,414],[755,281],[1146,415],[1147,415],[1145,32],[1148,415],[1154,416],[1149,415],[1150,415],[1151,32],[1153,417],[798,276],[1152,32],[1015,418],[799,281],[1017,32],[1016,419],[1018,32],[1019,420],[800,276],[899,32],[801,276],[1159,421],[1156,422],[1157,423],[1155,32],[1158,423],[816,276],[1162,424],[1164,425],[1161,426],[802,276],[1163,424],[1160,32],[1169,427],[803,281],[770,428],[756,429],[1171,430],[804,276],[1020,431],[1021,432],[898,431],[1023,433],[901,434],[900,435],[805,276],[1022,436],[934,437],[806,276],[933,438],[1024,32],[1025,439],[807,281],[737,440],[1173,441],[722,442],[817,443],[818,444],[819,445],[717,2],[718,2],[721,446],[719,2],[720,2],[715,2],[716,447],[742,448],[1172,269],[736,7],[735,2],[738,449],[740,317],[739,450],[741,451],[832,452],[1176,453],[808,276],[1175,454],[1174,455],[824,456],[823,457],[809,403],[1178,458],[908,459],[1177,460],[810,403],[914,461],[909,2],[911,462],[910,463],[912,382],[913,32],[811,276],[1041,464],[813,465],[1039,466],[1040,467],[812,317],[1038,468],[1180,469],[1185,470],[1181,471],[1182,471],[814,276],[1183,471],[1184,471],[1179,382],[1046,472],[1047,473],[918,474],[815,276],[1045,475],[1187,476],[1186,2],[1188,32],[597,2],[676,2],[84,2],[2438,2],[2909,477],[2888,478],[2985,2],[2889,479],[2825,477],[2826,2],[2827,2],[2828,2],[2829,2],[2830,2],[2831,2],[2832,2],[2833,2],[2834,2],[2835,2],[2836,2],[2837,477],[2838,477],[2839,2],[2840,2],[2841,2],[2842,2],[2843,2],[2844,2],[2845,2],[2846,2],[2847,2],[2849,2],[2848,2],[2850,2],[2851,2],[2852,477],[2853,2],[2854,2],[2855,477],[2856,2],[2857,2],[2858,477],[2859,2],[2860,477],[2861,477],[2862,477],[2863,2],[2864,477],[2865,477],[2866,477],[2867,477],[2868,477],[2870,477],[2871,2],[2872,2],[2869,477],[2873,477],[2874,2],[2875,2],[2876,2],[2877,2],[2878,2],[2879,2],[2880,2],[2881,2],[2882,2],[2883,2],[2884,2],[2885,477],[2886,2],[2887,2],[2890,480],[2891,477],[2892,477],[2893,481],[2894,482],[2895,477],[2896,477],[2897,477],[2898,477],[2901,477],[2899,2],[2900,2],[1199,2],[2902,2],[2903,2],[2904,2],[2905,2],[2906,2],[2907,2],[2908,2],[2910,483],[2911,2],[2912,2],[2913,2],[2915,2],[2914,2],[2916,2],[2917,2],[2918,2],[2919,477],[2920,2],[2921,2],[2922,2],[2923,2],[2924,477],[2925,477],[2927,477],[2926,477],[2928,2],[2929,2],[2930,2],[2931,2],[3078,484],[2932,477],[2933,477],[2934,2],[2935,2],[2936,2],[2937,2],[2938,2],[2939,2],[2940,2],[2941,2],[2942,2],[2943,2],[2944,2],[2945,2],[2946,477],[2947,2],[2948,2],[2949,2],[2950,2],[2951,2],[2952,2],[2953,2],[2954,2],[2955,2],[2956,2],[2957,477],[2958,2],[2959,2],[2960,2],[2961,2],[2962,2],[2963,2],[2964,2],[2965,2],[2966,2],[2967,477],[2968,2],[2969,2],[2970,2],[2971,2],[2972,2],[2973,2],[2974,2],[2975,2],[2976,477],[2977,2],[2978,2],[2979,2],[2980,2],[2981,2],[2982,2],[2983,477],[2984,2],[2986,485],[1297,486],[1202,479],[1204,479],[1205,479],[1206,479],[1207,479],[1208,479],[1203,479],[1209,479],[1211,479],[1210,479],[1212,479],[1213,479],[1214,479],[1215,479],[1216,479],[1217,479],[1218,479],[1219,479],[1221,479],[1220,479],[1222,479],[1223,479],[1224,479],[1225,479],[1226,479],[1227,479],[1228,479],[1229,479],[1230,479],[1231,479],[1232,479],[1233,479],[1234,479],[1235,479],[1236,479],[1238,479],[1239,479],[1237,479],[1240,479],[1241,479],[1242,479],[1243,479],[1244,479],[1245,479],[1246,479],[1247,479],[1248,479],[1249,479],[1250,479],[1251,479],[1253,479],[1252,479],[1255,479],[1254,479],[1256,479],[1257,479],[1258,479],[1259,479],[1260,479],[1261,479],[1262,479],[1263,479],[1264,479],[1265,479],[1266,479],[1267,479],[1268,479],[1270,479],[1269,479],[1271,479],[1272,479],[1273,479],[1275,479],[1274,479],[1276,479],[1277,479],[1278,479],[1279,479],[1280,479],[1281,479],[1283,479],[1282,479],[1284,479],[1285,479],[1286,479],[1287,479],[1288,479],[1201,477],[1289,479],[1290,479],[1292,479],[1291,479],[1293,479],[1294,479],[1295,479],[1296,479],[2987,2],[2988,477],[2989,2],[2990,2],[2991,2],[2992,2],[2993,2],[2994,2],[2995,2],[2996,2],[2997,2],[2998,477],[2999,2],[3000,2],[3001,2],[3002,2],[3003,2],[3004,2],[3005,2],[3010,487],[3008,488],[3009,489],[3007,490],[3006,477],[3011,2],[3012,2],[3013,477],[3014,2],[3015,2],[3016,2],[3017,2],[3018,2],[3019,2],[3020,2],[3021,2],[3022,2],[3023,477],[3024,477],[3025,2],[3026,2],[3027,2],[3028,477],[3029,2],[3030,477],[3031,2],[3032,483],[3033,2],[3034,2],[3035,2],[3036,2],[3037,2],[3038,2],[3039,2],[3040,2],[3041,2],[3042,477],[3043,477],[3044,2],[3045,2],[3046,2],[3047,2],[3048,2],[3049,2],[3050,2],[3051,2],[3052,2],[3053,2],[3054,2],[3055,2],[3056,477],[3057,477],[3058,2],[3059,2],[3060,477],[3061,2],[3062,2],[3063,2],[3064,2],[3065,2],[3066,2],[3067,2],[3068,2],[3069,2],[3070,2],[3071,2],[3072,2],[3073,477],[1200,491],[3074,2],[3075,2],[3076,2],[3077,2],[831,492],[830,493],[829,2],[550,2],[3099,494],[3098,495],[2169,496],[2171,497],[2170,498],[2168,499],[2167,2],[3941,500],[2177,2],[2580,32],[4112,2],[4086,501],[4085,502],[4084,503],[4111,504],[4110,505],[4114,506],[4113,507],[4116,508],[4115,509],[3705,510],[3679,511],[3680,512],[3681,512],[3682,512],[3683,512],[3684,512],[3685,512],[3686,512],[3687,512],[3688,512],[3689,512],[3703,513],[3690,512],[3691,512],[3692,512],[3693,512],[3694,512],[3695,512],[3696,512],[3697,512],[3699,512],[3700,512],[3698,512],[3701,512],[3702,512],[3704,512],[3678,514],[4109,515],[4089,516],[4090,516],[4091,516],[4092,516],[4093,516],[4094,516],[4095,517],[4097,516],[4096,516],[4108,518],[4098,516],[4100,516],[4099,516],[4102,516],[4101,516],[4103,516],[4104,516],[4105,516],[4106,516],[4107,516],[4088,516],[4087,519],[4079,520],[4077,521],[4078,521],[4082,522],[4080,521],[4081,521],[4083,521],[4076,2],[2718,2],[481,523],[486,1],[493,524],[476,525],[231,2],[239,526],[379,527],[382,528],[354,2],[367,529],[374,530],[256,2],[356,2],[237,2],[353,531],[399,532],[238,2],[229,533],[381,534],[383,535],[384,536],[455,537],[348,538],[301,539],[361,540],[362,541],[360,542],[359,2],[355,543],[380,544],[240,545],[425,2],[426,546],[267,547],[241,548],[268,547],[304,547],[207,547],[377,549],[376,2],[366,550],[471,2],[216,2],[492,551],[433,552],[434,553],[430,554],[510,2],[331,2],[435,555],[431,556],[515,557],[514,558],[509,2],[282,2],[334,559],[333,2],[508,560],[432,32],[287,561],[294,562],[296,563],[286,2],[291,564],[293,565],[295,566],[290,567],[288,2],[292,568],[511,2],[507,2],[513,569],[512,2],[285,570],[502,571],[505,572],[275,573],[274,574],[273,575],[518,32],[272,576],[261,2],[520,2],[3106,577],[3105,2],[521,32],[522,578],[199,2],[363,579],[364,580],[365,581],[203,2],[368,2],[223,582],[198,2],[447,32],[205,583],[446,584],[445,585],[436,2],[437,2],[444,2],[439,2],[442,586],[438,2],[440,587],[443,588],[441,587],[236,2],[233,2],[234,547],[388,2],[393,589],[394,590],[392,591],[390,592],[391,593],[386,2],[453,555],[228,555],[480,594],[487,595],[491,596],[322,597],[321,2],[316,2],[467,598],[475,599],[349,600],[350,601],[428,602],[338,2],[451,603],[326,32],[343,604],[454,605],[339,2],[342,606],[340,2],[452,607],[449,608],[448,2],[450,2],[346,2],[424,609],[211,610],[324,611],[328,612],[344,613],[347,614],[336,615],[329,616],[474,617],[402,618],[320,619],[208,620],[473,621],[204,622],[395,623],[387,2],[396,624],[413,625],[385,2],[412,626],[92,2],[407,627],[232,2],[427,628],[403,2],[217,2],[219,2],[358,2],[411,629],[235,2],[259,630],[345,631],[265,632],[325,2],[410,2],[389,2],[415,633],[416,634],[357,2],[418,635],[420,636],[419,637],[369,2],[409,620],[422,638],[319,639],[408,640],[414,641],[244,2],[248,2],[247,2],[246,2],[251,2],[245,2],[254,2],[253,2],[250,2],[249,2],[252,2],[255,642],[243,2],[311,643],[310,2],[315,644],[312,645],[314,646],[317,644],[313,645],[224,647],[303,648],[470,649],[468,2],[497,650],[499,651],[463,652],[498,653],[212,654],[209,654],[242,2],[226,655],[225,656],[221,657],[222,658],[230,659],[258,659],[269,659],[305,660],[270,660],[214,661],[213,2],[309,662],[308,663],[307,664],[306,665],[215,666],[456,667],[257,668],[462,669],[429,670],[458,671],[461,672],[352,673],[351,674],[332,675],[318,676],[300,677],[302,678],[299,679],[421,680],[323,2],[485,2],[220,681],[423,682],[469,683],[330,2],[260,684],[337,685],[335,686],[262,687],[397,688],[464,2],[263,689],[398,689],[483,2],[482,2],[484,2],[466,2],[465,2],[400,690],[327,2],[297,691],[218,692],[276,2],[202,693],[264,2],[489,32],[201,2],[501,694],[284,32],[495,555],[283,695],[478,696],[281,694],[206,2],[503,697],[279,32],[280,32],[271,2],[200,2],[278,698],[277,699],[266,700],[341,226],[401,226],[417,2],[405,701],[404,2],[289,570],[210,2],[298,32],[472,582],[479,702],[87,32],[90,703],[91,704],[88,32],[89,2],[378,705],[373,706],[372,2],[371,707],[370,2],[477,708],[488,709],[490,710],[494,711],[3107,712],[496,713],[500,714],[528,715],[504,715],[527,716],[506,717],[516,718],[517,719],[519,720],[523,721],[526,582],[525,2],[524,722],[3237,2],[3243,723],[3236,2],[3240,2],[3242,724],[3239,725],[3312,726],[3306,726],[3267,727],[3263,728],[3278,729],[3268,730],[3275,731],[3262,732],[3276,2],[3274,733],[3271,734],[3272,735],[3269,736],[3277,737],[3244,725],[3307,738],[3258,739],[3255,740],[3256,741],[3257,742],[3246,743],[3265,744],[3284,745],[3280,746],[3279,747],[3283,748],[3281,749],[3282,749],[3259,750],[3261,751],[3260,752],[3264,753],[3308,754],[3266,755],[3248,756],[3309,757],[3247,758],[3310,759],[3249,760],[3287,761],[3285,740],[3286,762],[3250,749],[3291,763],[3289,764],[3290,765],[3251,766],[3294,767],[3293,768],[3296,769],[3295,770],[3299,771],[3297,770],[3298,772],[3292,773],[3288,774],[3300,773],[3252,749],[3311,775],[3253,770],[3254,749],[3270,776],[3273,777],[3245,2],[3301,749],[3302,778],[3304,779],[3303,780],[3305,781],[3238,782],[3241,783],[568,784],[566,785],[567,786],[555,787],[556,785],[563,788],[554,789],[559,790],[569,2],[560,791],[565,792],[571,793],[570,794],[553,795],[561,796],[562,797],[557,798],[564,784],[558,799],[2151,800],[2150,2],[941,801],[942,802],[939,803],[940,804],[873,32],[946,805],[947,806],[945,113],[620,807],[619,807],[618,808],[621,809],[961,810],[958,32],[960,811],[962,812],[959,32],[929,813],[928,2],[666,814],[670,814],[668,814],[669,814],[673,815],[665,816],[667,814],[671,814],[663,2],[664,817],[672,817],[662,354],[674,354],[1097,354],[646,818],[644,2],[645,819],[1103,32],[1107,820],[1108,821],[1105,32],[1104,822],[1106,823],[991,824],[990,825],[971,826],[973,827],[972,826],[970,828],[968,826],[969,2],[1000,829],[998,32],[999,830],[883,32],[884,831],[885,832],[878,32],[879,833],[880,831],[882,831],[881,831],[652,32],[649,834],[651,835],[653,836],[648,32],[650,32],[1113,32],[1114,837],[840,838],[838,839],[837,840],[839,840],[647,2],[661,841],[656,842],[658,843],[657,844],[659,844],[660,844],[1135,845],[1134,32],[1143,32],[848,846],[852,847],[853,848],[847,32],[849,849],[850,849],[851,850],[1013,851],[1009,851],[1010,852],[1014,853],[1008,32],[1011,32],[1012,854],[1168,855],[1165,32],[1166,856],[1167,857],[1170,32],[859,2],[863,858],[865,859],[862,32],[864,860],[872,861],[861,862],[860,2],[866,863],[867,864],[869,865],[870,863],[871,866],[925,867],[932,868],[930,869],[926,870],[927,32],[931,870],[981,871],[978,826],[980,872],[979,872],[682,110],[683,873],[1035,874],[1031,875],[1032,876],[1034,877],[1033,878],[1027,879],[1028,32],[1037,880],[1026,881],[1029,875],[1030,882],[1036,875],[1042,883],[1044,884],[915,32],[1043,885],[616,2],[615,32],[617,886],[841,32],[844,887],[842,32],[846,888],[845,32],[843,32],[2778,889],[2779,890],[3709,891],[3708,892],[1198,32],[4118,893],[4117,894],[3707,895],[3706,896],[547,897],[546,180],[677,898],[406,247],[552,2],[2439,2],[600,2],[536,2],[537,899],[3674,900],[3673,2],[81,2],[82,2],[13,2],[14,2],[16,2],[15,2],[2,2],[17,2],[18,2],[19,2],[20,2],[21,2],[22,2],[23,2],[24,2],[3,2],[25,2],[26,2],[4,2],[27,2],[31,2],[28,2],[29,2],[30,2],[32,2],[33,2],[34,2],[5,2],[35,2],[36,2],[37,2],[38,2],[6,2],[42,2],[39,2],[40,2],[41,2],[43,2],[7,2],[44,2],[49,2],[50,2],[45,2],[46,2],[47,2],[48,2],[8,2],[54,2],[51,2],[52,2],[53,2],[55,2],[9,2],[56,2],[57,2],[58,2],[60,2],[59,2],[61,2],[62,2],[10,2],[63,2],[64,2],[65,2],[11,2],[66,2],[67,2],[68,2],[69,2],[70,2],[1,2],[71,2],[72,2],[12,2],[76,2],[74,2],[79,2],[78,2],[73,2],[77,2],[75,2],[80,2],[118,901],[128,902],[117,901],[138,903],[109,904],[108,905],[137,722],[131,906],[136,907],[111,908],[125,909],[110,910],[134,911],[106,912],[105,722],[135,913],[107,914],[112,915],[113,2],[116,915],[103,2],[139,916],[129,917],[120,918],[121,919],[123,920],[119,921],[122,922],[132,722],[114,923],[115,924],[124,925],[104,926],[127,917],[126,915],[130,2],[133,927],[3676,928],[3672,2],[3675,929],[3936,930],[3920,2],[3921,2],[3923,931],[3924,2],[3922,2],[3925,931],[3926,931],[3928,932],[3927,931],[3929,931],[3930,932],[3931,931],[3932,2],[3933,931],[3934,2],[3935,2],[3669,933],[3668,181],[3671,934],[3670,935],[602,936],[588,937],[589,936],[587,2],[543,938],[576,939],[549,940],[544,938],[542,2],[548,941],[574,2],[572,2],[573,2],[551,2],[575,942],[608,943],[601,944],[594,945],[603,946],[582,947],[2164,948],[2165,949],[605,950],[2166,951],[606,952],[595,953],[2163,954],[607,955],[2172,956],[581,2],[3908,957],[3826,958],[3824,959],[3827,960],[3825,961],[3909,962],[3828,963],[2143,964],[3829,965],[3912,966],[3911,967],[2638,968],[3910,969],[3913,970],[3121,971],[2196,972],[2197,973],[2195,974],[2198,975],[2199,975],[2200,975],[2203,976],[2202,977],[2204,978],[2447,979],[2449,980],[2448,978],[2451,981],[2450,978],[2453,982],[2452,978],[2456,983],[2455,984],[2457,985],[2181,964],[2458,986],[2460,987],[2459,988],[2461,987],[2463,989],[2462,990],[2465,991],[2464,974],[2467,992],[2466,990],[2468,990],[2469,993],[2471,994],[2470,990],[2473,995],[2472,996],[2474,997],[2475,998],[2476,978],[2477,990],[2478,993],[2480,999],[2479,990],[2482,1000],[2481,1001],[2484,1002],[2483,1003],[2485,1003],[2487,1004],[2486,993],[2489,1005],[2488,990],[2491,1006],[2490,1007],[2493,1008],[2492,990],[2496,1009],[2495,1010],[2498,1011],[2497,1010],[2500,1012],[2499,1013],[2501,1014],[2494,974],[2503,1015],[2502,1010],[2505,1016],[2504,993],[2507,1017],[2506,990],[2509,1018],[2511,1019],[2510,990],[2513,1020],[2515,1021],[2514,998],[2517,1022],[2516,990],[2519,1023],[2518,998],[2520,1024],[2522,1025],[2521,1026],[2524,1027],[2523,1028],[2525,1029],[2182,993],[2527,1030],[2526,993],[2529,1031],[2528,993],[2184,1032],[2183,1033],[2186,1034],[2187,1034],[2189,1035],[2188,1034],[2191,1036],[2190,1034],[2193,1037],[2192,1034],[2194,1034],[2531,1038],[2530,990],[2533,1039],[2532,974],[3185,1040],[3123,1041],[3915,1042],[3129,1043],[3916,1044],[3130,1045],[3132,1046],[3914,1047],[3196,1048],[2535,1049],[2534,964],[2142,986],[3917,1050],[3720,1051],[3823,1052],[4015,1053],[4042,1054],[4016,1055],[4035,1056],[4043,1057],[4017,1058],[2537,1059],[4019,1060],[4020,1053],[4044,1061],[4018,1062],[4045,1063],[4030,1064],[4046,1065],[4034,1066],[4047,1067],[4021,1068],[4022,1069],[4048,1070],[4023,1071],[4050,1072],[4049,1073],[4051,1074],[4024,1075],[4033,1076],[4028,1077],[4031,1053],[4027,1062],[4029,1078],[4032,1079],[4052,1080],[4040,1081],[4053,1082],[4038,1083],[4054,1084],[4036,1085],[4055,1086],[4039,1053],[4057,1087],[4056,1045],[4058,1088],[4037,1089],[2540,1090],[2539,1091],[3919,1092],[2544,1093],[2543,1094],[2546,1095],[3939,1096],[4007,1097],[4059,1098],[4008,1099],[4060,1100],[4009,1101],[4061,1102],[4010,1103],[2538,986],[4011,1101],[4012,1101],[4014,1103],[4041,1104],[4067,1105],[4064,1106],[4070,1107],[4069,1108],[4071,1109],[4068,1110],[4073,1111],[4062,1112],[4074,1113],[4063,1114],[4075,1115],[2612,1116],[2614,1117],[2613,1118],[4072,1119],[4065,1120],[4066,1121],[4125,1122],[3111,1123],[4127,1124],[4126,1125],[4128,1126],[4129,1127],[4130,1128],[4131,1129],[4132,1130],[3779,1131],[4133,1132],[3781,1133],[4134,1134],[3780,1131],[4135,1135],[3778,1053],[3782,1136],[4148,1137],[3651,1138],[3142,1139],[3147,964],[4246,1140],[3149,1141],[4243,1142],[3148,1143],[4247,1144],[3144,1145],[3143,1146],[4244,1147],[3141,1148],[4248,1149],[3145,1150],[3139,1103],[4249,1151],[3133,1152],[4250,1153],[3146,1154],[3138,1155],[4251,1156],[3134,1157],[4245,1158],[3140,1148],[3163,1159],[4136,1160],[3215,1161],[4252,1162],[2562,1163],[4149,1164],[3223,1165],[3220,1166],[4254,1167],[4253,1168],[4255,1169],[3218,1170],[4257,1171],[4256,1172],[2676,964],[3221,1173],[2678,1174],[2677,986],[3217,1175],[3222,1176],[2679,1177],[3216,1178],[3219,1179],[2201,964],[4164,1180],[3635,1181],[4167,1182],[3636,1183],[4168,1184],[3638,1185],[4169,1186],[3640,1187],[4165,1188],[3648,1189],[3644,1190],[4166,1191],[3642,1192],[3758,1193],[3757,1194],[2681,1195],[4258,1196],[2680,1197],[2443,1198],[4259,1199],[2446,1200],[2445,964],[2444,1201],[4150,1202],[2598,1203],[4137,1204],[3816,1205],[3230,1206],[3226,1207],[4261,1208],[4260,1209],[4262,1210],[3228,1211],[2682,964],[3229,1212],[4263,1213],[3227,1214],[2563,964],[4120,1215],[4124,1216],[4119,1217],[4122,1218],[4121,1219],[4123,1220],[2683,1221],[2684,1222],[4264,1223],[3646,1224],[4025,1225],[2536,964],[4026,1226],[2542,1053],[2541,964],[3234,1227],[4265,1228],[3231,1229],[2687,1230],[2686,1231],[3645,1232],[3232,1233],[3233,1234],[2685,964],[3652,1235],[4170,1236],[3763,1237],[4171,1238],[3760,1239],[4172,1240],[3759,1241],[4173,1242],[3762,1243],[4174,1244],[3761,1245],[2454,964],[2564,1246],[3188,1247],[2565,1131],[4275,1248],[3649,1249],[2135,1250],[4266,1251],[3184,1241],[4267,1252],[2206,1053],[4268,1253],[3168,1241],[3783,986],[4276,1254],[3717,1255],[4277,1256],[3718,1257],[4278,1258],[3719,1257],[2715,1259],[4279,1260],[2441,1261],[4280,1262],[2442,1263],[4269,1264],[2566,1055],[4270,1265],[3186,1266],[4271,1267],[3120,1268],[3182,1269],[2569,1270],[2568,1271],[4272,1272],[2618,1273],[4273,1274],[2594,1163],[3162,1275],[2570,1163],[3160,1045],[2573,1276],[2595,1277],[4274,1278],[2574,1053],[2585,1279],[2624,1214],[4281,1280],[2756,1281],[2593,1282],[3661,1282],[3167,1283],[2719,555],[4175,1284],[2630,1285],[4176,1286],[2628,1285],[4177,1287],[2642,1288],[4178,1289],[2639,1290],[2643,1291],[4181,1292],[2636,1293],[4182,1294],[2634,1295],[4183,1296],[2633,1297],[2647,1298],[2632,1299],[2631,1300],[2648,1301],[2635,1302],[4179,1303],[2627,1304],[2644,1305],[2626,1306],[4180,1307],[2629,1304],[2623,964],[2645,1308],[2640,1309],[2646,1310],[2641,1309],[4138,1311],[2601,1312],[4139,1313],[3122,1314],[4140,1315],[3818,1316],[4184,1317],[3807,1318],[4185,1319],[3806,1320],[4186,1321],[3809,1322],[4187,1323],[3808,1324],[3154,1325],[3817,1326],[2688,1327],[2689,1328],[1197,1329],[3756,1330],[4188,1331],[2655,1332],[4189,1333],[2651,1334],[4190,1335],[2652,1214],[4191,1336],[2653,1334],[2657,1337],[2650,1338],[4192,1339],[2656,1340],[2658,1341],[2654,1342],[3317,1343],[4151,1344],[3355,1345],[4286,1346],[3341,1347],[3344,1053],[3332,1163],[3331,1348],[3333,1349],[3345,1350],[4293,1351],[3346,1352],[4294,1353],[3327,1131],[3328,1131],[3330,1053],[4295,1354],[3326,1131],[3329,1053],[2693,1355],[2694,1356],[3342,1357],[3353,1358],[4287,1359],[3351,1360],[2690,964],[2691,964],[3352,1361],[4288,1362],[3347,1363],[4289,1364],[3334,964],[3335,1365],[3336,1366],[4290,1367],[3343,1368],[4282,1369],[3161,1370],[4283,1371],[3349,1372],[4284,1373],[3350,1374],[4285,1375],[3348,1376],[3337,1053],[4291,1377],[3338,1378],[4292,1379],[3339,1380],[3354,1381],[4296,1382],[3340,1055],[2692,964],[3169,1053],[3319,1148],[4194,1383],[4193,1053],[3322,1384],[4195,1385],[3325,1386],[3324,1387],[3320,1388],[4196,1389],[3321,555],[2659,964],[4197,1390],[3323,1214],[4141,1391],[2637,969],[4152,1392],[3189,964],[2602,1131],[4297,1393],[2599,986],[2696,1394],[2695,1395],[4298,1396],[3784,1397],[1195,1398],[2698,1399],[2697,1400],[3157,1055],[4198,1401],[2716,1402],[4153,1403],[2620,1404],[4299,1405],[3918,1406],[2545,964],[2567,986],[4300,1407],[4013,1408],[3170,1409],[3637,1181],[2603,1410],[4301,1411],[2606,1412],[3627,1413],[4307,1414],[3617,1415],[3613,1053],[3634,1416],[3618,1417],[4308,1418],[3606,1055],[3626,1419],[3605,1420],[3621,1421],[4309,1422],[3620,1423],[3622,1424],[4310,1425],[3629,1426],[4311,1427],[3607,1428],[4312,1429],[3633,1430],[2605,1431],[4302,1432],[3612,1053],[3625,1348],[4303,1433],[3609,1434],[3619,1435],[4304,1436],[3601,1221],[3602,1437],[3937,1434],[3603,1438],[4305,1439],[3604,1420],[3611,1440],[3610,1163],[3608,1053],[4306,1441],[3630,1442],[4313,1443],[2138,964],[3628,1444],[4314,1445],[3614,1221],[3804,1446],[3802,1163],[3803,1447],[4315,1448],[3135,1449],[4317,1450],[3137,1451],[4316,1452],[3136,1453],[3155,1454],[3124,1455],[3151,1456],[4318,1457],[3152,1458],[4319,1459],[3128,1460],[3150,1461],[2699,964],[3639,1214],[3153,1462],[3641,1181],[4154,1463],[3156,1464],[4199,1465],[3171,1466],[2661,1467],[2660,964],[4200,1468],[2717,1469],[4320,1470],[2714,1471],[2700,1472],[1191,1473],[4323,1474],[3126,1475],[4322,1476],[3125,1477],[4321,1478],[2137,1479],[4155,1480],[3118,1481],[4201,1482],[3113,1483],[4202,1484],[3114,1485],[2663,1486],[2662,964],[2664,964],[4203,1487],[3115,1488],[4204,1489],[3116,1490],[4205,1491],[3117,1492],[2616,1493],[2141,1494],[3175,1495],[4142,1496],[3716,1497],[2600,1498],[4325,1499],[2611,1500],[4324,1501],[3190,1502],[2701,1503],[2610,964],[4326,1504],[3721,1505],[4156,1506],[3722,1507],[2617,964],[2622,1508],[2621,1509],[3164,1510],[3166,1511],[3654,1512],[3174,1513],[4327,1514],[3173,1515],[3172,1516],[4329,1517],[3594,1518],[3590,1519],[3599,1520],[4330,1521],[3592,1522],[2704,1523],[2703,1524],[4331,1525],[3597,1053],[4332,1526],[3591,1527],[4333,1528],[3593,1131],[4334,1529],[3600,1530],[3588,1531],[4335,1532],[3589,1533],[4336,1534],[3356,1535],[4337,1536],[3596,1537],[3595,1538],[4328,1539],[3191,1540],[3598,1454],[2702,964],[3131,1541],[3747,1542],[3727,1355],[3746,1543],[3736,1197],[3741,1544],[3737,1545],[3740,1055],[3738,1546],[2708,1547],[2709,1548],[3735,1131],[3739,555],[3733,1549],[3743,1550],[3745,1551],[3730,1552],[3725,1553],[3729,1554],[3734,1555],[3742,1045],[4338,1556],[3731,1557],[2705,964],[2707,1558],[2706,1559],[4339,1560],[3744,1163],[3726,1561],[3724,1562],[3723,1563],[3728,1131],[3732,1053],[4157,1564],[2625,964],[4158,1565],[3647,1566],[3158,1055],[3225,555],[3159,1348],[4345,1567],[3235,1568],[4340,1569],[2575,1131],[4341,1570],[2576,1131],[4342,1571],[2579,1572],[4343,1573],[2577,1131],[4344,1574],[2578,1131],[3316,1575],[3315,1576],[3314,1577],[2512,964],[3197,1578],[3750,1579],[3755,1580],[3748,1541],[3751,1581],[4208,1582],[3754,1583],[3176,1163],[4206,1584],[3752,1585],[4207,1586],[3753,1587],[3749,964],[4159,1588],[3765,1589],[2665,964],[3210,1590],[3212,1591],[4209,1592],[3211,1241],[4210,1593],[3198,1594],[4211,1595],[3624,1596],[4212,1597],[3623,1598],[2667,1599],[2666,1103],[2668,964],[4218,1600],[3200,1601],[4219,1602],[3199,1603],[4220,1604],[3201,1605],[4221,1606],[3202,1607],[4213,1608],[3203,1257],[4214,1609],[3204,1610],[4215,1611],[3207,1612],[4216,1613],[3205,1241],[4217,1614],[3206,1615],[2670,1616],[2669,1617],[4222,1618],[3208,1619],[4223,1620],[3209,1621],[4224,1622],[3764,1623],[2671,964],[4225,1624],[2584,1625],[4226,1626],[2581,1257],[2582,1257],[4228,1627],[3313,1628],[4227,1629],[2583,1630],[4347,1631],[3318,1632],[4348,1633],[3653,1634],[4346,1635],[2607,1636],[2136,964],[2571,1214],[3224,1214],[3643,1637],[4143,1638],[3213,1639],[4349,1640],[3770,1257],[4350,1641],[3769,1642],[3771,1643],[4351,1644],[3766,1645],[4352,1646],[3768,1257],[4353,1647],[3767,1642],[4356,1648],[3774,1649],[3775,1650],[3772,1651],[4354,1652],[3938,1653],[4355,1654],[3773,1655],[1194,964],[4362,1656],[3713,1657],[3177,1658],[4357,1659],[3178,1326],[4358,1660],[2572,1661],[4363,1662],[3180,1663],[3181,1664],[4364,1665],[3179,964],[2711,1666],[2710,964],[4359,1667],[3195,1668],[4360,1669],[3183,1670],[4361,1671],[3194,1672],[2712,998],[4144,1673],[3714,1674],[4367,1675],[3192,1676],[4368,1677],[4369,1678],[3193,1679],[4365,1680],[3187,1681],[4366,1682],[3799,1683],[3800,1684],[4229,1685],[3798,1131],[4145,1686],[3801,1687],[3776,1355],[4370,1688],[3715,1689],[4371,1690],[3119,1691],[3777,1692],[3214,1235],[4146,1693],[3787,1694],[4147,1695],[2619,1696],[4234,1697],[3657,1698],[4235,1699],[3658,1700],[4236,1701],[3659,1702],[4233,1703],[3660,1704],[4237,1705],[3664,1706],[4238,1707],[3665,1708],[4239,1709],[3662,1710],[4240,1711],[3663,1712],[4230,1713],[3650,1714],[4231,1715],[3710,1716],[4232,1717],[3712,1718],[4241,1719],[3711,1053],[2673,1720],[2672,964],[2675,1721],[2674,964],[4160,1722],[3655,1723],[4161,1724],[3786,1725],[4162,1726],[3815,1727],[4372,1728],[3795,1729],[4373,1730],[3793,1731],[3797,1732],[4374,1733],[3794,1148],[4375,1734],[3796,1735],[2608,964],[3792,1736],[4376,1737],[3790,1738],[4377,1739],[2609,1740],[4378,1741],[3788,1742],[3791,1743],[3789,964],[3811,1744],[3810,1745],[2759,1746],[4379,1747],[2774,555],[2713,964],[4380,1748],[2773,1749],[4382,1750],[4381,555],[2772,1053],[2761,1751],[2764,1752],[4390,1753],[2763,555],[2770,1163],[2769,555],[4391,1754],[2771,1755],[4392,1756],[2768,555],[4386,1757],[3814,1758],[4387,1759],[2760,1760],[4393,1761],[2793,1053],[2765,964],[2766,1762],[4394,1763],[2796,1764],[2803,1765],[4395,1766],[2797,1767],[2780,1768],[4396,1769],[2801,1770],[2802,1771],[4397,1772],[2798,1773],[2790,964],[2791,1774],[4398,1775],[2800,1776],[4399,1777],[2799,1778],[2792,1689],[4400,1779],[2795,1780],[4401,1781],[2794,1782],[2777,1241],[4402,1783],[2776,1784],[2767,1785],[2781,964],[4388,1786],[3812,1787],[3813,1788],[4384,1789],[4383,1790],[3165,1791],[4389,1792],[2757,555],[2784,1793],[2789,1794],[2785,1795],[2786,1796],[2787,1797],[4403,1798],[2788,1799],[2782,964],[2804,1798],[2783,1800],[4385,1801],[2758,964],[2762,1802],[2775,1803],[3127,964],[3656,1804],[4163,1805],[3822,1806],[3819,1807],[4404,1808],[3821,1809],[1196,964],[4405,1810],[3820,1811],[4242,1812],[3785,1813],[3805,1348],[3108,1814],[3109,1815],[3110,1816],[3112,1817],[2810,1818],[2808,1818],[2807,1818],[2809,1819],[2806,1818],[2805,1818],[2811,986],[4407,1820],[2814,1821],[2812,555],[4406,1822],[3615,1823],[3616,1824],[3631,1825],[3632,1826],[2813,1827],[2440,1828],[2815,1829],[2139,964],[2816,1830],[2140,964],[2817,2],[610,964],[2818,1831],[2819,1832],[1193,1833],[2820,1834],[2547,1835],[2821,964],[2823,1836],[2822,964],[2824,1837],[2178,1838],[3080,1839],[3079,1840],[3082,1841],[3081,964],[3083,1842],[2185,964],[3085,1843],[3084,964],[3086,1844],[1192,964],[3087,1845],[2604,964],[3088,1846],[2615,986],[3089,964],[3090,1847],[2508,986],[3091,1848],[2179,964],[3092,1849],[2180,986],[3093,964],[3094,1850],[2649,1400],[3095,1851],[2134,964],[531,964],[4408,1852],[3100,1853],[3104,1854],[3907,1855],[4409,1856],[609,1857]],"semanticDiagnosticsPerFile":[[2471,[{"start":5247,"length":6,"code":2322,"category":1,"messageText":"Type 'string' is not assignable to type 'undefined'."}]],[2473,[{"start":1354,"length":1404,"code":2741,"category":1,"messageText":"Property 'last_active' is missing in type '{ token: string; token_id: string; key_name: string; key_alias: string; spend: number; max_budget: number; expires: string; models: string[]; aliases: {}; config: {}; user_id: string; team_id: null; project_id: null; ... 40 more ...; user_email: string; }' but required in type 'KeyResponse'.","relatedInformation":[{"file":"./src/components/key_team_helpers/key_list.tsx","start":1578,"length":11,"messageText":"'last_active' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ token: string; token_id: string; key_name: string; key_alias: string; spend: number; max_budget: number; expires: string; models: string[]; aliases: {}; config: {}; user_id: string; team_id: null; project_id: null; ... 40 more ...; user_email: string; }' is not assignable to type 'KeyResponse'."}},{"start":2762,"length":1423,"code":2741,"category":1,"messageText":"Property 'last_active' is missing in type '{ token: string; token_id: string; key_name: string; key_alias: string; spend: number; max_budget: number; expires: string; models: string[]; aliases: {}; config: {}; user_id: string; team_id: string; ... 41 more ...; user_email: string; }' but required in type 'KeyResponse'.","relatedInformation":[{"file":"./src/components/key_team_helpers/key_list.tsx","start":1578,"length":11,"messageText":"'last_active' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ token: string; token_id: string; key_name: string; key_alias: string; spend: number; max_budget: number; expires: string; models: string[]; aliases: {}; config: {}; user_id: string; team_id: string; ... 41 more ...; user_email: string; }' is not assignable to type 'KeyResponse'."}}]],[2533,[{"start":1387,"length":5,"code":2322,"category":1,"messageText":{"messageText":"Type '{ user_id: string; user_email: string; user_alias: null; user_role: string; spend: number; max_budget: null; key_count: number; created_at: string; updated_at: string; sso_user_id: null; budget_duration: null; }[]' is not assignable to type 'UserInfo[]'.","category":1,"code":2322,"next":[{"messageText":"Property 'models' is missing in type '{ user_id: string; user_email: string; user_alias: null; user_role: string; spend: number; max_budget: null; key_count: number; created_at: string; updated_at: string; sso_user_id: null; budget_duration: null; }' but required in type 'UserInfo'.","category":1,"code":2741,"canonicalHead":{"code":2322,"messageText":"Type '{ user_id: string; user_email: string; user_alias: null; user_role: string; spend: number; max_budget: null; key_count: number; created_at: string; updated_at: string; sso_user_id: null; budget_duration: null; }' is not assignable to type 'UserInfo'."}}]},"relatedInformation":[{"file":"./src/components/view_users/types.ts","start":167,"length":6,"messageText":"'models' is declared here.","category":3,"code":2728},{"file":"./src/components/networking.tsx","start":30183,"length":5,"messageText":"The expected type comes from property 'users' which is declared here on type 'UserListResponse'","category":3,"code":6500}]}]],[2707,[{"start":328,"length":6,"code":2741,"category":1,"messageText":"Property 'environment' is missing in type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' but required in type 'PromptType'.","relatedInformation":[{"file":"./src/components/prompts/prompt_editor_view/types.ts","start":368,"length":11,"messageText":"'environment' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' is not assignable to type 'PromptType'."}},{"start":784,"length":6,"code":2741,"category":1,"messageText":"Property 'environment' is missing in type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' but required in type 'PromptType'.","relatedInformation":[{"file":"./src/components/prompts/prompt_editor_view/types.ts","start":368,"length":11,"messageText":"'environment' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' is not assignable to type 'PromptType'."}},{"start":1182,"length":6,"code":2741,"category":1,"messageText":"Property 'environment' is missing in type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' but required in type 'PromptType'.","relatedInformation":[{"file":"./src/components/prompts/prompt_editor_view/types.ts","start":368,"length":11,"messageText":"'environment' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' is not assignable to type 'PromptType'."}},{"start":1684,"length":6,"code":2741,"category":1,"messageText":"Property 'environment' is missing in type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' but required in type 'PromptType'.","relatedInformation":[{"file":"./src/components/prompts/prompt_editor_view/types.ts","start":368,"length":11,"messageText":"'environment' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' is not assignable to type 'PromptType'."}},{"start":2044,"length":6,"code":2741,"category":1,"messageText":"Property 'environment' is missing in type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' but required in type 'PromptType'.","relatedInformation":[{"file":"./src/components/prompts/prompt_editor_view/types.ts","start":368,"length":11,"messageText":"'environment' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' is not assignable to type 'PromptType'."}},{"start":2529,"length":6,"code":2741,"category":1,"messageText":"Property 'environment' is missing in type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' but required in type 'PromptType'.","relatedInformation":[{"file":"./src/components/prompts/prompt_editor_view/types.ts","start":368,"length":11,"messageText":"'environment' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' is not assignable to type 'PromptType'."}},{"start":3149,"length":6,"code":2741,"category":1,"messageText":"Property 'environment' is missing in type '{ name: string; model: string; config: { temperature: number; max_tokens: number; top_p: number; }; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' but required in type 'PromptType'.","relatedInformation":[{"file":"./src/components/prompts/prompt_editor_view/types.ts","start":368,"length":11,"messageText":"'environment' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ name: string; model: string; config: { temperature: number; max_tokens: number; top_p: number; }; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' is not assignable to type 'PromptType'."}},{"start":3683,"length":6,"code":2741,"category":1,"messageText":"Property 'environment' is missing in type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' but required in type 'PromptType'.","relatedInformation":[{"file":"./src/components/prompts/prompt_editor_view/types.ts","start":368,"length":11,"messageText":"'environment' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' is not assignable to type 'PromptType'."}},{"start":4135,"length":6,"code":2741,"category":1,"messageText":"Property 'environment' is missing in type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' but required in type 'PromptType'.","relatedInformation":[{"file":"./src/components/prompts/prompt_editor_view/types.ts","start":368,"length":11,"messageText":"'environment' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' is not assignable to type 'PromptType'."}},{"start":4538,"length":6,"code":2741,"category":1,"messageText":"Property 'environment' is missing in type '{ name: string; model: string; config: {}; tools: { name: string; description: string; json: string; }[]; developerMessage: string; messages: { role: string; content: string; }[]; }' but required in type 'PromptType'.","relatedInformation":[{"file":"./src/components/prompts/prompt_editor_view/types.ts","start":368,"length":11,"messageText":"'environment' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ name: string; model: string; config: {}; tools: { name: string; description: string; json: string; }[]; developerMessage: string; messages: { role: string; content: string; }[]; }' is not assignable to type 'PromptType'."}},{"start":5174,"length":6,"code":2741,"category":1,"messageText":"Property 'environment' is missing in type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' but required in type 'PromptType'.","relatedInformation":[{"file":"./src/components/prompts/prompt_editor_view/types.ts","start":368,"length":11,"messageText":"'environment' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' is not assignable to type 'PromptType'."}}]],[2815,[{"start":1240,"length":3,"messageText":"Tuple type '[]' of length '0' has no element at index '0'.","category":1,"code":2493},{"start":1245,"length":4,"messageText":"Tuple type '[]' of length '0' has no element at index '1'.","category":1,"code":2493},{"start":1409,"length":4,"messageText":"'init' is possibly 'undefined'.","category":1,"code":18048},{"start":1534,"length":4,"messageText":"'init' is possibly 'undefined'.","category":1,"code":18048},{"start":1861,"length":4,"messageText":"Tuple type '[]' of length '0' has no element at index '1'.","category":1,"code":2493},{"start":1905,"length":4,"messageText":"'init' is possibly 'undefined'.","category":1,"code":18048},{"start":1943,"length":4,"messageText":"'init' is possibly 'undefined'.","category":1,"code":18048},{"start":3779,"length":4,"messageText":"Tuple type '[]' of length '0' has no element at index '1'.","category":1,"code":2493},{"start":3823,"length":4,"messageText":"'init' is possibly 'undefined'.","category":1,"code":18048}]],[3091,[{"start":227,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":309,"length":10,"messageText":"Cannot find name 'beforeEach'.","category":1,"code":2304},{"start":862,"length":9,"messageText":"Cannot find name 'afterEach'.","category":1,"code":2304},{"start":1031,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1069,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1154,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1231,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1293,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1436,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1515,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1620,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1667,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1712,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1791,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1873,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1931,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1978,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2325,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2402,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2710,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2757,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2805,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":3146,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3274,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":3599,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3648,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":3993,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4055,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":4092,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":4515,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4591,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":5256,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":5333,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":5600,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":5647,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":5688,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":5754,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":5818,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":5881,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":5938,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6003,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":6127,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6281,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6370,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":6428,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6494,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6567,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":6612,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6667,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":6714,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6778,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":6851,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6922,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6995,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":7040,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7135,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":7518,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7596,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":8048,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":8128,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":8605,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":8746,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":8872,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":8950,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":8991,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":9776,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9846,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9900,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":10185,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":10228,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":11085,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":11162,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":11433,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[3092,[{"start":3234,"length":405,"code":2741,"category":1,"messageText":"Property 'spend' is missing in type '{ team_id: string; team_alias: string; models: never[]; max_budget: null; budget_duration: null; tpm_limit: null; rpm_limit: null; organization_id: string; created_at: string; keys: never[]; members_with_roles: { ...; }[]; }' but required in type 'Team'.","relatedInformation":[{"file":"./src/components/key_team_helpers/key_list.tsx","start":480,"length":5,"messageText":"'spend' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ team_id: string; team_alias: string; models: never[]; max_budget: null; budget_duration: null; tpm_limit: null; rpm_limit: null; organization_id: string; created_at: string; keys: never[]; members_with_roles: { ...; }[]; }' is not assignable to type 'Team'."}},{"start":3649,"length":406,"code":2741,"category":1,"messageText":"Property 'spend' is missing in type '{ team_id: string; team_alias: string; models: never[]; max_budget: null; budget_duration: null; tpm_limit: null; rpm_limit: null; organization_id: string; created_at: string; keys: never[]; members_with_roles: { ...; }[]; }' but required in type 'Team'.","relatedInformation":[{"file":"./src/components/key_team_helpers/key_list.tsx","start":480,"length":5,"messageText":"'spend' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ team_id: string; team_alias: string; models: never[]; max_budget: null; budget_duration: null; tpm_limit: null; rpm_limit: null; organization_id: string; created_at: string; keys: never[]; members_with_roles: { ...; }[]; }' is not assignable to type 'Team'."}},{"start":4255,"length":405,"code":2741,"category":1,"messageText":"Property 'spend' is missing in type '{ team_id: string; team_alias: string; models: never[]; max_budget: null; budget_duration: null; tpm_limit: null; rpm_limit: null; organization_id: string; created_at: string; keys: never[]; members_with_roles: { ...; }[]; }' but required in type 'Team'.","relatedInformation":[{"file":"./src/components/key_team_helpers/key_list.tsx","start":480,"length":5,"messageText":"'spend' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ team_id: string; team_alias: string; models: never[]; max_budget: null; budget_duration: null; tpm_limit: null; rpm_limit: null; organization_id: string; created_at: string; keys: never[]; members_with_roles: { ...; }[]; }' is not assignable to type 'Team'."}},{"start":4670,"length":406,"code":2741,"category":1,"messageText":"Property 'spend' is missing in type '{ team_id: string; team_alias: string; models: never[]; max_budget: null; budget_duration: null; tpm_limit: null; rpm_limit: null; organization_id: string; created_at: string; keys: never[]; members_with_roles: { ...; }[]; }' but required in type 'Team'.","relatedInformation":[{"file":"./src/components/key_team_helpers/key_list.tsx","start":480,"length":5,"messageText":"'spend' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ team_id: string; team_alias: string; models: never[]; max_budget: null; budget_duration: null; tpm_limit: null; rpm_limit: null; organization_id: string; created_at: string; keys: never[]; members_with_roles: { ...; }[]; }' is not assignable to type 'Team'."}}]],[3915,[{"start":3783,"length":17,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}}]],[4071,[{"start":2903,"length":4,"code":2741,"category":1,"messageText":"Property 'user_alias' is missing in type '{ user_id: string; user_email: string; }' but required in type '{ user_id: string; user_email: string; user_alias: string | null; }'.","relatedInformation":[{"file":"./src/components/key_team_helpers/key_list.tsx","start":3048,"length":10,"messageText":"'user_alias' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ user_id: string; user_email: string; }' is not assignable to type '{ user_id: string; user_email: string; user_alias: string | null; }'."}}]],[4127,[{"start":9097,"length":9,"messageText":"Cannot find name 'afterEach'.","category":1,"code":2304}]],[4139,[{"start":401,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":442,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":647,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":711,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":957,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1064,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1307,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1383,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1651,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1701,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1948,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1998,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2220,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2297,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2528,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[4142,[{"start":24644,"length":82,"code":2740,"category":1,"messageText":"Type '{ organization_id: string; organization_alias: string; models: never[]; members: never[]; }' is missing the following properties from type 'Organization': budget_id, metadata, spend, model_spend, and 7 more.","canonicalHead":{"code":2322,"messageText":"Type '{ organization_id: string; organization_alias: string; models: never[]; members: never[]; }' is not assignable to type 'Organization'."}}]],[4164,[{"start":1907,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1950,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2028,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2097,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2301,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2405,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2514,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2577,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2677,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2739,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2854,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2921,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":3036,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3099,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3162,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3223,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":3332,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3395,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3467,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":3704,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3819,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3960,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":4069,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4131,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":4307,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4368,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":4464,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4556,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":4782,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4842,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":5078,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[4173,[{"start":2211,"length":27,"code":2769,"category":1,"messageText":{"messageText":"No overload matches this call.","category":1,"code":2769,"next":[{"messageText":"Overload 1 of 2, '(id: Matcher, options?: SelectorMatcherOptions | undefined): HTMLElement', gave the following error.","category":1,"code":2772,"next":[{"messageText":"Argument of type 'string | null' is not assignable to parameter of type 'Matcher'.","category":1,"code":2345,"next":[{"messageText":"Type 'null' is not assignable to type 'Matcher'.","category":1,"code":2322}]}]},{"messageText":"Overload 2 of 2, '(id: Matcher, options?: SelectorMatcherOptions | undefined): HTMLElement', gave the following error.","category":1,"code":2772,"next":[{"messageText":"Argument of type 'string | null' is not assignable to parameter of type 'Matcher'.","category":1,"code":2345,"next":[{"messageText":"Type 'null' is not assignable to type 'Matcher'.","category":1,"code":2322}]}]}]},"relatedInformation":[]},{"start":2290,"length":26,"code":2769,"category":1,"messageText":{"messageText":"No overload matches this call.","category":1,"code":2769,"next":[{"messageText":"Overload 1 of 2, '(id: Matcher, options?: SelectorMatcherOptions | undefined): HTMLElement', gave the following error.","category":1,"code":2772,"next":[{"messageText":"Argument of type 'string | null' is not assignable to parameter of type 'Matcher'.","category":1,"code":2345,"next":[{"messageText":"Type 'null' is not assignable to type 'Matcher'.","category":1,"code":2322}]}]},{"messageText":"Overload 2 of 2, '(id: Matcher, options?: SelectorMatcherOptions | undefined): HTMLElement', gave the following error.","category":1,"code":2772,"next":[{"messageText":"Argument of type 'string | null' is not assignable to parameter of type 'Matcher'.","category":1,"code":2345,"next":[{"messageText":"Type 'null' is not assignable to type 'Matcher'.","category":1,"code":2322}]}]}]},"relatedInformation":[]}]],[4184,[{"start":505,"length":14,"code":2739,"category":1,"messageText":"Type '{ token: string; token_id: string; key_name: string; key_alias: string; spend: number; max_budget: number; expires: string; models: string[]; aliases: {}; config: {}; user_id: string; team_id: string; ... 42 more ...; deleted_by: string; }' is missing the following properties from type 'DeletedKeyResponse': project_id, last_active","canonicalHead":{"code":2322,"messageText":"Type '{ token: string; token_id: string; key_name: string; key_alias: string; spend: number; max_budget: number; expires: string; models: string[]; aliases: {}; config: {}; user_id: string; team_id: string; ... 42 more ...; deleted_by: string; }' is not assignable to type 'DeletedKeyResponse'."}}]],[4185,[{"start":307,"length":14,"code":2739,"category":1,"messageText":"Type '{ token: string; token_id: string; key_name: string; key_alias: string; spend: number; max_budget: number; expires: string; models: string[]; aliases: {}; config: {}; user_id: string; team_id: string; ... 42 more ...; deleted_by: string; }' is missing the following properties from type 'DeletedKeyResponse': project_id, last_active","canonicalHead":{"code":2322,"messageText":"Type '{ token: string; token_id: string; key_name: string; key_alias: string; spend: number; max_budget: number; expires: string; models: string[]; aliases: {}; config: {}; user_id: string; team_id: string; ... 42 more ...; deleted_by: string; }' is not assignable to type 'DeletedKeyResponse'."}}]],[4189,[{"start":162,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":205,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":319,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":384,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":517,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":602,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":751,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[4190,[{"start":119,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":155,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":397,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":451,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":699,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":788,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":880,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1165,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1233,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1492,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1560,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1820,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[4191,[{"start":211,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":252,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":384,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":454,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":615,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":698,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":788,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":875,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1063,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1159,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1503,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1570,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1738,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[4192,[{"start":877,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":917,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1015,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1106,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1371,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1444,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1769,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1848,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2010,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2082,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2521,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[4194,[{"start":198,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":366,"length":9,"messageText":"Cannot find name 'afterEach'.","category":1,"code":2304},{"start":417,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":500,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":569,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":702,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":944,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1191,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1266,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1353,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1621,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1713,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2502,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2590,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2781,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2875,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2988,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3090,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":3429,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3509,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":3922,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4001,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":4113,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4271,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[4196,[{"start":144,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":177,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":286,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":359,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":488,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":554,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":618,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":732,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":793,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":961,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1031,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1172,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1248,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1396,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1468,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1599,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[4203,[{"start":234,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":274,"length":10,"messageText":"Cannot find name 'beforeEach'.","category":1,"code":2304},{"start":328,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":586,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":734,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":801,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":860,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":934,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1176,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1270,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1593,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1754,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1854,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2165,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2265,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2978,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[4229,[{"start":208,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":242,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":313,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":387,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":451,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":525,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":605,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":681,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":716,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":864,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":932,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1101,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1167,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1339,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1423,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1528,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1706,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1817,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1863,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1916,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1971,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2038,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2089,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[4239,[{"start":1769,"length":8,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":13971,"length":49,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ accessToken: string; token: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ accessToken: string; token: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}}]],[4242,[{"start":2275,"length":7,"code":2741,"category":1,"messageText":"Property 'project_id' is missing in type '{ token: string; token_id: string; key_name: string; key_alias: string; spend: number; max_budget: number; expires: string; models: string[]; aliases: {}; config: {}; user_id: string; team_id: string; ... 43 more ...; user: { ...; }; }' but required in type 'KeyResponse'.","relatedInformation":[{"file":"./src/components/key_team_helpers/key_list.tsx","start":947,"length":10,"messageText":"'project_id' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ token: string; token_id: string; key_name: string; key_alias: string; spend: number; max_budget: number; expires: string; models: string[]; aliases: {}; config: {}; user_id: string; team_id: string; ... 43 more ...; user: { ...; }; }' is not assignable to type 'KeyResponse'."}},{"start":3893,"length":8,"code":2741,"category":1,"messageText":"Property 'spend' is missing in type '{ team_id: string; team_alias: string; models: string[]; max_budget: number; budget_duration: string; tpm_limit: number; rpm_limit: number; organization_id: string; created_at: string; keys: never[]; members_with_roles: never[]; }' but required in type 'Team'.","relatedInformation":[{"file":"./src/components/key_team_helpers/key_list.tsx","start":480,"length":5,"messageText":"'spend' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ team_id: string; team_alias: string; models: string[]; max_budget: number; budget_duration: string; tpm_limit: number; rpm_limit: number; organization_id: string; created_at: string; keys: never[]; members_with_roles: never[]; }' is not assignable to type 'Team'."}},{"start":8218,"length":335,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ filters: { \"Team ID\": string; \"Organization ID\": string; \"Key Alias\": string; \"User ID\": string; \"Sort By\": string; \"Sort Order\": string; }; filteredKeys: never[]; allTeams: Team[]; allOrganizations: Organization[]; handleFilterChange: Mock<...>; handleFilterReset: Mock<...>; }' is not assignable to parameter of type '{ filters: FilterState; filteredKeys: KeyResponse[]; filteredTotalCount: number | null; allTeams: Team[]; allOrganizations: Organization[]; handleFilterChange: (newFilters: Record, skipDebounce?: boolean) => void; handleFilterReset: () => void; }'.","category":1,"code":2345,"next":[{"messageText":"Property 'filteredTotalCount' is missing in type '{ filters: { \"Team ID\": string; \"Organization ID\": string; \"Key Alias\": string; \"User ID\": string; \"Sort By\": string; \"Sort Order\": string; }; filteredKeys: never[]; allTeams: Team[]; allOrganizations: Organization[]; handleFilterChange: Mock<...>; handleFilterReset: Mock<...>; }' but required in type '{ filters: FilterState; filteredKeys: KeyResponse[]; filteredTotalCount: number | null; allTeams: Team[]; allOrganizations: Organization[]; handleFilterChange: (newFilters: Record, skipDebounce?: boolean) => void; handleFilterReset: () => void; }'.","category":1,"code":2741}]},"relatedInformation":[{"file":"./src/components/key_team_helpers/filter_logic.tsx","start":5626,"length":18,"messageText":"'filteredTotalCount' is declared here.","category":3,"code":2728}]},{"start":9154,"length":352,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ filters: { \"Team ID\": string; \"Organization ID\": string; \"Key Alias\": string; \"User ID\": string; \"Sort By\": string; \"Sort Order\": string; }; filteredKeys: { models: string[]; token: string; token_id: string; ... 66 more ...; created_by_user?: { user_id: string; user_email: string; user_alias: string | null; }; }[]...' is not assignable to parameter of type '{ filters: FilterState; filteredKeys: KeyResponse[]; filteredTotalCount: number | null; allTeams: Team[]; allOrganizations: Organization[]; handleFilterChange: (newFilters: Record, skipDebounce?: boolean) => void; handleFilterReset: () => void; }'.","category":1,"code":2345,"next":[{"messageText":"Property 'filteredTotalCount' is missing in type '{ filters: { \"Team ID\": string; \"Organization ID\": string; \"Key Alias\": string; \"User ID\": string; \"Sort By\": string; \"Sort Order\": string; }; filteredKeys: { models: string[]; token: string; token_id: string; ... 66 more ...; created_by_user?: { user_id: string; user_email: string; user_alias: string | null; }; }[]...' but required in type '{ filters: FilterState; filteredKeys: KeyResponse[]; filteredTotalCount: number | null; allTeams: Team[]; allOrganizations: Organization[]; handleFilterChange: (newFilters: Record, skipDebounce?: boolean) => void; handleFilterReset: () => void; }'.","category":1,"code":2741}]},"relatedInformation":[{"file":"./src/components/key_team_helpers/filter_logic.tsx","start":5626,"length":18,"messageText":"'filteredTotalCount' is declared here.","category":3,"code":2728}]},{"start":13592,"length":355,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ filters: { \"Team ID\": string; \"Organization ID\": string; \"Key Alias\": string; \"User ID\": string; \"Sort By\": string; \"Sort Order\": string; }; filteredKeys: { user_id: string; user_email: string; user: { user_id: string; user_email: string; user_alias: null; }; ... 66 more ...; created_by_user?: { user_id: string; u...' is not assignable to parameter of type '{ filters: FilterState; filteredKeys: KeyResponse[]; filteredTotalCount: number | null; allTeams: Team[]; allOrganizations: Organization[]; handleFilterChange: (newFilters: Record, skipDebounce?: boolean) => void; handleFilterReset: () => void; }'.","category":1,"code":2345,"next":[{"messageText":"Property 'filteredTotalCount' is missing in type '{ filters: { \"Team ID\": string; \"Organization ID\": string; \"Key Alias\": string; \"User ID\": string; \"Sort By\": string; \"Sort Order\": string; }; filteredKeys: { user_id: string; user_email: string; user: { user_id: string; user_email: string; user_alias: null; }; ... 66 more ...; created_by_user?: { user_id: string; u...' but required in type '{ filters: FilterState; filteredKeys: KeyResponse[]; filteredTotalCount: number | null; allTeams: Team[]; allOrganizations: Organization[]; handleFilterChange: (newFilters: Record, skipDebounce?: boolean) => void; handleFilterReset: () => void; }'.","category":1,"code":2741}]},"relatedInformation":[{"file":"./src/components/key_team_helpers/filter_logic.tsx","start":5626,"length":18,"messageText":"'filteredTotalCount' is declared here.","category":3,"code":2728}]},{"start":14559,"length":358,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ filters: { \"Team ID\": string; \"Organization ID\": string; \"Key Alias\": string; \"User ID\": string; \"Sort By\": string; \"Sort Order\": string; }; filteredKeys: { created_by: string; token: string; token_id: string; ... 66 more ...; created_by_user?: { user_id: string; user_email: string; user_alias: string | null; }; }...' is not assignable to parameter of type '{ filters: FilterState; filteredKeys: KeyResponse[]; filteredTotalCount: number | null; allTeams: Team[]; allOrganizations: Organization[]; handleFilterChange: (newFilters: Record, skipDebounce?: boolean) => void; handleFilterReset: () => void; }'.","category":1,"code":2345,"next":[{"messageText":"Property 'filteredTotalCount' is missing in type '{ filters: { \"Team ID\": string; \"Organization ID\": string; \"Key Alias\": string; \"User ID\": string; \"Sort By\": string; \"Sort Order\": string; }; filteredKeys: { created_by: string; token: string; token_id: string; ... 66 more ...; created_by_user?: { user_id: string; user_email: string; user_alias: string | null; }; }...' but required in type '{ filters: FilterState; filteredKeys: KeyResponse[]; filteredTotalCount: number | null; allTeams: Team[]; allOrganizations: Organization[]; handleFilterChange: (newFilters: Record, skipDebounce?: boolean) => void; handleFilterReset: () => void; }'.","category":1,"code":2741}]},"relatedInformation":[{"file":"./src/components/key_team_helpers/filter_logic.tsx","start":5626,"length":18,"messageText":"'filteredTotalCount' is declared here.","category":3,"code":2728}]},{"start":15784,"length":355,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ filters: { \"Team ID\": string; \"Organization ID\": string; \"Key Alias\": string; \"User ID\": string; \"Sort By\": string; \"Sort Order\": string; }; filteredKeys: { created_by: string; created_by_user: { user_id: string; user_email: string; user_alias: null; }; ... 67 more ...; user?: { user_id: string; user_email: string...' is not assignable to parameter of type '{ filters: FilterState; filteredKeys: KeyResponse[]; filteredTotalCount: number | null; allTeams: Team[]; allOrganizations: Organization[]; handleFilterChange: (newFilters: Record, skipDebounce?: boolean) => void; handleFilterReset: () => void; }'.","category":1,"code":2345,"next":[{"messageText":"Property 'filteredTotalCount' is missing in type '{ filters: { \"Team ID\": string; \"Organization ID\": string; \"Key Alias\": string; \"User ID\": string; \"Sort By\": string; \"Sort Order\": string; }; filteredKeys: { created_by: string; created_by_user: { user_id: string; user_email: string; user_alias: null; }; ... 67 more ...; user?: { user_id: string; user_email: string...' but required in type '{ filters: FilterState; filteredKeys: KeyResponse[]; filteredTotalCount: number | null; allTeams: Team[]; allOrganizations: Organization[]; handleFilterChange: (newFilters: Record, skipDebounce?: boolean) => void; handleFilterReset: () => void; }'.","category":1,"code":2741}]},"relatedInformation":[{"file":"./src/components/key_team_helpers/filter_logic.tsx","start":5626,"length":18,"messageText":"'filteredTotalCount' is declared here.","category":3,"code":2728}]},{"start":16868,"length":355,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ filters: { \"Team ID\": string; \"Organization ID\": string; \"Key Alias\": string; \"User ID\": string; \"Sort By\": string; \"Sort Order\": string; }; filteredKeys: { created_by: string; created_by_user: { user_id: string; user_email: string; user_alias: string; }; ... 67 more ...; user?: { user_id: string; user_email: stri...' is not assignable to parameter of type '{ filters: FilterState; filteredKeys: KeyResponse[]; filteredTotalCount: number | null; allTeams: Team[]; allOrganizations: Organization[]; handleFilterChange: (newFilters: Record, skipDebounce?: boolean) => void; handleFilterReset: () => void; }'.","category":1,"code":2345,"next":[{"messageText":"Property 'filteredTotalCount' is missing in type '{ filters: { \"Team ID\": string; \"Organization ID\": string; \"Key Alias\": string; \"User ID\": string; \"Sort By\": string; \"Sort Order\": string; }; filteredKeys: { created_by: string; created_by_user: { user_id: string; user_email: string; user_alias: string; }; ... 67 more ...; user?: { user_id: string; user_email: stri...' but required in type '{ filters: FilterState; filteredKeys: KeyResponse[]; filteredTotalCount: number | null; allTeams: Team[]; allOrganizations: Organization[]; handleFilterChange: (newFilters: Record, skipDebounce?: boolean) => void; handleFilterReset: () => void; }'.","category":1,"code":2741}]},"relatedInformation":[{"file":"./src/components/key_team_helpers/filter_logic.tsx","start":5626,"length":18,"messageText":"'filteredTotalCount' is declared here.","category":3,"code":2728}]},{"start":17800,"length":352,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ filters: { \"Team ID\": string; \"Organization ID\": string; \"Key Alias\": string; \"User ID\": string; \"Sort By\": string; \"Sort Order\": string; }; filteredKeys: { models: string[]; token: string; token_id: string; ... 66 more ...; created_by_user?: { user_id: string; user_email: string; user_alias: string | null; }; }[]...' is not assignable to parameter of type '{ filters: FilterState; filteredKeys: KeyResponse[]; filteredTotalCount: number | null; allTeams: Team[]; allOrganizations: Organization[]; handleFilterChange: (newFilters: Record, skipDebounce?: boolean) => void; handleFilterReset: () => void; }'.","category":1,"code":2345,"next":[{"messageText":"Property 'filteredTotalCount' is missing in type '{ filters: { \"Team ID\": string; \"Organization ID\": string; \"Key Alias\": string; \"User ID\": string; \"Sort By\": string; \"Sort Order\": string; }; filteredKeys: { models: string[]; token: string; token_id: string; ... 66 more ...; created_by_user?: { user_id: string; user_email: string; user_alias: string | null; }; }[]...' but required in type '{ filters: FilterState; filteredKeys: KeyResponse[]; filteredTotalCount: number | null; allTeams: Team[]; allOrganizations: Organization[]; handleFilterChange: (newFilters: Record, skipDebounce?: boolean) => void; handleFilterReset: () => void; }'.","category":1,"code":2741}]},"relatedInformation":[{"file":"./src/components/key_team_helpers/filter_logic.tsx","start":5626,"length":18,"messageText":"'filteredTotalCount' is declared here.","category":3,"code":2728}]},{"start":18783,"length":357,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ filters: { \"Team ID\": string; \"Organization ID\": string; \"Key Alias\": string; \"User ID\": string; \"Sort By\": string; \"Sort Order\": string; }; filteredKeys: { models: string[]; token: string; token_id: string; ... 66 more ...; created_by_user?: { user_id: string; user_email: string; user_alias: string | null; }; }[]...' is not assignable to parameter of type '{ filters: FilterState; filteredKeys: KeyResponse[]; filteredTotalCount: number | null; allTeams: Team[]; allOrganizations: Organization[]; handleFilterChange: (newFilters: Record, skipDebounce?: boolean) => void; handleFilterReset: () => void; }'.","category":1,"code":2345,"next":[{"messageText":"Property 'filteredTotalCount' is missing in type '{ filters: { \"Team ID\": string; \"Organization ID\": string; \"Key Alias\": string; \"User ID\": string; \"Sort By\": string; \"Sort Order\": string; }; filteredKeys: { models: string[]; token: string; token_id: string; ... 66 more ...; created_by_user?: { user_id: string; user_email: string; user_alias: string | null; }; }[]...' but required in type '{ filters: FilterState; filteredKeys: KeyResponse[]; filteredTotalCount: number | null; allTeams: Team[]; allOrganizations: Organization[]; handleFilterChange: (newFilters: Record, skipDebounce?: boolean) => void; handleFilterReset: () => void; }'.","category":1,"code":2741}]},"relatedInformation":[{"file":"./src/components/key_team_helpers/filter_logic.tsx","start":5626,"length":18,"messageText":"'filteredTotalCount' is declared here.","category":3,"code":2728}]},{"start":20677,"length":356,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ filters: { \"Team ID\": string; \"Organization ID\": string; \"Key Alias\": string; \"User ID\": string; \"Sort By\": string; \"Sort Order\": string; }; filteredKeys: { last_active: null; token: string; token_id: string; ... 66 more ...; created_by_user?: { user_id: string; user_email: string; user_alias: string | null; }; }[...' is not assignable to parameter of type '{ filters: FilterState; filteredKeys: KeyResponse[]; filteredTotalCount: number | null; allTeams: Team[]; allOrganizations: Organization[]; handleFilterChange: (newFilters: Record, skipDebounce?: boolean) => void; handleFilterReset: () => void; }'.","category":1,"code":2345,"next":[{"messageText":"Property 'filteredTotalCount' is missing in type '{ filters: { \"Team ID\": string; \"Organization ID\": string; \"Key Alias\": string; \"User ID\": string; \"Sort By\": string; \"Sort Order\": string; }; filteredKeys: { last_active: null; token: string; token_id: string; ... 66 more ...; created_by_user?: { user_id: string; user_email: string; user_alias: string | null; }; }[...' but required in type '{ filters: FilterState; filteredKeys: KeyResponse[]; filteredTotalCount: number | null; allTeams: Team[]; allOrganizations: Organization[]; handleFilterChange: (newFilters: Record, skipDebounce?: boolean) => void; handleFilterReset: () => void; }'.","category":1,"code":2741}]},"relatedInformation":[{"file":"./src/components/key_team_helpers/filter_logic.tsx","start":5626,"length":18,"messageText":"'filteredTotalCount' is declared here.","category":3,"code":2728}]},{"start":21582,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":24872,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":27201,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582}]],[4243,[{"start":3421,"length":8,"code":2741,"category":1,"messageText":"Property 'spend' is missing in type '{ team_id: string; team_alias: string; models: string[]; max_budget: number; budget_duration: string; tpm_limit: null; rpm_limit: null; organization_id: string; created_at: string; keys: never[]; members_with_roles: never[]; }' but required in type 'Team'.","relatedInformation":[{"file":"./src/components/key_team_helpers/key_list.tsx","start":480,"length":5,"messageText":"'spend' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ team_id: string; team_alias: string; models: string[]; max_budget: number; budget_duration: string; tpm_limit: null; rpm_limit: null; organization_id: string; created_at: string; keys: never[]; members_with_roles: never[]; }' is not assignable to type 'Team'."}},{"start":5020,"length":49,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":5537,"length":49,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":6471,"length":49,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":7419,"length":49,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":8366,"length":49,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":9161,"length":43,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}}]],[4244,[{"start":434,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":479,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":630,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":718,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":891,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":956,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1021,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1087,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1160,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1339,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1399,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1479,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1568,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1645,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1834,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1917,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2113,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2179,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2250,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2321,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[4246,[{"start":3122,"length":311,"code":2741,"category":1,"messageText":"Property 'spend' is missing in type '{ team_id: string; team_alias: string; models: string[]; max_budget: number; budget_duration: string; tpm_limit: null; rpm_limit: null; organization_id: string; created_at: string; keys: never[]; members_with_roles: never[]; }' but required in type 'Team'.","relatedInformation":[{"file":"./src/components/key_team_helpers/key_list.tsx","start":480,"length":5,"messageText":"'spend' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ team_id: string; team_alias: string; models: string[]; max_budget: number; budget_duration: string; tpm_limit: null; rpm_limit: null; organization_id: string; created_at: string; keys: never[]; members_with_roles: never[]; }' is not assignable to type 'Team'."}}]],[4257,[{"start":1160,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1196,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1290,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1361,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1471,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1537,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1608,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1758,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1835,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2011,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2113,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2296,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2406,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2782,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[4273,[{"start":795,"length":13,"code":2322,"category":1,"messageText":{"messageText":"Type '{ organization_id: string; organization_alias: string; budget_id: string; metadata: {}; models: never[]; spend: number; model_spend: {}; created_at: string; created_by: string; updated_at: string; }[]' is not assignable to type 'Organization[]'.","category":1,"code":2322,"next":[{"messageText":"Type '{ organization_id: string; organization_alias: string; budget_id: string; metadata: {}; models: never[]; spend: number; model_spend: {}; created_at: string; created_by: string; updated_at: string; }' is missing the following properties from type 'Organization': updated_by, litellm_budget_table, teams, users, members","category":1,"code":2739,"canonicalHead":{"code":2322,"messageText":"Type '{ organization_id: string; organization_alias: string; budget_id: string; metadata: {}; models: never[]; spend: number; model_spend: {}; created_at: string; created_by: string; updated_at: string; }' is not assignable to type 'Organization'."}}]},"relatedInformation":[{"file":"./src/components/common_components/organizationdropdown.tsx","start":187,"length":13,"messageText":"The expected type comes from property 'organizations' which is declared here on type 'IntrinsicAttributes & OrganizationDropdownProps'","category":3,"code":6500}]},{"start":1034,"length":13,"code":2322,"category":1,"messageText":{"messageText":"Type '{ organization_id: string; organization_alias: string; budget_id: string; metadata: {}; models: never[]; spend: number; model_spend: {}; created_at: string; created_by: string; updated_at: string; }[]' is not assignable to type 'Organization[]'.","category":1,"code":2322,"next":[{"messageText":"Type '{ organization_id: string; organization_alias: string; budget_id: string; metadata: {}; models: never[]; spend: number; model_spend: {}; created_at: string; created_by: string; updated_at: string; }' is missing the following properties from type 'Organization': updated_by, litellm_budget_table, teams, users, members","category":1,"code":2739,"canonicalHead":{"code":2322,"messageText":"Type '{ organization_id: string; organization_alias: string; budget_id: string; metadata: {}; models: never[]; spend: number; model_spend: {}; created_at: string; created_by: string; updated_at: string; }' is not assignable to type 'Organization'."}}]},"relatedInformation":[{"file":"./src/components/common_components/organizationdropdown.tsx","start":187,"length":13,"messageText":"The expected type comes from property 'organizations' which is declared here on type 'IntrinsicAttributes & OrganizationDropdownProps'","category":3,"code":6500}]},{"start":1448,"length":13,"code":2322,"category":1,"messageText":{"messageText":"Type '{ organization_id: string; organization_alias: string; budget_id: string; metadata: {}; models: never[]; spend: number; model_spend: {}; created_at: string; created_by: string; updated_at: string; }[]' is not assignable to type 'Organization[]'.","category":1,"code":2322,"next":[{"messageText":"Type '{ organization_id: string; organization_alias: string; budget_id: string; metadata: {}; models: never[]; spend: number; model_spend: {}; created_at: string; created_by: string; updated_at: string; }' is missing the following properties from type 'Organization': updated_by, litellm_budget_table, teams, users, members","category":1,"code":2739,"canonicalHead":{"code":2322,"messageText":"Type '{ organization_id: string; organization_alias: string; budget_id: string; metadata: {}; models: never[]; spend: number; model_spend: {}; created_at: string; created_by: string; updated_at: string; }' is not assignable to type 'Organization'."}}]},"relatedInformation":[{"file":"./src/components/common_components/organizationdropdown.tsx","start":187,"length":13,"messageText":"The expected type comes from property 'organizations' which is declared here on type 'IntrinsicAttributes & OrganizationDropdownProps'","category":3,"code":6500}]},{"start":1828,"length":13,"code":2322,"category":1,"messageText":{"messageText":"Type '{ organization_id: string; organization_alias: string; budget_id: string; metadata: {}; models: never[]; spend: number; model_spend: {}; created_at: string; created_by: string; updated_at: string; }[]' is not assignable to type 'Organization[]'.","category":1,"code":2322,"next":[{"messageText":"Type '{ organization_id: string; organization_alias: string; budget_id: string; metadata: {}; models: never[]; spend: number; model_spend: {}; created_at: string; created_by: string; updated_at: string; }' is missing the following properties from type 'Organization': updated_by, litellm_budget_table, teams, users, members","category":1,"code":2739,"canonicalHead":{"code":2322,"messageText":"Type '{ organization_id: string; organization_alias: string; budget_id: string; metadata: {}; models: never[]; spend: number; model_spend: {}; created_at: string; created_by: string; updated_at: string; }' is not assignable to type 'Organization'."}}]},"relatedInformation":[{"file":"./src/components/common_components/organizationdropdown.tsx","start":187,"length":13,"messageText":"The expected type comes from property 'organizations' which is declared here on type 'IntrinsicAttributes & OrganizationDropdownProps'","category":3,"code":6500}]}]],[4274,[{"start":378,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":422,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":581,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":659,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":842,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":920,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1103,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1181,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1372,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1453,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1842,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[4289,[{"start":3286,"length":15,"code":2339,"category":1,"messageText":{"messageText":"Property 'CustomGuardrail' does not exist on type 'Record | typeof GuardrailProviders'.","category":1,"code":2339,"next":[{"messageText":"Property 'CustomGuardrail' does not exist on type 'typeof GuardrailProviders'.","category":1,"code":2339}]}}]],[4295,[{"start":1308,"length":16,"code":2322,"category":1,"messageText":{"messageText":"Type '{ name: string; category: string; description: string; }[]' is not assignable to type 'PrebuiltPattern[]'.","category":1,"code":2322,"next":[{"messageText":"Property 'display_name' is missing in type '{ name: string; category: string; description: string; }' but required in type 'PrebuiltPattern'.","category":1,"code":2741,"canonicalHead":{"code":2322,"messageText":"Type '{ name: string; category: string; description: string; }' is not assignable to type 'PrebuiltPattern'."}}]},"relatedInformation":[{"file":"./src/components/guardrails/content_filter/patternmodal.tsx","start":196,"length":12,"messageText":"'display_name' is declared here.","category":3,"code":2728},{"file":"./src/components/guardrails/content_filter/patternmodal.tsx","start":316,"length":16,"messageText":"The expected type comes from property 'prebuiltPatterns' which is declared here on type 'IntrinsicAttributes & PatternModalProps'","category":3,"code":6500}]}]],[4303,[{"start":788,"length":8,"messageText":"Property 'children' does not exist on type '{}'.","category":1,"code":2339},{"start":1006,"length":7,"code":2559,"category":1,"messageText":"Type '{ children: Element; }' has no properties in common with type 'IntrinsicAttributes'."},{"start":1655,"length":8,"messageText":"Property 'children' does not exist on type '{}'.","category":1,"code":2339},{"start":2175,"length":7,"code":2559,"category":1,"messageText":"Type '{ children: Element; }' has no properties in common with type 'IntrinsicAttributes'."}]],[4306,[{"start":2737,"length":5,"code":2339,"category":1,"messageText":"Property 'value' does not exist on type 'HTMLElement'."},{"start":2867,"length":5,"code":2339,"category":1,"messageText":"Property 'value' does not exist on type 'HTMLElement'."},{"start":3883,"length":5,"code":2339,"category":1,"messageText":"Property 'value' does not exist on type 'HTMLElement'."}]],[4325,[{"start":5117,"length":4,"code":2339,"category":1,"messageText":"Property 'type' does not exist on type '{}'."}]],[4360,[{"start":2078,"length":20,"code":2741,"category":1,"messageText":"Property 'budget_reset_at' is missing in type '{ budget_id: string; soft_budget: null; max_budget: number; max_parallel_requests: null; tpm_limit: number; rpm_limit: number; model_max_budget: null; budget_duration: null; }' but required in type '{ budget_id: string; soft_budget: number | null; max_budget: number | null; max_parallel_requests: number | null; tpm_limit: number | null; rpm_limit: number | null; model_max_budget: Record<...> | null; budget_duration: string | null; budget_reset_at: string | null; allowed_models?: string[] | ... 1 more ... | unde...'.","relatedInformation":[{"file":"./src/components/team/teaminfo.tsx","start":3703,"length":15,"messageText":"'budget_reset_at' is declared here.","category":3,"code":2728},{"file":"./src/components/team/teaminfo.tsx","start":3398,"length":20,"messageText":"The expected type comes from property 'litellm_budget_table' which is declared here on type 'TeamMembership'","category":3,"code":6500}],"canonicalHead":{"code":2322,"messageText":"Type '{ budget_id: string; soft_budget: null; max_budget: number; max_parallel_requests: null; tpm_limit: number; rpm_limit: number; model_max_budget: null; budget_duration: null; }' is not assignable to type '{ budget_id: string; soft_budget: number | null; max_budget: number | null; max_parallel_requests: number | null; tpm_limit: number | null; rpm_limit: number | null; model_max_budget: Record<...> | null; budget_duration: string | null; budget_reset_at: string | null; allowed_models?: string[] | ... 1 more ... | unde...'."}}]],[4367,[{"start":2922,"length":13,"code":2739,"category":1,"messageText":"Type '{ token: string; token_id: string; key_name: string; key_alias: string; spend: number; max_budget: number; expires: string; models: never[]; aliases: {}; config: {}; user_id: string; team_id: null; max_parallel_requests: number; ... 44 more ...; key_rotation_at: undefined; }' is missing the following properties from type 'KeyResponse': project_id, last_active","canonicalHead":{"code":2322,"messageText":"Type '{ token: string; token_id: string; key_name: string; key_alias: string; spend: number; max_budget: number; expires: string; models: never[]; aliases: {}; config: {}; user_id: string; team_id: null; max_parallel_requests: number; ... 44 more ...; key_rotation_at: undefined; }' is not assignable to type 'KeyResponse'."}}]],[4368,[{"start":3494,"length":14,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ accessToken: string; userId: string; userRole: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ accessToken: string; userId: string; userRole: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":4884,"length":10,"code":2322,"category":1,"messageText":"Type 'null' is not assignable to type 'number'."}]],[4369,[{"start":2191,"length":13,"code":2741,"category":1,"messageText":"Property 'last_active' is missing in type '{ token: string; token_id: string; key_name: string; key_alias: string; spend: number; max_budget: number; expires: string; models: never[]; aliases: {}; config: {}; user_id: string; team_id: null; project_id: null; ... 45 more ...; key_rotation_at: undefined; }' but required in type 'KeyResponse'.","relatedInformation":[{"file":"./src/components/key_team_helpers/key_list.tsx","start":1578,"length":11,"messageText":"'last_active' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ token: string; token_id: string; key_name: string; key_alias: string; spend: number; max_budget: number; expires: string; models: never[]; aliases: {}; config: {}; user_id: string; team_id: null; project_id: null; ... 45 more ...; key_rotation_at: undefined; }' is not assignable to type 'KeyResponse'."}},{"start":4382,"length":21,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ accessToken: string; userId: string; userRole: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ accessToken: string; userId: string; userRole: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":4827,"length":21,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ accessToken: string; userId: string; userRole: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ accessToken: string; userId: string; userRole: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":5552,"length":104,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":6779,"length":94,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":7495,"length":94,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":8230,"length":94,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":8965,"length":115,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":10280,"length":96,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":10938,"length":21,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ accessToken: string; userId: string; userRole: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ accessToken: string; userId: string; userRole: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":12181,"length":115,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":12627,"length":111,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":13083,"length":115,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":13567,"length":113,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":14675,"length":102,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":15096,"length":21,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ accessToken: string; userId: string; userRole: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ accessToken: string; userId: string; userRole: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":15727,"length":21,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ accessToken: string; userId: string; userRole: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ accessToken: string; userId: string; userRole: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":16357,"length":21,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ accessToken: string; userId: string; userRole: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ accessToken: string; userId: string; userRole: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":16940,"length":112,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":18133,"length":102,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":18885,"length":102,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":19679,"length":112,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":20626,"length":112,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":21913,"length":112,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}}]],[4384,[{"start":7365,"length":23,"messageText":"'failedLogEntry.metadata' is possibly 'undefined'.","category":1,"code":18048}]],[4409,[{"start":1980,"length":293,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: false; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: false; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":2424,"length":10,"code":2739,"category":1,"messageText":"Type '{ showTags: false; topKeys: never[]; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is missing the following properties from type 'TopKeyViewProps': topKeysLimit, setTopKeysLimit","canonicalHead":{"code":2322,"messageText":"Type '{ showTags: false; topKeys: never[]; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is not assignable to type 'TopKeyViewProps'."}},{"start":2638,"length":10,"code":2739,"category":1,"messageText":"Type '{ showTags: true; topKeys: never[]; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is missing the following properties from type 'TopKeyViewProps': topKeysLimit, setTopKeysLimit","canonicalHead":{"code":2322,"messageText":"Type '{ showTags: true; topKeys: never[]; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is not assignable to type 'TopKeyViewProps'."}},{"start":2857,"length":10,"code":2739,"category":1,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: TagUsage[]; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is missing the following properties from type 'TopKeyViewProps': topKeysLimit, setTopKeysLimit","canonicalHead":{"code":2322,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: TagUsage[]; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is not assignable to type 'TopKeyViewProps'."}},{"start":5736,"length":10,"code":2739,"category":1,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: TagUsage[]; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is missing the following properties from type 'TopKeyViewProps': topKeysLimit, setTopKeysLimit","canonicalHead":{"code":2322,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: TagUsage[]; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is not assignable to type 'TopKeyViewProps'."}},{"start":6118,"length":10,"code":2739,"category":1,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: TagUsage[]; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is missing the following properties from type 'TopKeyViewProps': topKeysLimit, setTopKeysLimit","canonicalHead":{"code":2322,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: TagUsage[]; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is not assignable to type 'TopKeyViewProps'."}},{"start":6926,"length":10,"code":2739,"category":1,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: never[]; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is missing the following properties from type 'TopKeyViewProps': topKeysLimit, setTopKeysLimit","canonicalHead":{"code":2322,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: never[]; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is not assignable to type 'TopKeyViewProps'."}},{"start":7351,"length":10,"code":2739,"category":1,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: undefined; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is missing the following properties from type 'TopKeyViewProps': topKeysLimit, setTopKeysLimit","canonicalHead":{"code":2322,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: undefined; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is not assignable to type 'TopKeyViewProps'."}},{"start":7757,"length":10,"code":2739,"category":1,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: null; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is missing the following properties from type 'TopKeyViewProps': topKeysLimit, setTopKeysLimit","canonicalHead":{"code":2322,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: null; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is not assignable to type 'TopKeyViewProps'."}},{"start":8309,"length":10,"code":2739,"category":1,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: TagUsage[]; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is missing the following properties from type 'TopKeyViewProps': topKeysLimit, setTopKeysLimit","canonicalHead":{"code":2322,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: TagUsage[]; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is not assignable to type 'TopKeyViewProps'."}},{"start":9294,"length":10,"code":2739,"category":1,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: TagUsage[]; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is missing the following properties from type 'TopKeyViewProps': topKeysLimit, setTopKeysLimit","canonicalHead":{"code":2322,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: TagUsage[]; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is not assignable to type 'TopKeyViewProps'."}},{"start":9836,"length":10,"code":2739,"category":1,"messageText":"Type '{ showTags: true; topKeys: never[]; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is missing the following properties from type 'TopKeyViewProps': topKeysLimit, setTopKeysLimit","canonicalHead":{"code":2322,"messageText":"Type '{ showTags: true; topKeys: never[]; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is not assignable to type 'TopKeyViewProps'."}},{"start":10256,"length":10,"code":2739,"category":1,"messageText":"Type '{ showTags: false; topKeys: never[]; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is missing the following properties from type 'TopKeyViewProps': topKeysLimit, setTopKeysLimit","canonicalHead":{"code":2322,"messageText":"Type '{ showTags: false; topKeys: never[]; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is not assignable to type 'TopKeyViewProps'."}},{"start":10874,"length":10,"code":2739,"category":1,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: never[]; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is missing the following properties from type 'TopKeyViewProps': topKeysLimit, setTopKeysLimit","canonicalHead":{"code":2322,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: never[]; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is not assignable to type 'TopKeyViewProps'."}}]]],"affectedFilesPendingEmit":[4411,3908,3826,3824,3827,3825,3909,3828,2143,3829,3912,3911,2638,3910,3913,3121,2196,2197,2195,2198,2199,2200,2203,2202,2204,2447,2449,2448,2451,2450,2453,2452,2456,2455,2457,2181,2458,2460,2459,2461,2463,2462,2465,2464,2467,2466,2468,2469,2471,2470,2473,2472,2474,2475,2476,2477,2478,2480,2479,2482,2481,2484,2483,2485,2487,2486,2489,2488,2491,2490,2493,2492,2496,2495,2498,2497,2500,2499,2501,2494,2503,2502,2505,2504,2507,2506,2509,2511,2510,2513,2515,2514,2517,2516,2519,2518,2520,2522,2521,2524,2523,2525,2182,2527,2526,2529,2528,2184,2183,2186,2187,2189,2188,2191,2190,2193,2192,2194,2531,2530,2533,2532,3185,3123,3915,3129,3916,3130,3132,3914,3196,2535,2534,2142,3917,3720,3823,4015,4042,4016,4035,4043,4017,2537,4019,4020,4044,4018,4045,4030,4046,4034,4047,4021,4022,4048,4023,4050,4049,4051,4024,4033,4028,4031,4027,4029,4032,4052,4040,4053,4038,4054,4036,4055,4039,4057,4056,4058,4037,2540,2539,3919,2544,2543,2546,3939,4007,4059,4008,4060,4009,4061,4010,2538,4011,4012,4014,4041,4067,4064,4070,4069,4071,4068,4073,4062,4074,4063,4075,2612,2614,2613,4072,4065,4066,4125,3111,4127,4126,4128,4129,4130,4131,4132,3779,4133,3781,4134,3780,4135,3778,3782,4148,3651,3142,3147,4246,3149,4243,3148,4247,3144,3143,4244,3141,4248,3145,3139,4249,3133,4250,3146,3138,4251,3134,4245,3140,3163,4136,3215,4252,2562,4149,3223,3220,4254,4253,4255,3218,4257,4256,2676,3221,2678,2677,3217,3222,2679,3216,3219,2201,4164,3635,4167,3636,4168,3638,4169,3640,4165,3648,3644,4166,3642,3758,3757,2681,4258,2680,2443,4259,2446,2445,2444,4150,2598,4137,3816,3230,3226,4261,4260,4262,3228,2682,3229,4263,3227,2563,4120,4124,4119,4122,4121,4123,2683,2684,4264,3646,4025,2536,4026,2542,2541,3234,4265,3231,2687,2686,3645,3232,3233,2685,3652,4170,3763,4171,3760,4172,3759,4173,3762,4174,3761,2454,2564,3188,2565,4275,3649,2135,4266,3184,4267,2206,4268,3168,3783,4276,3717,4277,3718,4278,3719,2715,4279,2441,4280,2442,4269,2566,4270,3186,4271,3120,3182,2569,2568,4272,2618,4273,2594,3162,2570,3160,2573,2595,4274,2574,2585,2624,4281,2756,2593,3661,3167,2719,4175,2630,4176,2628,4177,2642,4178,2639,2643,4181,2636,4182,2634,4183,2633,2647,2632,2631,2648,2635,4179,2627,2644,2626,4180,2629,2623,2645,2640,2646,2641,4138,2601,4139,3122,4140,3818,4184,3807,4185,3806,4186,3809,4187,3808,3154,3817,2688,2689,1197,3756,4188,2655,4189,2651,4190,2652,4191,2653,2657,2650,4192,2656,2658,2654,3317,4151,3355,4286,3341,3344,3332,3331,3333,3345,4293,3346,4294,3327,3328,3330,4295,3326,3329,2693,2694,3342,3353,4287,3351,2690,2691,3352,4288,3347,4289,3334,3335,3336,4290,3343,4282,3161,4283,3349,4284,3350,4285,3348,3337,4291,3338,4292,3339,3354,4296,3340,2692,3169,3319,4194,4193,3322,4195,3325,3324,3320,4196,3321,2659,4197,3323,4141,2637,4152,3189,2602,4297,2599,2696,2695,4298,3784,1195,2698,2697,3157,4198,2716,4153,2620,4299,3918,2545,2567,4300,4013,3170,3637,2603,4301,2606,3627,4307,3617,3613,3634,3618,4308,3606,3626,3605,3621,4309,3620,3622,4310,3629,4311,3607,4312,3633,2605,4302,3612,3625,4303,3609,3619,4304,3601,3602,3937,3603,4305,3604,3611,3610,3608,4306,3630,4313,2138,3628,4314,3614,3804,3802,3803,4315,3135,4317,3137,4316,3136,3155,3124,3151,4318,3152,4319,3128,3150,2699,3639,3153,3641,4154,3156,4199,3171,2661,2660,4200,2717,4320,2714,2700,1191,4323,3126,4322,3125,4321,2137,4155,3118,4201,3113,4202,3114,2663,2662,2664,4203,3115,4204,3116,4205,3117,2616,2141,3175,4142,3716,2600,4325,2611,4324,3190,2701,2610,4326,3721,4156,3722,2617,2622,2621,3164,3166,3654,3174,4327,3173,3172,4329,3594,3590,3599,4330,3592,2704,2703,4331,3597,4332,3591,4333,3593,4334,3600,3588,4335,3589,4336,3356,4337,3596,3595,4328,3191,3598,2702,3131,3747,3727,3746,3736,3741,3737,3740,3738,2708,2709,3735,3739,3733,3743,3745,3730,3725,3729,3734,3742,4338,3731,2705,2707,2706,4339,3744,3726,3724,3723,3728,3732,4157,2625,4158,3647,3158,3225,3159,4345,3235,4340,2575,4341,2576,4342,2579,4343,2577,4344,2578,3316,3315,3314,2512,3197,3750,3755,3748,3751,4208,3754,3176,4206,3752,4207,3753,3749,4159,3765,2665,3210,3212,4209,3211,4210,3198,4211,3624,4212,3623,2667,2666,2668,4218,3200,4219,3199,4220,3201,4221,3202,4213,3203,4214,3204,4215,3207,4216,3205,4217,3206,2670,2669,4222,3208,4223,3209,4224,3764,2671,4225,2584,4226,2581,2582,4228,3313,4227,2583,4347,3318,4348,3653,4346,2607,2136,2571,3224,3643,4143,3213,4349,3770,4350,3769,3771,4351,3766,4352,3768,4353,3767,4356,3774,3775,3772,4354,3938,4355,3773,1194,4362,3713,3177,4357,3178,4358,2572,4363,3180,3181,4364,3179,2711,2710,4359,3195,4360,3183,4361,3194,2712,4144,3714,4367,3192,4368,4369,3193,4365,3187,4366,3799,3800,4229,3798,4145,3801,3776,4370,3715,4371,3119,3777,3214,4146,3787,4147,2619,4234,3657,4235,3658,4236,3659,4233,3660,4237,3664,4238,3665,4239,3662,4240,3663,4230,3650,4231,3710,4232,3712,4241,3711,2673,2672,2675,2674,4160,3655,4161,3786,4162,3815,4372,3795,4373,3793,3797,4374,3794,4375,3796,2608,3792,4376,3790,4377,2609,4378,3788,3791,3789,3811,3810,2759,4379,2774,2713,4380,2773,4382,4381,2772,2761,2764,4390,2763,2770,2769,4391,2771,4392,2768,4386,3814,4387,2760,4393,2793,2765,2766,4394,2796,2803,4395,2797,2780,4396,2801,2802,4397,2798,2790,2791,4398,2800,4399,2799,2792,4400,2795,4401,2794,2777,4402,2776,2767,2781,4388,3812,3813,4384,4383,3165,4389,2757,2784,2789,2785,2786,2787,4403,2788,2782,2804,2783,4385,2758,2762,2775,3127,3656,4163,3822,3819,4404,3821,1196,4405,3820,4242,3785,3805,3108,3109,3110,3112,2810,2808,2807,2809,2806,2805,2811,4407,2814,2812,4406,3615,3616,3631,3632,2813,2440,2815,2139,2816,2140,610,2818,2819,1193,2820,2547,2821,2823,2822,2824,2178,3080,3079,3082,3081,3083,2185,3085,3084,3086,1192,3087,2604,3088,2615,3089,3090,2508,3091,2179,3092,2180,3093,3094,2649,3095,2134,531,4408,3100,3104,3907,4409,609],"version":"5.9.3"} \ No newline at end of file +{"fileNames":["../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es5.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2015.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2016.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2017.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2018.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2019.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2020.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2021.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2022.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2023.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2024.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.esnext.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.dom.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.dom.iterable.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2015.core.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2015.collection.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2015.generator.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2015.promise.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2016.intl.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2017.date.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2017.object.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2017.string.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2017.intl.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2018.intl.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2018.promise.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2019.array.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2019.object.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2019.string.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2019.intl.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2020.date.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2020.promise.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2020.string.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2020.intl.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2020.number.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2021.promise.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2021.string.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2021.intl.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2022.array.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2022.error.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2022.intl.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2022.object.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2022.string.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2023.array.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2023.collection.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2023.intl.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2024.collection.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2024.object.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2024.promise.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2024.regexp.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.es2024.string.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.esnext.array.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.esnext.collection.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.esnext.intl.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.esnext.disposable.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.esnext.promise.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.esnext.decorators.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.esnext.iterator.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.esnext.float16.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.esnext.error.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.esnext.sharedmemory.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.decorators.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/react/global.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/csstype/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/prop-types/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/react/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/react/jsx-runtime.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@vitest/spy/dist/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@vitest/pretty-format/dist/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@vitest/utils/dist/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@vitest/utils/dist/helpers.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/tinyrainbow/dist/index-8b61d5bc.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/tinyrainbow/dist/node.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@vitest/utils/dist/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@vitest/utils/dist/types.d-bcelap-c.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@vitest/utils/dist/diff.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@vitest/expect/dist/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/compatibility/disposable.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/compatibility/indexable.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/compatibility/iterators.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/compatibility/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/globals.typedarray.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/buffer.buffer.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/globals.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/web-globals/abortcontroller.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/web-globals/domexception.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/web-globals/events.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/header.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/readable.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/file.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/fetch.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/formdata.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/connector.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/client.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/errors.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/dispatcher.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/global-dispatcher.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/global-origin.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/pool-stats.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/pool.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/handlers.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/balanced-pool.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/agent.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/mock-interceptor.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/mock-agent.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/mock-client.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/mock-pool.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/mock-errors.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/proxy-agent.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/env-http-proxy-agent.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/retry-handler.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/retry-agent.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/api.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/interceptors.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/util.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/cookies.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/patch.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/websocket.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/eventsource.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/filereader.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/diagnostics-channel.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/content-type.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/cache.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/undici-types/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/web-globals/fetch.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/assert.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/assert/strict.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/async_hooks.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/buffer.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/child_process.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/cluster.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/console.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/constants.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/crypto.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/dgram.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/diagnostics_channel.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/dns.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/dns/promises.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/domain.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/events.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/fs.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/fs/promises.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/http.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/http2.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/https.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/inspector.generated.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/module.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/net.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/os.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/path.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/perf_hooks.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/process.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/punycode.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/querystring.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/readline.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/readline/promises.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/repl.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/sea.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/stream.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/stream/promises.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/stream/consumers.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/stream/web.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/string_decoder.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/test.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/timers.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/timers/promises.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/tls.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/trace_events.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/tty.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/url.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/util.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/v8.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/vm.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/wasi.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/worker_threads.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/zlib.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/vite/types/hmrpayload.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/vite/dist/node/chunks/modulerunnertransport.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/vite/types/customevent.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/estree/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rollup/dist/rollup.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rollup/dist/parseast.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/vite/types/hot.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/vite/dist/node/module-runner.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/esbuild/lib/main.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/vite/types/internal/terseroptions.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/source-map-js/source-map.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/postcss/lib/previous-map.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/postcss/lib/input.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/postcss/lib/css-syntax-error.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/postcss/lib/declaration.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/postcss/lib/root.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/postcss/lib/warning.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/postcss/lib/lazy-result.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/postcss/lib/no-work-result.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/postcss/lib/processor.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/postcss/lib/result.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/postcss/lib/document.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/postcss/lib/rule.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/postcss/lib/node.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/postcss/lib/comment.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/postcss/lib/container.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/postcss/lib/at-rule.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/postcss/lib/list.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/postcss/lib/postcss.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/postcss/lib/postcss.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/vite/types/internal/csspreprocessoroptions.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/lightningcss/node/ast.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/lightningcss/node/targets.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/lightningcss/node/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/vite/types/internal/lightningcssoptions.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/vite/types/importglob.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/vite/types/metadata.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/vite/dist/node/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@vitest/runner/dist/tasks.d-cksck4of.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@vitest/runner/dist/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@vitest/utils/dist/error.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@vitest/runner/dist/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/vitest/optional-types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/vitest/dist/chunks/environment.d.cl3nlxbe.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@vitest/mocker/dist/registry.d-d765pazg.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@vitest/mocker/dist/types.d-d_arzrdy.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@vitest/mocker/dist/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@vitest/utils/dist/source-map.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/vite-node/dist/trace-mapping.d-dlvdeqop.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/vite-node/dist/index.d-dgmxd2u7.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/vite-node/dist/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@vitest/snapshot/dist/environment.d-dhdq1csl.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@vitest/snapshot/dist/rawsnapshot.d-lfsmjfud.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@vitest/snapshot/dist/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@vitest/snapshot/dist/environment.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/vitest/dist/chunks/config.d.bkdhh7zx.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/vitest/dist/chunks/worker.d.cugipz9v.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/deep-eql/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/assertion-error/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/chai/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@vitest/runner/dist/utils.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/tinybench/dist/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/vitest/dist/chunks/benchmark.d.bwvbvtda.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/vite-node/dist/client.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/vitest/dist/chunks/coverage.d.s9rmnxie.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@vitest/snapshot/dist/manager.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/vitest/dist/chunks/reporters.d.buron0i0.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/vitest/dist/chunks/vite.d.bnoppc46.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/vitest/dist/config.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/vitest/config.d.ts","./vitest.config.ts","./src/types.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/_util/responsiveobserver.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/_util/type.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/_util/throttlebyanimationframe.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/affix/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-util/lib/portal.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-util/lib/dom/scrolllocker.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-util/lib/portalwrapper.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-dialog/lib/idialogproptypes.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-dialog/lib/dialogwrap.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-dialog/lib/dialog/content/panel.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-dialog/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/_util/aria-data-attrs.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/_util/hooks/useclosable.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/_util/hooks/useforceupdate.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/_util/hooks/usemergesemantic.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/_util/hooks/usemultipleselect.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/_util/hooks/usepatchelement.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/_util/hooks/useproxyimperativehandle.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/_util/hooks/usesyncstate.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/_util/hooks/usezindex.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/_util/hooks/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/alert/alert.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/alert/errorboundary.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/alert/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/anchor/anchorlink.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/anchor/anchor.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/anchor/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/message/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/config-provider/sizecontext.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/button/button-group.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/button/buttonhelpers.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/button/button.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/_util/warning.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-field-form/lib/namepathtype.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-field-form/lib/useform.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-field-form/lib/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-picker/lib/generate/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-motion/es/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-motion/es/cssmotion.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-motion/es/util/diff.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-motion/es/cssmotionlist.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-motion/es/context.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-motion/es/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@rc-component/trigger/lib/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@rc-component/trigger/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-picker/lib/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-picker/lib/pickerinput/selector/rangeselector.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-picker/lib/pickerinput/rangepicker.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-picker/lib/pickerinput/singlepicker.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-picker/lib/pickerpanel/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-picker/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-field-form/lib/field.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-field-form/es/namepathtype.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-field-form/es/useform.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-field-form/es/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-field-form/es/field.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-field-form/es/list.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-field-form/es/form.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-field-form/es/formcontext.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-field-form/es/fieldcontext.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-field-form/es/listcontext.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-field-form/es/usewatch.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-field-form/es/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-field-form/lib/form.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/grid/col.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/compute-scroll-into-view/dist/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/scroll-into-view-if-needed/dist/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/form/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/form/hooks/useform.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/form/form.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/form/formiteminput.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-tooltip/lib/placements.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-tooltip/lib/tooltip.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs/lib/cache.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs/lib/hooks/useglobalcache.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs/lib/util/css-variables.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs/lib/extractstyle.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs/lib/theme/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs/lib/theme/theme.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs/lib/hooks/usecachetoken.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs/lib/hooks/usecssvarregister.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs/lib/keyframes.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs/lib/linters/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs/lib/linters/contentquoteslinter.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs/lib/linters/hashedanimationlinter.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs/lib/linters/legacynotselectorlinter.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs/lib/linters/logicalpropertieslinter.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs/lib/linters/nanlinter.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs/lib/linters/parentselectorlinter.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs/lib/linters/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs/lib/transformers/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs/lib/stylecontext.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs/lib/hooks/usestyleregister.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs/lib/theme/calc/calculator.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs/lib/theme/calc/csscalculator.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs/lib/theme/calc/numcalculator.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs/lib/theme/calc/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs/lib/theme/createtheme.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs/lib/theme/themecache.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs/lib/theme/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs/lib/transformers/legacylogicalproperties.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs/lib/transformers/px2rem.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs/lib/util/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/theme/interface/presetcolors.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/theme/interface/seeds.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/theme/interface/maps/colors.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/theme/interface/maps/font.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/theme/interface/maps/size.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/theme/interface/maps/style.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/theme/interface/maps/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/theme/interface/alias.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs-utils/lib/interface/components.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs-utils/lib/interface/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs-utils/lib/util/calc/calculator.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs-utils/lib/hooks/usecsp.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs-utils/lib/hooks/useprefix.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs-utils/lib/hooks/usetoken.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs-utils/lib/util/genstyleutils.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs-utils/lib/util/calc/csscalculator.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs-utils/lib/util/calc/numcalculator.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs-utils/lib/util/calc/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs-utils/lib/util/statistic.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/cssinjs-utils/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/theme/themes/shared/genfontsizes.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/theme/themes/default/theme.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/theme/context.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/theme/usetoken.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/theme/util/genstyleutils.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/theme/util/genpresetcolor.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/theme/util/usereseticonstyle.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/theme/internal.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/_util/wave/style.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/affix/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/alert/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/anchor/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/app/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/avatar/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/back-top/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/badge/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/breadcrumb/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/button/style/token.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/button/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/input/style/token.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/select/style/token.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/style/roundedarrow.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/date-picker/style/token.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/date-picker/style/panel.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/date-picker/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/calendar/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/card/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/carousel/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/cascader/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/checkbox/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/collapse/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/color-picker/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/descriptions/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/divider/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/drawer/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/style/placementarrow.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/dropdown/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/empty/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/flex/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/float-button/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/form/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/grid/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/image/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/input-number/style/token.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/input-number/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/input/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/layout/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/list/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/mentions/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/menu/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/message/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/modal/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/notification/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/pagination/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/popconfirm/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/popover/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/progress/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/qr-code/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/radio/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/rate/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/result/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/segmented/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/select/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/skeleton/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/slider/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/space/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/spin/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/statistic/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/steps/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/switch/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/table/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/tabs/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/tag/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/timeline/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/tooltip/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/tour/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/transfer/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/tree/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/tree-select/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/typography/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/upload/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/splitter/style/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/theme/interface/components.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/theme/interface/cssinjs-utils.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/theme/interface/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/_util/colors.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/_util/getrenderpropvalue.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/_util/placements.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/tooltip/purepanel.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/tooltip/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/form/formitemlabel.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/form/hooks/useformitemstatus.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/form/formitem/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/_util/statusutils.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/dayjs/locale/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/dayjs/locale/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/dayjs/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/time-picker/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/date-picker/generatepicker/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/button/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/date-picker/generatepicker/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/empty/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-pagination/lib/options.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-pagination/lib/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-pagination/lib/pagination.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-pagination/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-virtual-list/lib/filler.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-virtual-list/lib/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-virtual-list/lib/utils/cachemap.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-virtual-list/lib/hooks/usescrollto.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-virtual-list/lib/scrollbar.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-virtual-list/lib/list.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-select/lib/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-select/lib/baseselect/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-select/lib/optgroup.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-select/lib/option.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-select/lib/select.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-select/lib/hooks/usebaseprops.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-select/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/_util/motion.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/select/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/pagination/pagination.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/popconfirm/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/popconfirm/purepanel.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-table/lib/constant.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-table/lib/namepathtype.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-table/lib/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-table/lib/footer/row.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-table/lib/footer/cell.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-table/lib/footer/summary.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-table/lib/footer/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-table/lib/sugar/column.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-table/lib/sugar/columngroup.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@rc-component/context/lib/immutable.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-table/lib/table.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-table/lib/utils/legacyutil.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-table/lib/virtualtable/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-table/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-checkbox/es/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/checkbox/checkbox.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/checkbox/groupcontext.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/checkbox/group.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/checkbox/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-menu/lib/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-menu/lib/menu.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-menu/lib/menuitem.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-menu/lib/submenu/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-menu/lib/menuitemgroup.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-menu/lib/context/pathcontext.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-menu/lib/divider.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-menu/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/menu/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/layout/sider.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/menu/menucontext.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/menu/menu.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/menu/menudivider.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/menu/menuitem.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/menu/submenu.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/menu/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/dropdown/dropdown.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/dropdown/dropdown-button.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/dropdown/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/pagination/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/table/hooks/useselection.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/spin/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/table/internaltable.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/table/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@rc-component/tour/es/placements.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@rc-component/tour/es/hooks/usetarget.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@rc-component/tour/es/tourstep/defaultpanel.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@rc-component/tour/es/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@rc-component/tour/es/tour.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@rc-component/tour/es/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/tour/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/transfer/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/transfer/listbody.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/transfer/list.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/transfer/operation.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/transfer/search.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/transfer/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-upload/lib/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/progress/progress.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/progress/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/upload/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/locale/uselocale.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/locale/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/_util/wave/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/badge/ribbon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/badge/scrollnumber.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/badge/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-tabs/lib/hooks/useindicator.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-tabs/lib/tabnavlist/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-tabs/lib/tabpanellist/tabpane.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-dropdown/lib/placements.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-dropdown/lib/dropdown.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-tabs/lib/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-tabs/lib/tabs.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-tabs/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/tabs/tabpane.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/tabs/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/card/card.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/card/grid.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/card/meta.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/card/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-cascader/lib/panel.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-cascader/lib/utils/commonutil.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-cascader/lib/cascader.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-cascader/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/cascader/panel.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/cascader/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-collapse/es/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-collapse/es/collapse.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-collapse/es/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/collapse/collapsepanel.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/collapse/collapse.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/collapse/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/date-picker/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/descriptions/descriptionscontext.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/descriptions/item.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/descriptions/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@rc-component/portal/es/portal.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@rc-component/portal/es/mock.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@rc-component/portal/es/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-drawer/lib/drawerpanel.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-drawer/lib/inter.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-drawer/lib/drawerpopup.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-drawer/lib/drawer.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-drawer/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/drawer/drawerpanel.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/drawer/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/flex/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/float-button/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/input/group.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-input/lib/utils/commonutils.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-input/lib/utils/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-input/lib/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-input/lib/baseinput.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-input/lib/input.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-input/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/input/input.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/input/otp/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/input/password.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/input/search.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-textarea/lib/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-textarea/lib/textarea.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-textarea/lib/resizabletextarea.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-textarea/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/input/textarea.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/input/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@rc-component/mini-decimal/es/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@rc-component/mini-decimal/es/bigintdecimal.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@rc-component/mini-decimal/es/numberdecimal.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@rc-component/mini-decimal/es/minidecimal.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@rc-component/mini-decimal/es/numberutil.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@rc-component/mini-decimal/es/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-input-number/es/inputnumber.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-input-number/es/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/input-number/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/grid/row.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/grid/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/list/item.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/list/context.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/list/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-mentions/lib/option.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-mentions/lib/util.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-mentions/lib/mentions.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/mentions/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/modal/modal.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/modal/purepanel.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/modal/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/notification/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/popover/purepanel.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/popover/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-slider/lib/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-slider/lib/handles/handle.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-slider/lib/handles/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-slider/lib/marks/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-slider/lib/slider.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-slider/lib/context.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-slider/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/slider/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/space/compact.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/space/addon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/space/context.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/space/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/table/column.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/table/columngroup.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/table/table.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/table/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/tag/checkabletag.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/tag/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-tree/lib/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-tree/lib/contexttypes.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-tree/lib/dropindicator.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-tree/lib/nodelist.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-tree/lib/tree.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-tree-select/lib/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-tree-select/lib/treenode.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-tree-select/lib/utils/strategyutil.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-tree-select/lib/treeselect.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-tree-select/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-tree/lib/treenode.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-tree/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/tree/tree.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/tree/directorytree.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/tree/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/tree-select/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-upload/lib/ajaxuploader.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-upload/lib/upload.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-upload/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/upload/upload.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/upload/dragger.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/upload/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/config-provider/defaultrenderempty.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/config-provider/context.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/config-provider/hooks/useconfig.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/config-provider/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/modal/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/modal/confirm.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/modal/usemodal/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/app/context.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/app/app.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/app/useapp.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/app/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/auto-complete/autocomplete.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/auto-complete/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/avatar/avatarcontext.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/avatar/avatar.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/avatar/avatargroup.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/avatar/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/back-top/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/breadcrumb/breadcrumbitem.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/breadcrumb/breadcrumb.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/breadcrumb/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/date-picker/locale/en_us.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/calendar/locale/en_us.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/calendar/generatecalendar.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/calendar/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/react-slick/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/carousel/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/col/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/fast-color/lib/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/fast-color/lib/fastcolor.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/fast-color/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@rc-component/color-picker/lib/color.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@rc-component/color-picker/lib/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@rc-component/color-picker/lib/components/slider.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@rc-component/color-picker/lib/hooks/usecomponent.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@rc-component/color-picker/lib/colorpicker.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@rc-component/color-picker/lib/components/colorblock.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@rc-component/color-picker/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/color-picker/color.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/color-picker/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/color-picker/colorpicker.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/color-picker/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/divider/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/flex/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/float-button/backtop.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/float-button/floatbuttongroup.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/float-button/purepanel.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/float-button/floatbutton.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/float-button/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-field-form/lib/formcontext.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/form/context.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/form/errorlist.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/form/formlist.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/form/hooks/useforminstance.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/form/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-image/lib/hooks/useimagetransform.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-image/lib/preview.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-image/lib/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-image/lib/previewgroup.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-image/lib/image.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-image/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/image/previewgroup.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/image/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/layout/layout.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/layout/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-notification/lib/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-notification/lib/notice.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/message/purepanel.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/message/usemessage.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/message/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/notification/purepanel.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/notification/usenotification.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/notification/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@rc-component/qrcode/lib/libs/qrcodegen.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@rc-component/qrcode/lib/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@rc-component/qrcode/lib/utils.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@rc-component/qrcode/lib/qrcodecanvas.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@rc-component/qrcode/lib/qrcodesvg.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@rc-component/qrcode/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/qr-code/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/qr-code/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/radio/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/radio/group.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/radio/radio.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/radio/radiobutton.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/radio/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-rate/lib/star.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-rate/lib/rate.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/rate/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons-svg/lib/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/components/icon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/components/twotoneprimarycolor.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/components/antdicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/result/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/row/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-segmented/es/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/segmented/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/skeleton/element.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/skeleton/avatar.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/skeleton/button.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/skeleton/image.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/skeleton/input.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/skeleton/node.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/skeleton/paragraph.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/skeleton/title.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/skeleton/skeleton.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/skeleton/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/splitter/splitbar.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/splitter/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/splitter/panel.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/splitter/splitter.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/splitter/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/statistic/utils.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/statistic/statistic.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/statistic/countdown.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/statistic/timer.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/statistic/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-steps/lib/interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-steps/lib/step.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-steps/lib/steps.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-steps/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/steps/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/rc-switch/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/switch/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/theme/themes/default/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/theme/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/timeline/timelineitem.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/timeline/timeline.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/timeline/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/tour/purepanel.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/tour/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/typography/typography.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/typography/base/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/typography/link.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/typography/paragraph.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/typography/text.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/typography/title.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/typography/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/version/version.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/version/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/watermark/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/config-provider/unstablecontext.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/antd/es/index.d.ts","./src/components/molecules/message_manager.tsx","./src/utils/mcptokenstore.ts","./src/utils/cookieutils.ts","./src/components/tag_management/types.tsx","./src/components/key_team_helpers/key_list.tsx","./src/components/email_events/types.ts","./src/lib/http/schema.d.ts","./src/components/claude_code_plugins/types.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/recharts/types/component/defaulttooltipcontent.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/fp/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/af.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/ar.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/ar-dz.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/ar-eg.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/ar-ma.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/ar-sa.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/ar-tn.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/az.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/be.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/be-tarask.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/bg.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/bn.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/bs.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/ca.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/ckb.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/cs.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/cy.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/da.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/de.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/de-at.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/el.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/en-au.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/en-ca.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/en-gb.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/en-ie.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/en-in.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/en-nz.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/en-us.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/en-za.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/eo.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/es.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/et.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/eu.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/fa-ir.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/fi.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/fr.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/fr-ca.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/fr-ch.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/fy.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/gd.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/gl.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/gu.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/he.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/hi.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/hr.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/ht.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/hu.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/hy.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/id.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/is.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/it.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/it-ch.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/ja.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/ja-hira.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/ka.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/kk.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/km.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/kn.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/ko.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/lb.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/lt.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/lv.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/mk.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/mn.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/ms.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/mt.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/nb.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/nl.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/nl-be.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/nn.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/oc.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/pl.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/pt.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/pt-br.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/ro.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/ru.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/se.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/sk.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/sl.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/sq.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/sr.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/sr-latn.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/sv.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/ta.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/te.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/th.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/tr.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/ug.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/uk.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/uz.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/uz-cyrl.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/vi.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/zh-cn.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/zh-hk.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale/zh-tw.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/locale.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tremor/react/dist/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/accountbookfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/accountbookoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/accountbooktwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/aimoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/alertfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/alertoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/alerttwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/alibabaoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/aligncenteroutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/alignleftoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/alignrightoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/alipaycirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/alipaycircleoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/alipayoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/alipaysquarefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/aliwangwangfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/aliwangwangoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/aliyunoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/amazoncirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/amazonoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/amazonsquarefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/androidfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/androidoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/antcloudoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/antdesignoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/apartmentoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/apifilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/apioutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/apitwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/applefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/appleoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/appstoreaddoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/appstorefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/appstoreoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/appstoretwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/areachartoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/arrowdownoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/arrowleftoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/arrowrightoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/arrowupoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/arrowsaltoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/audiofilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/audiomutedoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/audiooutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/audiotwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/auditoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/backwardfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/backwardoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/baiduoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/bankfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/bankoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/banktwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/barchartoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/barcodeoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/barsoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/behancecirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/behanceoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/behancesquarefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/behancesquareoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/bellfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/belloutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/belltwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/bgcolorsoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/bilibilifilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/bilibilioutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/blockoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/boldoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/bookfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/bookoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/booktwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/borderbottomoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/borderhorizontaloutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/borderinneroutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/borderleftoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/borderouteroutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/borderoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/borderrightoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/bordertopoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/borderverticleoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/borderlesstableoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/boxplotfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/boxplotoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/boxplottwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/branchesoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/bugfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/bugoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/bugtwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/buildfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/buildoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/buildtwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/bulbfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/bulboutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/bulbtwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/calculatorfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/calculatoroutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/calculatortwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/calendarfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/calendaroutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/calendartwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/camerafilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/cameraoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/cameratwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/carfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/caroutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/cartwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/caretdownfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/caretdownoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/caretleftfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/caretleftoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/caretrightfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/caretrightoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/caretupfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/caretupoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/carryoutfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/carryoutoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/carryouttwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/checkcirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/checkcircleoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/checkcircletwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/checkoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/checksquarefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/checksquareoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/checksquaretwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/chromefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/chromeoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/cicirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/cicircleoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/cicircletwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/cioutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/citwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/clearoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/clockcirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/clockcircleoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/clockcircletwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/closecirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/closecircleoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/closecircletwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/closeoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/closesquarefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/closesquareoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/closesquaretwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/clouddownloadoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/cloudfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/cloudoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/cloudserveroutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/cloudsyncoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/cloudtwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/clouduploadoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/clusteroutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/codefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/codeoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/codesandboxcirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/codesandboxoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/codesandboxsquarefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/codetwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/codepencirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/codepencircleoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/codepenoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/codepensquarefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/coffeeoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/columnheightoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/columnwidthoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/commentoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/compassfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/compassoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/compasstwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/compressoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/consolesqloutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/contactsfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/contactsoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/contactstwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/containerfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/containeroutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/containertwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/controlfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/controloutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/controltwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/copyfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/copyoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/copytwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/copyrightcirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/copyrightcircleoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/copyrightcircletwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/copyrightoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/copyrighttwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/creditcardfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/creditcardoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/creditcardtwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/crownfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/crownoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/crowntwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/customerservicefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/customerserviceoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/customerservicetwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/dashoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/dashboardfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/dashboardoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/dashboardtwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/databasefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/databaseoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/databasetwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/deletecolumnoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/deletefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/deleteoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/deleterowoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/deletetwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/deliveredprocedureoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/deploymentunitoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/desktopoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/difffilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/diffoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/difftwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/dingdingoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/dingtalkcirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/dingtalkoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/dingtalksquarefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/disconnectoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/discordfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/discordoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/dislikefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/dislikeoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/disliketwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/dockeroutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/dollarcirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/dollarcircleoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/dollarcircletwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/dollaroutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/dollartwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/dotchartoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/dotnetoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/doubleleftoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/doublerightoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/downcirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/downcircleoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/downcircletwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/downoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/downsquarefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/downsquareoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/downsquaretwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/downloadoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/dragoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/dribbblecirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/dribbbleoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/dribbblesquarefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/dribbblesquareoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/dropboxcirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/dropboxoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/dropboxsquarefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/editfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/editoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/edittwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/ellipsisoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/enteroutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/environmentfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/environmentoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/environmenttwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/eurocirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/eurocircleoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/eurocircletwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/eurooutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/eurotwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/exceptionoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/exclamationcirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/exclamationcircleoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/exclamationcircletwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/exclamationoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/expandaltoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/expandoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/experimentfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/experimentoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/experimenttwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/exportoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/eyefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/eyeinvisiblefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/eyeinvisibleoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/eyeinvisibletwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/eyeoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/eyetwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/facebookfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/facebookoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/falloutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fastbackwardfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fastbackwardoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fastforwardfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fastforwardoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fieldbinaryoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fieldnumberoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fieldstringoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fieldtimeoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fileaddfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fileaddoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fileaddtwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/filedoneoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fileexcelfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fileexceloutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fileexceltwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fileexclamationfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fileexclamationoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fileexclamationtwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/filefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/filegifoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fileimagefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fileimageoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fileimagetwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/filejpgoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/filemarkdownfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/filemarkdownoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/filemarkdowntwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fileoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/filepdffilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/filepdfoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/filepdftwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/filepptfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/filepptoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fileppttwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fileprotectoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/filesearchoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/filesyncoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/filetextfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/filetextoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/filetexttwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/filetwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fileunknownfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fileunknownoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fileunknowntwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/filewordfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/filewordoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/filewordtwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/filezipfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/filezipoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fileziptwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/filterfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/filteroutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/filtertwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/firefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fireoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/firetwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/flagfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/flagoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/flagtwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/folderaddfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/folderaddoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/folderaddtwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/folderfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/folderopenfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/folderopenoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/folderopentwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/folderoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/foldertwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/folderviewoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fontcolorsoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fontsizeoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/forkoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/formoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/formatpainterfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/formatpainteroutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/forwardfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/forwardoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/frownfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/frownoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/frowntwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fullscreenexitoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fullscreenoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/functionoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fundfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fundoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fundprojectionscreenoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fundtwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/fundviewoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/funnelplotfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/funnelplotoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/funnelplottwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/gatewayoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/gifoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/giftfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/giftoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/gifttwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/githubfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/githuboutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/gitlabfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/gitlaboutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/globaloutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/goldfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/goldoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/goldtwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/goldenfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/googlecirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/googleoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/googlepluscirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/googleplusoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/googleplussquarefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/googlesquarefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/groupoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/harmonyosoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/hddfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/hddoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/hddtwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/heartfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/heartoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/hearttwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/heatmapoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/highlightfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/highlightoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/highlighttwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/historyoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/holderoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/homefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/homeoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/hometwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/hourglassfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/hourglassoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/hourglasstwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/html5filled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/html5outlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/html5twotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/idcardfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/idcardoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/idcardtwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/iecirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/ieoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/iesquarefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/importoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/inboxoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/infocirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/infocircleoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/infocircletwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/infooutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/insertrowaboveoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/insertrowbelowoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/insertrowleftoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/insertrowrightoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/instagramfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/instagramoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/insurancefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/insuranceoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/insurancetwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/interactionfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/interactionoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/interactiontwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/issuescloseoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/italicoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/javaoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/javascriptoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/keyoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/kubernetesoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/laptopoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/layoutfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/layoutoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/layouttwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/leftcirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/leftcircleoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/leftcircletwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/leftoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/leftsquarefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/leftsquareoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/leftsquaretwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/likefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/likeoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/liketwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/linechartoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/lineheightoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/lineoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/linkoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/linkedinfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/linkedinoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/linuxoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/loading3quartersoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/loadingoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/lockfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/lockoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/locktwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/loginoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/logoutoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/maccommandfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/maccommandoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/mailfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/mailoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/mailtwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/manoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/medicineboxfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/medicineboxoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/medicineboxtwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/mediumcirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/mediumoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/mediumsquarefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/mediumworkmarkoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/mehfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/mehoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/mehtwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/menufoldoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/menuoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/menuunfoldoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/mergecellsoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/mergefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/mergeoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/messagefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/messageoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/messagetwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/minuscirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/minuscircleoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/minuscircletwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/minusoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/minussquarefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/minussquareoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/minussquaretwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/mobilefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/mobileoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/mobiletwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/moneycollectfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/moneycollectoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/moneycollecttwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/monitoroutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/moonfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/moonoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/moreoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/mutedfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/mutedoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/nodecollapseoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/nodeexpandoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/nodeindexoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/notificationfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/notificationoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/notificationtwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/numberoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/onetooneoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/openaifilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/openaioutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/orderedlistoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/paperclipoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/partitionoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/pausecirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/pausecircleoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/pausecircletwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/pauseoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/paycirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/paycircleoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/percentageoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/phonefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/phoneoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/phonetwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/piccenteroutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/picleftoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/picrightoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/picturefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/pictureoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/picturetwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/piechartfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/piechartoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/piecharttwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/pinterestfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/pinterestoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/playcirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/playcircleoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/playcircletwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/playsquarefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/playsquareoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/playsquaretwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/pluscirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/pluscircleoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/pluscircletwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/plusoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/plussquarefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/plussquareoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/plussquaretwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/poundcirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/poundcircleoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/poundcircletwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/poundoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/poweroffoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/printerfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/printeroutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/printertwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/productfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/productoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/profilefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/profileoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/profiletwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/projectfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/projectoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/projecttwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/propertysafetyfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/propertysafetyoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/propertysafetytwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/pullrequestoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/pushpinfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/pushpinoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/pushpintwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/pythonoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/qqcirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/qqoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/qqsquarefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/qrcodeoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/questioncirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/questioncircleoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/questioncircletwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/questionoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/radarchartoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/radiusbottomleftoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/radiusbottomrightoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/radiussettingoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/radiusupleftoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/radiusuprightoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/readfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/readoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/reconciliationfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/reconciliationoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/reconciliationtwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/redenvelopefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/redenvelopeoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/redenvelopetwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/redditcirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/redditoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/redditsquarefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/redooutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/reloadoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/restfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/restoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/resttwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/retweetoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/rightcirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/rightcircleoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/rightcircletwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/rightoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/rightsquarefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/rightsquareoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/rightsquaretwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/riseoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/robotfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/robotoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/rocketfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/rocketoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/rockettwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/rollbackoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/rotateleftoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/rotaterightoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/rubyoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/safetycertificatefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/safetycertificateoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/safetycertificatetwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/safetyoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/savefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/saveoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/savetwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/scanoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/schedulefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/scheduleoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/scheduletwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/scissoroutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/searchoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/securityscanfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/securityscanoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/securityscantwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/selectoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/sendoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/settingfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/settingoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/settingtwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/shakeoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/sharealtoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/shopfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/shopoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/shoptwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/shoppingcartoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/shoppingfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/shoppingoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/shoppingtwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/shrinkoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/signalfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/signaturefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/signatureoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/sisternodeoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/sketchcirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/sketchoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/sketchsquarefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/skinfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/skinoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/skintwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/skypefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/skypeoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/slackcirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/slackoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/slacksquarefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/slacksquareoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/slidersfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/slidersoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/sliderstwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/smalldashoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/smilefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/smileoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/smiletwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/snippetsfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/snippetsoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/snippetstwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/solutionoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/sortascendingoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/sortdescendingoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/soundfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/soundoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/soundtwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/splitcellsoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/spotifyfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/spotifyoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/starfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/staroutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/startwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/stepbackwardfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/stepbackwardoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/stepforwardfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/stepforwardoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/stockoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/stopfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/stopoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/stoptwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/strikethroughoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/subnodeoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/sunfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/sunoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/swapleftoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/swapoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/swaprightoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/switcherfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/switcheroutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/switchertwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/syncoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/tableoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/tabletfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/tabletoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/tablettwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/tagfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/tagoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/tagtwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/tagsfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/tagsoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/tagstwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/taobaocirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/taobaocircleoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/taobaooutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/taobaosquarefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/teamoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/thunderboltfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/thunderboltoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/thunderbolttwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/tiktokfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/tiktokoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/totopoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/toolfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/tooloutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/tooltwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/trademarkcirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/trademarkcircleoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/trademarkcircletwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/trademarkoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/transactionoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/translationoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/trophyfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/trophyoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/trophytwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/truckfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/truckoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/twitchfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/twitchoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/twittercirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/twitteroutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/twittersquarefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/underlineoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/undooutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/ungroupoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/unlockfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/unlockoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/unlocktwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/unorderedlistoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/upcirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/upcircleoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/upcircletwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/upoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/upsquarefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/upsquareoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/upsquaretwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/uploadoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/usbfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/usboutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/usbtwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/useraddoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/userdeleteoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/useroutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/userswitchoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/usergroupaddoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/usergroupdeleteoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/verifiedoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/verticalalignbottomoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/verticalalignmiddleoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/verticalaligntopoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/verticalleftoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/verticalrightoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/videocameraaddoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/videocamerafilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/videocameraoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/videocameratwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/walletfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/walletoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/wallettwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/warningfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/warningoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/warningtwotone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/wechatfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/wechatoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/wechatworkfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/wechatworkoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/weibocirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/weibocircleoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/weibooutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/weibosquarefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/weibosquareoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/whatsappoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/wifioutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/windowsfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/windowsoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/womanoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/xfilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/xoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/yahoofilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/yahoooutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/youtubefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/youtubeoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/yuquefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/yuqueoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/zhihucirclefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/zhihuoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/zhihusquarefilled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/zoominoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/zoomoutoutlined.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/icons/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/components/iconfont.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/components/context.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@ant-design/icons/lib/index.d.ts","./src/utils/textutils.ts","./src/components/common_components/check_openapi_schema.tsx","./src/components/shared/errorutils.tsx","./src/components/molecules/notifications_manager.tsx","./src/components/mcp_tools/types.tsx","./src/components/mcp_tools/constants.ts","./src/lib/http/client.ts","./src/lib/http/resolveapibase.ts","./src/lib/serverrootpath.ts","./src/components/networking.tsx","./src/app/(dashboard)/networking.ts","./src/app/(dashboard)/access-groups/components/types.ts","./src/app/(dashboard)/budgets/components/constants.ts","./src/app/(dashboard)/caching/components/cache_settings/cachesettingsfields.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/vitest/dist/chunks/worker.d.uzwscv9x.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/vitest/dist/chunks/global.d.mamajcmj.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/vitest/dist/chunks/mocker.d.be_2ls6u.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/vitest/dist/chunks/suite.d.fvehnv49.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/expect-type/dist/utils.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/expect-type/dist/overloads.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/expect-type/dist/branding.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/expect-type/dist/messages.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/expect-type/dist/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/vitest/dist/index.d.ts","./src/app/(dashboard)/caching/components/cache_settings/cachesettingsutils.ts","./src/app/(dashboard)/caching/components/cache_settings/cachesettingsutils.test.ts","./src/app/(dashboard)/cost-tracking/components/types.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/academiccapicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/adjustmentsicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/annotationicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/archiveicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/arrowcircledownicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/arrowcirclelefticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/arrowcirclerighticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/arrowcircleupicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/arrowdownicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/arrowlefticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/arrownarrowdownicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/arrownarrowlefticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/arrownarrowrighticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/arrownarrowupicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/arrowrighticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/arrowsmdownicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/arrowsmlefticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/arrowsmrighticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/arrowsmupicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/arrowupicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/arrowsexpandicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/atsymbolicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/backspaceicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/badgecheckicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/banicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/beakericon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/bellicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/bookopenicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/bookmarkalticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/bookmarkicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/briefcaseicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/cakeicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/calculatoricon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/calendaricon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/cameraicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/cashicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/chartbaricon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/chartpieicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/chartsquarebaricon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/chatalt2icon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/chatalticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/chaticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/checkcircleicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/checkicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/chevrondoubledownicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/chevrondoublelefticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/chevrondoublerighticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/chevrondoubleupicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/chevrondownicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/chevronlefticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/chevronrighticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/chevronupicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/chipicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/clipboardcheckicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/clipboardcopyicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/clipboardlisticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/clipboardicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/clockicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/clouddownloadicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/clouduploadicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/cloudicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/codeicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/cogicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/collectionicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/colorswatchicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/creditcardicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/cubetransparenticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/cubeicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/currencybangladeshiicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/currencydollaricon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/currencyeuroicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/currencypoundicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/currencyrupeeicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/currencyyenicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/cursorclickicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/databaseicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/desktopcomputericon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/devicemobileicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/devicetableticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/documentaddicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/documentdownloadicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/documentduplicateicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/documentremoveicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/documentreporticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/documentsearchicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/documenttexticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/documenticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/dotscirclehorizontalicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/dotshorizontalicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/dotsverticalicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/downloadicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/duplicateicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/emojihappyicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/emojisadicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/exclamationcircleicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/exclamationicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/externallinkicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/eyeofficon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/eyeicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/fastforwardicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/filmicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/filtericon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/fingerprinticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/fireicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/flagicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/folderaddicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/folderdownloadicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/folderopenicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/folderremoveicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/foldericon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/gifticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/globealticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/globeicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/handicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/hashtagicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/hearticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/homeicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/identificationicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/inboxinicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/inboxicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/informationcircleicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/keyicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/libraryicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/lightbulbicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/lightningbolticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/linkicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/locationmarkericon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/lockclosedicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/lockopenicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/loginicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/logouticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/mailopenicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/mailicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/mapicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/menualt1icon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/menualt2icon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/menualt3icon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/menualt4icon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/menuicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/microphoneicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/minuscircleicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/minussmicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/minusicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/moonicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/musicnoteicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/newspapericon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/officebuildingicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/paperairplaneicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/paperclipicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/pauseicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/pencilalticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/pencilicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/phoneincomingicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/phonemissedcallicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/phoneoutgoingicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/phoneicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/photographicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/playicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/pluscircleicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/plussmicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/plusicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/presentationchartbaricon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/presentationchartlineicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/printericon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/puzzleicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/qrcodeicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/questionmarkcircleicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/receiptrefundicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/receipttaxicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/refreshicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/replyicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/rewindicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/rssicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/saveasicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/saveicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/scaleicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/scissorsicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/searchcircleicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/searchicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/selectoricon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/servericon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/shareicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/shieldcheckicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/shieldexclamationicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/shoppingbagicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/shoppingcarticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/sortascendingicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/sortdescendingicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/sparklesicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/speakerphoneicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/staricon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/statusofflineicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/statusonlineicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/stopicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/sunicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/supporticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/switchhorizontalicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/switchverticalicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/tableicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/tagicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/templateicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/terminalicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/thumbdownicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/thumbupicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/ticketicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/translateicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/trashicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/trendingdownicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/trendingupicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/truckicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/uploadicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/useraddicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/usercircleicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/usergroupicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/userremoveicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/usericon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/usersicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/variableicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/videocameraicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/viewboardsicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/viewgridaddicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/viewgridicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/viewlisticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/volumeofficon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/volumeupicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/wifiicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/xcircleicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/xicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/zoominicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/zoomouticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/outline/index.d.ts","./src/components/common_components/simple_table.tsx","./src/lib/assetpaths.ts","./src/components/provider_info_helpers.tsx","./src/app/(dashboard)/cost-tracking/components/provider_display_helpers.ts","./src/app/(dashboard)/cost-tracking/components/provider_discount_table.tsx","./src/app/(dashboard)/cost-tracking/components/add_provider_form.tsx","./src/app/(dashboard)/cost-tracking/components/provider_margin_table.tsx","./src/app/(dashboard)/cost-tracking/components/add_margin_form.tsx","./src/app/(dashboard)/cost-tracking/components/pricing_calculator/types.ts","./src/utils/datautils.ts","./src/app/(dashboard)/cost-tracking/components/pricing_calculator/multi_export_utils.ts","./src/app/(dashboard)/cost-tracking/components/pricing_calculator/multi_export_dropdown.tsx","./src/app/(dashboard)/cost-tracking/components/pricing_calculator/multi_cost_results.tsx","./src/app/(dashboard)/cost-tracking/components/pricing_calculator/use_multi_cost_estimate.ts","./src/app/(dashboard)/cost-tracking/components/pricing_calculator/index.tsx","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/lucide-react/dist/lucide-react.d.ts","./src/components/helplink.tsx","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/react-syntax-highlighter/index.d.ts","./src/app/(dashboard)/api-reference/components/codeblock.tsx","./src/app/(dashboard)/cost-tracking/components/how_it_works.tsx","./src/app/(dashboard)/cost-tracking/components/use_discount_config.ts","./src/app/(dashboard)/cost-tracking/components/use_margin_config.ts","./src/components/llm_calls/fetch_models.tsx","./src/app/(dashboard)/cost-tracking/components/cost_tracking_settings.tsx","./src/app/(dashboard)/cost-tracking/components/index.ts","./src/app/(dashboard)/cost-tracking/components/provider_display_helpers.test.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/react-dom/client.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/aria-query/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/dom/types/matches.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/dom/types/wait-for.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/dom/types/query-helpers.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/dom/types/queries.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/dom/types/get-queries-for-element.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/pretty-format/build/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/pretty-format/build/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/dom/types/screen.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/dom/types/wait-for-element-to-be-removed.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/dom/types/get-node-text.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/dom/types/events.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/dom/types/pretty-dom.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/dom/types/role-helpers.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/dom/types/config.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/dom/types/suggestions.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/dom/types/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/react-dom/test-utils/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/react/types/index.d.ts","./src/app/(dashboard)/cost-tracking/components/use_discount_config.test.ts","./src/app/(dashboard)/cost-tracking/components/use_margin_config.test.ts","./src/app/(dashboard)/cost-tracking/components/pricing_calculator/multi_export_utils.test.ts","./src/app/(dashboard)/cost-tracking/components/pricing_calculator/use_multi_cost_estimate.test.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/query-core/build/modern/_tsup-dts-rollup.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/query-core/build/modern/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/react-query/build/modern/_tsup-dts-rollup.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/react-query/build/modern/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/jwt-decode/build/esm/index.d.ts","./src/utils/jwtutils.ts","./src/utils/returnurlutils.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/request/search-params.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/shared/lib/segment-cache/vary-params-decoding.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/app-render/vary-params.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/request/params.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/shared/lib/app-router-types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/flight-data-helpers.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/components/app-router-headers.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/components/segment-cache/cache-key.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/components/router-reducer/fetch-server-response.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/components/segment-cache/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/shared/lib/segment-cache/segment-value-encoding.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/components/segment-cache/scheduler.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/components/segment-cache/cache-map.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/components/segment-cache/vary-path.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/components/segment-cache/cache.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/components/router-reducer/ppr-navigations.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/components/segment-cache/navigation.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/components/router-reducer/router-reducer-types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/shared/lib/app-router-context.shared-runtime.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/components/readonly-url-search-params.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/shared/lib/hooks-client-context.shared-runtime.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/shared/lib/server-inserted-html.shared-runtime.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/components/unrecognized-action-error.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/components/redirect-status-code.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/components/redirect-error.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/components/redirect.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/components/not-found.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/components/forbidden.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/components/unauthorized.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/components/unstable-rethrow.server.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/components/unstable-rethrow.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/components/navigation.react-server.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/components/navigation.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/navigation.d.ts","./src/utils/roles.ts","./src/app/(dashboard)/hooks/common/querykeysfactory.ts","./src/app/(dashboard)/hooks/uiconfig/useuiconfig.ts","./src/app/(dashboard)/hooks/useauthorized.ts","./src/app/(dashboard)/hooks/useauthorized.test.ts","./src/utils/localstorageutils.ts","./src/app/(dashboard)/hooks/usedisableblogposts.ts","./src/app/(dashboard)/hooks/usedisablebouncingicon.ts","./src/app/(dashboard)/hooks/usedisableshownewbadge.ts","./src/app/(dashboard)/hooks/usedisableshownewbadge.test.ts","./src/app/(dashboard)/hooks/usedisableshowprompts.ts","./src/app/(dashboard)/hooks/usedisableshowprompts.test.ts","./src/app/(dashboard)/hooks/usedisableusageindicator.ts","./src/app/(dashboard)/hooks/usedisableusageindicator.test.ts","./src/app/(dashboard)/hooks/usehideagentplatformbanner.ts","./src/app/(dashboard)/hooks/accessgroups/useaccessgroups.ts","./src/app/(dashboard)/hooks/accessgroups/useaccessgroupdetails.ts","./src/app/(dashboard)/hooks/accessgroups/useaccessgroups.test.ts","./src/app/(dashboard)/hooks/accessgroups/usecreateaccessgroup.ts","./src/app/(dashboard)/hooks/accessgroups/usedeleteaccessgroup.ts","./src/app/(dashboard)/hooks/accessgroups/useeditaccessgroup.ts","./src/components/agents/types.ts","./src/app/(dashboard)/hooks/agents/useagents.ts","./src/app/(dashboard)/hooks/agents/useagents.test.ts","./src/app/(dashboard)/hooks/blogposts/useblogposts.ts","./src/app/(dashboard)/hooks/budgets/usebudgets.ts","./src/app/(dashboard)/hooks/cloudzero/usecloudzerocreate.ts","./src/app/(dashboard)/hooks/cloudzero/usecloudzerocreate.test.ts","./src/app/(dashboard)/hooks/cloudzero/usecloudzerodryrun.ts","./src/app/(dashboard)/hooks/cloudzero/usecloudzerodryrun.test.ts","./src/app/(dashboard)/hooks/cloudzero/usecloudzeroexport.ts","./src/app/(dashboard)/hooks/cloudzero/usecloudzeroexport.test.ts","./src/components/cloudzerocosttracking/types.ts","./src/app/(dashboard)/hooks/cloudzero/usecloudzerosettings.ts","./src/app/(dashboard)/hooks/cloudzero/usecloudzerosettings.test.ts","./src/app/(dashboard)/hooks/common/querykeysfactory.test.ts","./src/app/(dashboard)/hooks/configoverrides/hashicorpvaultapi.ts","./src/app/(dashboard)/hooks/configoverrides/usehashicorpvaultconfig.ts","./src/app/(dashboard)/hooks/configoverrides/usedeletehashicorpvaultconfig.ts","./src/app/(dashboard)/hooks/configoverrides/useupdatehashicorpvaultconfig.ts","./src/app/(dashboard)/hooks/credentials/usecredentials.ts","./src/app/(dashboard)/hooks/credentials/usecredentials.test.ts","./src/app/(dashboard)/hooks/customers/usecustomers.ts","./src/app/(dashboard)/hooks/customers/usecustomers.test.ts","./src/app/(dashboard)/hooks/guardrails/useguardrails.ts","./src/app/(dashboard)/hooks/guardrails/useguardrails.test.ts","./src/app/(dashboard)/hooks/guardrails/useregisterguardrail.ts","./src/app/(dashboard)/hooks/healthreadiness/usehealthreadinessdetails.ts","./src/app/(dashboard)/hooks/keys/usekeyaliases.ts","./src/app/(dashboard)/hooks/keys/usekeyaliases.test.ts","./src/app/(dashboard)/hooks/keys/usekeys.ts","./src/app/(dashboard)/hooks/keys/usekeys.test.ts","./src/app/(dashboard)/hooks/keys/useresetkeyspend.ts","./src/app/(dashboard)/hooks/logdetails/uselogdetails.ts","./src/app/(dashboard)/hooks/login/uselogin.ts","./src/app/(dashboard)/hooks/mcpsemanticfiltersettings/usemcpsemanticfiltersettings.ts","./src/app/(dashboard)/hooks/mcpsemanticfiltersettings/useupdatemcpsemanticfiltersettings.ts","./src/app/(dashboard)/hooks/mcpservers/usemcpaccessgroups.ts","./src/app/(dashboard)/hooks/mcpservers/usemcpaccessgroups.test.ts","./src/app/(dashboard)/hooks/mcpservers/usemcpserverhealth.ts","./src/app/(dashboard)/hooks/mcpservers/usemcpserverhealth.test.ts","./src/app/(dashboard)/hooks/mcpservers/usemcpservers.ts","./src/app/(dashboard)/hooks/mcpservers/usemcpservers.test.ts","./src/app/(dashboard)/hooks/mcpservers/usemcptoolsets.ts","./src/app/(dashboard)/hooks/models/usemodelcostmap.ts","./src/app/(dashboard)/hooks/models/usemodelcostmap.test.ts","./src/app/(dashboard)/hooks/models/usemodels.ts","./src/app/(dashboard)/hooks/models/usemodels.test.ts","./src/app/(dashboard)/hooks/onboarding/useonboarding.ts","./src/app/(dashboard)/hooks/onboarding/useonboarding.test.ts","./src/app/(dashboard)/hooks/organizations/useorganizations.ts","./src/app/(dashboard)/hooks/organizations/useorganizations.test.ts","./src/app/(dashboard)/hooks/projects/useprojects.ts","./src/app/(dashboard)/hooks/projects/usecreateproject.ts","./src/app/(dashboard)/hooks/projects/usecreateproject.test.ts","./src/app/(dashboard)/hooks/projects/usedeleteproject.ts","./src/app/(dashboard)/hooks/projects/usedeleteproject.test.ts","./src/app/(dashboard)/hooks/projects/useprojectdetails.ts","./src/app/(dashboard)/hooks/projects/useprojectdetails.test.ts","./src/app/(dashboard)/hooks/projects/useprojects.test.ts","./src/app/(dashboard)/hooks/projects/useupdateproject.ts","./src/app/(dashboard)/hooks/projects/useupdateproject.test.ts","./src/app/(dashboard)/hooks/providers/useproviderfields.ts","./src/app/(dashboard)/hooks/providers/useproviderfields.test.ts","./src/app/(dashboard)/hooks/proxyconfig/useproxyconfig.ts","./src/app/(dashboard)/hooks/proxyconfig/useproxyconfig.test.ts","./src/utils/proxyutils.ts","./src/app/(dashboard)/hooks/proxysettings/useproxysettings.ts","./src/app/(dashboard)/hooks/router/userouterfields.ts","./src/app/(dashboard)/hooks/router/userouterfields.test.ts","./src/app/(dashboard)/hooks/routersettings/useupdateretrypolicy.ts","./src/components/routing_groups/types.ts","./src/app/(dashboard)/hooks/routinggroups/useroutinggroups.ts","./src/app/(dashboard)/hooks/sso/useeditssosettings.ts","./src/app/(dashboard)/hooks/sso/useeditssosettings.test.ts","./src/app/(dashboard)/hooks/sso/usessosettings.ts","./src/app/(dashboard)/hooks/sso/usessosettings.test.ts","./src/app/(dashboard)/hooks/storemodelindb/usestoremodelindb.ts","./src/app/(dashboard)/hooks/storemodelindb/usestoremodelindb.test.ts","./src/app/(dashboard)/hooks/storerequestinspendlogs/usestorerequestinspendlogs.ts","./src/app/(dashboard)/hooks/tags/usetags.ts","./src/app/(dashboard)/hooks/tags/usetags.test.ts","./src/app/(dashboard)/hooks/teams/useteams.ts","./src/app/(dashboard)/hooks/teams/useteams.test.ts","./src/app/(dashboard)/hooks/uiconfig/useuiconfig.test.ts","./src/app/(dashboard)/hooks/uisettings/useuisettings.ts","./src/app/(dashboard)/hooks/uisettings/useuisettings.test.ts","./src/app/(dashboard)/hooks/uisettings/useupdateuisettings.ts","./src/app/(dashboard)/hooks/uisettings/useupdateuisettings.test.ts","./src/app/(dashboard)/hooks/users/usecurrentuser.ts","./src/app/(dashboard)/hooks/users/usecurrentuser.test.ts","./src/app/(dashboard)/hooks/users/useusers.ts","./src/app/(dashboard)/hooks/users/useusers.test.ts","./src/app/(dashboard)/models-and-endpoints/utils/modeldatatransformer.ts","./src/app/(dashboard)/models-and-endpoints/utils/modeldatatransformer.test.ts","./src/components/chat_ui/mode_endpoint_mapping.tsx","./src/app/(dashboard)/playground/components/chat_ui/chatconstants.ts","./src/app/(dashboard)/playground/llm_calls/fetch_agents.tsx","./src/app/(dashboard)/playground/components/compareui/endpoint_config.ts","./src/app/(dashboard)/playground/components/compareui/endpoint_config.test.ts","./src/components/chat_ui/types.ts","./src/components/chat_ui/responsemetrics.tsx","./src/app/(dashboard)/playground/hooks/usechathistory.ts","./src/app/(dashboard)/playground/hooks/usechathistory.test.ts","./src/components/llm_calls/code_interpreter_handler.ts","./src/app/(dashboard)/playground/hooks/usecodeinterpreter.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/lodash/common/common.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/lodash/common/array.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/lodash/common/collection.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/lodash/common/date.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/lodash/common/function.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/lodash/common/lang.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/lodash/common/math.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/lodash/common/number.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/lodash/common/object.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/lodash/common/seq.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/lodash/common/string.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/lodash/common/util.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/lodash/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/lodash/debounce.d.ts","./src/components/agent_management/agentselector.tsx","./src/components/callback_info_helpers.tsx","./src/components/common_components/accessgroupselector.tsx","./src/components/common_components/budget_duration_dropdown.tsx","./src/components/common_components/keylifecyclesettings.tsx","./src/components/common_components/modelselector.tsx","./src/components/common_components/modelaliasmanager.tsx","./src/components/common_components/passthroughroutesselector.tsx","./src/components/shared/numerical_input.tsx","./src/components/team/loggingsettings.tsx","./src/components/common_components/premiumloggingsettings.tsx","./src/components/common_components/ratelimittypeformitem.tsx","./src/components/router_settings/latencybasedconfiguration.tsx","./src/components/router_settings/reliabilityretriessection.tsx","./src/components/router_settings/routingstrategyselector.tsx","./src/components/router_settings/tagfilteringtoggle.tsx","./src/components/router_settings/routersettingsform.tsx","./src/components/settings/routersettings/fallbacks/addfallbacksmodal.tsx","./src/components/settings/routersettings/fallbacks/fallbackgroupconfig.tsx","./src/components/settings/routersettings/fallbacks/fallbackselectionform.tsx","./src/components/settings/routersettings/fallbacks/addfallbacks.tsx","./src/components/common_components/routersettingsaccordion.tsx","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/pacer/dist/esm/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/pacer/dist/esm/debouncer.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/react-pacer/dist/esm/debouncer/usedebouncedcallback.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/react-pacer/dist/esm/debouncer/usedebouncedstate.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/react-pacer/dist/esm/debouncer/usedebouncedvalue.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/react-pacer/dist/esm/debouncer/usedebouncer.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/react-pacer/dist/esm/debouncer/index.d.ts","./src/components/common_components/team_dropdown.tsx","./src/components/common_components/organizationdropdown.tsx","./src/components/common_components/projectdropdown.tsx","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/papaparse/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/react-copy-to-clipboard/index.d.ts","./src/components/bulk_create_users_button.tsx","./src/components/key_team_helpers/fetch_available_models_team_key.tsx","./src/components/onboarding_link.tsx","./src/components/createuserbutton.tsx","./src/components/key_team_helpers/budgetfallbackseditor.tsx","./src/components/key_team_helpers/budgetwindowseditor.tsx","./src/components/mcp_server_management/mcpserverselector.tsx","./src/utils/mcptoolcrudclassification.ts","./src/components/mcp_tools/mcpcrudpermissionpanel.tsx","./src/components/mcp_server_management/mcptoolpermissions.tsx","./src/components/shared/createdkeydisplay.tsx","./src/components/vector_store_management/types.tsx","./src/components/vector_store_management/vectorstoreselector.tsx","./src/components/organisms/utils.ts","./src/components/organisms/create_key_button.tsx","./src/app/(dashboard)/projects/components/projectmodals/projectbaseform.tsx","./src/app/(dashboard)/projects/components/projectmodals/projectformutils.ts","./src/app/(dashboard)/projects/components/projectmodals/projectformutils.test.ts","./src/app/(dashboard)/prompts/components/prompt_editor_view/types.ts","./src/app/(dashboard)/prompts/components/prompt_editor_view/utils.ts","./src/app/(dashboard)/prompts/components/prompt_editor_view/utils.test.ts","./src/app/(dashboard)/prompts/components/prompt_editor_view/conversation_panel/types.ts","./src/app/(dashboard)/prompts/components/prompt_editor_view/conversation_panel/useconversation.ts","./src/utils/migratedpages.ts","./src/components/networking.test.ts","./src/components/page_metadata.ts","./src/components/common_components/newbadge.tsx","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/cva/dist/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/tailwind-merge/dist/types.d.ts","./src/lib/cva.config.ts","./src/components/usageindicator.tsx","./src/components/leftnav.tsx","./src/components/page_utils.ts","./src/components/page_utils.test.ts","./src/utils/teamutils.ts","./src/components/entityusageexport/types.ts","./src/components/entityusageexport/exportformatselector.tsx","./src/components/entityusageexport/exportsummary.tsx","./src/components/entityusageexport/exporttypeselector.tsx","./src/components/entityusageexport/utils.ts","./src/components/entityusageexport/entityusageexportmodal.tsx","./src/components/entityusageexport/usageexportheader.tsx","./src/components/entityusageexport/index.ts","./src/components/entityusageexport/utils.test.ts","./src/components/guardrailsmonitor/mockdata.ts","./src/components/modelselect/modelutils.ts","./src/components/modelselect/modelutils.test.ts","./src/components/navbar/navdisplayname.ts","./src/components/navbar/navdisplayname.test.ts","./src/components/navbar/navproductlinkclass.ts","./src/components/settings/adminsettings/hashicorpvault/constants.ts","./src/components/settings/adminsettings/mcpsemanticfiltersettings/semanticfiltertestutils.ts","./src/components/settings/adminsettings/mcpsemanticfiltersettings/semanticfiltertestutils.test.ts","./src/components/settings/adminsettings/ssosettings/constants.ts","./src/components/settings/adminsettings/ssosettings/utils.ts","./src/components/settings/adminsettings/ssosettings/utils.test.ts","./src/components/settings/loggingandalerts/loggingcallbacks/types.ts","./src/components/usagepage/types.ts","./src/components/usagepage/hooks/usepaginateddailyactivity.ts","./src/components/usagepage/utils/value_formatters.tsx","./src/components/usagepage/utils/value_formatters.test.ts","./src/components/agents/agent_config.ts","./src/components/agents/agent_discovery_utils.ts","./src/components/agents/agent_discovery_utils.test.ts","./src/components/agents/agent_type_utils.ts","./src/components/atoms/tooltip.tsx","./src/components/atoms/index.ts","./src/components/chat/types.ts","./src/components/chat/usechathistory.ts","./src/components/chat/usechathistory.test.ts","./src/components/claude_code_plugins/helpers.ts","./src/components/claude_code_plugins/helpers.test.ts","./src/components/email_events/email_event_settings.tsx","./src/components/email_events/index.ts","./src/components/guardrails/guardrail_garden_configs.ts","./src/components/guardrails/guardrail_garden_data.ts","./src/components/guardrails/types.ts","./src/components/guardrails/custom_code/customcodemodal.tsx","./src/components/guardrails/custom_code/index.ts","./src/components/key_team_helpers/filter_helpers.ts","./src/components/key_team_helpers/filter_helpers.test.ts","./src/components/key_team_helpers/transform_key_info.tsx","./src/components/key_team_helpers/transform_key_info.test.ts","./src/components/mcp_tools/testutils.ts","./src/components/model_add/credential_form_helpers.ts","./src/components/model_add/credential_form_helpers.test.ts","./src/components/model_dashboard/types.ts","./src/components/molecules/message_manager.test.ts","./src/components/organisms/utils.test.ts","./src/components/policies/types.ts","./src/components/policies/build_attachment_data.ts","./src/components/policies/build_attachment_data.test.ts","./src/components/policies/scope_validation.ts","./src/components/policies/scope_validation.test.ts","./src/components/team/tabvisibilityutils.ts","./src/components/team/tabvisibilityutils.test.ts","./src/components/team/usemyteammember.ts","./src/components/view_logs/constants.ts","./src/components/molecules/filter.tsx","./src/components/common_components/filterteamdropdown.tsx","./src/components/keyaliasselect/paginatedkeyaliasselect/paginatedkeyaliasselect.tsx","./src/components/modelselect/paginatedmodelselect/paginatedmodelselect.tsx","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/moment/ts3.1-typings/moment.d.ts","./src/components/constants.tsx","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/utils.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/core/table.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/features/columnvisibility.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/features/columnordering.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/features/columnpinning.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/features/rowpinning.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/core/headers.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/features/columnfaceting.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/features/globalfaceting.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/filterfns.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/features/columnfiltering.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/features/globalfiltering.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/sortingfns.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/features/rowsorting.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/aggregationfns.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/features/columngrouping.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/features/rowexpanding.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/features/columnsizing.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/features/rowpagination.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/features/rowselection.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/core/row.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/core/cell.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/core/column.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/columnhelper.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/utils/getcorerowmodel.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/utils/getexpandedrowmodel.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/utils/getfacetedminmaxvalues.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/utils/getfacetedrowmodel.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/utils/getfaceteduniquevalues.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/utils/getfilteredrowmodel.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/utils/getgroupedrowmodel.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/utils/getpaginationrowmodel.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/utils/getsortedrowmodel.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/table-core/build/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@tanstack/react-table/build/lib/index.d.ts","./src/components/common_components/tableheadersortdropdown/tableheadersortdropdown.tsx","./src/components/view_logs/time_cell.tsx","./src/components/view_logs/typebadges.tsx","./src/components/view_logs/columns.tsx","./src/components/view_logs/log_filter_logic.tsx","./src/components/view_logs/filter_options.ts","./src/components/view_logs/utils.ts","./src/components/view_logs/guardrailviewer/bedrockguardraildetails.tsx","./src/components/view_logs/guardrailviewer/__tests__/fixtures.ts","./src/components/view_logs/logdetailsdrawer/constants.ts","./src/components/view_logs/logdetailsdrawer/drawerheader.tsx","./src/components/view_logs/logdetailsdrawer/usekeyboardnavigation.ts","./src/components/view_logs/guardrailviewer/presidiodetectedentities.tsx","./src/components/view_logs/guardrailviewer/contentfilterdetails.tsx","./src/components/view_logs/guardrailviewer/compliancepanel.tsx","./src/components/view_logs/guardrailviewer/guardrailviewer.tsx","./src/components/view_logs/evalviewer/evalviewer.tsx","./src/components/view_logs/costbreakdownviewer.tsx","./src/components/view_logs/configinfomessage.tsx","./src/components/view_logs/vectorstoreviewer.tsx","./src/components/view_logs/logdetailsdrawer/truncatedvalue.tsx","./src/components/view_logs/logdetailsdrawer/tokenflow.tsx","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/react-json-view-lite/dist/datarenderer.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/react-json-view-lite/dist/index.d.ts","./src/components/view_logs/logdetailsdrawer/jsonviewer.tsx","./src/components/view_logs/logdetailsdrawer/utils.ts","./src/components/view_logs/toolssection/types.ts","./src/components/view_logs/toolssection/utils.ts","./src/components/view_logs/toolssection/formattedtoolview.tsx","./src/components/view_logs/toolssection/jsontoolview.tsx","./src/components/view_logs/toolssection/toolexpandedcontent.tsx","./src/components/view_logs/toolssection/toolitem.tsx","./src/components/view_logs/toolssection/toolssection.tsx","./src/components/view_logs/toolssection/index.ts","./src/components/view_logs/logdetailsdrawer/prettymessagestypes.ts","./src/components/view_logs/logdetailsdrawer/prettymessagesutils.ts","./src/components/view_logs/logdetailsdrawer/sectionheader.tsx","./src/components/view_logs/logdetailsdrawer/collapsiblemessage.tsx","./src/components/view_logs/logdetailsdrawer/simpletoolcallblock.tsx","./src/components/view_logs/logdetailsdrawer/simplemessageblock.tsx","./src/components/view_logs/logdetailsdrawer/historytree.tsx","./src/components/view_logs/logdetailsdrawer/inputcard.tsx","./src/components/view_logs/logdetailsdrawer/outputcard.tsx","./src/components/view_logs/logdetailsdrawer/realtimeprettyview.tsx","./src/components/view_logs/logdetailsdrawer/prettymessagesview.tsx","./src/components/view_logs/logdetailsdrawer/logdetailcontent.tsx","./src/components/view_logs/logdetailsdrawer/logdetailsdrawer.tsx","./src/components/view_logs/logdetailsdrawer/index.ts","./src/components/view_logs/toolssection/utils.test.ts","./src/data/insultscomplianceprompts.ts","./src/data/financialcomplianceprompts.ts","./src/data/codeexecutioncomplianceprompts.ts","./src/data/claimscomplianceprompts.ts","./src/data/complianceprompts.ts","./src/data/canadianpiicomplianceprompts.ts","./src/hooks/mcpoauthutils.ts","./src/hooks/use-safe-layout-effect.ts","./src/hooks/useworker.ts","./src/hooks/policies/usedeletepolicyattachment.ts","./src/lib/assetpaths.test.ts","./src/lib/http/client.test.ts","./src/lib/http/resolveapibase.test.ts","./src/utils/budgetutils.ts","./src/utils/cookieutils.test.ts","./src/utils/datautils.test.ts","./src/utils/errorpatterns.ts","./src/utils/errorutils.ts","./src/utils/errorutils.test.ts","./src/utils/jwtutils.test.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/add.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/addbusinessdays.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/adddays.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/addhours.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/addisoweekyears.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/addmilliseconds.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/addminutes.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/addmonths.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/addquarters.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/addseconds.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/addweeks.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/addyears.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/areintervalsoverlapping.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/clamp.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/closestindexto.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/closestto.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/compareasc.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/comparedesc.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/constructfrom.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/constructnow.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/daystoweeks.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/differenceinbusinessdays.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/differenceincalendardays.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/differenceincalendarisoweekyears.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/differenceincalendarisoweeks.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/differenceincalendarmonths.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/differenceincalendarquarters.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/differenceincalendarweeks.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/differenceincalendaryears.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/differenceindays.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/differenceinhours.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/differenceinisoweekyears.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/differenceinmilliseconds.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/differenceinminutes.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/differenceinmonths.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/differenceinquarters.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/differenceinseconds.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/differenceinweeks.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/differenceinyears.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/eachdayofinterval.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/eachhourofinterval.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/eachminuteofinterval.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/eachmonthofinterval.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/eachquarterofinterval.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/eachweekofinterval.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/eachweekendofinterval.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/eachweekendofmonth.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/eachweekendofyear.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/eachyearofinterval.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/endofday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/endofdecade.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/endofhour.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/endofisoweek.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/endofisoweekyear.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/endofminute.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/endofmonth.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/endofquarter.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/endofsecond.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/endoftoday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/endoftomorrow.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/endofweek.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/endofyear.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/endofyesterday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/_lib/format/formatters.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/_lib/format/longformatters.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/format.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/formatdistance.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/formatdistancestrict.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/formatdistancetonow.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/formatdistancetonowstrict.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/formatduration.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/formatiso.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/formatiso9075.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/formatisoduration.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/formatrfc3339.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/formatrfc7231.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/formatrelative.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/fromunixtime.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/getdate.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/getday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/getdayofyear.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/getdaysinmonth.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/getdaysinyear.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/getdecade.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/_lib/defaultoptions.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/getdefaultoptions.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/gethours.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/getisoday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/getisoweek.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/getisoweekyear.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/getisoweeksinyear.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/getmilliseconds.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/getminutes.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/getmonth.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/getoverlappingdaysinintervals.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/getquarter.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/getseconds.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/gettime.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/getunixtime.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/getweek.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/getweekofmonth.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/getweekyear.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/getweeksinmonth.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/getyear.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/hourstomilliseconds.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/hourstominutes.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/hourstoseconds.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/interval.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/intervaltoduration.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/intlformat.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/intlformatdistance.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/isafter.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/isbefore.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/isdate.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/isequal.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/isexists.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/isfirstdayofmonth.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/isfriday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/isfuture.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/islastdayofmonth.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/isleapyear.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/ismatch.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/ismonday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/ispast.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/issameday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/issamehour.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/issameisoweek.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/issameisoweekyear.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/issameminute.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/issamemonth.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/issamequarter.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/issamesecond.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/issameweek.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/issameyear.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/issaturday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/issunday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/isthishour.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/isthisisoweek.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/isthisminute.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/isthismonth.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/isthisquarter.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/isthissecond.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/isthisweek.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/isthisyear.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/isthursday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/istoday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/istomorrow.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/istuesday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/isvalid.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/iswednesday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/isweekend.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/iswithininterval.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/isyesterday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/lastdayofdecade.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/lastdayofisoweek.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/lastdayofisoweekyear.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/lastdayofmonth.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/lastdayofquarter.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/lastdayofweek.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/lastdayofyear.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/_lib/format/lightformatters.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/lightformat.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/max.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/milliseconds.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/millisecondstohours.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/millisecondstominutes.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/millisecondstoseconds.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/min.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/minutestohours.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/minutestomilliseconds.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/minutestoseconds.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/monthstoquarters.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/monthstoyears.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/nextday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/nextfriday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/nextmonday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/nextsaturday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/nextsunday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/nextthursday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/nexttuesday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/nextwednesday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/parse/_lib/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/parse/_lib/setter.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/parse/_lib/parser.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/parse/_lib/parsers.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/parse.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/parseiso.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/parsejson.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/previousday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/previousfriday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/previousmonday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/previoussaturday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/previoussunday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/previousthursday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/previoustuesday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/previouswednesday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/quarterstomonths.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/quarterstoyears.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/roundtonearesthours.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/roundtonearestminutes.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/secondstohours.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/secondstomilliseconds.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/secondstominutes.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/set.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/setdate.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/setday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/setdayofyear.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/setdefaultoptions.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/sethours.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/setisoday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/setisoweek.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/setisoweekyear.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/setmilliseconds.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/setminutes.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/setmonth.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/setquarter.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/setseconds.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/setweek.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/setweekyear.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/setyear.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/startofday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/startofdecade.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/startofhour.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/startofisoweek.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/startofisoweekyear.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/startofminute.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/startofmonth.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/startofquarter.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/startofsecond.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/startoftoday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/startoftomorrow.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/startofweek.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/startofweekyear.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/startofyear.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/startofyesterday.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/sub.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/subbusinessdays.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/subdays.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/subhours.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/subisoweekyears.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/submilliseconds.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/subminutes.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/submonths.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/subquarters.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/subseconds.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/subweeks.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/subyears.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/todate.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/transpose.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/weekstodays.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/yearstodays.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/yearstomonths.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/yearstoquarters.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/date-fns/index.d.mts","./src/utils/keyexpiryutils.ts","./src/utils/keyexpiryutils.test.ts","./src/utils/keyupdateutils.ts","./src/utils/keyupdateutils.test.ts","./src/utils/localstorageutils.test.ts","./src/utils/mcpheaderutils.ts","./src/utils/mcpheaderutils.test.ts","./src/utils/mcptokenstore.test.ts","./src/utils/mcptoolcrudclassification.test.ts","./src/utils/migratedpages.test.ts","./src/utils/pkce.ts","./src/utils/proxyutils.test.ts","./src/utils/returnurlutils.test.ts","./src/utils/roles.test.ts","./src/utils/securestorage.ts","./src/utils/teamutils.test.ts","./src/utils/textutils.test.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/json-schema/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@eslint/core/dist/cjs/types.d.cts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/eslint/lib/types/use-at-your-own-risk.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/eslint/lib/types/index.d.ts","./tests/fetch-location-rule.test.ts","./scripts/lint-budget-lib.mjs","./tests/lint-budget-lib.test.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/jest-dom/types/matchers.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/jest-dom/types/jest.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/jest-dom/types/index.d.ts","./tests/setuptests.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/styled-jsx/types/css.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/styled-jsx/types/macro.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/styled-jsx/types/style.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/styled-jsx/types/global.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/styled-jsx/types/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/get-page-files.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/react/canary.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/react/experimental.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/react-dom/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/react-dom/canary.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/react-dom/experimental.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/lib/fallback.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/compiled/webpack/webpack.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/shared/lib/modern-browserslist-target.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/shared/lib/entry-constants.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/shared/lib/constants.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/lib/bundler.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/config.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/lib/load-custom-routes.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/shared/lib/image-config.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/webpack/plugins/subresource-integrity-plugin.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/body-streams.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/route-kind.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/route-definitions/route-definition.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/route-matches/route-match.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/lib/cache-control.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/lib/cache-handlers/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/use-cache/use-cache-wrapper.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/resume-data-cache/cache-store.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/resume-data-cache/resume-data-cache.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/lib/constants.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/render-result.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/response-cache/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/response-cache/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/next-devtools/userspace/pages/pages-dev-overlay-setup.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/static-paths/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/route-definitions/app-page-route-definition.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/adapter/setup-node-env.external.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/instrumentation/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/lib/setup-exception-listeners.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/lib/worker.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/lib/experimental/ppr.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/lib/page-types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/segment-config/app/app-segment-config.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/segment-config/pages/pages-segment-config.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/analysis/get-page-static-info.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/webpack/loaders/get-module-build-info.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/webpack/plugins/middleware-plugin.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/require-hook.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/node-polyfill-crypto.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/node-environment-baseline.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/node-environment-extensions/error-inspect.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/node-environment-extensions/console-file.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/node-environment-extensions/console-exit.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/node-environment-extensions/console-dim.external.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/node-environment-extensions/unhandled-rejection.external.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/node-environment-extensions/random.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/node-environment-extensions/date.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/node-environment-extensions/web-crypto.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/node-environment-extensions/node-crypto.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/node-environment-extensions/fast-set-immediate.external.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/node-environment.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/page-extensions-type.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/route-modules/app-page/module.compiled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/route-definitions/app-route-route-definition.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/lib/i18n-provider.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/web/next-url.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/compiled/@edge-runtime/cookies/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/web/spec-extension/cookies.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/web/spec-extension/request.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/shared/lib/deep-readonly.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/lib/incremental-cache/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/shared/lib/router/utils/middleware-route-matcher.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/webpack/plugins/flight-manifest-plugin.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/webpack/plugins/next-font-manifest-plugin.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/route-definitions/locale-route-definition.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/route-definitions/pages-route-definition.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/shared/lib/mitt.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/with-router.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/router.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/route-loader.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/page-loader.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/shared/lib/bloom-filter.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/shared/lib/router/router.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/shared/lib/router-context.shared-runtime.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/shared/lib/loadable-context.shared-runtime.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/shared/lib/loadable.shared-runtime.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/shared/lib/image-config-context.shared-runtime.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/shared/lib/head-manager-context.shared-runtime.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/route-modules/pages/vendored/contexts/entrypoints.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/route-modules/pages/module.compiled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/templates/pages.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/route-modules/pages/module.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/render.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/webpack/plugins/pages-manifest-plugin.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/route-definitions/pages-api-route-definition.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/route-matches/pages-api-route-match.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/route-matchers/route-matcher.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/route-matcher-providers/route-matcher-provider.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/route-matcher-managers/route-matcher-manager.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/normalizers/normalizer.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/normalizers/locale-route-normalizer.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/normalizers/request/pathname-normalizer.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/normalizers/request/suffix.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/normalizers/request/rsc.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/normalizers/request/next-data.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/after/builtin-request-context.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/normalizers/request/segment-prefix-rsc.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/route-modules/pages/builtin/_error.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/load-default-error-components.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/base-server.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/after/after.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/after/after-context.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/use-cache/cache-life.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/app-render/work-async-storage-instance.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/lib/lazy-result.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/app-render/create-error-handler.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/shared/lib/action-revalidation-kind.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/app-render/work-async-storage.external.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/async-storage/work-store.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/web/http.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/components/hooks-server-context.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/route-modules/app-route/shared-modules.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/web/spec-extension/adapters/request-cookies.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/async-storage/draft-mode-provider.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/web/spec-extension/adapters/headers.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/app-render/cache-signal.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/app-render/instant-validation/boundary-tracking.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/app-render/instant-validation/instant-validation-error.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/shared/lib/router/utils/parse-relative-url.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/app-render/instant-validation/instant-samples.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/app-render/dynamic-rendering.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/app-render/work-unit-async-storage-instance.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/lib/implicit-tags.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/app-render/staged-rendering.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/app-render/work-unit-async-storage.external.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/templates/app-route.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/app-render/action-async-storage-instance.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/app-render/action-async-storage.external.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/route-modules/app-route/module.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/route-modules/app-route/module.compiled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/segment-config/app/app-segments.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/get-supported-browsers.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/utils.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/rendering-mode.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/lib/router-utils/build-prefetch-segment-data-route.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/lib/cpu-profile.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/turborepo-access-trace/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/turborepo-access-trace/result.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/turborepo-access-trace/helpers.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/turborepo-access-trace/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/export/routes/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/export/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/export/worker.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/worker.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/lib/coalesced-function.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/lib/router-utils/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/trace/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/trace/trace.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/trace/shared.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/trace/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/load-jsconfig.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@next/env/dist/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/webpack/plugins/telemetry-plugin/use-cache-tracker-utils.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/webpack/plugins/telemetry-plugin/telemetry-plugin.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/telemetry/storage.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/build-context.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/webpack-config.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/swc/generated-native.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/define-env.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/swc/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/swc/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/dev/parse-version-info.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/next-devtools/shared/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/dev/dev-indicator-server-state.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/next-devtools/dev-overlay/cache-indicator.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/lib/parse-stack.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/next-devtools/server/shared.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/next-devtools/shared/stack-frame.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/next-devtools/dev-overlay/utils/get-error-by-type.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/next-devtools/dev-overlay/container/runtime-error/render-error.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/next-devtools/dev-overlay/shared.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/dev/debug-channel.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/dev/hot-reloader-types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/web/spec-extension/fetch-event.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/web/spec-extension/response.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/segment-config/middleware/middleware-config.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/web/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/shared/lib/router/utils/parse-url.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/base-http/node.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/lib/async-callback-set.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/shared/lib/router/utils/route-regex.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/shared/lib/router/utils/route-matcher.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/sharp/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/image-optimizer.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/next-server.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/lib/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/lib/lru-cache.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/lib/dev-bundler-service.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/dev/static-paths-worker.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/dev/next-dev-server.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/next.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/lib/render-server.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/lib/router-server.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/shared/lib/router/utils/path-match.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/lib/router-utils/filesystem.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/lib/router-utils/setup-dev-bundler.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/lib/router-utils/router-server-context.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/route-modules/route-module.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/load-components.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/web/adapter.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/app-render/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/webpack/loaders/metadata/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/webpack/loaders/next-app-loader/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/lib/app-dir-module.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/app-render/app-render.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/route-modules/app-page/vendored/contexts/entrypoints.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/components/error-boundary.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/components/layout-router.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/components/render-from-template-context.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/components/client-page.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/components/client-segment.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/components/http-access-fallback/error-boundary.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/lib/metadata/types/alternative-urls-types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/lib/metadata/types/extra-types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/lib/metadata/types/metadata-types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/lib/metadata/types/manifest-types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/lib/metadata/types/opengraph-types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/lib/metadata/types/twitter-types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/lib/metadata/types/metadata-interface.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/lib/metadata/types/resolvers.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/lib/metadata/types/icons.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/lib/metadata/resolve-metadata.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/lib/metadata/metadata.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/lib/framework/boundary-components.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/app-render/rsc/preloads.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/app-render/rsc/postpone.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/app-render/rsc/taint.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/app-render/collect-segment-data.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/app-render/instant-validation/instant-validation.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/next-devtools/userspace/app/segment-explorer-node.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/app-render/entry-base.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/templates/app-page.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/route-modules/app-page/helpers/prerender-manifest-matcher.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/react/jsx-dev-runtime.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/route-modules/app-page/vendored/rsc/entrypoints.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/react-dom/server.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/route-modules/app-page/vendored/ssr/entrypoints.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/route-modules/app-page/module.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/request/fallback-params.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/web/spec-extension/image-response.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/web/spec-extension/user-agent.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/web/spec-extension/url-pattern.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/after/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/request/connection.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/web/exports/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/request-meta.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/cli/next-test.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/shared/lib/size-limit.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/config-shared.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/base-http/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/api-utils/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/build/adapter/build-complete.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/shared/lib/html-context.shared-runtime.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/shared/lib/utils.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/pages/_app.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/app.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/web/spec-extension/unstable-cache.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/web/spec-extension/revalidate.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/web/spec-extension/unstable-no-store.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/use-cache/cache-tag.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/cache.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/pages/_document.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/document.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/shared/lib/dynamic.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dynamic.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/pages/_error.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/components/catch-error.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/api/error.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/error.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/shared/lib/head.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/head.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/request/cookies.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/request/headers.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/server/request/draft-mode.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/headers.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/shared/lib/get-img-props.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/image-component.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/shared/lib/image-external.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/image.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/link.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/link.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/router.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/client/script.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/script.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/compiled/@edge-runtime/primitives/url.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/compiled/@vercel/og/satori/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/compiled/@vercel/og/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/server.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/types/global.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/types/compiled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/compiled/@next/font/dist/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/dist/compiled/@next/font/dist/google/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/next/font/google/index.d.ts","./src/contexts/antdglobalprovider.tsx","./src/contexts/authcontext.tsx","./src/contexts/reactqueryprovider.tsx","./src/app/layout.tsx","./src/contexts/themecontext.tsx","./src/components/navbar/blogdropdown/blogdropdown.tsx","./src/components/navbar/communityengagementbuttons/communityengagementbuttons.tsx","./src/components/navbar/notificationsbell/notificationsbell.tsx","./src/components/navbar/userdropdown/userdropdown.tsx","./src/contexts/pluginmodecontext.tsx","./src/components/navbar/viewswitcher.tsx","./src/components/navbar/workerdropdown/workerdropdown.tsx","./src/components/navbar.tsx","./src/components/ui/ui-loading-spinner.tsx","./src/components/common_components/loadingscreen.tsx","./src/app/(dashboard)/components/sidebarprovider.tsx","./src/components/debugwarningbanner.tsx","./src/app/(dashboard)/layout.tsx","./src/app/(dashboard)/agentcontrolplaneview.test.tsx","./src/app/(dashboard)/layout.test.tsx","./src/app/onboarding/onboardingloadingview.tsx","./src/app/onboarding/onboardingerrorview.tsx","./src/app/onboarding/onboardingformbody.tsx","./src/app/onboarding/onboardingform.tsx","./src/app/onboarding/page.tsx","./src/components/common_components/fetch_teams.tsx","./src/components/common_components/defaultproxyadmintag.tsx","./src/app/(dashboard)/hooks/useteams.tsx","./src/components/common_components/labeledfield.tsx","./src/components/templates/keyinfoheader.tsx","./src/components/common_components/autorotationview.tsx","./src/components/common_components/deleteresourcemodal.tsx","./src/components/key_info_utils.tsx","./src/components/logging_settings_view.tsx","./src/components/permissions/vectorstorepermissions.tsx","./src/components/permissions/mcpserverpermissions.tsx","./src/components/permissions/agentpermissions.tsx","./src/components/object_permissions_view.tsx","./src/components/organisms/regeneratekeymodal.tsx","./src/components/guardrails/guardrailselector.tsx","./src/components/policies/policyselector.tsx","./src/components/team/editloggingsettings.tsx","./src/components/templates/key_edit_view.tsx","./src/components/templates/key_info_view.tsx","./src/components/virtualkeyspage/virtualkeystable.tsx","./src/components/user_dashboard.tsx","./src/app/(dashboard)/api-keys/apikeysdashboard.tsx","./src/app/(dashboard)/page.tsx","./src/components/common_components/iconactionbutton/baseactionbutton.tsx","./src/components/common_components/iconactionbutton/tableiconactionbuttons/tableiconactionbutton.tsx","./src/components/modelselect/modelselect.tsx","./src/app/(dashboard)/access-groups/components/accessgroupsmodal/accessgroupbaseform.tsx","./src/app/(dashboard)/access-groups/components/accessgroupsmodal/accessgroupeditmodal.tsx","./src/app/(dashboard)/access-groups/components/accessgroupsdetailspage.tsx","./src/app/(dashboard)/access-groups/components/accessgroupsmodal/accessgroupcreatemodal.tsx","./src/app/(dashboard)/access-groups/components/accessgroupspage.tsx","./src/app/(dashboard)/access-groups/page.tsx","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/event/eventmap.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/event/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/event/dispatchevent.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/event/focus.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/event/input.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utils/click/isclickableinput.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utils/datatransfer/blob.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utils/datatransfer/datatransfer.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utils/datatransfer/filelist.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utils/datatransfer/clipboard.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utils/edit/timevalue.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utils/edit/iscontenteditable.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utils/edit/iseditable.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utils/edit/maxlength.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utils/edit/setfiles.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utils/focus/cursor.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utils/focus/getactiveelement.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utils/focus/gettabdestination.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utils/focus/isfocusable.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utils/focus/selection.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utils/focus/selector.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utils/keydef/readnextdescriptor.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utils/misc/cloneevent.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utils/misc/findclosest.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utils/misc/getdocumentfromnode.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utils/misc/gettreediff.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utils/misc/getwindow.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utils/misc/isdescendantorself.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utils/misc/iselementtype.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utils/misc/isvisible.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utils/misc/isdisabled.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utils/misc/level.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utils/misc/wait.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utils/pointer/csspointerevents.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utils/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/document/ui.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/document/getvalueortextcontent.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/document/copyselection.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/document/trackvalue.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/document/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/event/selection/getinputrange.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/event/selection/modifyselection.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/event/selection/moveselection.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/event/selection/setselectionpermouse.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/event/selection/modifyselectionpermouse.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/event/selection/selectall.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/event/selection/setselectionrange.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/event/selection/setselection.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/event/selection/updateselectiononfocus.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/event/selection/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/event/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/system/pointer/buttons.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/system/pointer/shared.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/system/pointer/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/system/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/system/keyboard.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/options.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/convenience/click.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/convenience/hover.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/convenience/tab.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/convenience/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/keyboard/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/clipboard/copy.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/clipboard/cut.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/clipboard/paste.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/clipboard/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/pointer/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utility/clear.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utility/selectoptions.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utility/type.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utility/upload.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/utility/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/setup/api.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/setup/directapi.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/setup/setup.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/setup/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@testing-library/user-event/dist/types/index.d.ts","./tests/test-utils.tsx","./src/app/(dashboard)/access-groups/components/accessgroupsdetailspage.test.tsx","./src/app/(dashboard)/access-groups/components/accessgroupspage.test.tsx","./src/components/scim.tsx","./src/components/settings/adminsettings/loggingsettings/loggingsettings.tsx","./src/components/settings/adminsettings/ssosettings/modals/basessosettingsform.tsx","./src/components/settings/adminsettings/ssosettings/modals/addssosettingsmodal.tsx","./src/components/settings/adminsettings/ssosettings/modals/deletessosettingsmodal.tsx","./src/components/settings/adminsettings/ssosettings/modals/editssosettingsmodal.tsx","./src/components/settings/adminsettings/ssosettings/redactablefield.tsx","./src/components/settings/adminsettings/ssosettings/rolemappings.tsx","./src/components/settings/adminsettings/ssosettings/ssosettingsemptyplaceholder.tsx","./src/components/settings/adminsettings/ssosettings/ssosettingsloadingskeleton.tsx","./src/components/settings/adminsettings/ssosettings/ssosettings.tsx","./src/components/settings/adminsettings/uisettings/pagevisibilitysettings.tsx","./src/components/settings/adminsettings/uisettings/uisettings.tsx","./src/components/settings/adminsettings/hashicorpvault/edithashicorpvaultmodal.tsx","./src/components/settings/adminsettings/hashicorpvault/hashicorpvaultemptyplaceholder.tsx","./src/components/settings/adminsettings/hashicorpvault/hashicorpvault.tsx","./src/components/settings/adminsettings/pluginsettings/pluginsettings.tsx","./src/components/ssomodals.tsx","./src/components/uiaccesscontrolform.tsx","./src/components/adminpanel.tsx","./src/app/(dashboard)/admin-panel/page.tsx","./src/components/agents/cost_config_fields.tsx","./src/components/agents/agent_form_fields.tsx","./src/components/agents/agent_card_discovery.tsx","./src/components/agents/dynamic_agent_form_fields.tsx","./src/components/agents/add_agent_form.tsx","./src/components/agents/agent_virtual_keys.tsx","./src/components/agents/agent_cost_view.tsx","./src/components/agents/agent_info.tsx","./src/components/agents.tsx","./src/app/(dashboard)/agents/page.tsx","./src/app/(dashboard)/api-keys/apikeysdashboard.test.tsx","./src/app/(dashboard)/api-keys/page.tsx","./src/app/(dashboard)/api-reference/components/doclink.tsx","./src/app/(dashboard)/api-reference/apireferenceview.tsx","./src/app/(dashboard)/api-reference/apireferenceview.test.tsx","./src/components/deprecationbanner.tsx","./src/app/(dashboard)/api-reference/page.tsx","./src/app/(dashboard)/budgets/components/budget_modal.tsx","./src/app/(dashboard)/budgets/components/edit_budget_modal.tsx","./src/app/(dashboard)/budgets/components/budget_panel.tsx","./src/app/(dashboard)/budgets/page.tsx","./src/app/(dashboard)/budgets/components/budget_panel.test.tsx","./src/components/shared/usage_date_picker.tsx","./src/app/(dashboard)/caching/components/response_time_indicator.tsx","./src/app/(dashboard)/caching/components/cache_health.tsx","./src/app/(dashboard)/caching/components/cache_settings/redistypeselector.tsx","./src/app/(dashboard)/caching/components/cache_settings/cacheformfield.tsx","./src/app/(dashboard)/caching/components/cache_settings/cachefieldsection.tsx","./src/app/(dashboard)/caching/components/cache_settings/index.tsx","./src/app/(dashboard)/caching/components/cache_dashboard.tsx","./src/app/(dashboard)/caching/page.tsx","./src/app/(dashboard)/caching/components/cache_settings/redistypeselector.test.tsx","./src/app/(dashboard)/caching/components/cache_settings/index.test.tsx","./src/app/(dashboard)/cost-tracking/page.tsx","./src/app/(dashboard)/cost-tracking/components/add_margin_form.test.tsx","./src/app/(dashboard)/cost-tracking/components/add_provider_form.test.tsx","./src/app/(dashboard)/cost-tracking/components/cost_tracking_settings.test.tsx","./src/app/(dashboard)/cost-tracking/components/how_it_works.test.tsx","./src/app/(dashboard)/cost-tracking/components/provider_discount_table.test.tsx","./src/app/(dashboard)/cost-tracking/components/provider_margin_table.test.tsx","./src/app/(dashboard)/cost-tracking/components/pricing_calculator/index.test.tsx","./src/app/(dashboard)/cost-tracking/components/pricing_calculator/multi_cost_results.test.tsx","./src/app/(dashboard)/cost-tracking/components/pricing_calculator/multi_export_dropdown.test.tsx","./src/components/guardrails/content_filter/patternmodal.tsx","./src/components/guardrails/content_filter/custompatternmodal.tsx","./src/components/guardrails/content_filter/keywordmodal.tsx","./src/components/guardrails/content_filter/patterntable.tsx","./src/components/guardrails/content_filter/keywordtable.tsx","./src/components/guardrails/content_filter/contentcategoryconfiguration.tsx","./src/components/guardrails/content_filter/competitorintentconfiguration.tsx","./src/components/guardrails/content_filter/contentfilterconfiguration.tsx","./src/components/guardrails/guardrail_info_helpers.tsx","./src/components/guardrails/guardrail_optional_params.tsx","./src/components/guardrails/guardrail_provider_fields.tsx","./src/components/guardrails/llm_judge/llmjudgefields.tsx","./src/components/guardrails/pii_components.tsx","./src/components/guardrails/pii_configuration.tsx","./src/components/guardrails/tool_permission/toolpermissionruleseditor.tsx","./src/components/guardrails/add_guardrail_form.tsx","./src/components/guardrails/edit_guardrail_form.tsx","./src/components/guardrails/guardrail_table.tsx","./src/components/guardrails/content_filter/categorytable.tsx","./src/components/guardrails/content_filter/contentfilterdisplay.tsx","./src/components/guardrails/content_filter/contentfiltermanager.tsx","./src/components/guardrails/guardrail_info.tsx","./src/components/guardrails/guardrailtestresults.tsx","./src/components/guardrails/guardrailtestpanel.tsx","./src/components/guardrails/guardrailtestplayground.tsx","./src/components/guardrails/guardrail_garden_card.tsx","./src/components/guardrails/guardrail_garden_detail.tsx","./src/components/guardrails/guardrail_garden.tsx","./src/components/guardrails/teamguardrailstab.tsx","./src/components/guardrails.tsx","./src/app/(dashboard)/guardrails/page.tsx","./src/components/shared/advanced_date_picker.tsx","./src/app/(dashboard)/guardrails-monitor/components/evaluationsettingsmodal.tsx","./src/components/guardrailsmonitor/logviewer.tsx","./src/components/guardrailsmonitor/metriccard.tsx","./src/app/(dashboard)/guardrails-monitor/components/guardraildetail.tsx","./src/app/(dashboard)/guardrails-monitor/components/scorechart.tsx","./src/app/(dashboard)/guardrails-monitor/components/guardrailsoverview.tsx","./src/app/(dashboard)/guardrails-monitor/components/guardrailsmonitorview.tsx","./src/app/(dashboard)/guardrails-monitor/page.tsx","./src/app/(dashboard)/guardrails-monitor/components/guardrailconfig.tsx","./src/app/(dashboard)/guardrails-monitor/components/guardrailconfig.test.tsx","./src/app/(dashboard)/guardrails-monitor/components/guardrailsmonitorview.test.tsx","./src/app/(dashboard)/guardrails-monitor/components/scorechart.test.tsx","./src/components/email_settings.tsx","./src/components/alerting/dynamic_form.tsx","./src/components/alerting/alerting_settings.tsx","./src/components/cloudzerocosttracking/cloudzeroemptyplaceholder.tsx","./src/components/cloudzerocosttracking/cloudzerocreatemodal.tsx","./src/components/cloudzerocosttracking/cloudzeroupdatemodal.tsx","./src/components/cloudzerocosttracking/cloudzerointegrationsettings.tsx","./src/components/cloudzerocosttracking/cloudzerocosttracking.tsx","./src/components/settings/loggingandalerts/loggingcallbacks/loggingcallbackstable.tsx","./src/components/settings.tsx","./src/app/(dashboard)/logging-and-alerts/page.tsx","./src/components/deletedkeyspage/deletedkeystable/deletedkeystable.tsx","./src/components/deletedkeyspage/deletedkeyspage.tsx","./src/components/deletedteamspage/deletedteamstable/deletedteamstable.tsx","./src/components/deletedteamspage/deletedteamspage.tsx","./src/components/view_logs/auditlogdrawer/auditlogdrawer.tsx","./src/components/view_logs/audit_logs.tsx","./src/components/view_logs/logs_utils.tsx","./src/components/view_logs/logstabletoolbar.tsx","./src/components/view_logs/table.tsx","./src/components/ui/antdloadingspinner.tsx","./src/components/view_logs/index.tsx","./src/app/(dashboard)/logs/page.tsx","./src/components/mcp_tools/mcpstandardssettings.tsx","./src/components/mcp_tools/mcpsubmissionstab.tsx","./src/components/mcp_tools/mcptoolsetstab.tsx","./src/components/mcp_tools/tokenendpointauthmethodfield.tsx","./src/components/mcp_tools/oauthformfields.tsx","./src/components/mcp_tools/tokenexchangeformfields.tsx","./src/components/mcp_tools/mcp_server_cost_config.tsx","./src/components/mcp_tools/mcp_connection_status.tsx","./src/components/mcp_tools/utils.tsx","./src/components/mcp_tools/mcp_tool_configuration.tsx","./src/components/mcp_tools/stdioconfiguration.tsx","./src/components/mcp_tools/mcppermissionmanagement.tsx","./src/components/mcp_tools/openapiquickpicker.tsx","./src/components/mcp_tools/openapiformsection.tsx","./src/components/mcp_tools/mcplogoselector.tsx","./src/components/mcp_tools/envvarssection.tsx","./src/hooks/usemcpoauthflow.tsx","./src/hooks/usetestmcpconnection.tsx","./src/components/mcp_tools/create_mcp_server.tsx","./src/components/mcp_tools/mcp_connect.tsx","./src/components/mcp_tools/mcpservercard.tsx","./src/components/mcp_tools/mcp_server_edit.tsx","./src/components/mcp_tools/mcp_server_cost_display.tsx","./src/components/mcp_tools/mcp_server_view.tsx","./src/components/settings/adminsettings/mcpsemanticfiltersettings/mcpsemanticfiltertestpanel.tsx","./src/components/settings/adminsettings/mcpsemanticfiltersettings/mcpsemanticfiltersettings.tsx","./src/components/mcp_tools/mcpnetworksettings.tsx","./src/components/mcp_tools/mcp_discovery.tsx","./src/components/mcp_tools/byokcredentialmodal.tsx","./src/components/mcp_tools/userenvvarsmodal.tsx","./src/components/mcp_tools/mcp_servers.tsx","./src/components/mcp_tools/tooltestpanel.tsx","./src/hooks/usetoolsoauthflow.tsx","./src/hooks/useusermcpoauthflow.tsx","./src/components/mcp_tools/mcp_tools.tsx","./src/components/mcp_tools/index.tsx","./src/app/(dashboard)/mcp-servers/page.tsx","./src/app/(dashboard)/memory/components/memoryeditmodal.tsx","./src/app/(dashboard)/memory/components/memoryview.tsx","./src/app/(dashboard)/memory/page.tsx","./src/components/aihub/agenthubtablecolumns.tsx","./src/components/aihub/forms/makeagentpublicform.tsx","./src/components/mcp_hub_table_columns.tsx","./src/components/aihub/forms/makemcppublicform.tsx","./src/components/model_filters.tsx","./src/components/aihub/forms/makemodelpublicform.tsx","./src/components/model_hub_table_columns.tsx","./src/components/aihub/usefullinksmanagement.tsx","./src/components/model_dashboard/table.tsx","./src/components/skill_hub_table_columns.tsx","./src/components/claude_code_plugins/skill_detail.tsx","./src/components/aihub/skillhubdashboard.tsx","./src/components/claude_code_plugins/makeskillpublicform.tsx","./src/components/chat_ui/codesnippets.tsx","./src/components/public_model_hub.tsx","./src/components/aihub/modelhubtable.tsx","./src/app/(dashboard)/model-hub-table/page.tsx","./src/components/model_dashboard/all_models_table.tsx","./src/components/molecules/models/providerlogo.tsx","./src/components/molecules/models/columns.tsx","./src/components/view_model/model_name_display.tsx","./src/components/model_dashboard/modelsettingsmodal/modelsettingsmodal.tsx","./src/app/(dashboard)/models-and-endpoints/components/allmodelstab.tsx","./src/components/molecules/cost_optimization_feedback_banner.tsx","./src/app/(dashboard)/models-and-endpoints/components/modelretrysettingstab.tsx","./src/components/price_data_reload.tsx","./src/app/(dashboard)/models-and-endpoints/components/pricedatamanagementtab.tsx","./src/components/add_model/handle_add_model_submit.tsx","./src/components/add_model/provider_specific_fields.tsx","./src/components/model_add/addcredentialmodal.tsx","./src/components/model_add/editcredentialmodal.tsx","./src/components/model_add/credentials.tsx","./src/components/add_model/model_connection_test.tsx","./src/components/add_model/handle_add_auto_router_submit.tsx","./src/components/add_model/routerconfigbuilder.tsx","./src/components/add_model/complexityrouterconfig.tsx","./src/components/add_model/add_auto_router_tab.tsx","./src/components/add_model/cache_control_settings.tsx","./src/components/add_model/advanced_settings.tsx","./src/components/add_model/conditional_public_model_name.tsx","./src/components/add_model/litellm_model_name.tsx","./src/components/add_model/add_model_modes.tsx","./src/components/add_model/addmodelform.tsx","./src/components/add_model/add_model_tab.tsx","./src/components/model_dashboard/health_check_columns.tsx","./src/components/model_dashboard/healthcheckcomponent.tsx","./src/components/model_group_alias_settings.tsx","./src/components/edit_auto_router/edit_auto_router_modal.tsx","./src/components/model_add/reuse_credentials.tsx","./src/components/update_model_credentials_modal.tsx","./src/components/model_info_view.tsx","./src/components/key_value_input.tsx","./src/components/query_param_input.tsx","./src/components/route_preview.tsx","./src/components/common_components/passthroughsecuritysection.tsx","./src/components/common_components/passthroughguardrailssection.tsx","./src/components/add_pass_through.tsx","./src/components/pass_through_info.tsx","./src/components/pass_through_settings.tsx","./src/components/common_components/user_search_modal.tsx","./src/components/common_components/durationselect.tsx","./src/components/guardrailsettingsview.tsx","./src/components/search_tools/searchtoolselector.tsx","./src/components/team/editmembership.tsx","./src/components/team/permission_definitions.tsx","./src/components/team/member_permissions.tsx","./src/components/team/myusertab.tsx","./src/components/common_components/membertable.tsx","./src/components/team/teammembertab.tsx","./src/components/team/teamvirtualkeystable.tsx","./src/components/team/teaminfo.tsx","./src/app/(dashboard)/models-and-endpoints/modelsandendpointsview.tsx","./src/app/(dashboard)/models-and-endpoints/modelsandendpointsview.test.tsx","./src/app/(dashboard)/models-and-endpoints/page.tsx","./src/app/(dashboard)/models-and-endpoints/components/allmodelstab.test.tsx","./src/app/(dashboard)/models-and-endpoints/components/modelretrysettingstab.test.tsx","./src/components/view_user_spend.tsx","./src/components/usagepage/components/entityusage/topkeyview.tsx","./src/components/usage.tsx","./src/app/(dashboard)/old-usage/page.tsx","./src/components/common_components/filters/filterinput.tsx","./src/components/common_components/filters/filtersbutton.tsx","./src/components/common_components/filters/resetfiltersbutton.tsx","./src/app/(dashboard)/organizations/organizationfilters.tsx","./src/app/(dashboard)/organizations/organizationfilters.test.tsx","./src/components/organization/organization_view.tsx","./src/components/organizations.tsx","./src/app/(dashboard)/organizations/page.tsx","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/_shims/manual-types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/_shims/auto/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/streaming.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/error.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/_shims/multipartbody.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/uploads.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/core.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/_shims/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/pagination.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/shared.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/batches.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/chat/completions/messages.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/chat/completions/completions.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/completions.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/embeddings.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/files.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/images.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/models.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/moderations.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/audio/speech.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/audio/transcriptions.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/audio/translations.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/audio/audio.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/beta/threads/messages.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/beta/threads/runs/steps.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/beta/threads/runs/runs.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/lib/eventstream.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/lib/assistantstream.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/beta/threads/threads.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/beta/assistants.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/chat/completions.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/lib/abstractchatcompletionrunner.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/lib/chatcompletionstream.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/lib/responsesparser.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/responses/input-items.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/lib/responses/eventtypes.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/lib/responses/responsestream.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/responses/responses.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/lib/parser.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/lib/chatcompletionstreamingrunner.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/lib/jsonschema.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/lib/runnablefunction.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/lib/chatcompletionrunner.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/beta/chat/completions.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/beta/chat/chat.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/beta/realtime/sessions.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/beta/realtime/transcription-sessions.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/beta/realtime/realtime.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/beta/beta.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/containers/files/content.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/containers/files/files.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/containers/containers.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/graders/grader-models.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/evals/runs/output-items.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/evals/runs/runs.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/evals/evals.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/fine-tuning/methods.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/fine-tuning/alpha/graders.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/fine-tuning/alpha/alpha.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/fine-tuning/checkpoints/permissions.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/fine-tuning/checkpoints/checkpoints.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/fine-tuning/jobs/checkpoints.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/fine-tuning/jobs/jobs.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/fine-tuning/fine-tuning.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/graders/graders.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/uploads/parts.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/uploads/uploads.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/vector-stores/files.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/vector-stores/file-batches.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/vector-stores/vector-stores.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resource.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/chat/chat.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/chat/completions/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/chat/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/resources/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/openai/index.d.mts","./src/components/llm_calls/chat_completion.tsx","./src/app/(dashboard)/playground/components/complianceui/complianceui.tsx","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/unist/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/hast/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/vfile-message/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/vfile-message/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/vfile/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/vfile/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/unified/lib/callable-instance.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/trough/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/trough/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/unified/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/unified/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/mdast/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-hast/lib/state.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-hast/lib/footer.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-hast/lib/handlers/blockquote.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-hast/lib/handlers/break.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-hast/lib/handlers/code.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-hast/lib/handlers/delete.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-hast/lib/handlers/emphasis.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-hast/lib/handlers/footnote-reference.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-hast/lib/handlers/heading.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-hast/lib/handlers/html.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-hast/lib/handlers/image-reference.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-hast/lib/handlers/image.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-hast/lib/handlers/inline-code.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-hast/lib/handlers/link-reference.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-hast/lib/handlers/link.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-hast/lib/handlers/list-item.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-hast/lib/handlers/list.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-hast/lib/handlers/paragraph.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-hast/lib/handlers/root.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-hast/lib/handlers/strong.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-hast/lib/handlers/table.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-hast/lib/handlers/table-cell.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-hast/lib/handlers/table-row.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-hast/lib/handlers/text.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-hast/lib/handlers/thematic-break.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-hast/lib/handlers/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-hast/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-hast/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/remark-rehype/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/remark-rehype/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/react-markdown/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/react-markdown/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/uuid/dist/max.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/uuid/dist/nil.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/uuid/dist/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/uuid/dist/parse.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/uuid/dist/stringify.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/uuid/dist/v1.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/uuid/dist/v1tov6.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/uuid/dist/v35.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/uuid/dist/v3.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/uuid/dist/v4.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/uuid/dist/v5.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/uuid/dist/v6.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/uuid/dist/v6tov1.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/uuid/dist/v7.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/uuid/dist/validate.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/uuid/dist/version.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/uuid/dist/index.d.ts","./src/components/mcp_tools/mcptoolargumentsform.tsx","./src/components/tag_management/tagselector.tsx","./src/app/(dashboard)/playground/llm_calls/a2a_send_message.tsx","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/internal/builtin-types.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/form-data/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node-fetch/externals.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/node-fetch/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/internal/types.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/internal/headers.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/internal/shim-types.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/core/streaming.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/internal/request-options.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/internal/utils/log.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/shared.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/core/error.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/internal/parse.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/core/api-promise.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/core/pagination.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/internal/uploads.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/internal/to-file.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/core/uploads.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/core/resource.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/beta/environments.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/beta/files.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/beta/models.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/beta/user-profiles.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/beta/agents/versions.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/beta/agents/agents.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memories.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memory-versions.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memory-stores.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/internal/decoders/line.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/internal/decoders/jsonl.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/error.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/messages/batches.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/messages/index.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/messages.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/lib/parser.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/lib/messagestream.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/messages/messages.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/beta/messages/batches.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/lib/beta-parser.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/lib/betamessagestream.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/beta/agents/index.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/index.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/beta/messages/index.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/beta/sessions/events.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/beta/sessions/sessions.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/beta/sessions/resources.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/beta/sessions/index.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/beta/skills/versions.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/beta/skills/skills.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/beta/skills/index.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/beta/vaults/credentials.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/beta/vaults/vaults.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/beta/vaults/index.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/beta/index.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/beta.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/lib/tools/betarunnabletool.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/lib/tools/compactioncontrol.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/lib/tools/betatoolrunner.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/lib/tools/toolerror.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/beta/messages/messages.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/beta/beta.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/completions.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/models.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/resources/index.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/client.d.mts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@anthropic-ai/sdk/index.d.mts","./src/app/(dashboard)/playground/llm_calls/anthropic_messages.tsx","./src/app/(dashboard)/playground/llm_calls/audio_speech.tsx","./src/app/(dashboard)/playground/llm_calls/audio_transcriptions.tsx","./src/app/(dashboard)/playground/llm_calls/embeddings_api.tsx","./src/app/(dashboard)/playground/llm_calls/image_edits.tsx","./src/app/(dashboard)/playground/llm_calls/image_generation.tsx","./src/components/llm_calls/responses_api.tsx","./src/app/(dashboard)/playground/llm_calls/interactions_api.tsx","./src/app/(dashboard)/playground/components/chat_ui/a2ametrics.tsx","./src/app/(dashboard)/playground/components/chat_ui/additionalmodelsettings.tsx","./src/app/(dashboard)/playground/components/chat_ui/audiorenderer.tsx","./src/app/(dashboard)/playground/components/chat_ui/chatimageutils.tsx","./src/app/(dashboard)/playground/components/chat_ui/chatimagerenderer.tsx","./src/app/(dashboard)/playground/components/chat_ui/chatimageupload.tsx","./src/app/(dashboard)/playground/components/chat_ui/codeinterpreteroutput.tsx","./src/app/(dashboard)/playground/components/chat_ui/codeinterpretertool.tsx","./src/app/(dashboard)/playground/components/chat_ui/endpointselector.tsx","./src/app/(dashboard)/playground/components/chat_ui/filepreviewcard.tsx","./src/components/chat_ui/mcpeventsdisplay.tsx","./src/components/chat_ui/reasoningcontent.tsx","./src/app/(dashboard)/playground/components/chat_ui/responsesimageutils.tsx","./src/app/(dashboard)/playground/components/chat_ui/responsesimagerenderer.tsx","./src/app/(dashboard)/playground/components/chat_ui/searchresultsdisplay.tsx","./src/app/(dashboard)/playground/components/chat_ui/chatmessagebubble.tsx","./src/app/(dashboard)/playground/components/chat_ui/responsesimageupload.tsx","./src/app/(dashboard)/playground/components/chat_ui/sessionmanagement.tsx","./src/app/(dashboard)/playground/components/chat_ui/realtimeplayground.tsx","./src/app/(dashboard)/playground/components/chat_ui/chatui.tsx","./src/app/(dashboard)/playground/components/chat_ui/agentbuilderview.tsx","./src/app/(dashboard)/playground/components/compareui/components/messagedisplay.tsx","./src/app/(dashboard)/playground/components/compareui/components/unifiedselector.tsx","./src/app/(dashboard)/playground/components/compareui/components/comparisonpanel.tsx","./src/app/(dashboard)/playground/components/compareui/components/messageinput.tsx","./src/app/(dashboard)/playground/components/compareui/compareui.tsx","./src/app/(dashboard)/playground/page.tsx","./src/app/(dashboard)/playground/components/chat_ui/additionalmodelsettings.test.tsx","./src/app/(dashboard)/playground/components/chat_ui/audiorenderer.test.tsx","./src/app/(dashboard)/playground/components/chat_ui/chatimageutils.test.tsx","./src/app/(dashboard)/playground/components/chat_ui/chatmessagebubble.test.tsx","./src/app/(dashboard)/playground/components/chat_ui/chatui.test.tsx","./src/app/(dashboard)/playground/components/chat_ui/codeinterpreteroutput.test.tsx","./src/app/(dashboard)/playground/components/chat_ui/endpointselector.test.tsx","./src/app/(dashboard)/playground/components/chat_ui/endpointutils.tsx","./src/app/(dashboard)/playground/components/chat_ui/endpointutils.test.tsx","./src/app/(dashboard)/playground/components/chat_ui/filepreviewcard.test.tsx","./src/app/(dashboard)/playground/components/compareui/compareui.test.tsx","./src/app/(dashboard)/playground/components/compareui/components/comparisonpanel.test.tsx","./src/app/(dashboard)/playground/components/compareui/components/messagedisplay.test.tsx","./src/app/(dashboard)/playground/components/compareui/components/messageinput.test.tsx","./src/app/(dashboard)/playground/components/compareui/components/modelselector.tsx","./src/app/(dashboard)/playground/components/compareui/components/modelselector.test.tsx","./src/app/(dashboard)/playground/components/compareui/components/unifiedselector.test.tsx","./src/app/(dashboard)/playground/llm_calls/audio_speech.test.tsx","./src/app/(dashboard)/playground/llm_calls/audio_transcriptions.test.tsx","./src/app/(dashboard)/playground/llm_calls/embeddings_api.test.tsx","./src/components/policies/policy_table.tsx","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/academiccapicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/adjustmentsicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/annotationicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/archiveicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/arrowcircledownicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/arrowcirclelefticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/arrowcirclerighticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/arrowcircleupicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/arrowdownicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/arrowlefticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/arrownarrowdownicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/arrownarrowlefticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/arrownarrowrighticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/arrownarrowupicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/arrowrighticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/arrowsmdownicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/arrowsmlefticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/arrowsmrighticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/arrowsmupicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/arrowupicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/arrowsexpandicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/atsymbolicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/backspaceicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/badgecheckicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/banicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/beakericon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/bellicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/bookopenicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/bookmarkalticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/bookmarkicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/briefcaseicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/cakeicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/calculatoricon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/calendaricon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/cameraicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/cashicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/chartbaricon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/chartpieicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/chartsquarebaricon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/chatalt2icon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/chatalticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/chaticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/checkcircleicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/checkicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/chevrondoubledownicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/chevrondoublelefticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/chevrondoublerighticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/chevrondoubleupicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/chevrondownicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/chevronlefticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/chevronrighticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/chevronupicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/chipicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/clipboardcheckicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/clipboardcopyicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/clipboardlisticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/clipboardicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/clockicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/clouddownloadicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/clouduploadicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/cloudicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/codeicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/cogicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/collectionicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/colorswatchicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/creditcardicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/cubetransparenticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/cubeicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/currencybangladeshiicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/currencydollaricon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/currencyeuroicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/currencypoundicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/currencyrupeeicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/currencyyenicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/cursorclickicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/databaseicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/desktopcomputericon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/devicemobileicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/devicetableticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/documentaddicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/documentdownloadicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/documentduplicateicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/documentremoveicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/documentreporticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/documentsearchicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/documenttexticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/documenticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/dotscirclehorizontalicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/dotshorizontalicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/dotsverticalicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/downloadicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/duplicateicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/emojihappyicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/emojisadicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/exclamationcircleicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/exclamationicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/externallinkicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/eyeofficon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/eyeicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/fastforwardicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/filmicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/filtericon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/fingerprinticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/fireicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/flagicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/folderaddicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/folderdownloadicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/folderopenicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/folderremoveicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/foldericon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/gifticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/globealticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/globeicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/handicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/hashtagicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/hearticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/homeicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/identificationicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/inboxinicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/inboxicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/informationcircleicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/keyicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/libraryicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/lightbulbicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/lightningbolticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/linkicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/locationmarkericon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/lockclosedicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/lockopenicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/loginicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/logouticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/mailopenicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/mailicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/mapicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/menualt1icon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/menualt2icon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/menualt3icon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/menualt4icon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/menuicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/microphoneicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/minuscircleicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/minussmicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/minusicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/moonicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/musicnoteicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/newspapericon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/officebuildingicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/paperairplaneicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/paperclipicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/pauseicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/pencilalticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/pencilicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/phoneincomingicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/phonemissedcallicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/phoneoutgoingicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/phoneicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/photographicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/playicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/pluscircleicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/plussmicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/plusicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/presentationchartbaricon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/presentationchartlineicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/printericon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/puzzleicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/qrcodeicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/questionmarkcircleicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/receiptrefundicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/receipttaxicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/refreshicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/replyicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/rewindicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/rssicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/saveasicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/saveicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/scaleicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/scissorsicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/searchcircleicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/searchicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/selectoricon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/servericon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/shareicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/shieldcheckicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/shieldexclamationicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/shoppingbagicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/shoppingcarticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/sortascendingicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/sortdescendingicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/sparklesicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/speakerphoneicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/staricon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/statusofflineicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/statusonlineicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/stopicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/sunicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/supporticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/switchhorizontalicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/switchverticalicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/tableicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/tagicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/templateicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/terminalicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/thumbdownicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/thumbupicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/ticketicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/translateicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/trashicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/trendingdownicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/trendingupicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/truckicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/uploadicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/useraddicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/usercircleicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/usergroupicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/userremoveicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/usericon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/usersicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/variableicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/videocameraicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/viewboardsicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/viewgridaddicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/viewgridicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/viewlisticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/volumeofficon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/volumeupicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/wifiicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/xcircleicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/xicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/zoominicon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/zoomouticon.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@heroicons/react/solid/index.d.ts","./src/components/policies/pipeline_flow_builder.tsx","./src/components/policies/policy_info.tsx","./src/components/policies/add_policy_form.tsx","./src/components/policies/impact_popover.tsx","./src/components/policies/attachment_table.tsx","./src/components/policies/impact_preview_alert.tsx","./src/components/policies/add_attachment_form.tsx","./src/components/policies/policy_test_panel.tsx","./src/components/policies/policy_templates.tsx","./src/components/policies/guardrail_selection_modal.tsx","./src/components/policies/template_parameter_modal.tsx","./src/components/policies/ai_suggestion_modal.tsx","./src/components/policies/index.tsx","./src/app/(dashboard)/policies/page.tsx","./src/app/(dashboard)/projects/components/projectmodals/createprojectmodal.tsx","./src/app/(dashboard)/projects/components/projectmodals/editprojectmodal.tsx","./src/app/(dashboard)/projects/components/projectdetailspage.tsx","./src/app/(dashboard)/projects/components/projectspage.tsx","./src/app/(dashboard)/projects/page.tsx","./src/app/(dashboard)/projects/components/projectdetailspage.test.tsx","./src/app/(dashboard)/projects/components/projectkeystable.tsx","./src/app/(dashboard)/projects/components/projectkeyssection.tsx","./src/app/(dashboard)/projects/components/projectkeyssection.test.tsx","./src/app/(dashboard)/projects/components/projectkeystable.test.tsx","./src/app/(dashboard)/projects/components/projectspage.test.tsx","./src/app/(dashboard)/projects/components/projectmodals/createprojectmodal.test.tsx","./src/app/(dashboard)/projects/components/projectmodals/editprojectmodal.test.tsx","./src/app/(dashboard)/projects/components/projectmodals/projectbaseform.test.tsx","./src/app/(dashboard)/prompts/components/prompt_utils.tsx","./src/app/(dashboard)/prompts/components/prompt_table.tsx","./src/app/(dashboard)/prompts/components/prompt_editor_view/promptcodesnippets.tsx","./src/app/(dashboard)/prompts/components/prompt_info.tsx","./src/app/(dashboard)/prompts/components/add_prompt_form.tsx","./src/app/(dashboard)/prompts/components/tool_modal.tsx","./src/app/(dashboard)/prompts/components/prompt_editor_view/prompteditorheader.tsx","./src/app/(dashboard)/prompts/components/prompt_editor_view/modelconfigcard.tsx","./src/app/(dashboard)/prompts/components/prompt_editor_view/toolscard.tsx","./src/app/(dashboard)/prompts/components/variable_textarea.tsx","./src/app/(dashboard)/prompts/components/prompt_editor_view/developermessagecard.tsx","./src/app/(dashboard)/prompts/components/prompt_editor_view/promptmessagescard.tsx","./src/app/(dashboard)/prompts/components/prompt_editor_view/conversation_panel/variableinput.tsx","./src/app/(dashboard)/prompts/components/prompt_editor_view/conversation_panel/emptystate.tsx","./src/app/(dashboard)/prompts/components/prompt_editor_view/conversation_panel/messagebubble.tsx","./src/app/(dashboard)/prompts/components/prompt_editor_view/conversation_panel/messagelist.tsx","./src/app/(dashboard)/prompts/components/prompt_editor_view/conversation_panel/variablewarning.tsx","./src/app/(dashboard)/prompts/components/prompt_editor_view/conversation_panel/messageinput.tsx","./src/app/(dashboard)/prompts/components/prompt_editor_view/conversation_panel/index.tsx","./src/app/(dashboard)/prompts/components/prompt_editor_view/publishmodal.tsx","./src/app/(dashboard)/prompts/components/prompt_editor_view/dotpromptviewtab.tsx","./src/app/(dashboard)/prompts/components/prompt_editor_view/versionhistorysidepanel.tsx","./src/app/(dashboard)/prompts/components/prompt_editor_view/index.tsx","./src/app/(dashboard)/prompts/components/prompt_editor_view.tsx","./src/app/(dashboard)/prompts/components/index.tsx","./src/app/(dashboard)/prompts/page.tsx","./src/app/(dashboard)/prompts/components/prompt_editor_view/toolscard.test.tsx","./src/app/(dashboard)/prompts/components/prompt_editor_view/versionhistorysidepanel.test.tsx","./src/components/router_settings/index.tsx","./src/components/settings/routersettings/fallbacks/fallbacks.tsx","./src/components/routing_groups/routinggroupstable.tsx","./src/components/routing_groups/routinggroupmodal.tsx","./src/components/routing_groups/index.tsx","./src/components/general_settings.tsx","./src/app/(dashboard)/router-settings/page.tsx","./src/app/(dashboard)/search-tools/_components/searchconnectiontest.tsx","./src/app/(dashboard)/search-tools/_components/types.tsx","./src/app/(dashboard)/search-tools/_components/createsearchtools.tsx","./src/app/(dashboard)/search-tools/_components/searchtoolcolumn.tsx","./src/app/(dashboard)/search-tools/_components/searchtooltester.tsx","./src/app/(dashboard)/search-tools/_components/searchtoolview.tsx","./src/app/(dashboard)/search-tools/_components/searchtools.tsx","./src/app/(dashboard)/search-tools/_components/index.tsx","./src/app/(dashboard)/search-tools/page.tsx","./src/app/(dashboard)/search-tools/_components/searchtooltester.test.tsx","./src/app/(dashboard)/search-tools/_components/searchtoolview.test.tsx","./src/app/(dashboard)/search-tools/_components/searchtools.test.tsx","./src/components/claude_code_plugins/add_plugin_form.tsx","./src/components/claude_code_plugins/plugin_table.tsx","./src/components/claude_code_plugins.tsx","./src/app/(dashboard)/skills/page.tsx","./src/components/tag_management/tag_info.tsx","./src/components/tag_management/tagtable.tsx","./src/components/tag_management/components/createtagmodal.tsx","./src/components/tag_management/index.tsx","./src/app/(dashboard)/tag-management/page.tsx","./src/components/team/available_teams.tsx","./src/components/teamssosettings.tsx","./src/components/oldteams.tsx","./src/app/(dashboard)/teams/page.tsx","./src/components/toolpolicies/policyselect.tsx","./src/components/tooldetail.tsx","./src/components/toolpolicies.tsx","./src/components/toolpoliciesview.tsx","./src/app/(dashboard)/tool-policies/page.tsx","./src/app/(dashboard)/transform-request/transformrequestpanel.tsx","./src/app/(dashboard)/transform-request/page.tsx","./src/app/(dashboard)/ui-theme/uithemesettings.tsx","./src/app/(dashboard)/ui-theme/page.tsx","./src/components/common_components/chartutils.tsx","./src/components/usagepage/components/keymodelusageview.tsx","./src/components/activity_metrics.tsx","./src/components/cloudzero_export_modal.tsx","./src/components/shared/chart_loader.tsx","./src/components/per_user_usage.tsx","./src/components/user_agent_activity.tsx","./src/components/usagepage/components/endpointusage/components/endpointusagebarchart.tsx","./src/components/usagepage/components/endpointusage/components/endpointusagelinechart.tsx","./src/components/usagepage/components/endpointusage/components/endpointusagetable.tsx","./src/components/usagepage/components/endpointusage/endpointusage.tsx","./src/components/common_components/team_multi_select.tsx","./src/components/usagepage/components/entityusage/topmodelview.tsx","./src/components/usagepage/components/entityusage/entityusage.tsx","./src/components/usagepage/components/entityusage/spendbyprovider.tsx","./src/components/usagepage/components/usageaichatpanel.tsx","./src/components/usagepage/components/usageviewselect/usageviewselect.tsx","./src/components/usagepage/components/usagepageview.tsx","./src/app/(dashboard)/usage/page.tsx","./src/app/(dashboard)/users/_components/user_edit_view.tsx","./src/app/(dashboard)/users/_components/bulkeditusers.tsx","./src/app/(dashboard)/users/_components/edit_user.tsx","./src/app/(dashboard)/users/_components/defaultusersettings.tsx","./src/app/(dashboard)/users/_components/view_users/columns.tsx","./src/app/(dashboard)/users/_components/view_users/user_info_view.tsx","./src/app/(dashboard)/users/_components/view_users/table.tsx","./src/app/(dashboard)/users/_components/view_users.tsx","./src/app/(dashboard)/users/_components/index.tsx","./src/app/(dashboard)/users/page.tsx","./src/app/(dashboard)/users/_components/bulkeditusers.test.tsx","./src/app/(dashboard)/users/_components/defaultusersettings.test.tsx","./src/app/(dashboard)/users/_components/user_edit_view.test.tsx","./src/app/(dashboard)/users/_components/view_users.test.tsx","./src/app/(dashboard)/users/_components/view_users/table.test.tsx","./src/app/(dashboard)/users/_components/view_users/user_info_view.test.tsx","./src/components/vector_store_management/vectorstoretable.tsx","./src/components/vector_store_providers.tsx","./src/components/vector_store_management/vectorstoreform.tsx","./src/components/vector_store_management/vectorstoretester.tsx","./src/components/vector_store_management/vector_store_info.tsx","./src/components/vector_store_management/documentstable.tsx","./src/components/vector_store_management/s3vectorsconfig.tsx","./src/components/vector_store_management/createvectorstore.tsx","./src/components/vector_store_management/testvectorstoretab.tsx","./src/components/vector_store_management/index.tsx","./src/app/(dashboard)/vector-stores/page.tsx","./src/app/(dashboard)/workflows/workflowruns.tsx","./src/app/(dashboard)/workflows/page.tsx","./src/contexts/chatshellcontext.tsx","./src/components/ui/button.tsx","./src/components/ui/separator.tsx","./src/components/ui/input.tsx","./src/components/ui/dialog.tsx","./src/components/ui/alert-dialog.tsx","./src/components/ui/tooltip.tsx","./src/components/ui/scroll-area.tsx","./src/components/chat/conversationlist.tsx","./src/components/chat/chatshell.tsx","./src/app/chat/layout.tsx","./src/app/chat/layout.test.tsx","./src/components/ui/popover.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/collapsible.tsx","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/micromark-util-types/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/micromark-extension-gfm-footnote/lib/html.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/micromark-extension-gfm-footnote/lib/syntax.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/micromark-extension-gfm-footnote/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/micromark-extension-gfm-strikethrough/lib/html.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/micromark-extension-gfm-strikethrough/lib/syntax.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/micromark-extension-gfm-strikethrough/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/micromark-extension-gfm/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-from-markdown/lib/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-from-markdown/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-from-markdown/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-markdown/lib/types.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-markdown/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-markdown/lib/handle/blockquote.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-markdown/lib/handle/break.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-markdown/lib/handle/code.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-markdown/lib/handle/definition.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-markdown/lib/handle/emphasis.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-markdown/lib/handle/heading.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-markdown/lib/handle/html.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-markdown/lib/handle/image.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-markdown/lib/handle/image-reference.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-markdown/lib/handle/inline-code.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-markdown/lib/handle/link.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-markdown/lib/handle/link-reference.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-markdown/lib/handle/list.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-markdown/lib/handle/list-item.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-markdown/lib/handle/paragraph.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-markdown/lib/handle/root.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-markdown/lib/handle/strong.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-markdown/lib/handle/text.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-markdown/lib/handle/thematic-break.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-markdown/lib/handle/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-to-markdown/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-gfm-footnote/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-gfm-footnote/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/markdown-table/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-gfm-table/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-gfm-table/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-gfm/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/mdast-util-gfm/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/remark-gfm/lib/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/remark-gfm/index.d.ts","./src/components/chat/chatmessages.tsx","./src/components/ui/switch.tsx","./src/components/chat/mcpconnectpicker.tsx","./src/app/chat/page.tsx","./src/components/ui/label.tsx","./src/components/ui/badge.tsx","./src/components/ui/table.tsx","./src/components/chat/keyspanel.tsx","./src/app/chat/api-keys/page.tsx","./src/components/chat/mcpcredentialstab.tsx","./src/app/chat/credentials/page.tsx","./src/components/ui/tabs.tsx","./src/components/chat/mcpappspanel.tsx","./src/app/chat/integrations/page.tsx","./src/components/chat/usagepanel.tsx","./src/app/chat/usage/page.tsx","./src/app/login/loginpage.tsx","./src/app/login/loginpage.test.tsx","./src/app/login/page.tsx","./src/app/mcp/oauth/callback/page.tsx","./src/app/model_hub/page.tsx","./src/app/model_hub_table/page.tsx","./src/app/onboarding/onboardingerrorview.test.tsx","./src/app/onboarding/onboardingform.test.tsx","./src/app/onboarding/onboardingformbody.test.tsx","./src/app/onboarding/onboardingloadingview.test.tsx","./src/components/adminpanel.test.tsx","./src/components/createuserbutton.test.tsx","./src/components/debugwarningbanner.test.tsx","./src/components/helplink.test.tsx","./src/components/oldteams.test.tsx","./src/components/ssomodals.test.tsx","./src/components/teamssosettings.test.tsx","./src/components/toolpoliciesview.test.tsx","./src/components/uiaccesscontrolform.unit.test.tsx","./src/components/usageindicator.test.tsx","./src/components/activity_metrics.test.tsx","./src/components/agents.test.tsx","./src/components/bulk_create_users_button.test.tsx","./src/components/guardrails.test.tsx","./src/components/key_info_utils.test.tsx","./src/components/leftnav.test.tsx","./src/components/mcp_hub_table_columns.test.tsx","./src/components/model_info_view.test.tsx","./src/components/navbar.test.tsx","./src/components/organizations.test.tsx","./src/components/provider_info_helpers.test.tsx","./src/components/public_model_hub.test.tsx","./src/components/settings.test.tsx","./src/components/update_model_credentials_modal.test.tsx","./src/components/user_agent_activity.test.tsx","./src/components/user_dashboard.test.tsx","./src/components/aihub/agenthubtablecolumns.test.tsx","./src/components/aihub/modelhubtable.test.tsx","./src/components/aihub/usefullinksmanagement.test.tsx","./src/components/aihub/forms/makeagentpublicform.test.tsx","./src/components/aihub/forms/makemcppublicform.test.tsx","./src/components/aihub/forms/makemodelpublicform.test.tsx","./src/components/cloudzerocosttracking/cloudzerocosttracking.test.tsx","./src/components/cloudzerocosttracking/cloudzerocreatemodal.test.tsx","./src/components/cloudzerocosttracking/cloudzeroemptyplaceholder.test.tsx","./src/components/cloudzerocosttracking/cloudzerointegrationsettings.test.tsx","./src/components/cloudzerocosttracking/cloudzeroupdatemodal.test.tsx","./src/components/deletedkeyspage/deletedkeyspage.test.tsx","./src/components/deletedkeyspage/deletedkeystable/deletedkeystable.test.tsx","./src/components/deletedteamspage/deletedteamspage.test.tsx","./src/components/deletedteamspage/deletedteamstable/deletedteamstable.test.tsx","./src/components/entityusageexport/entityusageexportmodal.test.tsx","./src/components/entityusageexport/exportformatselector.test.tsx","./src/components/entityusageexport/exportsummary.test.tsx","./src/components/entityusageexport/exporttypeselector.test.tsx","./src/components/entityusageexport/usageexportheader.test.tsx","./src/components/guardrailsmonitor/metriccard.test.tsx","./src/components/keyaliasselect/paginatedkeyaliasselect/paginatedkeyaliasselect.test.tsx","./src/components/modelselect/modelselect.test.tsx","./src/components/modelselect/paginatedmodelselect/paginatedmodelselect.test.tsx","./src/components/navbar/viewswitcher.test.tsx","./src/components/navbar/blogdropdown/blogdropdown.test.tsx","./src/components/navbar/communityengagementbuttons/communityengagementbuttons.test.tsx","./src/components/navbar/notificationsbell/notificationsbell.test.tsx","./src/components/navbar/userdropdown/userdropdown.test.tsx","./src/components/navbar/workerdropdown/workerdropdown.test.tsx","./src/components/settings/adminsettings/hashicorpvault/hashicorpvaultemptyplaceholder.test.tsx","./src/components/settings/adminsettings/loggingsettings/loggingsettings.test.tsx","./src/components/settings/adminsettings/mcpsemanticfiltersettings/mcpsemanticfiltersettings.test.tsx","./src/components/settings/adminsettings/mcpsemanticfiltersettings/mcpsemanticfiltertestpanel.test.tsx","./src/components/settings/adminsettings/ssosettings/redactablefield.test.tsx","./src/components/settings/adminsettings/ssosettings/rolemappings.test.tsx","./src/components/settings/adminsettings/ssosettings/ssosettings.test.tsx","./src/components/settings/adminsettings/ssosettings/ssosettingsemptyplaceholder.test.tsx","./src/components/settings/adminsettings/ssosettings/ssosettingsloadingskeleton.test.tsx","./src/components/settings/adminsettings/ssosettings/modals/addssosettingsmodal.test.tsx","./src/components/settings/adminsettings/ssosettings/modals/basessosettingsform.test.tsx","./src/components/settings/adminsettings/ssosettings/modals/deletessosettingsmodal.test.tsx","./src/components/settings/adminsettings/ssosettings/modals/editssosettingsmodal.test.tsx","./src/components/settings/adminsettings/uisettings/pagevisibilitysettings.test.tsx","./src/components/settings/adminsettings/uisettings/uisettings.test.tsx","./src/components/settings/loggingandalerts/loggingcallbacks/loggingcallbackstable.test.tsx","./src/components/settings/routersettings/fallbacks/addfallbacks.test.tsx","./src/components/settings/routersettings/fallbacks/addfallbacksmodal.test.tsx","./src/components/settings/routersettings/fallbacks/fallbackselectionform.test.tsx","./src/components/settings/routersettings/fallbacks/fallbacks.test.tsx","./src/components/toolpolicies/policyselect.test.tsx","./src/components/usagepage/components/keymodelusageview.test.tsx","./src/components/usagepage/components/usageaichatpanel.test.tsx","./src/components/usagepage/components/usagepageview.test.tsx","./src/components/usagepage/components/endpointusage/endpointusage.test.tsx","./src/components/usagepage/components/endpointusage/components/endpointusagebarchart.test.tsx","./src/components/usagepage/components/endpointusage/components/endpointusagelinechart.test.tsx","./src/components/usagepage/components/endpointusage/components/endpointusagetable.test.tsx","./src/components/usagepage/components/entityusage/entityusage.test.tsx","./src/components/usagepage/components/entityusage/spendbyprovider.test.tsx","./src/components/usagepage/components/entityusage/topkeyview.test.tsx","./src/components/usagepage/components/entityusage/topmodelview.test.tsx","./src/components/usagepage/components/usageviewselect/usageviewselect.test.tsx","./src/components/virtualkeyspage/virtualkeystable.test.tsx","./src/components/add_model/addmodelform.test.tsx","./src/components/add_model/complexityrouterconfig.test.tsx","./src/components/add_model/routerconfigbuilder.test.tsx","./src/components/add_model/add_model_tab.test.tsx","./src/components/add_model/advanced_settings.test.tsx","./src/components/add_model/conditional_public_model_name.test.tsx","./src/components/add_model/handle_add_model_submit.test.tsx","./src/components/add_model/litellm_model_name.test.tsx","./src/components/add_model/provider_specific_fields.test.tsx","./src/components/agent_management/agentselector.test.tsx","./src/components/agents/agent_card_discovery.test.tsx","./src/components/agents/agent_virtual_keys.test.tsx","./src/components/atoms/tooltip.test.tsx","./src/components/chat/chatshell.test.tsx","./src/components/chat_ui/codesnippets.test.tsx","./src/components/claude_code_plugins/add_plugin_form.test.tsx","./src/components/common_components/defaultproxyadmintag.test.tsx","./src/components/common_components/deleteresourcemodal.test.tsx","./src/components/common_components/durationselect.test.tsx","./src/components/common_components/keylifecyclesettings.test.tsx","./src/components/common_components/labeledfield.test.tsx","./src/components/common_components/loadingscreen.test.tsx","./src/components/common_components/newbadge.test.tsx","./src/components/common_components/organizationdropdown.test.tsx","./src/components/common_components/ratelimittypeformitem.test.tsx","./src/components/common_components/chartutils.test.tsx","./src/components/common_components/filters/filterinput.test.tsx","./src/components/common_components/filters/filtersbutton.test.tsx","./src/components/common_components/filters/resetfiltersbutton.test.tsx","./src/components/common_components/iconactionbutton/baseactionbutton.test.tsx","./src/components/common_components/iconactionbutton/tableiconactionbuttons/tableiconactionbutton.test.tsx","./src/components/common_components/tableheadersortdropdown/tableheadersortdropdown.test.tsx","./src/components/guardrails/guardrailselector.test.tsx","./src/components/guardrails/guardrailtestpanel.test.tsx","./src/components/guardrails/guardrailtestplayground.test.tsx","./src/components/guardrails/guardrailtestresults.test.tsx","./src/components/guardrails/add_guardrail_form.test.tsx","./src/components/guardrails/guardrail_garden_card.test.tsx","./src/components/guardrails/guardrail_info.test.tsx","./src/components/guardrails/guardrail_info_helpers.test.tsx","./src/components/guardrails/guardrail_table.test.tsx","./src/components/guardrails/pii_components.test.tsx","./src/components/guardrails/pii_configuration.test.tsx","./src/components/guardrails/content_filter/contentfiltermanager.test.tsx","./src/components/guardrails/content_filter/custompatternmodal.test.tsx","./src/components/guardrails/content_filter/patternmodal.test.tsx","./src/components/guardrails/tool_permission/toolpermissionruleseditor.test.tsx","./src/components/key_team_helpers/budgetfallbackseditor.test.tsx","./src/components/key_team_helpers/fetch_available_models_team_key.test.tsx","./src/components/llm_calls/chat_completion.test.tsx","./src/components/llm_calls/responses_api.test.tsx","./src/components/mcp_server_management/mcpserverselector.test.tsx","./src/components/mcp_server_management/mcptoolpermissions.test.tsx","./src/components/mcp_tools/mcplogoselector.test.tsx","./src/components/mcp_tools/mcppermissionmanagement.test.tsx","./src/components/mcp_tools/mcpservercard.test.tsx","./src/components/mcp_tools/mcpstandardssettings.test.tsx","./src/components/mcp_tools/oauthformfields.test.tsx","./src/components/mcp_tools/tooltestpanel.test.tsx","./src/components/mcp_tools/create_mcp_server.test.tsx","./src/components/mcp_tools/mcp_connection_status.test.tsx","./src/components/mcp_tools/mcp_server_edit.test.tsx","./src/components/mcp_tools/mcp_servers.test.tsx","./src/components/mcp_tools/mcp_tool_configuration.test.tsx","./src/components/mcp_tools/mcp_tools.test.tsx","./src/components/mcp_tools/types.test.tsx","./src/components/mcp_tools/utils.test.tsx","./src/components/model_add/addcredentialmodal.test.tsx","./src/components/model_add/editcredentialmodal.test.tsx","./src/components/model_add/credentials.test.tsx","./src/components/model_dashboard/healthcheckcomponent.test.tsx","./src/components/model_dashboard/modelsettingsmodal/modelsettingsmodal.test.tsx","./src/components/molecules/cost_optimization_feedback_banner.test.tsx","./src/components/molecules/filter.test.tsx","./src/components/molecules/notifications_manager.test.tsx","./src/components/molecules/models/providerlogo.test.tsx","./src/components/molecules/models/columns.test.tsx","./src/components/organisms/regeneratekeymodal.test.tsx","./src/components/organisms/create_key_button.test.tsx","./src/components/organization/organization_view.test.tsx","./src/components/permissions/mcpserverpermissions.test.tsx","./src/components/policies/policyselector.test.tsx","./src/components/policies/add_attachment_form.test.tsx","./src/components/policies/attachment_table.test.tsx","./src/components/policies/guardrail_selection_modal.test.tsx","./src/components/policies/impact_popover.test.tsx","./src/components/policies/impact_preview_alert.test.tsx","./src/components/policies/index.test.tsx","./src/components/policies/policy_info.test.tsx","./src/components/policies/policy_table.test.tsx","./src/components/policies/policy_templates.test.tsx","./src/components/router_settings/latencybasedconfiguration.test.tsx","./src/components/router_settings/reliabilityretriessection.test.tsx","./src/components/router_settings/routersettingsform.test.tsx","./src/components/router_settings/routingstrategyselector.test.tsx","./src/components/router_settings/tagfilteringtoggle.test.tsx","./src/components/router_settings/index.test.tsx","./src/components/shared/createdkeydisplay.test.tsx","./src/components/shared/advanced_date_picker.test.tsx","./src/components/shared/chart_loader.test.tsx","./src/components/shared/usage_date_picker.test.tsx","./src/components/tag_management/tagselector.test.tsx","./src/components/tag_management/tagtable.test.tsx","./src/components/tag_management/components/createtagmodal.test.tsx","./src/components/team/editmembership.test.tsx","./src/components/team/loggingsettings.test.tsx","./src/components/team/teaminfo.test.tsx","./src/components/team/teammembertab.test.tsx","./src/components/team/teamvirtualkeystable.test.tsx","./src/components/team/available_teams.test.tsx","./src/components/team/member_permissions.test.tsx","./src/components/team/permission_definitions.test.tsx","./src/components/templates/keyinfoheader.test.tsx","./src/components/templates/keyinfoview.handlekeyupdate.test.tsx","./src/components/templates/key_edit_view.test.tsx","./src/components/templates/key_info_view.budget_display.test.tsx","./src/components/templates/key_info_view.test.tsx","./src/components/ui/antdloadingspinner.test.tsx","./src/components/ui/alert-dialog.test.tsx","./src/components/ui/button.test.tsx","./src/components/ui/select.tsx","./src/components/ui/ui-loading-spinner.test.tsx","./src/components/vector_store_management/createvectorstore.test.tsx","./src/components/vector_store_management/documentstable.test.tsx","./src/components/vector_store_management/s3vectorsconfig.test.tsx","./src/components/vector_store_management/testvectorstoretab.test.tsx","./src/components/vector_store_management/vectorstoreform.test.tsx","./src/components/vector_store_management/vectorstoreselector.test.tsx","./src/components/vector_store_management/vectorstoretable.test.tsx","./src/components/view_logs/configinfomessage.test.tsx","./src/components/view_logs/costbreakdownviewer.test.tsx","./src/components/view_logs/typebadges.test.tsx","./src/components/view_logs/index.test.tsx","./src/components/view_logs/log_filter_logic.test.tsx","./src/components/view_logs/logs_utils.test.tsx","./src/components/view_logs/table.test.tsx","./src/components/view_logs/time_cell.test.tsx","./src/components/view_logs/guardrailviewer/bedrockguardraildetails.test.tsx","./src/components/view_logs/guardrailviewer/guardrailviewer.test.tsx","./src/components/view_logs/guardrailviewer/presidiodetectedentities.test.tsx","./src/components/view_logs/logdetailsdrawer/collapsiblemessage.test.tsx","./src/components/view_logs/logdetailsdrawer/historytree.test.tsx","./src/components/view_logs/logdetailsdrawer/inputcard.test.tsx","./src/components/view_logs/logdetailsdrawer/logdetailcontent.test.tsx","./src/components/view_logs/logdetailsdrawer/outputcard.test.tsx","./src/components/view_logs/logdetailsdrawer/prettymessagesview.test.tsx","./src/components/view_logs/logdetailsdrawer/realtimeprettyview.test.tsx","./src/components/view_logs/logdetailsdrawer/simplemessageblock.test.tsx","./src/components/view_logs/logdetailsdrawer/simpletoolcallblock.test.tsx","./src/components/view_logs/logdetailsdrawer/truncatedvalue.test.tsx","./src/components/view_logs/toolssection/toolssection.test.tsx","./src/contexts/pluginmodecontext.test.tsx","./src/hooks/usemcpoauthflow.test.tsx","./src/hooks/policies/usedeletepolicyattachment.test.tsx","./tests/createkeypage.expiredtoken.test.tsx","./tests/top_key_view.test.tsx","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/d3-array/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/d3-color/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/d3-ease/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/d3-interpolate/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/d3-path/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/d3-time/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/d3-scale/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/d3-shape/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/d3-timer/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/ms/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/debug/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/estree-jsx/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/json5/index.d.ts","../../../litellm-lit4214/ui/litellm-dashboard/node_modules/@types/scheduler/index.d.ts"],"fileIdsList":[[103,149],[103,149,373,383],[103,149,383,384,388,391,392],[103,149,373],[86,103,149,382],[103,149,384],[103,149,384,389,390],[86,103,149,373,383,384,385,386,387],[103,149,383],[103,149,343,344,345],[103,149,344,348],[103,149,344,345],[103,149,343],[84,86,103,149,344,351,359,361,373],[103,149,345,346,349,350,351,359,360,361,362,369,370,371,372],[103,149,362],[103,149,352],[103,149,352,353,354,355,356,357,358],[86,103,149,343,352,360],[103,149,363],[103,149,363,364,365],[103,149,347,348],[103,149,347,348,363,366,367,368],[103,149,347],[103,149,360],[103,149,735],[103,149,735,736],[86,103,149,796,797,798],[86,103,149],[86,103,149,797],[86,103,149,799],[103,149,959,960,961,962,963,964,965,966,967,968,969,970,971,972,973,974,975,976,977,978,979,980,981,982,983,984,985,986,987,988,989,990,991,992,993,994,995,996,997,998,999,1000,1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1011,1012,1013,1014,1015,1016,1017,1018,1019,1020,1021,1022,1023,1024,1025,1026,1027,1028,1029,1030,1031,1032,1033,1034,1035,1036,1037,1038,1039,1040,1041,1042,1043,1044,1045,1046,1047,1048,1049,1050,1051,1052,1053,1054,1055,1056,1057,1058,1059,1060,1061,1062,1063,1064,1065,1066,1067,1068,1069,1070,1071,1072,1073,1074,1075,1076,1077,1078,1079,1080,1081,1082,1083,1084,1085,1086,1087,1088,1089,1090,1091,1092,1093,1094,1095,1096,1097,1098,1099,1100,1101,1102,1103,1104,1105,1106,1107,1108,1109,1110,1111,1112,1113,1114,1115,1116,1117,1118,1119,1120,1121,1122,1123,1124,1125,1126,1127,1128,1129,1130,1131,1132,1133,1134,1135,1136,1137,1138,1139,1140,1141,1142,1143,1144,1145,1146,1147,1148,1149,1150,1151,1152,1153,1154,1155,1156,1157,1158,1159,1160,1161,1162,1163,1164,1165,1166,1167,1168,1169,1170,1171,1172,1173,1174,1175,1176,1177,1178,1179,1180,1181,1182,1183,1184,1185,1186,1187,1188,1189,1190,1191,1192,1193,1194,1195,1196,1197,1198,1199,1200,1201,1202,1203,1204,1205,1206,1207,1208,1209,1210,1211,1212,1213,1214,1215,1216,1217,1218,1219,1220,1221,1222,1223,1224,1225,1226,1227,1228,1229,1230,1231,1232,1233,1234,1235,1236,1237,1238,1239,1240,1241,1242,1243,1244,1245,1246,1247,1248,1249,1250,1251,1252,1253,1254,1255,1256,1257,1258,1259,1260,1261,1262,1263,1264,1265,1266,1267,1268,1269,1270,1271,1272,1273,1274,1275,1276,1277,1278,1279,1280,1281,1282,1283,1284,1285,1286,1287,1288,1289,1290,1291,1292,1293,1294,1295,1296,1297,1298,1299,1300,1301,1302,1303,1304,1305,1306,1307,1308,1309,1310,1311,1312,1313,1314,1315,1316,1317,1318,1319,1320,1321,1322,1323,1324,1325,1326,1327,1328,1329,1330,1331,1332,1333,1334,1335,1336,1337,1338,1339,1340,1341,1342,1343,1344,1345,1346,1347,1348,1349,1350,1351,1352,1353,1354,1355,1356,1357,1358,1359,1360,1361,1362,1363,1364,1365,1366,1367,1368,1369,1370,1371,1372,1373,1374,1375,1376,1377,1378,1379,1380,1381,1382,1383,1384,1385,1386,1387,1388,1389,1390,1391,1392,1393,1394,1395,1396,1397,1398,1399,1400,1401,1402,1403,1404,1405,1406,1407,1408,1409,1410,1411,1412,1413,1414,1415,1416,1417,1418,1419,1420,1421,1422,1423,1424,1425,1426,1427,1428,1429,1430,1431,1432,1433,1434,1435,1436,1437,1438,1439,1440,1441,1442,1443,1444,1445,1446,1447,1448,1449,1450,1451,1452,1453,1454,1455,1456,1457,1458,1459,1460,1461,1462,1463,1464,1465,1466,1467,1468,1469,1470,1471,1472,1473,1474,1475,1476,1477,1478,1479,1480,1481,1482,1483,1484,1485,1486,1487,1488,1489,1490,1491,1492,1493,1494,1495,1496,1497,1498,1499,1500,1501,1502,1503,1504,1505,1506,1507,1508,1509,1510,1511,1512,1513,1514,1515,1516,1517,1518,1519,1520,1521,1522,1523,1524,1525,1526,1527,1528,1529,1530,1531,1532,1533,1534,1535,1536,1537,1538,1539,1540,1541,1542,1543,1544,1545,1546,1547,1548,1549,1550,1551,1552,1553,1554,1555,1556,1557,1558,1559,1560,1561,1562,1563,1564,1565,1566,1567,1568,1569,1570,1571,1572,1573,1574,1575,1576,1577,1578,1579,1580,1581,1582,1583,1584,1585,1586,1587,1588,1589,1590,1591,1592,1593,1594,1595,1596,1597,1598,1599,1600,1601,1602,1603,1604,1605,1606,1607,1608,1609,1610,1611,1612,1613,1614,1615,1616,1617,1618,1619,1620,1621,1622,1623,1624,1625,1626,1627,1628,1629,1630,1631,1632,1633,1634,1635,1636,1637,1638,1639,1640,1641,1642,1643,1644,1645,1646,1647,1648,1649,1650,1651,1652,1653,1654,1655,1656,1657,1658,1659,1660,1661,1662,1663,1664,1665,1666,1667,1668,1669,1670,1671,1672,1673,1674,1675,1676,1677,1678,1679,1680,1681,1682,1683,1684,1685,1686,1687,1688,1689,1690,1691,1692,1693,1694,1695,1696,1697,1698,1699,1700,1701,1702,1703,1704,1705,1706,1707,1708,1709,1710,1711,1712,1713,1714,1715,1716,1717,1718,1719,1720,1721,1722,1723,1724,1725,1726,1727,1728,1729,1730,1731,1732,1733,1734,1735,1736,1737,1738,1739,1740,1741,1742,1743,1744,1745,1746,1747,1748,1749,1750,1751,1752,1753,1754,1755,1756,1757,1758,1759,1760,1761,1762,1763,1764,1765,1766,1767,1768,1769,1770,1771,1772,1773,1774,1775,1776,1777,1778,1779,1780,1781,1782,1783,1784,1785,1786,1787,1788,1789],[86,103,149,797,798,1790,1791,1792],[103,149,3655,3659,3660,3663,3664,3666,3668,3669,3672,3691,3716,3717,3718,3719],[103,149,3659,3667,3720],[103,149,3665],[103,149,3663,3667,3668,3720],[103,149,3720],[103,149,3661,3720],[103,149,3670,3671],[103,149,3666],[103,149,3666,3668,3669,3672,3689,3720],[103,149,3683],[103,149,3663,3669,3720],[103,149,3655,3659,3660,3662],[103,149,182],[103,149,3655],[103,144,149,3658],[103,149,3655,3663,3720],[103,149,3663,3720],[103,149,3715,3720],[103,149,3663,3685,3693,3715,3720],[103,149,3663,3685,3688,3689,3720],[103,149,3691,3720],[103,149,3709],[103,149,3663,3694,3709,3710,3712,3721],[103,149,3711],[103,149,3719],[103,149,3708],[103,149,3663,3668,3669,3673,3678,3716],[103,149,3678,3679],[103,149,3663,3669,3673,3679,3716],[103,149,3673,3674,3675,3676,3677,3679,3682,3699,3703,3706,3715],[103,149,3663,3668,3669,3673,3716],[103,149,3663,3668,3669,3672,3673,3716],[103,149,3674,3675,3676,3677,3695,3696,3697,3701,3704,3707,3716],[103,149,3680,3681,3682],[103,149,3663,3668,3669,3673,3680,3681,3716],[103,149,3663,3668,3669,3673,3680,3716],[103,149,3663,3668,3669,3673,3684,3691,3715,3716],[103,149,3692,3715],[103,149,3662,3663,3668,3673,3691,3692,3693,3694,3713,3714,3715,3716],[103,149,3662,3663,3668,3669,3673,3716],[103,149,3698,3699,3700],[103,149,3663,3668,3669,3673,3699,3716],[103,149,3663,3668,3669,3673,3679,3698,3700,3716],[103,149,3702,3703],[103,149,3663,3668,3669,3672,3673,3702,3716],[103,149,3705,3706],[103,149,3663,3668,3669,3673,3705,3716],[103,149,3662,3663,3668,3673,3691,3716,3717],[103,149,3665,3691,3716,3717,3718],[103,149,3687],[103,149,3663,3665,3668,3669,3673,3684,3691],[103,149,3686,3691],[103,149,3662,3663,3668,3673,3686,3689,3690,3691],[103,149,2797],[103,149,1821,1822,1823,1824,1825,1826,1827,1828,1829,1830,1831,1832,1833,1834,1835,1836,1837,1838,1839,1840,1841,1842,1843,1844,1845,1846,1847,1848,1849,1850,1851,1852,1853,1854,1855,1856,1857,1858,1859,1860,1861,1862,1863,1864,1865,1866,1867,1868,1869,1870,1871,1872,1873,1874,1875,1876,1877,1878,1879,1880,1881,1882,1883,1884,1885,1886,1887,1888,1889,1890,1891,1892,1893,1894,1895,1896,1897,1898,1899,1900,1901,1902,1903,1904,1905,1906,1907,1908,1909,1910,1911,1912,1913,1914,1915,1916,1917,1918,1919,1920,1921,1922,1923,1924,1925,1926,1927,1928,1929,1930,1931,1932,1933,1934,1935,1936,1937,1938,1939,1940,1941,1942,1943,1944,1945,1946,1947,1948,1949,1950,1951,1952,1953,1954,1955,1956,1957,1958,1959,1960,1961,1962,1963,1964,1965,1966,1967,1968,1969,1970,1971,1972,1973,1974,1975,1976,1977,1978,1979,1980,1981,1982,1983,1984,1985,1986,1987,1988,1989,1990,1991,1992,1993,1994,1995,1996,1997,1998,1999,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022,2023,2024,2025,2026,2027,2028,2029,2030,2031,2032,2033,2034,2035,2036,2037,2038,2039,2040,2041,2042,2043,2044,2045,2046,2047,2048,2049,2050],[103,149,3778,3779,3780,3781,3782,3783,3784,3785,3786,3787,3788,3789,3790,3791,3792,3793,3794,3795,3796,3797,3798,3799,3800,3801,3802,3803,3804,3805,3806,3807,3808,3809,3810,3811,3812,3813,3814,3815,3816,3817,3818,3819,3820,3821,3822,3823,3824,3825,3826,3827,3828,3829,3830,3831,3832,3833,3834,3835,3836,3837,3838,3839,3840,3841,3842,3843,3844,3845,3846,3847,3848,3849,3850,3851,3852,3853,3854,3855,3856,3857,3858,3859,3860,3861,3862,3863,3864,3865,3866,3867,3868,3869,3870,3871,3872,3873,3874,3875,3876,3877,3878,3879,3880,3881,3882,3883,3884,3885,3886,3887,3888,3889,3890,3891,3892,3893,3894,3895,3896,3897,3898,3899,3900,3901,3902,3903,3904,3905,3906,3907,3908,3909,3910,3911,3912,3913,3914,3915,3916,3917,3918,3919,3920,3921,3922,3923,3924,3925,3926,3927,3928,3929,3930,3931,3932,3933,3934,3935,3936,3937,3938,3939,3940,3941,3942,3943,3944,3945,3946,3947,3948,3949,3950,3951,3952,3953,3954,3955,3956,3957,3958,3959,3960,3961,3962,3963,3964,3965,3966,3967,3968,3969,3970,3971,3972,3973,3974,3975,3976,3977,3978,3979,3980,3981,3982,3983,3984,3985,3986,3987,3988,3989,3990,3991,3992,3993,3994,3995,3996,3997,3998,3999,4000,4001,4002,4003,4004,4005,4006,4007],[103,149,737,739],[86,103,149,739,741],[86,103,149,738,739],[86,103,149,740],[103,149,738,739,740,742,743],[103,149,738],[103,149,643],[103,149,646,647],[103,149,643,644,645],[103,149,614,615],[103,149,781,782,783,784],[86,103,149,780],[86,103,149,781],[103,149,781],[103,149,566],[103,149,564,565],[86,103,149,314,561,562,563],[103,149,314],[86,103,149,564],[86,103,149,312,313],[86,103,149,312],[103,149,2305],[103,149,2102],[103,149,2306,2307,2308,2309,2310],[103,149,2305,2306],[103,149,2306],[86,87,103,149,2103],[103,149,2104],[86,103,149,2455],[103,149,2436],[103,149,2421,2444],[103,149,2444],[103,149,2444,2455],[103,149,2430,2444,2455],[103,149,2435,2444,2455],[103,149,2425,2444],[103,149,2433,2444,2455],[103,149,2431],[103,149,2421,2422,2423,2424,2425,2426,2427,2428,2429,2430,2431,2432,2433,2434,2435,2436,2437,2438,2439,2440,2441,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2453,2454],[103,149,2434],[103,149,2421,2422,2423,2424,2425,2426,2427,2428,2429,2431,2432,2434,2436,2437,2438,2439,2440,2441,2442,2443],[103,149,2083],[103,149,2080,2081,2082,2083,2084,2087,2088,2089,2090,2091,2092,2093,2094],[103,149,2079],[103,149,2086],[103,149,2080,2081,2082],[103,149,2080,2081],[103,149,2083,2084,2086],[103,149,2081],[103,149,2805],[103,149,2804],[86,103,149,2078,2095,2096,2817],[103,149,3248],[103,149,3235,3236,3237],[103,149,3230,3231,3232],[103,149,3208,3209,3210,3211],[103,149,3174,3248],[103,149,3174],[103,149,3174,3175,3176,3177,3222],[103,149,3212],[103,149,3207,3213,3214,3215,3216,3217,3218,3219,3220,3221],[103,149,3222],[103,149,3173],[103,149,3226,3228,3229,3247,3248],[103,149,3226,3228],[103,149,3223,3226,3248],[103,149,3233,3234,3238,3239,3244],[103,149,3227,3229,3239,3247],[103,149,3246,3247],[103,149,3223,3227,3229,3245,3246],[103,149,3227,3248],[103,149,3225],[103,149,3225,3227,3248],[103,149,3223,3224],[103,149,3240,3241,3242,3243],[103,149,3229,3248],[103,149,3184],[103,149,3178,3185],[103,149,3178,3179,3180,3181,3182,3183,3184,3185,3186,3187,3188,3189,3190,3191,3192,3193,3194,3195,3196,3197,3198,3199,3200,3201,3202,3203,3204,3205,3206],[103,149,3204,3248],[86,103,149,858,957],[103,149,255,256],[103,149,4485],[103,149,4489],[103,149,4488],[103,149,4493],[103,149,201,202,4495],[103,149,3591],[103,149,2269,2271,2272,2273,2274,2275,2276,2277,2278,2279,2280,2281],[103,149,2269,2270,2272,2273,2274,2275,2276,2277,2278,2279,2280,2281],[103,149,2270,2271,2272,2273,2274,2275,2276,2277,2278,2279,2280,2281],[103,149,2269,2270,2271,2273,2274,2275,2276,2277,2278,2279,2280,2281],[103,149,2269,2270,2271,2272,2274,2275,2276,2277,2278,2279,2280,2281],[103,149,2269,2270,2271,2272,2273,2275,2276,2277,2278,2279,2280,2281],[103,149,2269,2270,2271,2272,2273,2274,2276,2277,2278,2279,2280,2281],[103,149,2269,2270,2271,2272,2273,2274,2275,2277,2278,2279,2280,2281],[103,149,2269,2270,2271,2272,2273,2274,2275,2276,2278,2279,2280,2281],[103,149,2269,2270,2271,2272,2273,2274,2275,2276,2277,2279,2280,2281],[103,149,2269,2270,2271,2272,2273,2274,2275,2276,2277,2278,2280,2281],[103,149,2269,2270,2271,2272,2273,2274,2275,2276,2277,2278,2279,2281],[103,149,2281],[103,149,2269,2270,2271,2272,2273,2274,2275,2276,2277,2278,2279,2280],[103,149,163,190,197,3656,3657],[103,146,149],[103,148,149],[149],[103,149,154,182],[103,149,150,155,160,168,179,190],[103,149,150,151,160,168],[98,99,100,103,149],[103,149,152,191],[103,149,153,154,161,169],[103,149,154,179,187],[103,149,155,157,160,168],[103,148,149,156],[103,149,157,158],[103,149,159,160],[103,148,149,160],[103,149,160,161,162,179,190],[103,149,160,161,162,175,179,182],[103,149,157,160,163,168,179,190],[103,149,160,161,163,164,168,179,187,190],[103,149,163,165,179,187,190],[101,102,103,104,105,106,107,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196],[103,149,160,166],[103,149,167,190,195],[103,149,157,160,168,179],[103,149,169],[103,149,170],[103,148,149,171],[103,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196],[103,149,173],[103,149,174],[103,149,160,175,176],[103,149,175,177,191,193],[103,149,160,179,180,182],[103,149,181,182],[103,149,179,180],[103,149,183],[103,146,149,179,184],[103,149,160,185,186],[103,149,185,186],[103,149,154,168,179,187],[103,149,188],[103,149,168,189],[103,149,163,174,190],[103,149,154,191],[103,149,179,192],[103,149,167,193],[103,149,194],[103,144,149],[103,144,149,160,162,171,179,182,190,193,195],[103,149,179,196],[103,149,179,197],[86,103,149,2078,2816,2817,2818],[86,103,149,2816,2817],[86,103,149,2078,2817],[86,103,149,2096],[86,103,149,2069],[86,103,149,2811,2815,3072,3107],[86,103,149,2811,2814,3072,3107],[83,84,85,103,149],[88,93,94,96,103,149],[103,149,242,243],[94,96,103,149,236,237,238],[94,103,149],[94,96,103,149,236],[94,103,149,236],[103,149,249],[89,103,149,249,250],[89,103,149,249],[89,95,103,149],[90,103,149],[89,90,91,93,103,149],[89,103,149],[103,149,478],[103,149,282,283,284,285,286,287,288,289],[86,103,149,280,281],[103,149,271],[103,149,312],[103,149,314,429],[103,149,486],[103,149,401],[103,149,383,401],[86,103,149,272],[86,103,149,290],[103,149,291,292],[86,103,149,401],[86,103,149,273,294],[103,149,294,295],[86,103,149,271,714],[86,103,149,297,664,713],[103,149,715,716],[103,149,714],[86,103,149,487,512,514],[86,103,149,271,509,718],[86,103,149,720],[86,103,149,270],[86,103,149,666,720],[103,149,721,722],[86,103,149,271,401,479,581,582],[86,103,149,271,479],[86,103,149,271,555,725],[86,103,149,553],[103,149,725,726],[86,103,149,298],[86,103,149,298,299,300],[86,103,149,301],[103,149,298,299,300,301],[103,149,411],[86,103,149,271,306,315,729],[86,103,149,490,730],[103,149,728],[103,149,373,401,418],[86,103,149,589,593],[103,149,594,595,596],[86,103,149,732],[86,103,149,271,298,487,513,601,602,710],[86,103,149,598,603],[86,103,149,532],[86,103,149,533,534],[86,103,149,535],[103,149,532,533,535],[103,149,373,401],[103,149,653],[86,103,149,298,606,607],[103,149,607,608],[103,149,737,746],[86,103,149,271,746],[103,149,745,746,747],[86,103,149,298,483,666,744,745],[86,103,149,293,302,339,478,483,491,493,495,514,516,552,556,558,567,573,579,580,583,593,597,603,609,610,613,623,624,625,642,651,656,660,663,664,666,674,678,682,684,700,706,707],[103,149,298],[86,103,149,298,302,579,707,708,709],[86,103,149,271,306,320,487,492,493,710],[103,149,271,298,315,320,487,491,710],[86,103,149,271,320,487,490,492,493,494,710],[103,149,494],[103,149,416,417],[103,149,373,401,416],[103,149,401,413,414,415],[86,103,149,270,611,612],[86,103,149,290,621],[86,103,149,620,621,622],[86,103,149,299,493,553],[86,103,149,314,481,544,552],[103,149,553,554],[86,103,149,401,415,429],[86,103,149,271,624],[86,103,149,271,298],[86,103,149,625],[86,103,149,625,751,752,753],[103,149,754],[86,103,149,483,493,583],[86,103,149,305,334,337,339,486,756],[86,103,149,486],[86,103,149,298,305,332,333,334,337,338,486,710],[86,103,149,321,339,340,484,485],[86,103,149,334,486],[86,103,149,334,337,483],[86,103,149,305],[103,149,332,337],[103,149,338],[103,149,305,339,486,757,758,759,760],[103,149,305,336],[86,103,149,270,271],[103,149,334,652,849],[86,103,149,767,768],[86,103,149,765],[103,149,270,271,273,293,296,483,491,493,495,514,516,536,552,555,556,558,567,573,576,583,593,597,602,603,609,610,613,623,624,625,642,651,653,656,660,663,666,674,678,682,684,699,700,706,710,717,719,723,724,727,731,733,734,748,749,750,755,761,769,771,776,779,786,787,792,795,800,801,803,813,818,823,828,830,832,835,837,844,846,847,848],[86,103,149,298,487,650,710],[103,149,437],[103,149,401,413],[103,149,626,633,634,635,636,641],[86,103,149,298,487,627,632,710],[86,103,149,298,487,710],[86,103,149,633],[103,149,373,401,413],[86,103,149,298,487,633,640,710],[103,149,546,770],[86,103,149,656],[86,103,149,556,558,653,654,655],[86,103,149,305,494,495,515,517,560,567,573,577,578,711],[103,149,579],[86,103,149,271,487,657,659,710],[86,103,149,544,545,547,548,549,550,551],[103,149,537],[86,103,149,544,545,546,547],[86,103,149,710],[86,103,149,544],[86,103,149,545],[86,103,149,297,774,775],[86,103,149,297,773],[86,103,149,297],[103,149,711],[103,149,661,662,711,712,713],[86,103,149,270,280,301,710],[86,103,149,711],[86,103,149,279,711],[86,103,149,712],[86,103,149,664,777,778],[86,103,149,664,773],[86,103,149,664],[103,149,515],[86,103,149,499,514],[86,103,149,301,480,483,517],[86,103,149,516],[86,103,149,480,483,665],[86,103,149,666],[103,149,401,415,429],[103,149,575],[86,103,149,786],[86,103,149,579,785],[86,103,149,788],[103,149,788,789,790,791],[86,103,149,298,532,533,535],[86,103,149,533,788],[86,103,149,794],[86,103,149,298,802],[86,103,149,271,298,487,509,510,512,513,710],[103,149,414],[86,103,149,804],[103,149,812],[86,103,149,805,806,807,808,809,810,811],[86,103,149,271,483,671,673],[86,103,149,298,710],[86,103,149,298,675,676,677],[103,149,815,816,817],[103,149,814],[86,103,149,815],[86,103,149,819,820],[103,149,820,821,822],[86,103,149,281,819],[86,103,149,826,827],[103,149,373,401,415],[103,149,373,401,478],[86,103,149,829],[103,149,271,560],[86,103,149,271,560,679],[103,149,531,559,560,679,681],[86,103,149,270,271,483,520,531,536,555,556,557,559],[103,149,271,298,531,558,560],[103,149,531,557,560,679,680],[86,103,149,298,584,589,591,592],[86,103,149,586,593],[86,103,149,271,290,479,683],[86,103,149,373,395,478],[86,103,149,373,396,478,831,849],[86,103,149,380],[103,149,402,403,404,405,406,407,408,409,410,412,418,419,420,421,422,423,424,425,426,427,428,430,431,432,433,434,435,436,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475],[103,149,381,393,476],[103,149,271,373,374,375,380,381,476,477],[103,149,374,375,376,377,378,379],[103,149,374],[103,149,373,393,394,396,397,398,399,400,478],[103,149,373,396,478],[103,149,383,388,393,478],[103,149,710],[86,103,149,271,320,487,490,492],[103,149,833,834],[86,103,149,833],[86,103,149,271],[86,103,149,271,341,342,479,480,481,482],[86,103,149,483],[86,103,149,567,836],[86,103,149,566],[86,103,149,567],[86,103,149,487,568,570,571,572],[86,103,149,568,569,573],[86,103,149,568,570,573],[86,103,149,271,298,487,512,513,690,694,697,699,710],[103,149,401,471],[86,103,149,685,696,697],[103,149,685,696,697,698],[86,103,149,685,696],[86,103,149,483,640,838],[103,149,838,840,841,842,843],[86,103,149,839],[86,103,149,577,704],[103,149,577,704,705],[86,103,149,574,576],[86,103,149,577,703],[103,149,845],[103,149,860],[103,149,860,861],[103,149,861],[103,149,860,2589,2590],[103,149,2592],[103,149,2593],[103,149,2610],[103,149,860,2526,2527,2528,2529,2530,2531,2532,2533,2534,2535,2536,2537,2538,2539,2540,2541,2542,2543,2544,2545,2546,2547,2548,2549,2550,2551,2552,2553,2554,2555,2556,2557,2558,2559,2560,2561,2562,2563,2564,2565,2566,2567,2568,2569,2570,2571,2572,2573,2574,2575,2576,2577,2578,2579,2580,2581,2582,2583,2584,2585,2586,2587,2588,2591,2592,2593,2594,2595,2596,2597,2598,2599,2600,2601,2602,2603,2604,2605,2606,2607,2608,2609,2611,2612,2613,2614,2615,2616,2617,2618,2619,2620,2621,2622,2623,2624,2625,2626,2627,2628,2629,2630,2631,2632,2633,2634,2635,2636,2637,2638,2639,2640,2641,2642,2643,2644,2645,2646,2647,2648,2649,2650,2651,2652,2653,2654,2655,2656,2657,2658,2659,2660,2661,2662,2663,2664,2665,2666,2667,2668,2669,2670,2671,2672,2673,2674,2675,2676,2677,2678,2679,2680,2681,2682,2683,2684,2685,2687,2688,2689,2690,2691,2692,2693,2694,2695,2696,2697,2698,2699,2700,2701,2702,2703,2704,2705,2706,2711,2712,2713,2714,2715,2716,2717,2718,2719,2720,2721,2722,2723,2724,2725,2726,2727,2728,2729,2730,2731,2732,2733,2734,2735,2736,2737,2738,2739,2740,2741,2742,2743,2744,2745,2746,2747,2748,2749,2750,2751,2752,2753,2754,2755,2756,2757,2758,2759,2760,2761,2762,2763,2764,2765,2766,2767,2768,2769,2770,2771,2772,2773,2774,2775,2776,2777,2778],[103,149,2686],[103,149,861,862,863,864,865,866,867,868,869,870,871,872,873,874,875,876,877,878,879,880,881,882,883,884,885,886,887,888,889,890,891,892,893,894,895,896,897,898,899,900,901,902,903,904,905,906,907,908,909,910,911,912,913,914,915,916,917,918,919,920,921,922,923,924,925,926,927,928,929,930,931,932,933,934,935,936,937,938,939,940,941,942,943,944,945,946,947,948,949,950,951,952,953,954,955,956],[103,149,860,2590,2710],[103,149,861,2707,2708],[103,149,2709],[103,149,2707],[103,149,859,861],[103,149,489],[103,149,488],[103,149,201,202,2798,2799,4495],[103,149,2800],[103,149,1812,1813],[103,149,1812,1813,1814,1815],[103,149,1812,1814],[103,149,1812],[103,149,163,179,197],[103,149,229,230],[103,149,4169,4172,4175,4177,4178,4179],[103,149,3602,3630,4169,4172,4175,4177,4179],[103,149,3602,3630,4169,4172,4175,4179],[103,149,4202,4203,4207],[103,149,4179,4202,4204,4207],[103,149,4179,4202,4204,4206],[103,149,3602,3630,4179,4202,4204,4205,4207],[103,149,4204,4207,4208],[103,149,4179,4202,4204,4207,4209],[103,149,3592,3602,3603,3604,3628,3629,3630],[103,149,3592,3603,3630],[103,149,3592,3602,3603,3630],[103,149,3605,3606,3607,3608,3609,3610,3611,3612,3613,3614,3615,3616,3617,3618,3619,3620,3621,3622,3623,3624,3625,3626,3627],[103,149,3592,3596,3602,3604,3630],[103,149,4180,4181,4201],[103,149,3602,3630,4202,4204,4207],[103,149,3602,3630],[103,149,4182,4183,4184,4185,4186,4187,4188,4189,4190,4191,4192,4193,4194,4195,4196,4197,4198,4199,4200],[103,149,3591,3602,3630],[103,149,4169,4170,4171,4175,4179],[103,149,4169,4172,4175,4179],[103,149,4169,4172,4173,4174,4179],[103,149,3075],[103,149,3077,3078,3079,3080],[103,149,3026,3086,3087],[103,149,2823,2824,2826,2833,2855,2952,2963,3068],[103,149,2826,2850,2851,2852,2854,3068],[103,149,2826,2969,2971,2973,2974,2976,3068,3070],[103,149,2826,2853,2890,3068],[103,149,2115,2824,2826,2833,2838,2843,2848,2951,2952,2953,2962,3068,3070],[103,149,3068],[103,149,2112,2113,2851,2871,2948],[103,149,2826],[103,149,2112,2113,2819],[103,149,2980],[103,149,2977,2978,2980],[103,149,2977,2979,3068],[103,149,163,2871,3050,3065],[103,149,163,2926,2929,2943,2948,3065],[103,149,163,2898,3065],[103,149,2956],[103,149,2955,2956,2957],[103,149,2955],[103,149,163,2813,2819,2826,2833,2838,2843,2849,2851,2855,2856,2869,2870,2921,2949,2950,2963,3068,3072],[103,149,2823,2826,2853,2890,2969,2970,2975,3068,3110],[103,149,2853,3110],[103,149,2823,2870,3021,3068,3110],[103,149,3110],[103,149,2826,2853,2854,3110],[103,149,2972,3110],[103,149,2856,2951,2954,2961],[86,103,149,3026],[87,103,149,174,2112],[87,103,149,2112],[86,103,149,2127],[86,87,103,149],[86,87,103,149,2113,3026],[103,149,2112,2127,2129,2130,2131,2140],[103,149,2128,2134,2135,2136,2137,2139],[103,149,2132],[103,149,2132,2133],[103,149,2113,2114,2115,2116],[103,149,2113,2122,2123],[103,149,2113,2117,2125],[103,149,2122],[103,149,2110,2113,2114,2116,2117,2118,2119,2120,2121,2122,2125],[103,149,2113,2114,2122,2123,2124,2126],[103,149,2113,2116,2118,2119],[103,149,2116,2118,2121,2123],[103,149,2138],[103,149,2113],[86,103,149,2827,3096],[86,103,149,190],[86,103,149,2853,2888],[86,103,149,2853,2963],[103,149,2886,2891],[86,103,149,2887,3074],[103,149,3113],[86,103,149,163,2811,2814,2815,3072,3106],[103,149,163,2113],[103,149,163,2833,2837,2901,2918,2958,2959,2963,3018,3020,3068,3069],[103,149,2869,2960],[103,149,3072],[103,149,2825],[86,103,149,2109,2112,3023,3039,3041],[103,149,174,2112,3023,3038,3039,3040,3109],[103,149,3032,3033,3034,3035,3036,3037],[103,149,3034],[103,149,3038],[87,103,149,2987,2988,2990],[86,103,149,2113,2981,2982,2983,2984,2989],[103,149,2987,2989],[103,149,2985],[103,149,2986],[86,87,103,149,2887,3074],[86,87,103,149,3073,3074],[86,87,103,149,3074],[103,149,2918,2919],[103,149,2919],[103,149,163,3069,3074],[103,149,2946],[103,148,149,2945],[103,149,2112,2113,2839,2841,2926,2937,2941,2943,3020,3023,3057,3058,3065,3069],[103,149,2113,2119,2881],[103,149,2926,2935,2938,2943],[86,103,149,2109,2112,2926,2929,2943,2946,2980,3027,3028,3029,3030,3031,3042,3043,3044,3045,3046,3047,3048,3049,3110],[103,149,2109,2112,2851,2926,2931,2932,2933,2936,2937],[103,149,179,2113,2851,2935,2942,3023,3024,3065],[103,149,2939],[103,149,163,174,2113,2827,2837,2846,2878,2879,2882,2918,2921,2984,3018,3019,3057,3068,3069,3070,3072,3110],[103,149,2109,2110,2112],[103,149,2926],[103,148,149,2851,2878,2879,2920,2921,2922,2923,2924,2925,3069],[103,149,2943],[103,148,149,2111,2112,2837,2841,2876,2926,2931,2932,2933,2934,2935,2938,2939,2940,2941,2942,3058],[103,149,163,2876,2877,2931,3069,3070],[103,149,2851,2879,2918,2921,2926,3020,3069],[103,149,163,3068,3070],[103,149,163,179,3065,3069,3070],[103,149,163,174,2112,2819,2833,2839,2841,2843,2846,2853,2873,2878,2879,2880,2881,2882,2901,2902,2904,2907,2909,2912,2913,2914,2915,2917,2963,3018,3020,3065,3068,3069,3070],[103,149,163,179],[103,149,2826,2827,2828,2849,3065,3066,3067,3072,3074,3110],[103,149,2823,2824,3068],[103,149,2992],[103,149,163,179,190,2831,2976,2980,2981,2982,2983,2984,2990,2991,3110],[103,149,174,190,2112,2819,2831,2841,2843,2879,2902,2907,2917,2918,2969,2996,2997,2998,3004,3007,3008,3018,3020,3065,3068],[103,149,2843,2849,2856,2869,2879,2921,3068],[103,149,163,190,2827,2833,2841,2879,3002,3065,3068],[103,149,3022],[103,149,163,2992,3005,3006,3015],[103,149,3065,3068],[103,149,2923,3058],[103,149,2841,2878,2963,3074],[103,149,163,174,2825,2907,2965,2969,2998,3004,3007,3010,3065],[103,149,163,2856,2869,2969,3011],[103,149,2826,2880,2963,3013,3068,3070],[103,149,163,190,2984,3068],[103,149,163,2853,2880,2963,2964,2965,2974,2992,3012,3014,3068],[103,149,163,2813,2878,3017,3072,3074],[103,149,2916,3018],[103,149,163,174,2112,2113,2832,2833,2839,2841,2846,2855,2856,2869,2879,2882,2902,2904,2914,2917,2918,2963,2996,2997,2998,2999,3001,3003,3018,3020,3065,3074],[103,149,163,179,2856,3004,3009,3015,3065],[103,149,2859,2860,2861,2862,2863,2864,2865,2866,2867,2868],[103,149,2873,2908],[103,149,2910],[103,149,2908],[103,149,2910,2911],[103,149,163,2113,2115,2833,2837,2838,3069],[103,149,163,174,2825,2827,2839,2842,2878,2881,2882,2900,3018,3065,3070,3072,3074],[103,149,163,174,190,2115,2829,2832,2841,2842,2879,3016,3058,3064,3069],[103,149,2931],[103,149,2932],[103,149,2113,2843,3057],[103,149,2933],[103,149,2111],[103,149,2830,2840],[103,149,163,2830,2833,2839],[103,149,2835,2840],[103,149,2836],[103,149,2830,2831],[103,149,2830,2883],[103,149,2830],[103,149,2832,2873,2906],[103,149,2905],[103,149,2112,2831,2832],[103,149,2832,2903],[103,149,2112,2831],[103,149,2878,2963],[103,149,3057],[103,149,163,190,2839,2841,2844,2878,2963,3017,3020,3023,3024,3025,3051,3052,3054,3056,3058,3065,3069],[103,149,2127,2129,2130,2892,2895,2896],[86,87,103,149,2816,2817,2818,3053],[86,87,103,149,2816,2817,2818,3053,3055],[103,149,2947],[103,149,2133,2851,2872,2877,2878,2926,2927,2928,2929,2930,2943,2944,2946,2949,3017,3020,3068,3070],[103,149,2127],[103,149,163,2900,3065],[103,149,2900],[103,149,163,2839,2884,2897,2899,2901,3017,3065,3072,3074],[103,149,2127,2129,2130,2892,2893,2894,2895,2896,3073],[103,149,163,174,190,2813,2830,2831,2841,2846,2878,2879,2882,2963,3015,3016,3018,3065,3068,3069,3072],[103,149,2109,2112,2834],[103,149,2877,2879,2993,2996],[103,149,2877,2994,3059,3060,3061,3062,3063],[103,149,163,2873,3068],[103,149,163],[103,149,2876,2943],[103,149,2875],[103,149,2877,2914],[103,149,2874,2876,3068],[103,149,163,2829,2877,2993,2994,2995,3065,3068,3069],[86,103,149,2112,2113,2126],[86,103,149,2110],[103,149,2821,2822],[86,103,149,2827],[86,103,149,2112,2128],[86,103,149,2813,2878,2882,3072,3074],[103,149,2827,3096,3097],[86,103,149,2891],[86,103,149,174,190,2825,2885,2887,2889,2890,3074],[103,149,2112,2853,3069],[103,149,2112,3000],[86,103,149,161,163,174,2823,2825,2891,2971,3072,3073],[86,103,149,2814,2815,3072,3107],[86,103,149,2808,2809,2810,2811],[103,149,154],[103,149,2966,2967,2968],[103,149,2966],[86,103,149,163,165,174,197,2811,2814,2815,2816,2818,2819,2825,2846,2851,3010,3038,3070,3071,3074,3107],[103,149,3082],[103,149,3084],[103,149,3088],[103,149,3114],[103,149,3090],[103,149,3092,3093,3094],[103,149,3098],[103,149,2142,2812,3076,3081,3083,3085,3089,3091,3095,3099,3101,3102,3104,3108,3109,3110,3111],[103,149,3100],[103,149,2141],[103,149,2887],[103,149,3103],[103,148,149,2877,2993,2994,2996,3059,3060,3062,3063,3105,3107],[103,149,197],[103,149,3512,3513,3518],[103,149,3514,3515,3517,3519],[103,149,3518],[103,149,3515,3517,3518,3519,3520,3522,3524,3525,3526,3527,3528,3529,3530,3534,3549,3560,3563,3567,3575,3576,3578,3581,3584,3587],[103,149,3518,3525,3538,3542,3551,3553,3554,3555,3582],[103,149,3518,3519,3535,3536,3537,3538,3540,3541],[103,149,3542,3543,3550,3553,3582],[103,149,3518,3519,3524,3543,3555,3582],[103,149,3519,3542,3543,3544,3550,3553,3582],[103,149,3515],[103,149,3521,3542,3549,3555],[103,149,3549],[103,149,3518,3538,3545,3547,3549,3582],[103,149,3542,3549,3550],[103,149,3551,3552,3554],[103,149,3582],[103,149,3531,3532,3533,3583],[103,149,3518,3519,3583],[103,149,3514,3518,3532,3534,3583],[103,149,3518,3532,3534,3583],[103,149,3518,3520,3521,3522,3583],[103,149,3518,3520,3521,3535,3536,3537,3539,3540,3583],[103,149,3540,3541,3556,3559,3583],[103,149,3555,3583],[103,149,3518,3542,3543,3544,3550,3551,3553,3554,3583],[103,149,3521,3557,3558,3559,3583],[103,149,3518,3583],[103,149,3518,3520,3521,3541,3583],[103,149,3514,3518,3520,3521,3535,3536,3537,3539,3540,3541,3583],[103,149,3518,3520,3521,3536,3583],[103,149,3514,3518,3521,3535,3537,3539,3540,3541,3583],[103,149,3521,3524,3583],[103,149,3524],[103,149,3514,3518,3520,3521,3523,3524,3525,3583],[103,149,3523,3524],[103,149,3518,3520,3524,3583],[103,149,3584,3585],[103,149,3514,3518,3524,3525,3583],[103,149,3518,3520,3562,3583],[103,149,3518,3520,3561,3583],[103,149,3518,3520,3521,3549,3564,3566,3583],[103,149,3518,3520,3566,3583],[103,149,3518,3520,3521,3549,3565,3583],[103,149,3518,3519,3520,3583],[103,149,3569,3583],[103,149,3518,3564,3583],[103,149,3571,3583],[103,149,3518,3520,3583],[103,149,3568,3570,3572,3574,3583],[103,149,3518,3520,3568,3573,3583],[103,149,3564,3583],[103,149,3549,3583],[103,149,3521,3522,3525,3526,3527,3528,3529,3530,3534,3549,3560,3563,3567,3575,3576,3578,3581,3586],[103,149,3518,3520,3549,3583],[103,149,3514,3518,3520,3521,3545,3546,3548,3549,3583],[103,149,3518,3527,3577,3583],[103,149,3518,3520,3579,3581,3583],[103,149,3518,3520,3581,3583],[103,149,3518,3520,3521,3579,3580,3583],[103,149,3519],[103,149,3516,3518,3519],[103,149,223],[103,149,221,223],[103,149,212,220,221,222,224,226],[103,149,210],[103,149,213,218,223,226],[103,149,209,226],[103,149,213,214,217,218,219,226],[103,149,213,214,215,217,218,226],[103,149,210,211,212,213,214,218,219,220,222,223,224,226],[103,149,226],[103,149,208,210,211,212,213,214,215,217,218,219,220,221,222,223,224,225],[103,149,208,226],[103,149,213,215,216,218,219,226],[103,149,217,226],[103,149,218,219,223,226],[103,149,211,221],[103,149,2085],[86,103,149,313,507,512,598,599],[103,149,598,600],[86,103,149,600],[103,149,600],[86,103,149,604],[86,103,149,604,605],[86,103,149,277],[86,103,149,276],[103,149,277,278,279],[86,103,149,616,617,618,619],[86,103,149,312,617,618],[103,149,620],[86,103,149,313,314,587],[86,103,149,324],[86,103,149,323,324,325,326,327,328,329,330,331],[86,103,149,322,323],[103,149,324],[86,103,149,303,304],[103,149,305],[86,103,149,276,277,762,763,765],[103,149,766],[86,103,149,280,762,766],[86,103,149,762,763,764,766],[103,149,649],[86,103,149,627,629,648],[86,103,149,629],[103,149,629,630,631],[86,103,149,627,628],[86,103,149,629,640,657,658],[103,149,657,659],[86,103,149,537],[103,149,537,538,539,540,541,542,543],[86,103,149,312,537],[86,103,149,307],[86,103,149,308,309],[103,149,307,308,310,311],[86,103,149,772],[103,149,497,498],[86,103,149,496],[86,103,149,497],[103,149,315,317,318,319],[86,103,149,306,314],[86,103,149,315,316],[86,103,149,315],[86,103,149,793],[86,103,149,313,505,506],[86,103,149,507],[103,149,507,508,509,510,511],[86,103,149,510],[86,103,149,506,507,508,509],[86,103,149,667],[86,103,149,667,668],[103,149,671,672],[86,103,149,667,669,670],[103,149,825,826],[86,103,149,824,826],[86,103,149,824,825],[86,103,149,520],[86,103,149,520,523],[86,103,149,521,522],[103,149,518,520,524,525,526,528,529,530],[86,103,149,519],[103,149,520],[86,103,149,520,525],[86,103,149,518,520,524,525,526,527],[86,103,149,520,527,528],[86,103,149,589],[103,149,590],[86,103,149,312,585,586,588],[86,103,149,584,589],[103,149,637,638,639],[86,103,149,629,632,637],[86,103,149,313,314],[103,149,691,692,693],[86,103,149,685],[86,103,149,690],[86,103,149,512,685,689,690,691,692],[103,149,685,690],[86,103,149,685,689],[103,149,685,686,689,695],[86,103,149,505],[86,103,149,685,686,687,688],[86,103,149,574],[103,149,574,702],[86,103,149,574,701],[86,103,149,274,275],[86,103,149,501,502],[86,103,149,500,501,503,504],[86,103,149,2480],[86,103,149,2479],[103,149,3633],[86,103,149,3592,3601,3630,3632],[103,149,4176,4209,4210],[103,149,4211],[103,149,3630,3631],[103,149,3592,3596,3601,3602,3630],[103,149,202,234,235],[103,149,335],[92,103,149],[103,149,3598],[103,116,120,149,190],[103,116,149,179,190],[103,111,149],[103,113,116,149,187,190],[103,149,168,187],[103,111,149,197],[103,113,116,149,168,190],[103,108,109,112,115,149,160,179,190],[103,116,123,149],[103,108,114,149],[103,116,137,138,149],[103,112,116,149,182,190,197],[103,137,149,197],[103,110,111,149,197],[103,116,149],[103,110,111,112,113,114,115,116,117,118,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,138,139,140,141,142,143,149],[103,116,131,149],[103,116,123,124,149],[103,114,116,124,125,149],[103,115,149],[103,108,111,116,149],[103,116,120,124,125,149],[103,120,149],[103,114,116,119,149,190],[103,108,113,116,123,149],[103,149,179],[103,111,116,137,149,195,197],[103,149,3596,3600],[103,149,3591,3596,3597,3599,3601],[103,149,3635,3636,3637,3638,3639,3640,3641,3643,3644,3645,3646,3647,3648,3649,3650],[103,149,3637],[103,149,3637,3642],[103,149,3593],[103,149,3594,3595],[103,149,3591,3594,3596],[103,149,246,247],[103,149,246],[103,149,198],[103,149,160,161,163,164,165,168,179,187,190,196,197,198,199,200,202,203,205,206,207,227,228,232,233,234,235],[103,149,198,199,200,204],[103,149,200],[103,149,231],[103,149,202,235],[97,103,149,266,1809],[103,149,239,258,259,1809],[89,96,103,149,239,251,252,1809],[103,149,261],[103,149,240],[89,97,103,149,239,241,251,260,1809],[103,149,244],[89,94,96,103,149,152,161,179,235,239,241,244,245,248,251,253,254,257,260,262,263,265,1809],[103,149,239,258,259,260,1809],[103,149,235,264,265],[103,149,239,241,248,251,253,1809],[103,149,195,254],[89,94,96,103,149,152,161,179,235,239,240,241,244,245,248,251,252,253,254,257,258,259,260,261,262,263,264,265,1809],[88,89,94,96,97,103,149,152,161,179,195,235,239,240,241,244,245,248,251,252,253,254,257,258,259,260,261,262,263,264,265,1808,1809,1810,1811,1816],[87,103,149],[87,103,149,1817,2097,2158,2159,3169,3249,3250],[86,87,103,149,849,2067,2159,3142,3168],[87,103,149,849,2067,2165,2204,3166],[86,87,103,149,849,850,2161,3167],[86,87,103,149,849,850,2158,2163,3167],[87,103,149,1817,2158,3171,3249,3250],[86,87,103,149,849,1793,1805,2067,2143,2146,2158,2162,2456,2457,3147,3165,3169,3170,3171,3432,3441],[87,103,149,2146,3171],[87,103,149,2146,2230,3272],[87,103,149,1817,2097,3133],[87,103,149,2146,2245,3282],[87,103,149,1817,2097,3162],[86,87,103,149,854,2142,2146,2245,2331,3117,3161],[86,87,103,149,2146,3130,3162],[87,103,149,1817,2097,3287],[86,87,103,149,958,2070,3286],[86,87,103,149,2067,2069],[86,87,103,149,2067,2346],[87,103,149,2146,2230,3287,3289],[86,87,103,149,849,958,1797,2168],[87,103,149,1817,2097,2105,2168,3293],[86,87,103,149,958,1797,1806,2069,2143,2146,2168,3147,3165,3291,3292],[87,103,149,2146,3293],[86,87,103,149,958,1797,1803,2051,3296,3298,3302],[86,87,103,149,958,2051,3297],[86,87,103,149,1807,1818,3300],[86,87,103,149,849,1807],[87,103,149,849],[87,103,149,1817,1818],[87,103,149,1807],[87,103,149,1817,2097,3249,3302],[86,87,103,149,849,958,1797,1803,1807,1818,2074,3299,3300,3301],[87,103,149,1817,2097,3299],[86,87,103,149,958],[87,103,149,2146,3303],[86,87,103,149,1803,2146,2348],[86,87,103,149,1817,1820,2059,2097,3249,3250],[86,87,103,149,849,958,1793,1820,2053,2054,2055],[86,87,103,149,1817,1820,2057,2097,3249,3250],[86,87,103,149,1817,2075,2097,3249,3250],[86,87,103,149,849,958,1793,1820,2056,2057,2058,2059,2066,2068,2071,2072,2073,2074],[86,87,103,149,1817,2071,2097,3249,3250],[86,87,103,149,958,2070],[87,103,149,1820,2055,2056,2057,2058,2059,2071,2072,2073,2075],[86,87,103,149,1817,2060,2066,2097,3249,3250],[86,87,103,149,849,1793,2060,2064,2065],[86,87,103,149,1817,1820,2060,2064,2097,3249,3250],[86,87,103,149,849,958,1793,1820,2060,2061,2063],[86,87,103,149,1817,2060,2062,2063,2097,3249,3250],[86,87,103,149,958,1793,2060,2062],[87,103,149,1817,1820,2060,2062],[87,103,149,1820,2060,2061],[87,103,149,1820],[87,103,149,1817,1820,2060,2065,2097],[86,87,103,149,1803,1820,2060],[86,87,103,149,1817,2056,2097,3249,3250],[86,87,103,149,958,1820,2051,2052,2055],[87,103,149,1817,2055],[87,103,149,2053,2054],[86,87,103,149,1817,2058,2097,3249,3250],[87,103,149,1797,1817,2072,2097],[86,87,103,149,1797,1803,1820,2054,2055],[87,103,149,1797,1817,2073,2097],[87,103,149,2076,2146],[86,87,103,149,849,1793,2074],[87,103,149,1817,2097,3249,3357],[86,87,103,149,849,1793],[86,87,103,149,849,1793,1803,2105,2361,3349,3350,3351],[87,103,149,1803,1817,2097,2105,3355],[86,87,103,149,958,1803,3348,3352,3354],[86,87,103,149,682,849,1793,1803,2105,2361,3349,3351,3353],[86,87,103,149,958,1817,2097,3250,3353],[87,103,149,2146,3355],[87,103,149,2146,3346],[87,103,149,1803,2105,2143,2146,2158],[86,87,103,149,1803,1817,2097,2105,2146,2158],[87,103,149,1803,2105,2143,2144,2146],[87,103,149,1803,2105,2146,2158],[86,87,103,149,1803,1817,2097,2105,2164,2165],[87,103,149,1803,2105,2143,2144,2146,2164],[87,103,149,1803,2105],[87,103,149,1803,2105,2144,2146],[86,87,103,149,1817,2097,2105,2169],[86,87,103,149,1817,2097,2105,2171],[86,87,103,149,1817,2097,2105,2173],[86,87,103,149,1817,2097,2105,2175,2176],[87,103,149,1803,2105,2144,2175],[87,103,149,1817,2144],[87,103,149,1803],[87,103,149,2105,2179,2180],[87,103,149,2105,2144,2146,2179],[86,87,103,149,1803,1817,2097,2105,2183],[86,87,103,149,1803,1817,2097,2105,2185],[86,87,103,149,1803,1817,2097,2105,2187],[87,103,149,1803,2105,2144],[86,87,103,149,1803,1817,2097,2105,2191],[86,87,103,149,854,1817,2097,2105,2193],[87,103,149,854,1803,2105,2144,2146],[87,103,149,1803,2105,2146,2193],[87,103,149,1803,2105,2146],[86,87,103,149,1803,1817,2097,2105,2146,2200],[86,87,103,149,1803,1817,2097,2105,2146,2202],[86,87,103,149,1803,2105,2144,2146],[86,87,103,149,1803,1817,2097,2105,2146,2204],[87,103,149,1798,1803,2105,2144,2146],[86,87,103,149,1803,1817,2097,2105,2207],[86,87,103,149,1803,1817,2097,2105,2209],[86,87,103,149,1803,1817,2097,2105,2211],[87,103,149,1803,2105,2144,2145],[86,87,103,149,1803,1817,2097,2105,2213],[86,87,103,149,1817,2097,2105,2215,2216],[87,103,149,1803,2105,2146,2215],[86,87,103,149,1817,2097,2105,2215,2218],[86,87,103,149,1817,2097,2105,2215,2220],[87,103,149,1803,2105,2143,2146,2215],[86,87,103,149,1817,2097,2105,2215],[86,87,103,149,1817,2097,2105,2215,2223],[86,87,103,149,1803,1817,2097,2105,2225],[86,87,103,149,1817,2097,2105,2227],[87,103,149,2105,2144,2229],[86,87,103,149,1817,2097,2105,2231],[87,103,149,1803,2105,2144,2146,2234],[86,87,103,149,1803,1817,2097,2105,2236],[86,87,103,149,1803,1817,2097,2105,2238],[86,87,103,149,1817,2097,2105,2146,2240],[87,103,149,1803,2105,2146,2227],[86,87,103,149,853,1803,1817,2097,2105,2243],[87,103,149,853,1803,2105,2144,2146],[86,87,103,149,854,1803,1804,1817,2097,2105,2245],[87,103,149,854,1803,1804,2105,2144,2146],[86,87,103,149,1803,1817,2097,2105,2145],[86,87,103,149,1803,1817,2097,2105,2248],[86,87,103,149,1803,1817,2097,2105,2250],[86,87,103,149,852,1803,1817,2097,2105,2107,2108,2146],[86,87,103,149,852,1803,2107,2108,2142,2143,2145],[86,87,103,149,2148],[87,103,149,1817,2097,2148,2151],[87,103,149,1817,2097,2148,2153],[87,103,149,1817,2097,2148,2155],[86,87,103,149,1803,1817,2097,2105,2252],[86,87,103,149,1803,1817,2097,2105,2254],[86,87,103,149,854,1804,2146],[87,103,149,1803,1817,2097,3117,3133],[86,87,103,149,1800,1803,2142,2340,3117,3120,3125,3128,3130,3131,3132],[87,103,149,2146,3370],[87,103,149,2146,3382],[87,103,149,2146,3419],[86,87,103,149,849,1803],[86,87,103,149,682,849,1793,1803,2105,3147,3421],[87,103,149,2146,3289,3422],[87,103,149,2143,2146,3438,3439],[87,103,149,1817,2097,2146,3250,3446],[86,87,103,149,849,854,958,1793,1797,1803,2105,2146,2207,2209,2245,2256,2282,2456,3147,3171,3432,3441,3443,3444,3445],[86,87,103,149,958,1817,2097,3249,3448],[86,87,103,149,849,958],[86,87,103,149,958,2146,2207,3449],[87,103,149,1817,2097,2105,3495],[86,87,103,149,849,854,958,1797,1803,2051,2054,2105,2143,2146,2183,2207,2209,2233,2248,2256,3444,3446,3447,3448,3450,3451,3455,3467,3469,3470,3474,3482,3494],[87,103,149,2146,2245,3495],[87,103,149,1817,2256],[87,103,149,2146,3289,3502],[87,103,149,1817,2097,3249,3507],[87,103,149,2067,3504,3505,3506],[87,103,149,2146,3510],[86,87,103,149,854,1803,2108,2142,2245,2340,3117,3130,3161,3162],[87,103,149,1817,2097,3249,3731],[86,87,103,149,849,958,1793],[86,87,103,149,849,1793,1797,1798,1803,2070,2074,2260,3590,3749],[87,103,149,1817,2097,2263,3732],[86,87,103,149,2263],[87,103,149,2258],[86,87,103,149,1793,2263,3099,3733],[87,103,149,1817,2263,3733],[87,103,149,2263],[87,103,149,1817,2097,2258,2263,3745],[86,87,103,149,1793,1798,2069,2258,2263,2264,2267,3634,3730,3732,3734,3736,3740,3741,3743,3744],[87,103,149,1817,2074,2097,3749],[86,87,103,149,849,958,1793,1797,1798,1803,2069,2074,2258,2259,2260,2263,2264,2265,2268,2329,2794,3155,3156,3412,3437,3589,3634,3651,3652,3653,3654,3722,3723,3724,3725,3726,3727,3728,3729,3730,3731,3732,3733,3734,3735,3736,3737,3738,3739,3740,3741,3742,3743,3744,3745,3746,3747,3748],[87,103,149,1817,2097,3249,3736],[86,87,103,149,849,1793,1803,2069],[86,87,103,149,849,850,958,1793],[87,103,149,1817,2097,2259,3249,3738],[86,87,103,149,849,2259],[87,103,149,1817,2074,2258,3764],[87,103,149,2074,2258],[87,103,149,1817,2097,3249,3739],[87,103,149,1793],[86,87,103,149,849,1793,1803,2259],[86,87,103,149,1793,2263,3742],[86,87,103,149,849,1793,2263],[86,87,103,149,849,1793,1797,2258],[87,103,149,1817,2097,3249,3589,3755],[86,87,103,149,849,1793,1797,2074,2260,2261,2263,2264,3589,3651,3654,3733,3735,3753,3754],[87,103,149,1817,2097,2261,3249,3753,3755],[86,87,103,149,849,2067,2261,2329,3155,3653,3751,3752,3755],[87,103,149,1817,2097,2263,3751],[86,87,103,149,2067,2069,2263,2264,3634,3734,3741,3744],[87,103,149,1817,2097,3754],[87,103,149,1817,2097,3249,3771],[87,103,149,1817,2097,2261,3249,3752],[87,103,149,849,2261],[87,103,149,1817,2260,2261],[87,103,149,2260],[86,87,103,149,1803,2067,2315,2406,2510,3156,3589],[87,103,149,1817,2097,2265],[86,87,103,149,1794,1798,2263,2264],[86,87,103,149,2267],[87,103,149,1803,2263,3651],[87,103,149,1797,1803,2263,2264,3721],[87,103,149,1817,3588,3723],[87,103,149,1797,1803,2259,3588],[87,103,149,1817,3588,3724],[87,103,149,1797,1803,3588],[87,103,149,1817,3725],[87,103,149,1797,1803],[86,87,103,149,958,2146,2229,3289,3590,3749,3750,3755],[87,103,149,2146,4021],[87,103,149,1817,2215,3249,3250,4025],[86,87,103,149,849,958,1793,2067,2220,2245,3142,4024],[87,103,149,1817,3250,4030],[86,87,103,149,849,1793,2067,2193,4029],[87,103,149,854,1817,3250,4029],[87,103,149,682,849,854,3142],[87,103,149,1817,3249,3250,4023],[87,103,149,849,850,1793,2216,2332,2333],[87,103,149,1817,2215,3249,3250,4024],[86,87,103,149,849,850,1793,2215,2223,2332,2333],[86,87,103,149,849,1817,2332,3249,3250],[86,87,103,149,849,854,1793,1803,2146,2245,2318,2331],[87,103,149,1817,2332,2333],[87,103,149,2332],[87,103,149,1817,2215,3249,3250,4026],[86,87,103,149,682,849,1793,2067,2215,2245,4023,4025],[87,103,149,2146,4026],[86,87,103,149,849,958,1793,1797,1803],[86,87,103,149,849,958,1797,1803,2143,4038,4040,4041,4060],[87,103,149,2335,4059],[86,87,103,149,1793],[86,87,103,149,958,1793,2338,2339,4049,4052,4053,4054],[86,87,103,149,1793,2069,2264,2338,3634],[86,87,103,149,849,1793,2338,4050,4051],[87,103,149,2264],[86,87,103,149,1797,1803,2264,2336,2338],[86,87,103,149,849],[86,87,103,149,958,4046],[86,87,103,149,2335,2336],[86,87,103,149,1797,1803,2335,2336,4042,4043,4044,4045,4047,4048,4055,4056,4057,4058],[86,87,103,149,849,958,2067,2288],[86,87,103,149,849,958,1793,1797,2069],[86,87,103,149,849,958,2067,4039],[86,87,103,149,849,958,2067,2335,4046],[87,103,149,1817,2097,2335,4045],[86,87,103,149,958,2067,2335],[87,103,149,1817,2335,2336],[87,103,149,2335],[87,103,149,1803,1817,2097,4058],[86,87,103,149,849,958,1797,1803,2051,2061,2067,4037,4039],[86,87,103,149,849,958,1793,1803,2051,2054,2456,3171,3432,3441,4037],[87,103,149,1803,2336],[87,103,149,2146,3289,4061],[87,103,149,2146,4070],[86,87,103,149,849,958,1793,1797,1803,2053,2105,2143,4072,4073],[87,103,149,4078],[86,87,103,149,849,1793,1797,1803],[87,103,149,682,849,3165,4073],[87,103,149,1803,1817,2097,2105,2143,3249,4073,4078],[86,87,103,149,849,958,1793,1797,1803,2105,2143,3147,4073,4074,4075,4077],[87,103,149,1797,1803,1817,2097,3249,4076],[86,87,103,149,849,850,958,1793,1797,1803],[87,103,149,1817,2061,2097,3249,4073,4077],[86,87,103,149,849,958,2051,2061,2067,4073,4076],[87,103,149,2146,4079],[87,103,149,2146,4086],[87,103,149,2146,4091],[87,103,149,2146,4095],[87,103,149,2146,4100],[87,103,149,2146,4102],[87,103,149,2146,4104],[86,87,103,149,958,1797,1803,3120],[87,103,149,2146,2213,2245,4123],[87,103,149,1797,1803,1817,3249,3250,4126],[86,87,103,149,849,850,1797,1803,4125],[87,103,149,1803,1817,2097,4128],[86,87,103,149,849,958,1793,1797,1803,2061,2286,2318],[86,87,103,149,849,958,2286,2291],[87,103,149,4132],[86,87,103,149,849,958,1817,2097,3249,3250,4125],[86,87,103,149,849,958,1793,2143,2286,2291,2318],[86,87,103,149,1817,2097,2105,4132],[86,87,103,149,849,958,1797,1803,2061,2105,2143,2311,2319,2320,3147,4126,4127,4128,4129,4131],[87,103,149,849,958,1793,1803,2051,2061,2456,3171,3432,3441],[87,103,149,1803,1817,2097,4129,4131],[86,87,103,149,849,958,1803,2051,2067,2456,3171,3432,3441,3504,3505,3506,4129,4130],[87,103,149,1817,2097,3249,4130],[86,87,103,149,849,958,1797,1803,2051,2061,2067,2143,2286,2319,3147,4125],[87,103,149,2146,2245,4133],[87,103,149,2146,4150],[87,103,149,2146,3289,4152],[86,87,103,149,849,1793,1803],[87,103,149,4154,4219],[87,103,149,4154,4221],[86,87,103,149,2142,4154,4224],[87,103,149,1817,2097,4164],[86,87,103,149,2142,2146,2248,2340,3120,3128,4154,4163],[86,87,103,149,850,2054,2067,2074,2142,2384,3728,4154,4155,4157,4161,4163,4166,4167,4212,4214],[87,103,149,4154,4226],[87,103,149,3109,3112,3115,3116,3117,3118],[87,103,149,852,1803,1817,2097,2105,2107,2145,4228],[86,87,103,149,849,852,1793,1803,2107,2108,2142,2145,2197,2514,3130],[87,103,149,4228],[86,87,103,149,2142,2794],[86,87,103,149,2142,3438],[86,87,103,149,2142,3439],[86,87,103,149,1817,2097,3137],[86,87,103,149,1817,2097,3139],[86,87,103,149,852,1803,2106,2142,2211,3136,3137,3138],[86,87,103,149,1817,2097,3138,3249],[86,87,103,149,1817,2097,3136],[86,87,103,149,2142,3139],[86,87,103,149,854,1817,2097,2374,4108],[86,87,103,149,849,854,958,2061,2351,2374,2376,4106,4107],[86,87,103,149,849,958,1793,1797,1803,2074,2143,3456,3457,3458,3459],[87,103,149,706,849,854,1803,1817,2054,2097,2105,3249,3467],[86,87,103,149,706,849,854,958,1803,2054,3457,3460,3466],[87,103,149,706,849,854,1803,1817,2054,2146,3249,3250,3466],[86,87,103,149,706,849,854,958,1803,2054,2143,2146,2187,2225,2243,2312,3442,3452,3456,3462,3463,3464,3465],[87,103,149,1817,2097,3462],[86,87,103,149,641,849,853,854,958,1793,1794,2329,3461],[86,87,103,149,849,1793,2291],[87,103,149,1817,3249,3250,3459],[87,103,149,849,1817,2097,3463],[86,87,103,149,849,958,2054,2383],[87,103,149,1817,3451],[87,103,149,1797,1803,2054],[87,103,149,849,1817,2054,2097,3464],[86,87,103,149,849,958,2054],[86,87,103,149,849,1793,1797,1803,3451],[87,103,149,849,1817,2054,2097,2105,3452],[86,87,103,149,849,958,1793,1803,2054,2225],[87,103,149,1817,2097,3249,3458],[86,87,103,149,849,958,1793,1797,1803,2291,3475,3476,3477,3478,3479,3482],[87,103,149,1817,2097,3249,3272],[86,87,103,149,849,958,1797,1803,2146,2343,2420,3253,3254,3263,3265,3268,3269,3270,3271],[87,103,149,1817,2097,2283,3249],[86,87,103,149,1803,1817,2097,3282],[86,87,103,149,849,854,958,1793,1797,1803,2061,2143,2164,3165,3278,3281],[86,87,103,149,849,850,854,958,1793,1803,2053,2146,2312,2318,2323,2326,2327,2378,2379,3155,3275,3276,3277],[86,87,103,149,1803,1817,2097,3249,3250,3276],[86,87,103,149,849,1793,1803,2379],[86,87,103,149,849,958,2164],[87,103,149,1817,2379],[86,87,103,149,849,1793,2378,3274],[86,87,103,149,849,850,854,958,1803,2051,2164,2193,2378,2379,2381,3159,3275,3276,3277,3279,3280],[87,103,149,1803,2164],[86,87,103,149,854,1817,2097,3249,3250,3279],[86,87,103,149,849,854,1793],[86,87,103,149,849,2378],[86,87,103,149,849,1803,2378,3274],[87,103,149,1817,2097,2456,3171,3249,3424,3432,3441],[87,103,149,849,958,1793,2456,3171,3432,3441],[87,103,149,1803,1817,2097,3424,3425],[86,87,103,149,849,958,1797,1803,3424],[86,87,103,149,958,1803,1817,2097,3426,3427],[86,87,103,149,849,958,1797,1803,3426],[87,103,149,1803,1817,2097,3429],[86,87,103,149,849,958,1797,1803,3428],[87,103,149,1803,1817,3250,3439],[86,87,103,149,849,852,857,958,1793,1797,1803,2067,2069,2107,2142,2143,2248,3424,3425,3426,3427,3428,3429,3430,3431,3432,3435,3436,3438],[86,87,103,149,849,857,958,1793,3432,3433,3434],[87,103,149,1797,1803,1817,2097,3249,3431],[86,87,103,149,958,1797,1803,2051,2143,3101,3165],[86,87,103,149,1797,1803,3362],[86,87,103,149,849,958,2051],[87,103,149,2382],[87,103,149,1817,2097,2382,3249],[87,103,149,1817,2097,2317],[86,87,103,149,849,958,1793,1797,1803,2051,2315,2316],[86,87,103,149,2067,2069,2384,3634,3740,3741,4155,4160,4168,4211],[87,103,149,1817,2097,4163],[86,87,103,149,2067,2142,2340,4154,4155,4156,4162],[86,87,103,149,490,2067,2384,4155,4157,4158,4159,4160,4161],[86,87,103,149,850,854,1803,2067,2105,2316,2780,4155,4157,4158,4167,4216,4217,4218],[86,87,103,149,850,1798,1803,2067,2105,3417,4155,4157,4167,4223],[86,87,103,149,850,1798,1803,2067,4167,4213],[86,87,103,149,850,1803,2067,2105,4155,4159,4167,4217,4218],[87,103,149,1798],[86,87,103,149,1803,2067,2105,4155,4167],[87,103,149,1817,2097,2385],[86,87,103,149,2384],[87,103,149,1817,2258,3437],[87,103,149,1798,2258,2263],[86,87,103,149,849,1798],[86,87,103,149,849,1793,2069,3634],[86,87,103,149,849,857,958,1797,1803,2143,3434,4084,4085],[86,87,103,149,850,1803,1817,2097,3250,4084],[86,87,103,149,849,850,857,958,1803,2387],[87,103,149,857,1817,2387],[87,103,149,857],[86,87,103,149,849,857,958,1797,1803],[86,87,103,149,849,857,958,1793,1797,2051,2387,2456,3171,3432,3441],[86,87,103,149,857,1793,2387],[87,103,149,856],[86,87,103,149,849,958,1797,1803],[87,103,149,1817,2097,2105,3368],[86,87,103,149,849,2105,2144,2146,2176,3364,3365,3367],[87,103,149,1817,2097,2105,3365],[86,87,103,149,849,850,2146,2169],[87,103,149,1817,2097,3364],[87,103,149,1817,2097,2105,2175,3367],[86,87,103,149,849,850,2067,2146,2171,2173,2175,2176,3147,3366],[87,103,149,1817,2097,2105,2175,3366],[86,87,103,149,849,850,2146,2175,2176],[86,87,103,149,849,958,1793,2158],[86,87,103,149,958,2051],[87,103,149,958,1817,2097,2374,4106],[87,103,149,958,2374],[86,87,103,149,849,958,1793,1794,1803],[87,103,149,1817,2097,3142],[87,103,149,1817,3147,3249,3250],[87,103,149,1817,2097,3249,3484],[87,103,149,1817,2097,3504],[86,87,103,149,849,2067,2282,2346],[87,103,149,1817,2097,3249,3505],[86,87,103,149,849,2067],[87,103,149,1817,2097,3249,3506],[86,87,103,149,2312,2415],[87,103,149,1817,2051,2097,3164],[86,87,103,149,958,2346],[87,103,149,1817,2097,3165],[87,103,149,849,2051,3164],[87,103,149,1817,2287,3249,3250],[87,103,149,1817,2097,3144],[86,87,103,149,849,3142],[87,103,149,1817,2097,3130],[87,103,149,2346,3129],[86,87,103,149,682,849,1793,1803,3165],[86,87,103,149,958,1797,2051,2288],[86,87,103,149,849,958,1793,2074],[87,103,149,1817,2097,2151,2343],[87,103,149,849,2151],[87,103,149,1817,2097,2313,3249],[86,87,103,149,849,958,1793,3155],[86,87,103,149,958,2292],[86,87,103,149,849,1793,2215],[86,87,103,149,849,1817,2294,3249,3250],[86,87,103,149,958,1803,2074,2299,2301,2302,2303],[87,103,149,1817,2097,2457,3249],[86,87,103,149,849,2051],[86,87,103,149,849,854,1793,2245,2311],[86,87,103,149,849,1793,1803,2282],[87,103,149,1797,1803,1817,2097,2105,2213,2320,3249],[86,87,103,149,849,958,1793,1797,1803,2105,2213,2312,2317,2318,2319],[87,103,149,1817,2190,3132,3250],[86,87,103,149,849,2190],[87,103,149,1817,2097,2193,3250,3373],[86,87,103,149,849,2146,2193,3372],[87,103,149,1817,2097,2193,3250,3372],[86,87,103,149,849,854,958,2051,2061,2456,3171,3432,3441],[87,103,149,1817,2097,2245,3250,3375],[87,103,149,849,2146,2245,3374],[87,103,149,1817,2097,2245,3250,3374],[86,87,103,149,849,958,2051,2061,2245,2318,2456,3171,3432,3441],[86,87,103,149,849,3101],[86,87,103,149,849,958,1797,1803,2074,3458],[86,87,103,149,269,849,855,958,1797,1803],[87,103,149,855,2389],[87,103,149,269],[86,87,103,149,849,958,1797,1803,2390],[87,103,149,1817,2356,2357,3249,3250],[86,87,103,149,849,1797,2245,2351,2352,2353,2354,2355,2356],[87,103,149,1817,2353,3250],[86,87,103,149,849,2352],[87,103,149,2354,3250],[87,103,149,1817,2355,3249,3250],[87,103,149,2352,2357,2358],[87,103,149,854,958],[87,103,149,1817,2352,2358,3249,3250],[86,87,103,149,849,854,958,2352,2357],[87,103,149,958,1817,2315,2352,2356],[87,103,149,958,2061,2315,2352],[86,87,103,149,849,958,1803,2051,4065,4066,4069],[87,103,149,1803,1817,2097,3346],[86,87,103,149,849,1793,1797,1803,2143,2393,2395,3147,3325,3332,3334,3338,3341,3344,3345],[86,87,103,149,1817,2097,3250,3332],[86,87,103,149,849,1797,1803,2053,3324,3325,3326,3327,3328,3330,3331],[86,87,103,149,849,1793,1797,1803,3317,3318,3319,3320,3321,3322,3323],[86,87,103,149,958,3320,3321,3335],[86,87,103,149,849,1817,2097,3249,3337],[86,87,103,149,849,3323,3324,3336],[87,103,149,1817,2097,3249,3318],[87,103,149,1817,2097,3249,3317],[87,103,149,2394],[86,87,103,149,849,958,1797,1803,2053,3325,3330],[86,87,103,149,849,1793,2392,3342,3343],[87,103,149,1817,2097,2392,3249,3342],[86,87,103,149,1793,2053,2392],[86,87,103,149,849,1793,2053,2391,2392,3332],[87,103,149,1803,1817,2097,3338],[86,87,103,149,849,958,1793,1797,1803,2051,2061,2067,2394,3325,3326,3327,3330,3331,3337],[87,103,149,1817,3325],[87,103,149,2053],[86,87,103,149,849,2291],[86,87,103,149,849,1803,2291,3325],[87,103,149,1817,2097,2393,3334],[86,87,103,149,849,958,2051,2393,2456,3171,3325,3333,3432,3441],[87,103,149,1803,1817,2097,3155],[86,87,103,149,849,1803,2393],[87,103,149,1817,2097,3249,3340],[86,87,103,149,849,958,1793,1797,3339],[87,103,149,1817,2097,3249,3341],[86,87,103,149,849,1793,1797,1803,3340],[87,103,149,1817,2097,3249,3339],[86,87,103,149,849,958,1793,1797],[87,103,149,1817,2097,2393,3329],[86,87,103,149,849,1793,2393],[87,103,149,1817,2097,3330],[86,87,103,149,849,2393,3329],[86,87,103,149,849,1797,1803,2067,2189,2312],[86,87,103,149,1817,2097,3249,3331],[86,87,103,149,849,1793,1803,2105,2361,2419,2460,2504],[86,87,103,149,3250,3351],[86,87,103,149,1817,2068,2097,3249,3250],[86,87,103,149,2067],[87,103,149,1817,3148],[87,103,149,1817,2097,2321,3249],[87,103,149,1817,2318],[87,103,149,1817,2396],[87,103,149,854,1803],[86,87,103,149,269,1803],[87,103,149,1817,2398],[87,103,149,854],[86,87,103,149,1817,2097,2191,2417,3249,3250],[86,87,103,149,849,1793,2191,2311],[87,103,149,1817,2097,2348,3250],[86,87,103,149,849,1793,1803,2143,2146,2213,2245,2340,2343,2347],[87,103,149,1817,3589],[87,103,149,1798,1803,2263,2264,3542,3588],[87,103,149,1817,2263,3728],[87,103,149,1797,1798,1803,2263,2264,2267,3588],[86,87,103,149,849,2051,2053,2284],[87,103,149,1817,2097,2456,3171,3426,3432,3441],[87,103,149,849,1799,1817,2097,2200,2204,2206,2323,3249,3250],[86,87,103,149,849,1799,2200,2204,2206],[87,103,149,1803,1817,2097,2326,3249,3250],[86,87,103,149,849,958,1798,1803,2204,2324,2325],[86,87,103,149,849,850,1793,1798],[87,103,149,851,1803,1817,2097,2400,3249,3402],[86,87,103,149,849,851,958,1793,1797,1798,1803,2053,2143,2794,3388,3389,3390,3391,3392,3393,3394,3395,3397,3398,3399,3400,3401],[87,103,149,3414,3418],[86,87,103,149,849,958,1803,2061,2067],[86,87,103,149,1817,2097,3249,3391],[86,87,103,149,849,1798,1803,2053,3402],[86,87,103,149,849,958,1793,1798],[86,87,103,149,958,1798],[86,87,103,149,1797,1803,1817,2097,2400,3405],[86,87,103,149,849,851,958,1793,1797,1798,1803,2785,2794,3387,3389,3390,3392,3393,3394,3395,3398,3399,3400],[86,87,103,149,849,958,1798,2051,2061,2067,2794,3392,3405,3406,3419],[86,87,103,149,1803,1817,2097,2105,3414],[86,87,103,149,849,958,1793,1797,1798,1803,2105,2143,2202,2204,2343,2512,2794,3385,3386,3402,3403,3404,3407,3409,3410,3411,3412,3413],[86,87,103,149,1817,2097,3393],[86,87,103,149,849,958,1793,2325,3392],[87,103,149,851,1803,1817,2097,2105,3418],[86,87,103,149,849,851,958,1793,1798,1803,2053,2105,2512,2785,2794,3415,3416,3417],[86,87,103,149,849,958,2067,2324],[86,87,103,149,1817,2097,3249,3398],[86,87,103,149,849,1793,2053],[86,87,103,149,849,1793,1803,3289],[86,87,103,149,849,1817,2097,3249,3395],[86,87,103,149,849,1793,1798],[86,87,103,149,1798,1817,2097,3404],[86,87,103,149,849,1793,1798,3392],[87,103,149,1798,1817,3384],[86,87,103,149,1797,1798,1803,2067,3384],[86,87,103,149,849,958,1798,1803,2051,2105,2204,2206,2456,3171,3380,3432,3441],[86,87,103,149,849,1817,2097,3388],[86,87,103,149,849,958,1793,1798,3387],[86,87,103,149,761,849,1793,1798,3396],[87,103,149,1817,2097],[86,87,103,149,1798,1817,2097,3415],[86,87,103,149,849,958,1793,1797,1798,2053],[87,103,149,1798,1817],[86,87,103,149,849,1797,1798,1803,2105],[87,103,149,1817,3392],[87,103,149,1817,2054,2097,2105,3453],[86,87,103,149,706,849,958,2053,2054,2401,3452],[87,103,149,849,1817,2054,2401],[87,103,149,849,2054],[87,103,149,706,1803,1817,2097,2105,3455],[86,87,103,149,706,849,958,1797,1803,2051,2143,2146,2183,3147,3453,3454],[87,103,149,1803,1817,2054,2097,2105,3454],[86,87,103,149,706,849,958,1803,2053,2054,2401,3452],[86,87,103,149,849,958,1803],[86,87,103,149,958,2456,2457,3171,3432,3441],[87,103,149,849,854,958,2051,2456,3171,3432,3441],[87,103,149,1817,2097,3469],[86,87,103,149,849,854,958,1803,2456,2522,3171,3432,3441,3468],[87,103,149,1796,1797,1817,2097,2227,2240,3249,3250,3445],[86,87,103,149,849,1796,1797,2227,2240],[86,87,103,149,958,2051,2456,3171,3432,3441],[86,87,103,149,958,1797,1803,2051],[86,87,103,149,1797,1803,1817,2097,2105,3249,3474],[86,87,103,149,849,853,958,1793,1794,1797,1803,2051,2054,2061,2067,2105,2207,2209,2256,2291,2329,3147,3444,3461,3471,3472,3473],[87,103,149,849,1803,1817,2097,2209,2213,2245,2252,3166,3249,3250],[87,103,149,849,1803,2209,2213,2245,2252,2362],[87,103,149,1817,2362],[86,87,103,149,1817,2097,2209,2418,3249,3250],[86,87,103,149,849,1793,2209,2311],[87,103,149,1817,2097,3447],[87,103,149,1817,2097,2415,3249,3250],[86,87,103,149,849,2051,2282],[87,103,149,850,1817],[87,103,149,297,849],[86,87,103,149,958,1817,2054,2097,2403,2456,3171,3249,3432,3441,3443],[87,103,149,849,958,1793,2051,2403,2456,3171,3432,3441,3442],[86,87,103,149,1817,2054,2097,3442],[86,87,103,149,2054],[87,103,149,849,1797,1817],[86,87,103,149,664,779,849,1796],[86,87,103,149,849,852,1817,2148,3128,3249,3250],[86,87,103,149,849,852,1793,1803,2108,2150,2153,2190,2230,2366,2514,3101,3120,3121,3122,3123,3124,3126,3127],[87,103,149,1817,3121,3249,3250],[86,87,103,149,849,1793,2149,2167,2366],[87,103,149,1817,3122,3250],[86,87,103,149,849,1793,2153],[87,103,149,1817,2364],[86,87,103,149,3123,3249,3250],[86,87,103,149,849,1793,2148,2157],[87,103,149,1817,2148,3124,3249,3250],[86,87,103,149,849,1793,2146,2148,2149,2150,2153,2155,2364],[87,103,149,1817,2097,3126],[86,87,103,149,849,1793,2142,2248,2340,3125],[87,103,149,1817,2097,3127,3249],[86,87,103,149,849,1793,2514],[87,103,149,852,1797,1803,1817,2340],[87,103,149,850,852,853,854,855,857,1795,1797,1798,1799,1800,1801,1802],[86,87,103,149,958,3150,3151,3152],[86,87,103,149,1803,1817,2097,2105,2245,2318,4095],[86,87,103,149,560,682,849,854,958,1793,1797,1803,2061,2067,2143,2213,2245,2283,2285,2289,2290,2291,2293,2304,2313,2318,2323,2326,2329,3147,3165,3166,3381,3486,3494,4093,4094],[86,87,103,149,849,958,1797,2316],[87,103,149,1817,2097,2331,3250],[86,87,103,149,849,854,958,1793,1795,1797,1799,1803,2061,2105,2143,2146,2193,2213,2215,2243,2248,2282,2283,2284,2285,2286,2287,2289,2290,2291,2293,2294,2304,2312,2313,2314,2318,2320,2321,2322,2323,2326,2327,2329,2330],[87,103,149,854,1817,3154,3249,3250],[86,87,103,149,849,854,1793,1797,1803,2146,2316,2780],[87,103,149,1817,2330],[86,87,103,149,1817,2097,2213,3249,3250,3509],[86,87,103,149,682,849,958,1797,1803,2051,2061,2067,2105,2213,2245,2291,2323,2329,2351,3153,3166,3483,3487,3491],[86,87,103,149,1817,2097,2105,3510],[86,87,103,149,849,958,1793,1797,1803,2051,2061,2105,2209,2213,2291,2318,2323,2329,3147,3165,3166,3507,3509],[87,103,149,1817,2143,2342,2348,2349],[87,103,149,2143,2342,2348],[86,87,103,149,849,958,1797,1803,2067,3477,3478,3479],[86,87,103,149,849,958,1797,1803,2051,2067,2456,3171,3380,3432,3441,3480,3481],[86,87,103,149,958,1803],[86,87,103,149,849,958,1803,2051],[87,103,149,1799,1803,1817,2097,3151,3249],[86,87,103,149,849,958,1798,1799,1803,2051],[86,87,103,149,958,1803,2051],[86,87,103,149,1803,1817,2097,2406,3249,3250,4015],[86,87,103,149,849,958,1797,1803,2146,2406,2407,2409,4014],[86,87,103,149,849,958,1797,1803,2146,2393,2406],[86,87,103,149,849,958,1793,1803],[86,87,103,149,958,1817,2097,2406,3249,3250,4013],[86,87,103,149,849,958,2051,2406,2456,3171,3432,3441,4012],[87,103,149,1817,2407],[87,103,149,2406],[86,87,103,149,1817,2097,3249,3250,4018],[86,87,103,149,958,1803,1817,2097,2406,3249,3250,4012],[86,87,103,149,849,958,1803,2051,2406],[87,103,149,1817,2097,3250,4014],[86,87,103,149,849,958,1817,2097,3249,3250,4021],[86,87,103,149,849,850,958,1793,1803,2143,2393,2406,2515,3147,3777,4009,4010,4011,4013,4015,4016,4017,4018,4019,4020],[86,87,103,149,849,850,958,1797,1803,2051,2393,2406,2510,4008],[87,103,149,1803,1817,2097,2406,3249,3250,4010],[86,87,103,149,849,958,1803,2051,2406,4009],[86,87,103,149,958,1817,2097,2406,3249,3250,3777],[86,87,103,149,849,958,2051,2406,2456,3171,3432,3441],[86,87,103,149,1803,1817,2097,3249,3250,4017],[86,87,103,149,849,850,1803,2051],[86,87,103,149,849,958,1803,2146],[87,103,149,1803,1817,2097,2406,3156,3250],[86,87,103,149,849,1803,2406],[87,103,149,1817,2409],[87,103,149,1817,2054],[87,103,149,1803,1817,2097,2456,3171,3432,3438,3441],[86,87,103,149,849,857,958,1797,1803,2051,2054,2067,2258,2263,2456,3120,3128,3171,3432,3435,3437,3441],[87,103,149,849,1797,1803,1817,3249,3250,4065],[86,87,103,149,849,1797,1803,2299],[87,103,149,1817,2097,2295],[87,103,149,1817,2097,2296],[87,103,149,849,1817,2097,2299,3249],[86,87,103,149,2295,2296,2297,2298],[87,103,149,1817,2097,2297,3249],[87,103,149,1817,2097,2298,3249],[86,87,103,149,849,1793,1797,2146,2209,2230,2231,2234,2235,4067,4068],[86,87,103,149,849,2234],[86,87,103,149,682,849,1793,2234],[86,87,103,149,849,958,1793,1796,1797,1803,2316],[87,103,149,1803,1817,2097,3370],[86,87,103,149,486,849,958,1796,1797,1803,2053,2373,3147,3361,3363,3368,3369],[86,87,103,149,849,1797,2146,2180,2182,2367],[86,87,103,149,849,1797,2067,2146,2179,2180,2181,2182,2367,3147,3266,3267],[87,103,149,1817,2097,3249,3267],[87,103,149,1796,1797,1817,2097,2227,2242,3249,3250,3254],[86,87,103,149,849,1793,1796,1797,2227,2242],[86,87,103,149,1817,2097,2198,2199,3249,3409],[86,87,103,149,849,1793,1797,2074,2198,2199,2368,3408],[86,87,103,149,1817,2097,2368,3249,3408],[87,103,149,849,1793,2288,2368],[87,103,149,1797,1803,1817,2368],[86,87,103,149,849,1793,1803,2146],[87,103,149,1817,2097,3250,3256],[86,87,103,149,849,1796,1797,2236,2371,3255],[87,103,149,849,1817,2097,3250,3255],[86,87,103,149,849,958,2370],[87,103,149,1817,2097,2105,3257],[86,87,103,149,1796,1797,2236,2238,2371,3147],[87,103,149,1796,1797,1817,2097,2236,2238,2371,3258],[86,87,103,149,849,1796,1797,2236,2238,2371,3255],[87,103,149,1817,2097,3259],[87,103,149,1817,2097,2238,3250,3260],[87,103,149,849,2067,2238,2370],[87,103,149,1817,2097,2105,3263],[86,87,103,149,849,2067,2238,2370,2371,3256,3257,3258,3259,3260,3261,3262],[87,103,149,1817,2097,3261],[87,103,149,1817,2097,3262],[87,103,149,849,2067],[87,103,149,1817,2371],[87,103,149,2238],[87,103,149,1817,2097,3249,3264],[86,87,103,149,849,2349],[87,103,149,1797,1817,2097,3265],[87,103,149,849,1797,2146,2248,2250,3264],[87,103,149,1817,2097,3369],[86,87,103,149,843,849,958,2373,3165],[87,103,149,849,1817,2074,2097,2303,3249],[86,87,103,149,849,850,958,1797,2074,2300,2301,2302],[87,103,149,1817,2097,2300],[87,103,149,1803,1817,2074,2097,3249,4066],[86,87,103,149,849,958,1797,1803,2051,2143,2207,2303,3147,3442,3588],[87,103,149,849,1817,2097,2301,2302,3249],[86,87,103,149,849,850,958,2067,2301],[87,103,149,1817,2097,3348],[86,87,103,149,958,1793,2419],[87,103,149,1817,2097,4110],[86,87,103,149,3129],[87,103,149,850,1817,2097,2327,3249],[86,87,103,149,849,850,2316],[87,103,149,1817,2097,3249,3296],[87,103,149,849,857,958,1793,2456,3171,3432,3441],[87,103,149,849,1797,1803,1817,2097,3270],[86,87,103,149,849,958,1796,1797,1803],[87,103,149,1817,2097,3249,4090],[86,87,103,149,849,958,1793,2286,2291],[86,87,103,149,853,958,1797,1803,2051,4088,4089,4090],[86,87,103,149,849,853,958,1793,1797,1803,2061,2067,2286,2291,2318,2331],[87,103,149,1817,2097,3653],[86,87,103,149,849,853,1803],[87,103,149,853,1817,2097,4089],[86,87,103,149,849,853,958,2051,2456,3171,3432,3441],[87,103,149,1803,1817,2097,3250,4093],[86,87,103,149,958,1797,1803],[86,87,103,149,2292],[87,103,149,1817,2097,3250,3487],[86,87,103,149,1817,2292,3249,3250],[86,87,103,149,849,958,1793,2051,2053,2284,2291],[87,103,149,1803,1817,2097,3250,3489],[86,87,103,149,849,958,1793,1797,1803,3488],[86,87,103,149,849,1793,2061,2413,2519],[87,103,149,1817,3488],[87,103,149,1817,2411],[87,103,149,1803,1817,2097,2193,2209,2213,2245,2252,3249,3250,3494],[86,87,103,149,849,850,958,1793,1797,1803,2051,2061,2067,2105,2143,2146,2187,2213,2283,2285,2290,2291,2304,2318,2323,2326,2329,2411,2782,3147,3149,3153,3157,3166,3483,3484,3485,3486,3487,3489,3490,3492,3493],[87,103,149,1817,2097,2143,2146,2248,3249,3250,3492,3494],[87,103,149,682,849,1793,1803,2061,2143,2146,2248,2519,3491,3494],[87,103,149,854,1803,1817,2097,2146,2193,2396,3249,3250,3493],[86,87,103,149,849,854,958,1793,1803,2051,2061,2105,2146,2193,2318,2396,2415,2456,3142,3159,3171,3432,3441],[86,87,103,149,849,1797,1803,1817,2097,3249,3250,4094],[86,87,103,149,849,1793,1797,1803,2286,2318,3166],[87,103,149,854,1817,2097,3158,3249,3250],[86,87,103,149,849,853,854,958,1793,1797,1799,1803,2143,2213,2215,2248,2283,2284,2285,2287,2290,2291,2294,2313,2321,2322,2323,2326,2329,2331,3148,3155,3156,3157],[87,103,149,854,1817,2097,2146,3143,3159,3250],[87,103,149,854,1803,1817,2097,2105,2146,2195,2215,3143,3159,3249,3250],[86,87,103,149,849,854,958,1796,1797,1803,2051,2061,2105,2143,2146,2193,2195,2215,2248,2284,2782,3143,3145,3146,3147,3148,3149,3153,3154,3158],[87,103,149,1817,2097,3145,3249],[86,87,103,149,849,1793,3142,3144],[86,87,103,149,849,1817,2097,2105,2143,3159],[86,87,103,149,849,854,1793,1803,2105,2312,2361,3350,4097],[86,87,103,149,849,958,1803,2415,2457,2458,3351,4097],[87,103,149,1817,3250,4097],[86,87,103,149,1817,2097,3249,3250,4100],[86,87,103,149,4098,4099],[87,103,149,1817,2097,3249,4155,4159],[86,87,103,149,2346,4155],[87,103,149,1817,2097,3381],[87,103,149,849,1793],[86,87,103,149,2344,2346],[86,87,103,149,1817,2097,4155],[86,87,103,149,2067,2346,4155],[86,87,103,149,2346],[87,103,149,2346],[87,103,149,2344,2346],[87,103,149,1817,2097,3129],[86,87,103,149,2346,2513],[87,103,149,1803,1817],[87,103,149,1803,1817,2097,3249,3473],[86,87,103,149,849,1797,1803],[86,87,103,149,958,1803,2061,3161,3296,3500,3501],[86,87,103,149,1803,1817,2097,2155,2347,3249],[86,87,103,149,958,1803,2067,2155,2346],[86,87,103,149,1817,2097,4113],[86,87,103,149,958,2374,4106],[86,87,103,149,1817,2097,4114],[86,87,103,149,958,2374],[86,87,103,149,1817,2097,4115],[86,87,103,149,682,849,2061,2374],[87,103,149,1817,2097,4116],[86,87,103,149,2374,4113,4114,4115],[87,103,149,1803,1817,2097,4119],[86,87,103,149,849,958,1793,1803,2054,2061,2352,2359,2374,2375,2376,3143,3501,4108,4116,4117,4118],[87,103,149,1817,2097,4120],[86,87,103,149,849,958,1793,2061,3442,4110],[87,103,149,854,1803,1817,2097,2146,2398,3249,3501],[86,87,103,149,849,958,1803,2051,2061,2146,2374,2398,3159,3380],[87,103,149,1817,2097,3249,4118],[86,87,103,149,849,958,2061,3380],[87,103,149,1817,2097,2374,3249,4107],[86,87,103,149,682,849,958,2061,2374],[87,103,149,1817,2097,3250,4121],[86,87,103,149,849,1803,3634],[86,87,103,149,849,958,1803,1817,2097,2146,2165,2185,2252,2254,3250,4123],[86,87,103,149,849,853,854,958,1793,1803,2061,2143,2146,2165,2185,2252,2254,2311,2359,2374,2375,2376,3348,3500,3501,4108,4109,4110,4112,4116,4119,4120,4121,4122],[86,87,103,149,1817,2097,4122],[86,87,103,149,2374],[87,103,149,1817,2376],[87,103,149,1803,1817,2097,4112],[86,87,103,149,849,958,1803,4110,4111],[86,87,103,149,1803,1817,2097,3161,3250],[86,87,103,149,852,854,958,1803,2106,2142,2331,3140,3141,3160],[87,103,149,1803,1817,2097,4148],[86,87,103,149,849,850,958,1793,1797,1803,2053,2328,4142,4146,4147],[87,103,149,1817,2097,2328,4146],[86,87,103,149,849,850,1793,2328],[86,87,103,149,958,1797,1803,2051,2143,2328,3147,4141,4143,4145,4148,4149],[87,103,149,1817,2074,2097,4147],[87,103,149,1817,2097,2328,4149],[86,87,103,149,849,2328,4144],[86,87,103,149,849,958,1793,1797,1803,2051,2053,2054,2328,4144],[87,103,149,1803,1817,2097,4143],[86,87,103,149,849,958,1793,1797,1803,2053,2074,4142],[87,103,149,849,1817,2097,2328,2329],[86,87,103,149,849,1803,2328],[87,103,149,1817,2097,2328,3249,4141],[86,87,103,149,849,958,2051,2054,2328,2456,3165,3171,3432,3441],[86,87,103,149,849,850,1793,1797,1803],[86,87,103,149,682,849,1793,1803,2053,2105,2419,2460,3142,3376],[86,87,103,149,849,1793,2419,2460,3142],[86,87,103,149,849,958,2054,2061,2414,2456,2457,2458,2459,3171,3432,3441],[87,103,149,1817,2097,2475],[86,87,103,149,1817,2474,3249,3250],[86,87,103,149,849,2061],[87,103,149,1803,2414,2415,2416,2417,2418,2461],[87,103,149,2464],[86,87,103,149,1817,2464,2465,3250],[86,87,103,149,1817,2465,2472,3249,3250],[86,87,103,149,849,2464,2469,2470,2471],[86,87,103,149,1817,2465,2469,3249,3250],[87,103,149,1803,1817,2097,2419,2461,3249,3250,3382],[86,87,103,149,854,958,1803,2143,2414,2415,2419,2460,2461,2462,2504,3159,3373,3375,3377,3379,3380,3381],[86,87,103,149,1803,1817,2097,2105,2460,2461],[86,87,103,149,854,1803,2105,2396,2419,2420,2460],[86,87,103,149,1817,2097,2494,3249],[87,103,149,849,1793,2054,2419,2460,2466],[86,87,103,149,1817,2097,2491,2497,3249],[86,87,103,149,849,1793,2491,2496],[87,103,149,2502,2503],[86,87,103,149,849,1817,2097,2491,2498,3249],[86,87,103,149,850,2491,2493,2494,2496,2497],[87,103,149,849,2466,2480,3109],[87,103,149,1817,2097,2460,2502,3249],[86,87,103,149,849,2061,2419,2460,2466,2472,2473,2474,2475,2476,2477,2478,2481,2482,2490,2501],[86,87,103,149,849,1793,1803,2061,2067,2105,2196,2414,2460,2463,2466,2467,2468,2482,2502],[86,87,103,149,849,1817,2097,2491,2499,3249],[86,87,103,149,849,850,2491,2493,2496],[87,103,149,2491],[86,87,103,149,849,1817,2097,2501],[87,103,149,2492,2498,2499,2500],[86,87,103,149,849,1817,2097,2500,3249],[86,87,103,149,849,1793,2493],[86,87,103,149,1817,2097,2496],[87,103,149,849,2491,2495],[86,87,103,149,1817,2097,2495],[87,103,149,849,2491],[87,103,149,1817,2097,2477],[87,103,149,849,2466],[86,87,103,149,2460,2466],[87,103,149,1817,2419,3378],[87,103,149,2419],[86,87,103,149,849,1793,2414,2419,2461,3378],[87,103,149,1817,2097,2456,3171,3380,3432,3441],[86,87,103,149,958,2456,3171,3432,3441],[87,103,149,1817,2097,2458],[87,103,149,849,2483],[87,103,149,2483,2484,2489],[87,103,149,2483],[86,87,103,149,849,2483,2485,2486],[86,87,103,149,849,1793,2483,2487],[87,103,149,1817,2460,2484],[87,103,149,849,2460,2484,2488],[87,103,149,2460,2483],[87,103,149,1817,2097,2459],[87,103,149,2414],[86,87,103,149,849,2054],[86,87,103,149,1803,2061,2146],[86,87,103,149,854,1817,2097,2193,3143,3160,3250],[86,87,103,149,849,854,958,1793,2051,2061,2193,2213,2245,2311,2318,2415,2417,2456,3142,3159,3171,3432,3441],[86,87,103,149,373,849,850,1797],[86,87,103,149,852,1803,2106,2107,2143],[86,87,103,149,2142,2384,2385],[87,103,149,1817,2097,3125],[86,87,103,149,1800,1803],[87,103,149,2105],[86,87,103,149,1803],[87,103,149,2510],[87,103,149,2506,2507,2508,2509,2511],[86,87,103,149,850,1803,1817,2097,2105,2515],[87,103,149,850,1803,2105],[87,103,149,1803,1817,2097,2794,3400],[86,87,103,149,1797,1803,2523,2790,2794],[86,87,103,149,1798,1803],[86,87,103,149,851,1797,1803,2512,2523,2790,2794],[86,87,103,149,1797,1803,2512,2523,2790,2794],[86,87,103,149,1803,2145],[87,103,149,1817,2053],[87,103,149,1801,1802],[87,103,149,2344,2345],[87,103,149,1800,1817],[87,103,149,1801,1817],[87,103,149,490],[87,103,149,851,852,1817],[87,103,149,851],[87,103,149,1797,1817,2061],[87,103,149,1797],[87,103,149,1817,2523],[87,103,149,1817,2106,2107],[87,103,149,2106],[87,103,149,1817,2780],[87,103,149,2779],[87,103,149,1817,2782],[87,103,149,1817,2148],[87,103,149,1817,2785],[87,103,149,851,1817],[87,103,149,1817,2324],[87,103,149,1817,2340],[87,103,149,1803,1817,2229],[87,103,149,2108],[87,103,149,1803,1817,2143],[87,103,149,854,1817,2351],[87,103,149,1794,1817],[86,87,103,149,1817,2097,2105,2108,3117,3163],[87,103,149,1817,2800],[87,103,149,1817,2802],[86,87,103,149,958,1817,2097],[86,87,103,149,2097,2105],[87,103,149,1817,2146,2374,3250,3501],[87,103,149,170,267]],"fileInfos":[{"version":"c430d44666289dae81f30fa7b2edebf186ecc91a2d4c71266ea6ae76388792e1","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","impliedFormat":1},{"version":"ee7bad0c15b58988daa84371e0b89d313b762ab83cb5b31b8a2d1162e8eb41c2","impliedFormat":1},{"version":"27bdc30a0e32783366a5abeda841bc22757c1797de8681bbe81fbc735eeb1c10","impliedFormat":1},{"version":"8fd575e12870e9944c7e1d62e1f5a73fcf23dd8d3a321f2a2c74c20d022283fe","impliedFormat":1},{"version":"2ab096661c711e4a81cc464fa1e6feb929a54f5340b46b0a07ac6bbf857471f0","impliedFormat":1},{"version":"080941d9f9ff9307f7e27a83bcd888b7c8270716c39af943532438932ec1d0b9","affectsGlobalScope":true,"impliedFormat":1},{"version":"2e80ee7a49e8ac312cc11b77f1475804bee36b3b2bc896bead8b6e1266befb43","affectsGlobalScope":true,"impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fb0f136d372979348d59b3f5020b4cdb81b5504192b1cacff5d1fbba29378aa1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"a680117f487a4d2f30ea46f1b4b7f58bef1480456e18ba53ee85c2746eeca012","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"d6d7ae4d1f1f3772e2a3cde568ed08991a8ae34a080ff1151af28b7f798e22ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true,"impliedFormat":1},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true,"impliedFormat":1},{"version":"959d36cddf5e7d572a65045b876f2956c973a586da58e5d26cde519184fd9b8a","affectsGlobalScope":true,"impliedFormat":1},{"version":"965f36eae237dd74e6cca203a43e9ca801ce38824ead814728a2807b1910117d","affectsGlobalScope":true,"impliedFormat":1},{"version":"3925a6c820dcb1a06506c90b1577db1fdbf7705d65b62b99dce4be75c637e26b","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a3d63ef2b853447ec4f749d3f368ce642264246e02911fcb1590d8c161b8005","affectsGlobalScope":true,"impliedFormat":1},{"version":"8cdf8847677ac7d20486e54dd3fcf09eda95812ac8ace44b4418da1bbbab6eb8","affectsGlobalScope":true,"impliedFormat":1},{"version":"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a","affectsGlobalScope":true,"impliedFormat":1},{"version":"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004","affectsGlobalScope":true,"impliedFormat":1},{"version":"b4b67b1a91182421f5df999988c690f14d813b9850b40acd06ed44691f6727ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"df83c2a6c73228b625b0beb6669c7ee2a09c914637e2d35170723ad49c0f5cd4","affectsGlobalScope":true,"impliedFormat":1},{"version":"436aaf437562f276ec2ddbee2f2cdedac7664c1e4c1d2c36839ddd582eeb3d0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e3c06ea092138bf9fa5e874a1fdbc9d54805d074bee1de31b99a11e2fec239d","affectsGlobalScope":true,"impliedFormat":1},{"version":"87dc0f382502f5bbce5129bdc0aea21e19a3abbc19259e0b43ae038a9fc4e326","affectsGlobalScope":true,"impliedFormat":1},{"version":"b1cb28af0c891c8c96b2d6b7be76bd394fddcfdb4709a20ba05a7c1605eea0f9","affectsGlobalScope":true,"impliedFormat":1},{"version":"2fef54945a13095fdb9b84f705f2b5994597640c46afeb2ce78352fab4cb3279","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac77cb3e8c6d3565793eb90a8373ee8033146315a3dbead3bde8db5eaf5e5ec6","affectsGlobalScope":true,"impliedFormat":1},{"version":"56e4ed5aab5f5920980066a9409bfaf53e6d21d3f8d020c17e4de584d29600ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ece9f17b3866cc077099c73f4983bddbcb1dc7ddb943227f1ec070f529dedd1","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a6282c8827e4b9a95f4bf4f5c205673ada31b982f50572d27103df8ceb8013c","affectsGlobalScope":true,"impliedFormat":1},{"version":"1c9319a09485199c1f7b0498f2988d6d2249793ef67edda49d1e584746be9032","affectsGlobalScope":true,"impliedFormat":1},{"version":"e3a2a0cee0f03ffdde24d89660eba2685bfbdeae955a6c67e8c4c9fd28928eeb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811c71eee4aa0ac5f7adf713323a5c41b0cf6c4e17367a34fbce379e12bbf0a4","affectsGlobalScope":true,"impliedFormat":1},{"version":"51ad4c928303041605b4d7ae32e0c1ee387d43a24cd6f1ebf4a2699e1076d4fa","affectsGlobalScope":true,"impliedFormat":1},{"version":"60037901da1a425516449b9a20073aa03386cce92f7a1fd902d7602be3a7c2e9","affectsGlobalScope":true,"impliedFormat":1},{"version":"d4b1d2c51d058fc21ec2629fff7a76249dec2e36e12960ea056e3ef89174080f","affectsGlobalScope":true,"impliedFormat":1},{"version":"22adec94ef7047a6c9d1af3cb96be87a335908bf9ef386ae9fd50eeb37f44c47","affectsGlobalScope":true,"impliedFormat":1},{"version":"196cb558a13d4533a5163286f30b0509ce0210e4b316c56c38d4c0fd2fb38405","affectsGlobalScope":true,"impliedFormat":1},{"version":"73f78680d4c08509933daf80947902f6ff41b6230f94dd002ae372620adb0f60","affectsGlobalScope":true,"impliedFormat":1},{"version":"c5239f5c01bcfa9cd32f37c496cf19c61d69d37e48be9de612b541aac915805b","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"0bd5e7096c7bc02bf70b2cc017fc45ef489cb19bd2f32a71af39ff5787f1b56a","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac51dd7d31333793807a6abaa5ae168512b6131bd41d9c5b98477fc3b7800f9f","impliedFormat":1},{"version":"87d9d29dbc745f182683f63187bf3d53fd8673e5fca38ad5eaab69798ed29fbc","impliedFormat":1},{"version":"e6f3077b1780226627f76085397d10c77a4d851c7154fd4b3f1eb114f4c2e56d","affectsGlobalScope":true,"impliedFormat":1},{"version":"2879a055439b6c0c0132a1467120a0f85b56b5d735c973ad235acd958b1b5345","impliedFormat":1},{"version":"04471dc55f802c29791cc75edda8c4dd2a121f71c2401059da61eff83099e8ab","impliedFormat":99},{"version":"5c54a34e3d91727f7ae840bfe4d5d1c9a2f93c54cb7b6063d06ee4a6c3322656","impliedFormat":99},{"version":"db4da53b03596668cf6cc9484834e5de3833b9e7e64620cf08399fe069cd398d","impliedFormat":99},{"version":"ac7c28f153820c10850457994db1462d8c8e462f253b828ad942a979f726f2f9","impliedFormat":99},{"version":"f9b028d3c3891dd817e24d53102132b8f696269309605e6ed4f0db2c113bbd82","impliedFormat":99},{"version":"fb7c8d90e52e2884509166f96f3d591020c7b7977ab473b746954b0c8d100960","impliedFormat":99},{"version":"0bff51d6ed0c9093f6955b9d8258ce152ddb273359d50a897d8baabcb34de2c4","impliedFormat":99},{"version":"ef13c73d6157a32933c612d476c1524dd674cf5b9a88571d7d6a0d147544d529","impliedFormat":99},{"version":"13918e2b81c4288695f9b1f3dcc2468caf0f848d5c1f3dc00071c619d34ff63a","impliedFormat":99},{"version":"120a80aa556732f684db3ed61aeff1d6671e1655bd6cba0aa88b22b88ac9a6b1","affectsGlobalScope":true,"impliedFormat":99},{"version":"70521b6ab0dcba37539e5303104f29b721bfb2940b2776da4cc818c07e1fefc1","affectsGlobalScope":true,"impliedFormat":1},{"version":"ab41ef1f2cdafb8df48be20cd969d875602483859dc194e9c97c8a576892c052","affectsGlobalScope":true,"impliedFormat":1},{"version":"d153a11543fd884b596587ccd97aebbeed950b26933ee000f94009f1ab142848","affectsGlobalScope":true,"impliedFormat":1},{"version":"21d819c173c0cf7cc3ce57c3276e77fd9a8a01d35a06ad87158781515c9a438a","impliedFormat":1},{"version":"98cffbf06d6bab333473c70a893770dbe990783904002c4f1a960447b4b53dca","affectsGlobalScope":true,"impliedFormat":1},{"version":"ba481bca06f37d3f2c137ce343c7d5937029b2468f8e26111f3c9d9963d6568d","affectsGlobalScope":true,"impliedFormat":1},{"version":"6d9ef24f9a22a88e3e9b3b3d8c40ab1ddb0853f1bfbd5c843c37800138437b61","affectsGlobalScope":true,"impliedFormat":1},{"version":"1db0b7dca579049ca4193d034d835f6bfe73096c73663e5ef9a0b5779939f3d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","affectsGlobalScope":true,"impliedFormat":1},{"version":"f26b11d8d8e4b8028f1c7d618b22274c892e4b0ef5b3678a8ccbad85419aef43","affectsGlobalScope":true,"impliedFormat":1},{"version":"5929864ce17fba74232584d90cb721a89b7ad277220627cc97054ba15a98ea8f","impliedFormat":1},{"version":"763fe0f42b3d79b440a9b6e51e9ba3f3f91352469c1e4b3b67bfa4ff6352f3f4","impliedFormat":1},{"version":"25c8056edf4314820382a5fdb4bb7816999acdcb929c8f75e3f39473b87e85bc","impliedFormat":1},{"version":"c464d66b20788266e5353b48dc4aa6bc0dc4a707276df1e7152ab0c9ae21fad8","impliedFormat":1},{"version":"78d0d27c130d35c60b5e5566c9f1e5be77caf39804636bc1a40133919a949f21","impliedFormat":1},{"version":"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","impliedFormat":1},{"version":"1d6e127068ea8e104a912e42fc0a110e2aa5a66a356a917a163e8cf9a65e4a75","impliedFormat":1},{"version":"5ded6427296cdf3b9542de4471d2aa8d3983671d4cac0f4bf9c637208d1ced43","impliedFormat":1},{"version":"7f182617db458e98fc18dfb272d40aa2fff3a353c44a89b2c0ccb3937709bfb5","impliedFormat":1},{"version":"cadc8aced301244057c4e7e73fbcae534b0f5b12a37b150d80e5a45aa4bebcbd","impliedFormat":1},{"version":"385aab901643aa54e1c36f5ef3107913b10d1b5bb8cbcd933d4263b80a0d7f20","impliedFormat":1},{"version":"9670d44354bab9d9982eca21945686b5c24a3f893db73c0dae0fd74217a4c219","impliedFormat":1},{"version":"0b8a9268adaf4da35e7fa830c8981cfa22adbbe5b3f6f5ab91f6658899e657a7","impliedFormat":1},{"version":"11396ed8a44c02ab9798b7dca436009f866e8dae3c9c25e8c1fbc396880bf1bb","impliedFormat":1},{"version":"ba7bc87d01492633cb5a0e5da8a4a42a1c86270e7b3d2dea5d156828a84e4882","impliedFormat":1},{"version":"4893a895ea92c85345017a04ed427cbd6a1710453338df26881a6019432febdd","impliedFormat":1},{"version":"c21dc52e277bcfc75fac0436ccb75c204f9e1b3fa5e12729670910639f27343e","impliedFormat":1},{"version":"13f6f39e12b1518c6650bbb220c8985999020fe0f21d818e28f512b7771d00f9","impliedFormat":1},{"version":"9b5369969f6e7175740bf51223112ff209f94ba43ecd3bb09eefff9fd675624a","impliedFormat":1},{"version":"4fe9e626e7164748e8769bbf74b538e09607f07ed17c2f20af8d680ee49fc1da","impliedFormat":1},{"version":"24515859bc0b836719105bb6cc3d68255042a9f02a6022b3187948b204946bd2","impliedFormat":1},{"version":"ea0148f897b45a76544ae179784c95af1bd6721b8610af9ffa467a518a086a43","impliedFormat":1},{"version":"24c6a117721e606c9984335f71711877293a9651e44f59f3d21c1ea0856f9cc9","impliedFormat":1},{"version":"dd3273ead9fbde62a72949c97dbec2247ea08e0c6952e701a483d74ef92d6a17","impliedFormat":1},{"version":"405822be75ad3e4d162e07439bac80c6bcc6dbae1929e179cf467ec0b9ee4e2e","impliedFormat":1},{"version":"0db18c6e78ea846316c012478888f33c11ffadab9efd1cc8bcc12daded7a60b6","impliedFormat":1},{"version":"e61be3f894b41b7baa1fbd6a66893f2579bfad01d208b4ff61daef21493ef0a8","impliedFormat":1},{"version":"bd0532fd6556073727d28da0edfd1736417a3f9f394877b6d5ef6ad88fba1d1a","impliedFormat":1},{"version":"89167d696a849fce5ca508032aabfe901c0868f833a8625d5a9c6e861ef935d2","impliedFormat":1},{"version":"615ba88d0128ed16bf83ef8ccbb6aff05c3ee2db1cc0f89ab50a4939bfc1943f","impliedFormat":1},{"version":"a4d551dbf8746780194d550c88f26cf937caf8d56f102969a110cfaed4b06656","impliedFormat":1},{"version":"8bd86b8e8f6a6aa6c49b71e14c4ffe1211a0e97c80f08d2c8cc98838006e4b88","impliedFormat":1},{"version":"317e63deeb21ac07f3992f5b50cdca8338f10acd4fbb7257ebf56735bf52ab00","impliedFormat":1},{"version":"4732aec92b20fb28c5fe9ad99521fb59974289ed1e45aecb282616202184064f","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"bf67d53d168abc1298888693338cb82854bdb2e69ef83f8a0092093c2d562107","impliedFormat":1},{"version":"b52476feb4a0cbcb25e5931b930fc73cb6643fb1a5060bf8a3dda0eeae5b4b68","affectsGlobalScope":true,"impliedFormat":1},{"version":"e2677634fe27e87348825bb041651e22d50a613e2fdf6a4a3ade971d71bac37e","impliedFormat":1},{"version":"7394959e5a741b185456e1ef5d64599c36c60a323207450991e7a42e08911419","impliedFormat":1},{"version":"8c0bcd6c6b67b4b503c11e91a1fb91522ed585900eab2ab1f61bba7d7caa9d6f","impliedFormat":1},{"version":"8cd19276b6590b3ebbeeb030ac271871b9ed0afc3074ac88a94ed2449174b776","affectsGlobalScope":true,"impliedFormat":1},{"version":"696eb8d28f5949b87d894b26dc97318ef944c794a9a4e4f62360cd1d1958014b","impliedFormat":1},{"version":"3f8fa3061bd7402970b399300880d55257953ee6d3cd408722cb9ac20126460c","impliedFormat":1},{"version":"35ec8b6760fd7138bbf5809b84551e31028fb2ba7b6dc91d95d098bf212ca8b4","affectsGlobalScope":true,"impliedFormat":1},{"version":"5524481e56c48ff486f42926778c0a3cce1cc85dc46683b92b1271865bcf015a","impliedFormat":1},{"version":"68bd56c92c2bd7d2339457eb84d63e7de3bd56a69b25f3576e1568d21a162398","affectsGlobalScope":true,"impliedFormat":1},{"version":"3e93b123f7c2944969d291b35fed2af79a6e9e27fdd5faa99748a51c07c02d28","impliedFormat":1},{"version":"9d19808c8c291a9010a6c788e8532a2da70f811adb431c97520803e0ec649991","impliedFormat":1},{"version":"87aad3dd9752067dc875cfaa466fc44246451c0c560b820796bdd528e29bef40","impliedFormat":1},{"version":"4aacb0dd020eeaef65426153686cc639a78ec2885dc72ad220be1d25f1a439df","impliedFormat":1},{"version":"f0bd7e6d931657b59605c44112eaf8b980ba7f957a5051ed21cb93d978cf2f45","impliedFormat":1},{"version":"8db0ae9cb14d9955b14c214f34dae1b9ef2baee2fe4ce794a4cd3ac2531e3255","affectsGlobalScope":true,"impliedFormat":1},{"version":"15fc6f7512c86810273af28f224251a5a879e4261b4d4c7e532abfbfc3983134","impliedFormat":1},{"version":"58adba1a8ab2d10b54dc1dced4e41f4e7c9772cbbac40939c0dc8ce2cdb1d442","impliedFormat":1},{"version":"641942a78f9063caa5d6b777c99304b7d1dc7328076038c6d94d8a0b81fc95c1","impliedFormat":1},{"version":"714435130b9015fae551788df2a88038471a5a11eb471f27c4ede86552842bc9","impliedFormat":1},{"version":"855cd5f7eb396f5f1ab1bc0f8580339bff77b68a770f84c6b254e319bbfd1ac7","impliedFormat":1},{"version":"5650cf3dace09e7c25d384e3e6b818b938f68f4e8de96f52d9c5a1b3db068e86","impliedFormat":1},{"version":"1354ca5c38bd3fd3836a68e0f7c9f91f172582ba30ab15bb8c075891b91502b7","affectsGlobalScope":true,"impliedFormat":1},{"version":"27fdb0da0daf3b337c5530c5f266efe046a6ceb606e395b346974e4360c36419","impliedFormat":1},{"version":"2d2fcaab481b31a5882065c7951255703ddbe1c0e507af56ea42d79ac3911201","impliedFormat":1},{"version":"a192fe8ec33f75edbc8d8f3ed79f768dfae11ff5735e7fe52bfa69956e46d78d","impliedFormat":1},{"version":"ca867399f7db82df981d6915bcbb2d81131d7d1ef683bc782b59f71dda59bc85","affectsGlobalScope":true,"impliedFormat":1},{"version":"372413016d17d804e1d139418aca0c68e47a83fb6669490857f4b318de8cccb3","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e043a1bc8fbf2a255bccf9bf27e0f1caf916c3b0518ea34aa72357c0afd42ec","impliedFormat":1},{"version":"b4f70ec656a11d570e1a9edce07d118cd58d9760239e2ece99306ee9dfe61d02","impliedFormat":1},{"version":"3bc2f1e2c95c04048212c569ed38e338873f6a8593930cf5a7ef24ffb38fc3b6","impliedFormat":1},{"version":"6e70e9570e98aae2b825b533aa6292b6abd542e8d9f6e9475e88e1d7ba17c866","impliedFormat":1},{"version":"f9d9d753d430ed050dc1bf2667a1bab711ccbb1c1507183d794cc195a5b085cc","impliedFormat":1},{"version":"9eece5e586312581ccd106d4853e861aaaa1a39f8e3ea672b8c3847eedd12f6e","impliedFormat":1},{"version":"47ab634529c5955b6ad793474ae188fce3e6163e3a3fb5edd7e0e48f14435333","impliedFormat":1},{"version":"37ba7b45141a45ce6e80e66f2a96c8a5ab1bcef0fc2d0f56bb58df96ec67e972","impliedFormat":1},{"version":"45650f47bfb376c8a8ed39d4bcda5902ab899a3150029684ee4c10676d9fbaee","impliedFormat":1},{"version":"fad4e3c207fe23922d0b2d06b01acbfb9714c4f2685cf80fd384c8a100c82fd0","affectsGlobalScope":true,"impliedFormat":1},{"version":"74cf591a0f63db318651e0e04cb55f8791385f86e987a67fd4d2eaab8191f730","impliedFormat":1},{"version":"5eab9b3dc9b34f185417342436ec3f106898da5f4801992d8ff38ab3aff346b5","impliedFormat":1},{"version":"12ed4559eba17cd977aa0db658d25c4047067444b51acfdcbf38470630642b23","affectsGlobalScope":true,"impliedFormat":1},{"version":"f3ffabc95802521e1e4bcba4c88d8615176dc6e09111d920c7a213bdda6e1d65","impliedFormat":1},{"version":"809821b8a065e3234a55b3a9d7846231ed18d66dd749f2494c66288d890daf7f","impliedFormat":1},{"version":"ae56f65caf3be91108707bd8dfbccc2a57a91feb5daabf7165a06a945545ed26","impliedFormat":1},{"version":"a136d5de521da20f31631a0a96bf712370779d1c05b7015d7019a9b2a0446ca9","impliedFormat":1},{"version":"c3b41e74b9a84b88b1dca61ec39eee25c0dbc8e7d519ba11bb070918cfacf656","affectsGlobalScope":true,"impliedFormat":1},{"version":"4737a9dc24d0e68b734e6cfbcea0c15a2cfafeb493485e27905f7856988c6b29","affectsGlobalScope":true,"impliedFormat":1},{"version":"36d8d3e7506b631c9582c251a2c0b8a28855af3f76719b12b534c6edf952748d","impliedFormat":1},{"version":"1ca69210cc42729e7ca97d3a9ad48f2e9cb0042bada4075b588ae5387debd318","impliedFormat":1},{"version":"f5ebe66baaf7c552cfa59d75f2bfba679f329204847db3cec385acda245e574e","impliedFormat":1},{"version":"ed59add13139f84da271cafd32e2171876b0a0af2f798d0c663e8eeb867732cf","affectsGlobalScope":true,"impliedFormat":1},{"version":"b7c5e2ea4a9749097c347454805e933844ed207b6eefec6b7cfd418b5f5f7b28","impliedFormat":1},{"version":"b1810689b76fd473bd12cc9ee219f8e62f54a7d08019a235d07424afbf074d25","impliedFormat":1},{"version":"a7ca8df4f2931bef2aa4118078584d84a0b16539598eaadf7dce9104dfaa381c","impliedFormat":1},{"version":"10073cdcf56982064c5337787cc59b79586131e1b28c106ede5bff362f912b70","impliedFormat":99},{"version":"72950913f4900b680f44d8cab6dd1ea0311698fc1eefb014eb9cdfc37ac4a734","impliedFormat":1},{"version":"751764bb94219b4ce8f5475dc35d3de2e432fea01a0c9610cd7f69ad05e398c6","impliedFormat":1},{"version":"ee70b8037ecdf0de6c04f35277f253663a536d7e38f1539d270e4e916d225a3f","affectsGlobalScope":true,"impliedFormat":1},{"version":"a660aa95476042d3fdcc1343cf6bb8fdf24772d31712b1db321c5a4dcc325434","impliedFormat":1},{"version":"36977c14a7f7bfc8c0426ae4343875689949fb699f3f84ecbe5b300ebf9a2c55","impliedFormat":1},{"version":"ff0a83c9a0489a627e264ffcb63f2264b935b20a502afa3a018848139e3d8575","impliedFormat":99},{"version":"161c8e0690c46021506e32fda85956d785b70f309ae97011fd27374c065cac9b","affectsGlobalScope":true,"impliedFormat":1},{"version":"f582b0fcbf1eea9b318ab92fb89ea9ab2ebb84f9b60af89328a91155e1afce72","impliedFormat":1},{"version":"402e5c534fb2b85fa771170595db3ac0dd532112c8fa44fc23f233bc6967488b","impliedFormat":1},{"version":"52dcc257df5119fb66d864625112ce5033ac51a4c2afe376a0b299d2f7f76e4a","impliedFormat":1},{"version":"e5bab5f871ef708d52d47b3e5d0aa72a08ee7a152f33931d9a60809711a2a9a3","impliedFormat":1},{"version":"e16dc2a81595736024a206c7d5c8a39bfe2e6039208ef29981d0d95434ba8fcf","impliedFormat":1},{"version":"cc4a4903fb698ca1d961d4c10dce658aa3a479faf40509d526f122b044eaf6a4","impliedFormat":1},{"version":"19ee8416e6473ed6c7adb868fa796b5653cf0fa2a337658e677eaa0d134388c3","impliedFormat":1},{"version":"1328ab4e442614b28cdb3d4b414cf68325c0da0dca07287a338d0654b7a00261","impliedFormat":1},{"version":"a039dc21f045919f3cbee2ec13812cc6cc3eebc99dae4be00973230f468d19a6","impliedFormat":1},{"version":"3fbe57af01460e49dcd29df55d6931e1672bc6f1be0fb073d11410bc16f9037d","impliedFormat":1},{"version":"f760be449e8562ec5c09bb5187e8e1eabf3c113c0c58cddda53ef8c69f3e2131","impliedFormat":1},{"version":"44325ed13294fce6ab825b82947bbeed2611db7dad9d9135260192f375e5a189","impliedFormat":1},{"version":"e392e8fb5b514eafc585601c1d781485aa6dd6a320e75daf1064a4c6918a1b45","impliedFormat":1},{"version":"46e4a36e8ddbdfb4e7330e11c81c970dc8b218611df9183d39c41c5f8c653b55","impliedFormat":1},{"version":"370bde134aa8c2abc926d0e99d3a4d5d5dba65c6ee65459137e4f02670cbf841","impliedFormat":1},{"version":"6332f565867cf4a740a70e30f31cefba37ef7cebcf74f22eab8d744fde6d193e","impliedFormat":1},{"version":"2977b7884aedc895a1d0c9c210c7cf3272c29d6959a08a6fa3ff71e0aff08175","impliedFormat":1},{"version":"17f2922d41ddd032830a91371c948cd9ce903b35c95adca72271a54584f19b0b","impliedFormat":1},{"version":"3eed76ede2a1a14d7c9bb0a642041282dcc264811139d3dd275c9fe14efc9840","impliedFormat":1},{"version":"e3cf0611709328b449ec13f8c436712d62003620ce480139fae46ce001c2ee9f","impliedFormat":1},{"version":"8d369483f0c2b9ee388129cfdb6a43bc8112b377e86a41884bd06e19ce04f4c1","impliedFormat":99},{"version":"960bd764c62ac43edc24eaa2af958a4b4f1fa5d27df5237e176d0143b36a39c6","affectsGlobalScope":true,"impliedFormat":1},{"version":"3fd8a5aefd8c3feb3936ca66f5aa89dff7bf6e6537b4158dbd0f6e0d65ed3b9e","impliedFormat":1},{"version":"a18642ddf216f162052a16cba0944892c4c4c977d3306a87cb673d46abbb0cbf","impliedFormat":1},{"version":"41c41c6e90133bb2a14f7561f29944771886e5535945b2b372e2f6ed6987746e","impliedFormat":1},{"version":"4ec16d7a4e366c06a4573d299e15fe6207fc080f41beac5da06f4af33ea9761e","impliedFormat":1},{"version":"59f8dc89b9e724a6a667f52cdf4b90b6816ae6c9842ce176d38fcc973669009e","affectsGlobalScope":true,"impliedFormat":1},{"version":"e4af494f7a14b226bbe732e9c130d8811f8c7025911d7c58dd97121a85519715","impliedFormat":1},{"version":"cbb1c5ba5dbabe42c19ca31b83e48fec95895484fe1d1a8fb649b69ea224c5b8","impliedFormat":99},{"version":"45cec9a1ba6549060552eead8959d47226048e0b71c7d0702ae58b7e16a28912","impliedFormat":99},{"version":"6907b09850f86610e7a528348c15484c1e1c09a18a9c1e98861399dfe4b18b46","impliedFormat":99},{"version":"12deea8eaa7a4fc1a2908e67da99831e5c5a6b46ad4f4f948fd4759314ea2b80","impliedFormat":99},{"version":"f0a8b376568a18f9a4976ecb0855187672b16b96c4df1c183a7e52dc1b5d98e8","impliedFormat":99},{"version":"8124828a11be7db984fcdab052fd4ff756b18edcfa8d71118b55388176210923","impliedFormat":99},{"version":"092944a8c05f9b96579161e88c6f211d5304a76bd2c47f8d4c30053269146bc8","impliedFormat":99},{"version":"b34b5f6b506abb206b1ea73c6a332b9ee9c8c98be0f6d17cdbda9430ecc1efab","impliedFormat":99},{"version":"75d4c746c3d16af0df61e7b0afe9606475a23335d9f34fcc525d388c21e9058b","impliedFormat":99},{"version":"fa959bf357232201c32566f45d97e70538c75a093c940af594865d12f31d4912","impliedFormat":99},{"version":"d2c52abd76259fc39a30dfae70a2e5ce77fd23144457a7ff1b64b03de6e3aec7","impliedFormat":99},{"version":"e6233e1c976265e85aa8ad76c3881febe6264cb06ae3136f0257e1eab4a6cc5a","impliedFormat":99},{"version":"f73e2335e568014e279927321770da6fe26facd4ac96cdc22a56687f1ecbb58e","impliedFormat":99},{"version":"317878f156f976d487e21fd1d58ad0461ee0a09185d5b0a43eedf2a56eb7e4ea","impliedFormat":99},{"version":"324ac98294dab54fbd580c7d0e707d94506d7b2c3d5efe981a8495f02cf9ad96","impliedFormat":99},{"version":"9ec72eb493ff209b470467e24264116b6a8616484bca438091433a545dfba17e","impliedFormat":99},{"version":"d6ee22aba183d5fc0c7b8617f77ee82ecadc2c14359cc51271c135e23f6ed51f","impliedFormat":99},{"version":"49747416f08b3ba50500a215e7a55d75268b84e31e896a40313c8053e8dec908","impliedFormat":99},{"version":"5e91172586d1be7d1508d1a40c8bf76161f6f4179d1c25158a20599f9fb26a66","impliedFormat":99},{"version":"09d215b379a93f8cbb6caf9e9f5d7b4d48042295ee697e7e4771288c7917a21e","impliedFormat":99},{"version":"427fe2004642504828c1476d0af4270e6ad4db6de78c0b5da3e4c5ca95052a99","impliedFormat":1},{"version":"2eeffcee5c1661ddca53353929558037b8cf305ffb86a803512982f99bcab50d","impliedFormat":99},{"version":"9afb4cb864d297e4092a79ee2871b5d3143ea14153f62ef0bb04ede25f432030","affectsGlobalScope":true,"impliedFormat":99},{"version":"891694d3694abd66f0b8872997b85fd8e52bc51632ce0f8128c96962b443189f","impliedFormat":99},{"version":"69bf2422313487956e4dacf049f30cb91b34968912058d244cb19e4baa24da97","impliedFormat":99},{"version":"971a2c327ff166c770c5fb35699575ba2d13bba1f6d2757309c9be4b30036c8e","impliedFormat":99},{"version":"4f45e8effab83434a78d17123b01124259fbd1e335732135c213955d85222234","impliedFormat":99},{"version":"7bd51996fb7717941cbe094b05adc0d80b9503b350a77b789bbb0fc786f28053","impliedFormat":99},{"version":"b62006bbc815fe8190c7aee262aad6bff993e3f9ade70d7057dfceab6de79d2f","impliedFormat":99},{"version":"ab80d2cb170a92c61ca8c822d0db0fc50eb15c7c6cf1a24cb01852a5a15a7db8","impliedFormat":99},{"version":"210955af8573671abebb6e1391d134d82fbcff130aa68922ea1e839f8f48a0ad","impliedFormat":99},{"version":"78a8f2e95e2904914c509e4bbc68e626f8b8ff8f30ca96cffc7a795fa17394f5","impliedFormat":99},{"version":"7bbff6783e96c691a41a7cf12dd5486b8166a01b0c57d071dbcfca55c9525ec4","impliedFormat":99},{"version":"30aff351d39a530bb1e66291c2d1a3deb2e3437348415d8dc89f3005e59bc698","signature":"4b96dd19fd2949d28ce80e913412b0026dc421e5bf6c31d87c7b5eb11b5753b4"},{"version":"8ef0b457802d1883c0c185f90610a8aaf33283250633a31853de01bb45565b7b","signature":"b730dbc27807d6a94494d69e0154827379b8ed4606f3dd3a4584a1e2242b1e53"},{"version":"764fec087122d840f12f9f24e1dc1e4cc2dcb222f3d13d2a498bf332fbe460d7","impliedFormat":1},{"version":"92ee216a93c16d3724ce70c9a20f56b05659c7c67b86827d481ff89c1a5d23d9","impliedFormat":1},{"version":"05d1a8f963258d75216f13cf313f27108f83a8aa2bff482da356f2bfdfb59ab2","impliedFormat":1},{"version":"1a848ab32f6114131218358c47b81a2b6fd71789d3c9cda62a6218194cba5ecb","impliedFormat":1},{"version":"b1fb9f004934ac2ae15d74b329ac7f4c36320ff4ada680a18cc27e632b6baa82","impliedFormat":1},{"version":"f13c5c100055437e4cf58107e8cbd5bb4fa9c15929f7dc97cb487c2e19c1b7f6","impliedFormat":1},{"version":"ee423b86c3e071a3372c29362c2f26adc020a2d65bcbf63763614db49322234e","impliedFormat":1},{"version":"77d30b82131595dbb9a21c0e1e290247672f34216e1af69a586e4b7ad836694e","impliedFormat":1},{"version":"78d486dac53ad714133fc021b2b68201ba693fab2b245fda06a4fc266cead04a","impliedFormat":1},{"version":"06414fbc74231048587dedc22cd8cac5d80702b81cd7a25d060ab0c2f626f5c8","impliedFormat":1},{"version":"b8533e19e7e2e708ac6c7a16ae11c89ffe36190095e1af146d44bb54b2e596a1","impliedFormat":1},{"version":"b5f70f31ef176a91e4a9f46074b763adc321cd0fdb772c16ca57b17266c32d19","impliedFormat":1},{"version":"169035d6d96186b82cd6456a1dd0dca511abf191d4f59d8ab012d9a5ce25c2e0","impliedFormat":1},{"version":"a78a334d8e93cf70b3dded844963e5d0c529546b12ec3a8668afa05f707e8222","impliedFormat":1},{"version":"503d068eb2b24456c90d15b2331a3cb04aa03b07d35699dac828d8c654d22c4e","impliedFormat":1},{"version":"c133900491138f79cecffb0dca079393b8e704899e4fcf9a9d8b399f8b91c3db","impliedFormat":1},{"version":"0b43cdc862f70c9b37bca929513eab72ab764845ea5d83cef47d148a1ff3f0d5","impliedFormat":1},{"version":"4a193963d67a56bff9331232db719a9dc71ff8a7795cb9de2f047d0de214d709","impliedFormat":1},{"version":"59ce6c57619857ab7dfc367715a3dbf300880cd16e7c84c12ac4ba1e39cdee63","impliedFormat":1},{"version":"5a1c84eb2e4797d0a021fcb4033a1189941265d03d6a1930bf6132143ee4065d","impliedFormat":1},{"version":"d38293b3bcb73ba1c719ba50497859a2f37fa64a6de7f22eeb32ae9f3b1bcefc","impliedFormat":1},{"version":"d67484f1551a676c22ebb9be78723e839d630d6459794e32cc050aaab7641621","impliedFormat":1},{"version":"5eaf2e0f6ea59e43507586de0a91d17d0dd5c59f3919e9d12cbab0e5ed9d2d77","impliedFormat":1},{"version":"be97b1340a3f72edf8404d1d717df2aac5055faaff6c99c24f5a2b2694603745","impliedFormat":1},{"version":"1754df61456e51542219ee17301566ac439115b2a1e5da1a0ffb2197e49ccefe","impliedFormat":1},{"version":"2c90cb5d9288d3b624013a9ca40040b99b939c3a090f6bdca3b4cfc6b1445250","impliedFormat":1},{"version":"3c6d4463866f664a5f51963a2849cb844f2203693be570d0638ee609d75fe902","impliedFormat":1},{"version":"752677ae7ebfef0fa54a6642b48ad671654223c3cde56259ce41292081ef0f0e","impliedFormat":1},{"version":"e88b42f282b55c669a8f35158449b4f7e6e2bccec31fd0d4adb4278928a57a89","impliedFormat":1},{"version":"2a1ed52adfc72556f4846b003a7e5a92081147beef55f27f99466aa6e2a28060","impliedFormat":1},{"version":"a4cf825c93bb52950c8cdc0b94c5766786c81c8ee427fc6774fafb16d0015035","impliedFormat":1},{"version":"4acc7fae6789948156a2faabc1a1ba36d6e33adb09d53bccf9e80248a605b606","impliedFormat":1},{"version":"f9613793aa6b7d742e80302e65741a339b529218ae80820753a61808a9761479","impliedFormat":1},{"version":"b182e2043a595bca73dd39930020425d55c5ff2aae1719d466dadeadc78273c7","impliedFormat":1},{"version":"5b978a20707f2b3b4fa39ca3ba9d0d12590bf4c4167beb3195bcd1421115256f","impliedFormat":1},{"version":"ed1ee10044d15a302d95b2634e6344b9f630528e3d5d7ce0eacad5958f0976c3","impliedFormat":1},{"version":"d18588312a7634d07e733e7960caf78d5b890985f321683b932d21d8d0d69b7b","impliedFormat":1},{"version":"d1dac573a182cc40c170e38a56eb661182fcd8981e9fdf2ce11df9decb73485d","impliedFormat":1},{"version":"c264198b19a4b9718508b49f61e41b6b17a0f9b8ecbf3752e052ad96e476e446","impliedFormat":1},{"version":"9c488a313b2974a52e05100f8b33829aa3466b2bc83e9a89f79985a59d7e1f95","impliedFormat":1},{"version":"e306488a76352d3dd81d8055abf03c3471e79a2e5f08baede5062fa9dca3451c","impliedFormat":1},{"version":"ad7bdd54cf1f5c9493b88a49dc6cec9bc9598d9e114fcf7701627b5e65429478","impliedFormat":1},{"version":"0d274e2a6f13270348818139fd53316e79b336e8a6cf4a6909997c9cbf47883c","impliedFormat":1},{"version":"78664c8054da9cce6148b4a43724195b59e8a56304e89b2651f808d1b2efb137","impliedFormat":1},{"version":"a0568a423bd8fee69e9713dac434b6fccc5477026cda5a0fc0af59ae0bfd325c","impliedFormat":1},{"version":"2a176a57e9858192d143b7ebdeca0784ee3afdb117596a6ee3136f942abe4a01","impliedFormat":1},{"version":"c8ee4dd539b6b1f7146fa5b2d23bca75084ae3b8b51a029f2714ce8299b8f98e","impliedFormat":1},{"version":"c58f688364402b45a18bd4c272fc17b201e1feddc45d10c86cb7771e0dc98a21","impliedFormat":1},{"version":"2904898efb9f6fabfe8dcbe41697ef9b6df8e2c584d60a248af4558c191ce5cf","impliedFormat":1},{"version":"c13189caa4de435228f582b94fb0aae36234cba2b7107df2c064f6f03fc77c3d","impliedFormat":1},{"version":"c97110dbaa961cf90772e8f4ee41c9105ee7c120cb90b31ac04bb03d0e7f95fb","impliedFormat":1},{"version":"c30864ed20a4c8554e8025a2715ba806799eba20aba0fd9807750e57ee2f838f","impliedFormat":1},{"version":"b182e2043a595bca73dd39930020425d55c5ff2aae1719d466dadeadc78273c7","impliedFormat":1},{"version":"5b978a20707f2b3b4fa39ca3ba9d0d12590bf4c4167beb3195bcd1421115256f","impliedFormat":1},{"version":"ed1ee10044d15a302d95b2634e6344b9f630528e3d5d7ce0eacad5958f0976c3","impliedFormat":1},{"version":"c30864ed20a4c8554e8025a2715ba806799eba20aba0fd9807750e57ee2f838f","impliedFormat":1},{"version":"e0cd55e58a4a210488e9c292cc2fc7937d8fc0768c4a9518645115fe500f3f44","impliedFormat":1},{"version":"d0307177b720b32a05c0bbb921420160cba0d3b6e81b1d961481d9abe4a17f60","impliedFormat":1},{"version":"8c25b00a675743d7a381cf6389ae9fbdce82bdc9069b343cb1985b4cd17b14be","impliedFormat":1},{"version":"e72b4624985bd8541ae1d8bde23614d2c44d784bbe51db25789a96e15bb7107a","impliedFormat":1},{"version":"0fb1449ca2990076278f0f9882aa8bc53318fc1fd7bfcbde89eed58d32ae9e35","impliedFormat":1},{"version":"c2625e4ba5ed1cb7e290c0c9eca7cdc5a7bebab26823f24dd61bf58de0b90ad6","impliedFormat":1},{"version":"a20532d24f25d5e73f05d63ad1868c05b813e9eb64ec5d9456bbe5c98982fd2e","impliedFormat":1},{"version":"d0307177b720b32a05c0bbb921420160cba0d3b6e81b1d961481d9abe4a17f60","impliedFormat":1},{"version":"7a17edfdf23eaaf79058134449c7e1e92c03e2a77b09a25b333a63a14dca17ed","impliedFormat":1},{"version":"e78c5d07684e1bb4bf3e5c42f757f2298f0d8b364682201b5801acf4957e4fad","impliedFormat":99},{"version":"4085598deeaff1b924e347f5b6e18cee128b3b52d6756b3753b16257284ceda7","impliedFormat":99},{"version":"c58272e3570726797e7db5085a8063143170759589f2a5e50387eff774eadc88","impliedFormat":1},{"version":"e3d8342c9f537a4ffcab951e5f469ac9c5ed1d6147e9e2a499184cf45ab3c77f","impliedFormat":1},{"version":"bc3ee6fe6cab0459f4827f982dbe36dcbd16017e52c43fec4e139a91919e0630","impliedFormat":1},{"version":"41e0d68718bf4dc5e0984626f3af12c0a5262a35841a2c30a78242605fa7678e","impliedFormat":1},{"version":"6c747f11c6b2a23c4c0f3f440c7401ee49b5f96a7fe4492290dfd3111418321b","impliedFormat":1},{"version":"a6b6c40086c1809d02eff72929d0fc8ec33313f1c929398c9837d31a3b05c66b","impliedFormat":1},{"version":"4e87a7aa00637afd8ccbaf04f8d7fdbd61eb51438e8bd6718debcfd7e55e5d14","impliedFormat":1},{"version":"55d70bb1ac14f79caae20d1b02a2ad09440a6b0b633d125446e89d25e7fd157d","impliedFormat":1},{"version":"c27930b3269795039e392a9b27070e6e9ba9e7da03e6185d4d99b47e0b7929bc","impliedFormat":1},{"version":"ae22e71c8ebcf07a6ca7efb968a9bcdbfb1c2919273901151399c576b2bed4b8","impliedFormat":1},{"version":"47f30de14aa377b60f0cd43e95402d03166d3723f42043ae654ce0a25bc1b321","impliedFormat":1},{"version":"0edcda97d090708110daea417cfd75d6fd0c72c9963fec0a1471757b14f28ae5","impliedFormat":1},{"version":"f730a314c6e3cb76b667c2c268cd15bde7068b90cb61d1c3ab93d65b878d3e76","impliedFormat":1},{"version":"c60096bf924a5a44f792812982e8b5103c936dd7eec1e144ded38319a282087e","impliedFormat":1},{"version":"f9acf26d0b43ad3903167ac9b5d106e481053d92a1f3ab9fe1a89079e5f16b94","impliedFormat":1},{"version":"014e069a32d3ac6adde90dd1dfdb6e653341595c64b87f5b1b3e8a7851502028","impliedFormat":1},{"version":"ac46b462f6ae83bee6d3f61176f8da916c6fd43774b79142a6d1508745fbd152","impliedFormat":1},{"version":"ac46b462f6ae83bee6d3f61176f8da916c6fd43774b79142a6d1508745fbd152","impliedFormat":1},{"version":"ac46b462f6ae83bee6d3f61176f8da916c6fd43774b79142a6d1508745fbd152","impliedFormat":1},{"version":"ac46b462f6ae83bee6d3f61176f8da916c6fd43774b79142a6d1508745fbd152","impliedFormat":1},{"version":"ac46b462f6ae83bee6d3f61176f8da916c6fd43774b79142a6d1508745fbd152","impliedFormat":1},{"version":"86c8f1a471f03ac5232073884775b77d7673516a1eff3b9c4a866c64a5b1693a","impliedFormat":1},{"version":"5545aa84048e8ae5b22838a2b437abd647c58acc43f2f519933cd313ce84476c","impliedFormat":1},{"version":"0d2af812b3894a2daa900a365b727a58cc3cc3f07eb6c114751f9073c8031610","impliedFormat":1},{"version":"30be069b716d982a2ae943b6a3dab9ae1858aa3d0a7218ab256466577fd7c4ca","impliedFormat":1},{"version":"797b6a8e5e93ab462276eebcdff8281970630771f5d9038d7f14b39933e01209","impliedFormat":1},{"version":"549232dd97130463d39dac754cf7faa95c4c71511d11dd9b1d37c225bf675469","impliedFormat":1},{"version":"747779d60c02112794ca81f1641628387d68c8e406be602b87af9ae755d46fd6","impliedFormat":1},{"version":"0a22c78fc4cbf85f27e592bea1e7ece94aadf3c6bd960086f1eff2b3aedf2490","impliedFormat":1},{"version":"fea1857ed9f8e33be23a5a3638c487b25bb44b21032c6148144883165ad10fb0","impliedFormat":1},{"version":"d0cffd20a0deb57297c2bd8c4cd381ed79de7babf9d81198e28e3f56d9aff0db","impliedFormat":1},{"version":"77876c19517f1a79067a364423ba9e4f3c6169d01011320a6fde85a95e8f8f5c","impliedFormat":1},{"version":"84cf3736a269c74c711546db9a8078ad2baaf12e9edd5b33e30252c6fb59b305","impliedFormat":1},{"version":"8309b403027c438254d78ca2bb8ddd04bfaf70260a9db37219d9a49ad6df5d80","impliedFormat":1},{"version":"6a9d4bd7a551d55e912764633a086af149cc937121e011f60f9be60ee5156107","impliedFormat":1},{"version":"f1cea620ee7e602d798132c1062a0440f9d49a43d7fafdc5bdc303f6d84e3e70","impliedFormat":1},{"version":"5769d77cb83e1f931db5e3f56008a419539a1e02befe99a95858562e77907c59","impliedFormat":1},{"version":"1607892c103374a3dc1f45f277b5362d3cb3340bfe1007eec3a31b80dd0cf798","impliedFormat":1},{"version":"402da75bfdaf5b2cf388450cb56a4c5ba2ed67bc9f930eba0e7ce7fc57cddf11","impliedFormat":1},{"version":"220aafeafa992aa95f95017cb6aecea27d4a2b67bb8dd2ce4f5c1181e8d19c21","impliedFormat":1},{"version":"a71dd28388e784bf74a4bc40fd8170fa4535591057730b8e0fef4820cf4b4372","impliedFormat":1},{"version":"0e411566240d81c51c2d95e5f3fa2e8a35c3e7bbe67a43f4eb9c9a2912fdff05","impliedFormat":1},{"version":"4e4325429d6a967ef6aa72ca24890a7788a181d28599fe1b3bb6730a6026f048","impliedFormat":1},{"version":"dcbb4c3abdc5529aeda5d6b0a835d8a0883da2a76e9484a4f19e254e58faf3c6","impliedFormat":1},{"version":"0d81307f711468869759758160975dee18876615db6bf2b8f24188a712f1363b","impliedFormat":1},{"version":"22ddd9cd17d33609d95fb66ece3e6dff2e7b21fa5a075c11ef3f814ee9dd35c7","impliedFormat":1},{"version":"cb43ede907c32e48ba75479ca867464cf61a5f962c33712436fee81431d66468","impliedFormat":1},{"version":"549232dd97130463d39dac754cf7faa95c4c71511d11dd9b1d37c225bf675469","impliedFormat":1},{"version":"1e89d5e4c50ca57947247e03f564d916b3b6a823e73cde1ee8aece5df9e55fc9","impliedFormat":1},{"version":"8538eca908e485ccb8b1dd33c144146988a328aaa4ffcc0a907a00349171276e","impliedFormat":1},{"version":"7b878f38e8233e84442f81cc9f7fb5554f8b735aca2d597f7fe8a069559d9082","impliedFormat":1},{"version":"bf7d8edbd07928d61dbab4047f1e47974a985258d265e38a187410243e5a6ab9","impliedFormat":1},{"version":"747779d60c02112794ca81f1641628387d68c8e406be602b87af9ae755d46fd6","impliedFormat":1},{"version":"40b33243bbbddfe84dbdd590e202bdba50a3fe2fbaf138b24b092c078b541434","impliedFormat":1},{"version":"fea1857ed9f8e33be23a5a3638c487b25bb44b21032c6148144883165ad10fb0","impliedFormat":1},{"version":"f21d84106071ae3a54254bcabeaf82174a09b88d258dd32cafb80b521a387d42","impliedFormat":1},{"version":"21129c4f2a3ae3f21f1668adfda1a4103c8bdd4f25339a7d7a91f56a4a0c8374","impliedFormat":1},{"version":"7c4cf13b05d1c64ce1807d2e5c95fd657f7ef92f1eeb02c96262522c5797f862","impliedFormat":1},{"version":"eebe1715446b4f1234ce2549a8c30961256784d863172621eb08ae9bed2e67a3","impliedFormat":1},{"version":"64ad3b6cbeb3e0d579ebe85e6319d7e1a59892dada995820a2685a6083ea9209","impliedFormat":1},{"version":"5ebdc5a83f417627deff3f688789e08e74ad44a760cdc77b2641bb9bb59ddd29","impliedFormat":1},{"version":"a514beab4d3bc0d7afc9d290925c206a9d1b1a6e9aa38516738ce2ff77d66000","impliedFormat":1},{"version":"d80212bdff306ee2e7463f292b5f9105f08315859a3bdc359ba9daaf58bd9213","impliedFormat":1},{"version":"86b534b096a9cc35e90da2d26efbcb7d51bc5a0b2dde488b8c843c21e5c4701b","impliedFormat":1},{"version":"75519029c9e9389852d22714aec5956e00f090d18082e49f21d2875d554ebd26","impliedFormat":1},{"version":"e46d7758d8090d9b2c601382610894d71763a9909efb97b1eebbc6272d88d924","impliedFormat":1},{"version":"03af1b2c6ddc2498b14b66c5142a7876a8801fcac9183ae7c35aec097315337a","impliedFormat":1},{"version":"294b7d3c2afc0d8d3a7e42f76f1bac93382cb264318c2139ec313372bbfbde4f","impliedFormat":1},{"version":"a7bc0f0fd721b5da047c9d5a202c16be3f816954ad65ab684f00c9371bc8bac2","impliedFormat":1},{"version":"4bf7b966989eb48c30e0b4e52bfe7673fb7a3fb90747bdc5324637fc51505cd1","impliedFormat":1},{"version":"468308e0d01d8c073a6c442b6cbd5f0f7fcb68fbeabd3c30b0719cda2f5bfc38","impliedFormat":1},{"version":"c2d3538fabf7d43abd7599ff74c372800130e67674eb50b371a6c53646d2b977","impliedFormat":1},{"version":"10e006d13225983120773231f9fcc0f747a678056161db5c3c134697d0b4cb60","impliedFormat":1},{"version":"b456eb9cb3ff59d2ad86d53c656a0f07164e9dccbc0f09ac6a6f234dc44714ea","impliedFormat":1},{"version":"0fff2dbabbb30a467bbfef04d44819cb0b1baa84e669b46d4682c9d70ba11605","impliedFormat":1},{"version":"8baf3ec31869d4e82684fe062c59864b9d6d012b9105252e5697e64212e38b74","impliedFormat":1},{"version":"36a9827e64fa8e2af7d4fd939bf29e7ae6254fa9353ccebd849c894a4fd63e1b","impliedFormat":1},{"version":"3af8cee96336dd9dc44b27d94db5443061ff8a92839f2c8bbcc165ca3060fa6c","impliedFormat":1},{"version":"85d786a0accda19ef7beb6ae5a04511560110faa9c9298d27eaa4d44778fbf9e","impliedFormat":1},{"version":"7362683317d7deaa754bbf419d0a4561ee1d9b40859001556c6575ce349d95ea","impliedFormat":1},{"version":"408b6e0edb9d02acaf1f2d9f589aa9c6e445838b45c3bfa15b4bb98dc1453dc4","impliedFormat":1},{"version":"f8faa497faf04ffba0dd21cf01077ae07f0db08035d63a2e69838d173ae305bc","impliedFormat":1},{"version":"f8981c8de04809dccb993e59de5ea6a90027fcb9a6918701114aa5323d6d4173","impliedFormat":1},{"version":"7c9c89fd6d89c0ad443f17dc486aa7a86fa6b8d0767e1443c6c63311bdfbd989","impliedFormat":1},{"version":"a3486e635db0a38737d85e26b25d5fda67adef97db22818845e65a809c13c821","impliedFormat":1},{"version":"7c2918947143409b40385ca24adce5cee90a94646176a86de993fcdb732f8941","impliedFormat":1},{"version":"bdbf3acd48d637f947a0ef48c2301898e2eb8e5f9c1ad1d17b1e3f0d0ce3764c","impliedFormat":1},{"version":"55a36a053bfd464be800af2cd1b3ed83c6751277125786d62870bf159280b280","impliedFormat":1},{"version":"a8e7c075b87fda2dd45aa75d91f3ccb07bec4b3b1840bd4da4a8c60e03575cd2","impliedFormat":1},{"version":"f7b193e858e6c5732efa80f8073f5726dc4be1216450439eb48324939a7dd2be","impliedFormat":1},{"version":"f971e196cdf41219f744e8f435d4b7f8addacd1fbe347c6d7a7d125cd0eaeb99","impliedFormat":1},{"version":"fd38ff4bedf99a1cd2d0301d6ffef4781be7243dfbba1c669132f65869974841","impliedFormat":1},{"version":"e41e32c9fc04b97636e0dc89ecffe428c85d75bfc07e6b70c4a6e5e556fe1d6b","impliedFormat":1},{"version":"3a9522b8ed36c30f018446ec393267e6ce515ca40d5ee2c1c6046ce801c192cd","impliedFormat":1},{"version":"0e781e9e0dcd9300e7d213ce4fdec951900d253e77f448471d1bc749bd7f5f7c","impliedFormat":1},{"version":"bf8ea785d007b56294754879d0c9e7a9d78726c9a1b63478bf0c76e3a4446991","impliedFormat":1},{"version":"dbb439938d2b011e6b5880721d65f51abb80e09a502355af16de4f01e069cd07","impliedFormat":1},{"version":"f94a137a2b7c7613998433ca16fb7f1f47e4883e21cadfb72ff76198c53441a6","impliedFormat":1},{"version":"8296db5bbdc7e56cabc15f94c637502827c49af933a5b7ed0b552728f3fcfba8","impliedFormat":1},{"version":"ad46eedfff7188d19a71c4b8999184d1fb626d0379be2843d7fc20faea63be88","impliedFormat":1},{"version":"9ebac14f8ee9329c52d672aaf369be7b783a9685e8a7ab326cd54a6390c9daa6","impliedFormat":1},{"version":"dee395b372e64bfd6e55df9a76657b136e0ba134a7395e46e3f1489b2355b5b0","impliedFormat":1},{"version":"cf0ce107110a4b7983bacca4483ea8a1eac5e36901fc13c686ebef0ffbcbbacd","impliedFormat":1},{"version":"a4fc04fdc81ff1d4fdc7f5a05a40c999603360fa8c493208ccee968bd56e161f","impliedFormat":1},{"version":"8a2a61161d35afb1f07d10dbef42581e447aaeececc4b8766450c9314b6b4ee7","impliedFormat":1},{"version":"b817f19d56f68613a718e41d3ed545ecfd2c3096a0003d6a8e4f906351b3fb7d","impliedFormat":1},{"version":"bbdf5516dc4d55742ab23e76e0f196f31a038b4022c8aa7944a0964a7d36985e","impliedFormat":1},{"version":"981cca224393ac8f6b42c806429d5c5f3506e65edf963aa74bcef5c40b28f748","impliedFormat":1},{"version":"7239a60aab87af96a51cd8af59c924a55c78911f0ab74aa150e16a9da9a12e4f","impliedFormat":1},{"version":"258cbdcac1da6d114455af3ac7ca87eeff074001765e3b154dd57f25bda5fcb5","impliedFormat":1},{"version":"022e48d4e1ebd512e3fa5c3a321262ce05b53e8773fdb4b7de80d5288720993a","impliedFormat":1},{"version":"95fab99f991a8fb9514b3c9282bfa27ffc4b7391c8b294f2d8bf2ae0a092f120","impliedFormat":1},{"version":"62e46dac4178ba57a474dad97af480545a2d72cd8c0d13734d97e2d1481dbf06","impliedFormat":1},{"version":"3f3bc27ed037f93f75f1b08884581fb3ed4855950eb0dc9be7419d383a135b17","impliedFormat":1},{"version":"55fef00a1213f1648ac2e4becba3bb5758c185bc03902f36150682f57d2481d2","impliedFormat":1},{"version":"6fe2c13736b73e089f2bb5f92751a463c5d3dc6efb33f4494033fbd620185bff","impliedFormat":1},{"version":"6e249a33ce803216870ec65dc34bbd2520718c49b5a2d9afdee7e157b87617a2","impliedFormat":1},{"version":"e58f83151bb84b1c21a37cbc66e1e68f0f1cf60444b970ef3d1247cd9097fd94","impliedFormat":1},{"version":"83e46603ea5c3df5ae2ead2ee7f08dcb60aa071c043444e84675521b0daf496b","impliedFormat":1},{"version":"8baf3ec31869d4e82684fe062c59864b9d6d012b9105252e5697e64212e38b74","impliedFormat":1},{"version":"84de46efa2d75741d9d9bbdfdfe9f214b20f00d3459af52ef574d9f4f0dcc73a","impliedFormat":1},{"version":"fb02e489b353b21e32d32ea8aef49bdbe34d6768864cc40b6fb46727ac9d953a","impliedFormat":1},{"version":"c6ade0291b5eef6bf8a014c45fbac97b24eeae623dbacbe72afeab2b93025aa2","impliedFormat":1},{"version":"2c5e9ca373f23c9712da12f8efa976e70767a81eb3802e82182a2d1a3e4b190e","impliedFormat":1},{"version":"06bac29b70233e8c57e5eb3d2bda515c4bea6c0768416cd914b0336335f7069b","impliedFormat":1},{"version":"fded99673b5936855b8b914c5bdf6ada1f7443c773d5a955fa578ff257a6a70c","impliedFormat":1},{"version":"8e0e4155cdf91f9021f8929d7427f701214f3ba5650f51d8067c76af168a5b99","impliedFormat":1},{"version":"ef344f40acc77eafa0dd7a7a1bc921e0665b8b6fc70aeea7d39e439e9688d731","impliedFormat":1},{"version":"36a1dffdbb2d07df3b65a3ddda70f446eb978a43789c37b81a7de9338daff397","impliedFormat":1},{"version":"bcb2c91f36780ff3a32a4b873e37ebf1544fb5fcc8d6ffac5c0bf79019028dae","impliedFormat":1},{"version":"d13670a68878b76d725a6430f97008614acba46fcac788a660d98f43e9e75ba4","impliedFormat":1},{"version":"7a03333927d3cd3b3c3dd4e916c0359ab2e97de6fd2e14c30f2fb83a9990792e","impliedFormat":1},{"version":"fc6fe6efb6b28eb31216bd2268c1bc5c4c4df3b4bc85013e99cd2f462e30b6fc","impliedFormat":1},{"version":"6cc13aa49738790323a36068f5e59606928457691593d67106117158c6091c2f","impliedFormat":1},{"version":"68255dbc469f2123f64d01bfd51239f8ece8729988eec06cea160d2553bcb049","impliedFormat":1},{"version":"c3bd50e21be767e1186dacbd387a74004e07072e94e2e76df665c3e15e421977","impliedFormat":1},{"version":"3106b08c40971596efc54cc2d31d8248f58ba152c5ec4d741daf96cc0829caea","impliedFormat":1},{"version":"219d9a049a24c69d917d0d87d09edc4d009d527e6eb77b7eab97e560f8e59039","impliedFormat":1},{"version":"6df4ad74f47da1c7c3445b1dd7c63bd3d01bbc0eb31aaebdea371caa57192ce5","impliedFormat":1},{"version":"dcc26e727c39367a46931d089b13009b63df1e5b1c280b94f4a32409ffd3fa36","impliedFormat":1},{"version":"36979d4a469985635dd7539f25facd607fe1fb302ad1c6c2b3dce036025419e8","impliedFormat":1},{"version":"670a1df5b6f9df0d001d22620a50776153e04f8541d5b17298a6b8afced71e20","impliedFormat":1},{"version":"7e138dc97e3b2060f77c4b6ab3910b00b7bb3d5f8d8a747668953808694b1938","impliedFormat":1},{"version":"5b6d83c94236cf3e9e19315cc6d62b9787253c73a53faea34ead697863f81447","impliedFormat":1},{"version":"6d448f6bfeeef15718b82fd6ac9ae8871f7843a3082c297339398167f8786b2e","impliedFormat":1},{"version":"55cdcbc0af1398c51f01b48689e3ce503aa076cc57639a9351294e23366a401d","impliedFormat":1},{"version":"7e553f3b746352b0200dd91788b479a2b037a6a7d8d04aa6d002da09259f5687","impliedFormat":1},{"version":"32615eb16e819607b161e2561a2cd75ec17ac6301ba770658d5a960497895197","impliedFormat":1},{"version":"ac14cc1d1823cec0bf4abc1d233a995b91c3365451bf1859d9847279a38f16ee","impliedFormat":1},{"version":"f1142315617ac6a44249877c2405b7acda71a5acb3d4909f4b3cbcc092ebf8bd","impliedFormat":1},{"version":"3356f7498c6465efb74d0a6a5518b6b8f27d9e096abd140074fd24e9bd483dbd","impliedFormat":1},{"version":"73a0ee6395819b063df4b148211985f2e1442945c1a057204cf4cf6281760dc3","affectsGlobalScope":true,"impliedFormat":1},{"version":"d05d8c67116dceafc62e691c47ac89f8f10cf7313cd1b2fb4fe801c2bf1bb1a7","impliedFormat":1},{"version":"3c5bb5207df7095882400323d692957e90ec17323ccff5fd5f29a1ecf3b165d0","impliedFormat":1},{"version":"648ae35c81ab9cb90cb1915ede15527b29160cce0fa1b5e24600977d1ba11543","impliedFormat":1},{"version":"ddc0e8ba97c5ad221cf854999145186b917255b2a9f75d0de892f4d079fa0b5c","impliedFormat":1},{"version":"a9fc166c68c21fd4d4b4d4fb55665611c2196f325e9d912a7867fd67e2c178da","impliedFormat":1},{"version":"2f60a32bb6a05a722c42bb9709f917bb37f2484375367eb9c03bdafd9de42daf","impliedFormat":1},{"version":"d571fae704d8e4d335e30b9e6cf54bcc33858a60f4cf1f31e81b46cf82added4","impliedFormat":1},{"version":"b9406c40955c0dcf53a275697c4cddd7fe3fca35a423ade2ac750f3ba17bd66d","impliedFormat":1},{"version":"d7eb2711e78d83bc0a2703574bf722d50c76ef02b8dd6f8a8a9770e0a0f7279f","impliedFormat":1},{"version":"323127b2ac397332f21e88cd8e04c797ea6a48dedef19055cbd2fc467a3d8c84","impliedFormat":1},{"version":"f17613239e95ffcfa69fbba3b0c99b741000699db70d5e8feea830ec4bba641d","impliedFormat":1},{"version":"fff6aa61f22d8adb4476adfd8b14473bcdb6d1c9b513e1bfff14fe0c165ced3c","impliedFormat":1},{"version":"bdf97ac70d0b16919f2713613290872be2f3f7918402166571dbf7ce9cdc8df4","impliedFormat":1},{"version":"8667f65577822ab727b102f83fcd65d9048de1bf43ab55f217fbf22792dafafb","impliedFormat":1},{"version":"58f884ab71742b13c59fc941e2d4419aaf60f9cf7c1ab283aa990cb7f7396ec3","impliedFormat":1},{"version":"2c7720260175e2052299fd1ce10aa0a641063ae7d907480be63e8db508e78eb3","impliedFormat":1},{"version":"506823d1acd8978aa95f9106dfe464b65bdcd1e1539a994f4a9272db120fc832","impliedFormat":1},{"version":"d6a30821e37d7b935064a23703c226506f304d8340fa78c23fc7ea1b9dc57436","impliedFormat":1},{"version":"94a8650ade29691f97b9440866b6b1f77d4c1d0f4b7eea4eb7c7e88434ded8c7","impliedFormat":1},{"version":"bf26b847ce0f512536bd1f6d167363a3ae23621da731857828ce813c5cebc0db","impliedFormat":1},{"version":"87af268385a706c869adc8dd8c8a567586949e678ce615165ffcd2c9a45b74e7","impliedFormat":1},{"version":"affad9f315b72a6b5eb0d1e05853fa87c341a760556874da67643066672acdaf","impliedFormat":1},{"version":"6216f92d8119f212550c216e9bc073a4469932c130399368a707efb54f91468c","impliedFormat":1},{"version":"f7d86f9a241c5abf48794b76ac463a33433c97fc3366ce82dfa84a5753de66eb","impliedFormat":1},{"version":"01dab6f0b3b8ab86b120b5dd6a59e05fc70692d5fc96b86e1c5d54699f92989c","impliedFormat":1},{"version":"fe06598ceca505b18966573fbae84dfc1fda6f4e2adbb4369f3b3e2aef16bada","impliedFormat":1},{"version":"1ca7c8e38d1f5c343ab5ab58e351f6885f4677a325c69bb82d4cba466cdafeda","impliedFormat":1},{"version":"17c9ca339723ded480ca5f25c5706e94d4e96dcd03c9e9e6624130ab199d70e1","impliedFormat":1},{"version":"01aa1b58e576eb2586eedb97bcc008bbe663017cc49f0228da952e890c70319f","impliedFormat":1},{"version":"d57e64f90522b8cedf16ed8ba4785f64c297768ff145b95d3475114574c5b8e2","impliedFormat":1},{"version":"6a37dd9780f837be802142fe7dd70bb3f7279425422c893dd91835c0869cb7ac","impliedFormat":1},{"version":"167456e78d7c3a638170cbbca07a9b02df2bee81fbd995e2a0b1719a4e34f16b","impliedFormat":1},{"version":"22e1e1b1e1df66f6a1fdb7be8eb6b1dbb3437699e6b0115fbbae778c7782a39f","impliedFormat":1},{"version":"1a47e278052b9364140a6d24ef8251d433d958be9dd1a8a165f68cecea784f39","impliedFormat":1},{"version":"f7af9db645ecfe2a1ead1d675c1ccc3c81af5aa1a2066fe6675cd6573c50a7e3","impliedFormat":1},{"version":"3a9d25dcbb2cdcb7cd202d0d94f2ac8558558e177904cfb6eaff9e09e400c683","impliedFormat":1},{"version":"f65a5aa0e69c20579311e72e188d1df2ef56ca3a507d55ab3cb2b6426632fe9b","impliedFormat":1},{"version":"1144d12482a382de21d37291836a8aca0a427eb1dc383323e1ddbcf7ee829678","impliedFormat":1},{"version":"7a68ca7786ca810eb440ae1a20f5a0bd61f73359569d6faa4794509d720000e6","impliedFormat":1},{"version":"8f5f7f06129ffd3b4e4c4cf886faa54d85f79debd2651a17d9332b8289306b1a","impliedFormat":1},{"version":"5e97563ec4a9248074fdf7844640d3c532d6ce4f8969b15ccc23b059ed25a7c4","impliedFormat":1},{"version":"7d67d7bd6308dc2fb892ae1c5dca0cdee44bfcfd0b5db2e66d4b5520c1938518","impliedFormat":1},{"version":"0ba8f23451c2724360edfa9db49897e808fa926efb8c2b114498e018ed88488f","impliedFormat":1},{"version":"3e618bc95ef3958865233615fbb7c8bf7fe23c7f0ae750e571dc7e1fefe87e96","impliedFormat":1},{"version":"b901e1e57b1f9ce2a90b80d0efd820573b377d99337f8419fc46ee629ed07850","impliedFormat":1},{"version":"f720eb538fc2ca3c5525df840585a591a102824af8211ac28e2fd47aaf294480","impliedFormat":1},{"version":"ae9d0fa7c8ba01ea0fda724d40e7f181275c47d64951a13f8c1924ac958797bc","impliedFormat":1},{"version":"346d9528dcd89e77871a2decebd8127000958a756694a32512fe823f8934f145","impliedFormat":1},{"version":"d831ae2d17fd2ff464acbd9408638f06480cb8eb230a52d14e7105065713dca4","impliedFormat":1},{"version":"0a3dec0f968c9463b464a29f9099c1d5ca4cd3093b77a152f9ff0ae369c4d14b","impliedFormat":1},{"version":"a3fda2127b3185d339f80e6ccc041ce7aa85fcb637195b6c28ac6f3eed5d9d79","impliedFormat":1},{"version":"b238a1a5be5fbf8b5b85c087f6eb5817b997b4ce4ce33c471c3167a49524396c","impliedFormat":1},{"version":"ba849c0aba26864f2db0d29589fdcaec09da4ba367f127efdac1fcb4ef007732","impliedFormat":1},{"version":"ed10bc2be0faa78a2d1c8372f8564141c2360532e4567b81158ffe9943b8f070","impliedFormat":1},{"version":"b432f4a1f1d7e7601a870ab2c4cff33787de4aa7721978eb0eef543c5d7fe989","impliedFormat":1},{"version":"3f9d87ee262bd1620eb4fb9cb93ca7dc053b820f07016f03a1a653a5e9458a7a","impliedFormat":1},{"version":"a61d92e4a3c244f5b3f156def2671b10a727a777dc07e52c5e53e0ea2ddeefc8","impliedFormat":1},{"version":"de716ad71873d3d56e0d611a3d5c1eae627337c1f88790427c21f3cb47a7b6f7","impliedFormat":1},{"version":"a8072ae5bc04fea741eba493fddf84c8e6d242d2a847467428bf2cbab0b790a7","impliedFormat":1},{"version":"ce055e5bea657486c142afbf7c77538665e0cb9a2dc92a226c197d011be3e908","impliedFormat":1},{"version":"673b1fc746c54e7e16b562f06660ffdae5a00b0796b6b0d4d0aaf1f7507f1720","impliedFormat":1},{"version":"710202fdeb7a95fbf00ce89a67639f43693e05a71f495d104d8fb13133442cbc","impliedFormat":1},{"version":"11754fdc6f8c9c04e721f01d171aad19dac10a211ae0c8234f1d80f6c7accfd4","impliedFormat":1},{"version":"eb394bd8fe37e4f59057ef97404d6b4849bd636921101c25620d933f32ccebac","impliedFormat":1},{"version":"ebed2d323bfc3cb77205b7df5ad82b7299a22194d7185aba1f3aa9367d0582e2","impliedFormat":1},{"version":"199f93a537e4af657dc6f89617e3384b556ab251a292e038c7a57892a1fa479c","impliedFormat":1},{"version":"ead16b329693e880793fe14af1bbcaf2e41b7dee23a24059f01fdd3605cac344","impliedFormat":1},{"version":"ba14614494bccb80d56b14b229328db0849feb1cbfd6efdc517bc5b0cb21c02f","impliedFormat":1},{"version":"6c3760df827b88767e2a40e7f22ce564bb3e57d799b5932ec867f6f395b17c8f","impliedFormat":1},{"version":"885d19e9f8272f1816266a69d7e4037b1e05095446b71ea45484f97c648a6135","impliedFormat":1},{"version":"afcc443428acd72b171f3eba1c08b1f9dcbba8f1cc2430d68115d12176a78fb0","impliedFormat":1},{"version":"8ef33387e4661678691489e4a2cab1765efd8fad7cb5cb47f46f0ece1ad7903e","impliedFormat":1},{"version":"029774092e2d209dbf338eebc52f1163ddf73697a274cfdd9fa7046062b9d2b1","impliedFormat":1},{"version":"594692b6c292195e21efbddd0b1af9bd8f26f2695b9ffc7e9d6437a59905889e","impliedFormat":1},{"version":"092a816537ec14e80de19a33d4172e3679a3782bf0edfd3c137b1d2d603c923e","impliedFormat":1},{"version":"60f0efb13e1769b78bd5258b0991e2bf512d3476a909c5e9fd1ca8ee59d5ef26","impliedFormat":1},{"version":"3cfd46f0c1fe080a1c622742d5220bd1bf47fb659074f52f06c996b541e0fc9b","impliedFormat":1},{"version":"e8d8b23367ad1f5124f3d8403cf2e6d13b511ebb4c728f90ec59ceeb1d907cc1","impliedFormat":1},{"version":"291b182b1e01ded75105515bcefd64dcf675f98508c4ca547a194afd80331823","impliedFormat":1},{"version":"75ddb104faa8f4f84b3c73e587c317d2153fc20d0d712a19f77bea0b97900502","impliedFormat":1},{"version":"135785aa49ae8a82e23a492b5fc459f8a2044588633a124c5b8ff60bbb31b5d4","impliedFormat":1},{"version":"267d5f0f8b20eaeb586158436ba46c3228561a8e5bb5c89f3284940a0a305bd8","impliedFormat":1},{"version":"1d21320d3bf6b17b6caf7e736b78c3b3e26ee08b6ac1d59a8b194039aaaa93ae","impliedFormat":1},{"version":"8b2efbff78e96ddab0b581ecd0e44a68142124444e1ed9475a198f2340fe3ef7","impliedFormat":1},{"version":"6eff0590244c1c9daf80a3ac1e9318f8e8dcd1e31a89983c963bb61be97b981b","impliedFormat":1},{"version":"95f17c73be9d73da53780321cdce58737e915102ac334a75d3798333f5fe2a21","impliedFormat":1},{"version":"a069aef689b78d2131045ae3ecb7d79a0ef2eeab9bc5dff10a653c60494faa79","impliedFormat":1},{"version":"680db60ad1e95bbefbb302b1096b5ad3ce86600c9542179cc52adae8aee60f36","impliedFormat":1},{"version":"8fe6d4285c9486741b09ca3b32dde2da3cf94d18ae1ec490217ee8980c9f7eee","impliedFormat":1},{"version":"b775bfe85c7774cafc1f9b815c17f233c98908d380ae561748de52ccacc47e17","impliedFormat":1},{"version":"5a81c7117f8f1c393c09b3a108549825df175b4b388d2dbc7f11e6a1d234c0d4","impliedFormat":1},{"version":"ebe41fb9fe47a2cf7685a1250a56acf903d8593a8776403eca18d793edc0df54","impliedFormat":1},{"version":"4eb2a7789483e5b2e40707f79dcbd533f0871439e2e5be5e74dc0c8b0f8b9a05","impliedFormat":1},{"version":"984dcccd8abcfd2d38984e890f98e3b56de6b1dd91bf05b8d15a076efd7d84c0","impliedFormat":1},{"version":"d9f4968d55ba6925a659947fe4a2be0e58f548b2c46f3d42d9656829c452f35e","impliedFormat":1},{"version":"57fd651cc75edc35e1aa321fd86034616ec0b1bd70f3c157f2e1aee414e031a0","impliedFormat":1},{"version":"97fec1738c122037ca510f69c8396d28b5de670ceb1bd300d4af1782bd069b0b","impliedFormat":1},{"version":"74a16af8bbfaa038357ee4bceb80fad6a28d394a8faaac3c0d0aa0f9e95ea66e","impliedFormat":1},{"version":"044c44c136ae7fb9ff46ac0bb0ca4e7f41732ca3a3991844ba330fa1bfb121a2","impliedFormat":1},{"version":"d47c270ad39a7706c0f5b37a97e41dbaab295b87964c0c2e76b3d7ad68c0d9d6","impliedFormat":1},{"version":"13e6b949e30e37602fdb3ef961fd7902ccdc435552c9ead798d6de71b83fe1e3","impliedFormat":1},{"version":"f7884f326c4a791d259015267a6b2edbeef3b7cb2bc38dd641ce2e4ef76862e7","impliedFormat":1},{"version":"0f51484aff5bbb48a35a3f533be9fdc1eccac65e55b8a37ac32beb3c234f7910","impliedFormat":1},{"version":"17011e544a14948255dcaa6f9af2bcf93cce417e9e26209c9aa5cbd32852b5b2","impliedFormat":1},{"version":"e12c35fe5d5132ad688215a725ca48d15e5b1bfa26948de18f9e43e7d2cc07ad","impliedFormat":1},{"version":"db7fa2be9bddc963a6fb009099936a5108494adb9e70fd55c249948ea2780309","impliedFormat":1},{"version":"25db4e7179be81d7b9dbb3fde081050778d35fabcc75ada4e69d7f24eb03ce66","impliedFormat":1},{"version":"43ceb16649b428a65b23d08bfc5df7aaaba0b2d1fee220ba7bc4577e661c38a6","impliedFormat":1},{"version":"f3f2e18b3d273c50a8daa9f96dbc5d087554f47c43e922aa970368c7d5917205","impliedFormat":1},{"version":"c17c4fc020e41ddbe89cd63bed3232890b61f2862dd521a98eb2c4cb843b6a42","impliedFormat":1},{"version":"eb77c432329a1a00aac36b476f31333260cd81a123356a4bf2c562e6ac8dc5a4","impliedFormat":1},{"version":"6d2f991e9405c12b520e035bddb97b5311fed0a8bf82b28f7ef69df7184f36c2","impliedFormat":1},{"version":"8e002fd1fc6f8d77200af3d4b5dd6f4f2439a590bf15e037a289bb528ecc6a12","impliedFormat":1},{"version":"2d0748f645de665ca018f768f0fd8e290cf6ce86876df5fc186e2a547503b403","impliedFormat":1},{"version":"7cd50e4c093d0fe06f2ebe1ae5baeefae64098751fb7fa6ae03022035231cc97","impliedFormat":1},{"version":"334bfc2a6677bc60579dbf929fe1d69ac780a0becd1af812132b394e1f6a3ea6","impliedFormat":1},{"version":"ed8e02a44e1e0ddee029ef3c6804f42870ee2b9e17cecad213e8837f5fcd756b","impliedFormat":1},{"version":"b13b25bbfa55a784ec4ababc70e3d050390347694b128f41b3ae45f0202d5399","impliedFormat":1},{"version":"b9fc71b8e83bcc4b5d8dda7bcf474b156ef2d5372de98ac8c3710cfa2dc96588","impliedFormat":1},{"version":"85587f4466c53be818152cbf7f6be67c8384dcf00860290dca05e0f91d20f28d","impliedFormat":1},{"version":"9d4943145bd78babb9f3deb4fccd09dabd14005118ffe30935175056fa938c2b","impliedFormat":1},{"version":"325501db2249efa7194d7baf8f49782709d91bc3d93812b2636e1a7fd127b067","impliedFormat":1},{"version":"944fcf2e7415a20278f025b4587fb032d7174b89f7ba9219b8883affa6e7d2e3","impliedFormat":1},{"version":"589b3c977372b6a7ba79b797c3a21e05a6e423008d5b135247492cc929e84f25","impliedFormat":1},{"version":"ab16a687cfc7d148a8ae645ffd232c765a5ed190f76098207c159dc7c86a1c43","impliedFormat":1},{"version":"1aa722dee553fc377e4406c3ec87157e66e4d5ea9466f62b3054118966897957","impliedFormat":1},{"version":"55bf2aecbdc32ea4c60f87ae62e3522ef5413909c9a596d71b6ec4a3fafb8269","impliedFormat":1},{"version":"7832c3a946a38e7232f8231c054f91023c4f747ad0ce6b6bc3b9607d455944f7","impliedFormat":1},{"version":"696d56df9e55afa280df20d55614bb9f0ad6fcac30a49966bb01580e00e3a2d4","impliedFormat":1},{"version":"07e20b0265957b4fd8f8ce3df5e8aea0f665069e1059de5d2c0a21b1e8a7de09","impliedFormat":1},{"version":"08424c1704324a3837a809a52b274d850f6c6e1595073946764078885a3fa608","impliedFormat":1},{"version":"f5d9a7150b0782e13d4ed803ee73cf4dbc04e99b47b0144c9224fd4af3809d4d","impliedFormat":1},{"version":"551d60572f79a01b300e08917205d28f00356c3ee24569c7696bfd27b2e77bd7","impliedFormat":1},{"version":"8570e9ce13cf15050f0a825e46499c6dedd1989216657799c2c5d5a471d7acff","impliedFormat":1},{"version":"f04efd0fae5202872be8f8b6782b42802ff17de45af734f2baba0b9cc5105e12","impliedFormat":1},{"version":"36d4ae6f8e4c60dfffc8e8ce9ec7a61d01891a081c84856aeba083cb2d756552","impliedFormat":1},{"version":"243d3055f8cb29f0dd09f2f2cdd31b28b7b5ae441a8db32f28bd884f694720f9","impliedFormat":1},{"version":"367a2dbfd74532530c5b2d6b9c87d9e84599e639991151b73d42c720aa548611","impliedFormat":1},{"version":"3df200a7de1b2836c42b3e4843a6c119b4b0e4857a86ebc7cc5a98e084e907f0","impliedFormat":1},{"version":"ae05563905dc09283da42d385ca1125113c9eba83724809621e54ea46309b4e3","impliedFormat":1},{"version":"722fb0b5eff6878e8ad917728fa9977b7eaff7b37c6abb3bd5364cd9a1d7ebc3","impliedFormat":1},{"version":"8d4b70f717f7e997110498e3cfd783773a821cfba257785815b697b45d448e46","impliedFormat":1},{"version":"3735156a254027a2a3b704a06b4094ef7352fa54149ba44dd562c3f56f37b6ca","impliedFormat":1},{"version":"166b65cc6c34d400e0e9fcff96cd29cef35a47d25937a887c87f5305d2cb4cac","impliedFormat":1},{"version":"cf0e1a8d3d1739e50ab4b351cef347959c98c27d1a5ea3b3d922e346a18e4524","impliedFormat":1},{"version":"d17f800659c0b683ea73102ca542ab39009c0a074acf3546321a46c1119faf90","impliedFormat":1},{"version":"e6d61568c240780aaf02c717f950ba4a993c65f3b34ff1bacd9aeff88fa3ac4c","impliedFormat":1},{"version":"f89a15f66cf6ba42bce4819f10f7092cdecbad14bf93984bfb253ffaacf77958","impliedFormat":1},{"version":"822316d43872a628af734e84e450091d101b8b9aa768db8e15058c901d5321e6","impliedFormat":1},{"version":"f20e43033f56cec37fee8ea310a1fb32773afedb382fd33c4d0d109714291cbb","impliedFormat":1},{"version":"53f80bf906602b9cb84bb6ca737bfd71dd45b75949937cc898d0ddffb7a59cde","impliedFormat":1},{"version":"16cccc9037b4bab06d3a88b14644aa672bf0985252d782bbf8ff05df1a7241e8","impliedFormat":1},{"version":"0154d805e3f4f5a40d510c7fb363b57bf1305e983edde83ccd330cef2ba49ed0","impliedFormat":1},{"version":"89da9aeab1f9e59e61889fb1a5fdb629e354a914519956dfa3221e2a43361bb2","impliedFormat":1},{"version":"9703f7408c354bf0264ab25c88c74d7bfee7c6f164661e75813bc68c93836575","impliedFormat":1},{"version":"5ced0582128ed677df6ef83b93b46bffba4a38ddba5d4e2fb424aa1b2623d1d5","impliedFormat":1},{"version":"f1cc60471b5c7594fa2d4a621f2c3169faa93c5a455367be221db7ca8c9fddb1","impliedFormat":1},{"version":"d1bf63146a0dbbe04ba27877020724f165d3f40c4a26aeab373a4ceafc081dc5","impliedFormat":1},{"version":"2739797a759c3ebcab1cb4eb208155d578ef4898fcfb826324aa52b926558abc","impliedFormat":1},{"version":"33ce098f31987d84eb2dd1d6984f5c1c1cae06cc380cb9ec6b30a457ea03f824","impliedFormat":1},{"version":"59683bee0f65ae714cc3cf5fa0cb5526ca39d5c2c66db8606a1a08ae723262b8","impliedFormat":1},{"version":"bc8eb1da4e1168795480f09646dcb074f961dfe76cd74d40fc1c342240ac7be4","impliedFormat":1},{"version":"8d513d33766e10e9c34174600579ece2b57e70e4a6cb8639d3b47f6ae1d40ab5","impliedFormat":1},{"version":"4b31302539066a3c659827d9bfc8a8b87ced23f93bb3a2addc69de2b9755a9b3","impliedFormat":1},{"version":"03b9959bee04c98401c8915227bbaa3181ddc98a548fb4167cd1f7f504b4a1ea","impliedFormat":1},{"version":"2d18b7e666215df5d8becf9ffcfef95e1d12bfe0ac0b07bc8227b970c4d3f487","impliedFormat":1},{"version":"d7ebeb1848cd09a262a09c011c9fa2fc167d0dd6ec57e3101a25460558b2c0e3","impliedFormat":1},{"version":"6c27c0042aed02a14cc458bff4cf45b4da4ae3b26a68e1da66dbf5a1be8d0640","impliedFormat":1},{"version":"07df5b8be0ba528abc0b3fdc33a29963f58f7ce46ea3f0ccfaf4988d18f43fff","impliedFormat":1},{"version":"b0e19c66907ad996486e6b3a2472f4d31c309da8c41f38694e931d3462958d7f","impliedFormat":1},{"version":"3880b10e678e32fcfd75c37d4ad8873f2680ab50582672896700d050ce3f99b6","impliedFormat":1},{"version":"1a372d53e61534eacd7982f80118b67b37f5740a8e762561cd3451fb21b157ff","impliedFormat":1},{"version":"3784f188208c30c6d523d257e03c605b97bc386d3f08cabe976f0e74cd6a5ee5","impliedFormat":1},{"version":"49586fc10f706f9ebed332618093aaf18d2917cf046e96ea0686abaae85140a6","impliedFormat":1},{"version":"921a87943b3bbe03c5f7cf7d209cc21d01f06bf0d9838eee608dfab39ae7d7f4","impliedFormat":1},{"version":"1741f9ea7301b7e61c43bf79b067ffbc22daa0990f06ae6e6dcc0eb55ebb5ede","impliedFormat":1},{"version":"f0885de71d0dbf6d3e9e206d9a3fce14c1781d5f22bca7747fc0f5959357eeab","impliedFormat":1},{"version":"ddebc0a7aada4953b30b9abf07f735e9fec23d844121755309f7b7091be20b8d","impliedFormat":1},{"version":"6fdc397fc93c2d8770486f6a3e835c188ccbb9efac1a28a3e5494ea793bc427c","impliedFormat":1},{"version":"9cc02f7c626b430b3c3b783806262d7c18e9f3fd5a9b6eabb4f943340feaefb5","impliedFormat":1},{"version":"ea694ad54dd168114509a1c3e96141fb1cfbafe09e41180af3ecee66b063f997","impliedFormat":1},{"version":"b6e4cafbcb84c848dfeffeb9ca7f5906d47ed101a41bc068bb1bb27b75f18782","impliedFormat":1},{"version":"9799e6726908803d43992d21c00601dc339c379efabe5eee9b421dbd20c61679","impliedFormat":1},{"version":"dfa5d54c4a1f8b2a79eaa6ecb93254814060fba8d93c6b239168e3d18906d20e","impliedFormat":1},{"version":"858c71909635cf10935ce09116a251caed3ac7c5af89c75d91536eacb5d51166","impliedFormat":1},{"version":"b3eb56b920afafd8718dc11088a546eeb3adf6aa1cbc991c9956f5a1fe3265b3","impliedFormat":1},{"version":"605940ddc9071be96ec80dfc18ab56521f927140427046806c1cfc0adf410b27","impliedFormat":1},{"version":"1a350245a56fdf1f7bac061fce62689f940ea7dd38dee8ccbfc593619eeb4649","impliedFormat":1},{"version":"5194a7fd715131a3b92668d4992a1ac18c493a81a9a2bb064bcd38affc48f22d","impliedFormat":1},{"version":"b7dce3b64ac90cfb272ff277f0a250791829d4b3efc772f2d1c44c30a0218a8b","impliedFormat":1},{"version":"0d7dcf40ed5a67b344df8f9353c5aa8a502e2bbdad53977bc391b36b358a0a1c","impliedFormat":1},{"version":"093ad5bb0746fdb36f1373459f6a8240bc4473829723300254936fc3fdaee111","impliedFormat":1},{"version":"f2367181a67aff75790aa9a4255a35689110f7fb1b0adb08533913762a34f9e6","impliedFormat":1},{"version":"4a1a4800285e8fd30b13cb69142103845c6cb27086101c2950c93ffcd4c52b94","impliedFormat":1},{"version":"c295f6c684e8121b6f25f4767202e5baf9826fe16eec42f4a2bb2966da0f5898","impliedFormat":1},{"version":"fe255676a54e5a01f951e6f773c715391f7d902d197d9ca11a4f9c6b79ffa2ad","impliedFormat":1},{"version":"739708e7d4f5aba95d6304a57029dfbabe02cb594cf5d89944fd0fc7d1371c3a","impliedFormat":1},{"version":"22f31306ddc006e2e4a4817d44bf9ac8214caae39f5706d987ade187ecba09e3","impliedFormat":1},{"version":"4237f49cdd6db9e33c32ccc1743d10b01fdd929c74906e7eecd76ce0b6f3688a","impliedFormat":1},{"version":"4ed726e8489a57adcf586687ff50533e7fe446fb48a8791dbc75d8bf77d1d390","impliedFormat":1},{"version":"bbde826b04c01b41434728b45388528a36cc9505fda4aa3cdd9293348e46b451","impliedFormat":1},{"version":"02a432db77a4579267ff0a5d4669b6d02ebc075e4ff55c2ff2a501fc9433a763","impliedFormat":1},{"version":"086b7a1c4fe2a9ef6dfa030214457b027e90fc1577e188c855dff25f8bcf162c","impliedFormat":1},{"version":"68799ca5020829d2dbebfda86ed2207320fbf30812e00ed2443b2d0a035dda52","impliedFormat":1},{"version":"dc7f0f8e24d838dabe9065f7f55c65c4cfe68e3be243211f625fa8c778c9b85c","impliedFormat":1},{"version":"92169f790872f5f28be4fce7e371d2ccf17b0cc84057a651e0547ad63d8bcb68","impliedFormat":1},{"version":"765b8fe4340a1c7ee8750b4b76f080b943d85e770153e78503d263418b420358","impliedFormat":1},{"version":"12d71709190d96db7fbb355f317d50e72b52e16c3451a20dae13f4e78db5c978","impliedFormat":1},{"version":"7367c0d3442165e6164185b7950b8f70ea2be0142b2175748fef7dc23c6d2230","impliedFormat":1},{"version":"d66efc7ed427ca014754343a80cf2b4512ceaa776bc4a9139d06863abf01ac5c","impliedFormat":1},{"version":"cb0e8923b4d8d8a5bbcea59abc731a1cca90f69aef74f6b27df0bd890d6a00ed","impliedFormat":1},{"version":"dbeb4c3a24b95fe4ad6fdff9577455f5868fbb5ad12f7c22c68cb24374d0996d","impliedFormat":1},{"version":"c1a6eb35cd952ae43b898cc022f39461f7f31360849cdaff12ac56fc5d4cb00d","impliedFormat":1},{"version":"7393dadbd583b53cce10c7644f399d1226e05de29b264985968280614be9e0dd","impliedFormat":1},{"version":"5cd0e12398a8584c4a287978477dab249dc2a490255499a4f075177d1aba0467","impliedFormat":1},{"version":"e60ec884263e7ffcebaf4a45e95a17fc273120a5d474963d4d6d7a574e2e9b97","impliedFormat":1},{"version":"6fd6c4c9eef86c84dd1f09cbd8c10d8feb3ed871724ba8d96a7bd138825a0c1a","impliedFormat":1},{"version":"a420fa988570675d65a6c0570b71bebf0c793f658b4ae20efc4f8e21a1259b54","impliedFormat":1},{"version":"05e9608dfef139336fb2574266412a6352d605857de2f94b2ce454d53e813cd6","impliedFormat":1},{"version":"02de191d16b2797feb7dcebb865562ad148a9507e523c0470d308c5eef158eec","impliedFormat":1},{"version":"bb1c6786ef387ac7a2964ea61adfb76bf9f967bbd802b0494944d7eec31fea2e","impliedFormat":1},{"version":"df407b6c3a8a3ef06519fbe16923df440cbd0fb536effdaa15b312ac8e89dac2","impliedFormat":1},{"version":"77144f05a89288283c8647d605ad49a0b155d0619ed0ea91a15f50174480624f","impliedFormat":1},{"version":"318957769f5b75529bc378b984dacbd42fbfc0db7481bc69cd1b29de812ad54b","impliedFormat":1},{"version":"a5e704ce23f12bfe9df4e9d564656ccaa5a9a896fa7c70537eadec4c74d2a3dc","impliedFormat":1},{"version":"3ee349cda390e8f285b3d861fb5a78e9f69be0d7303607334e08a75ce925928f","impliedFormat":1},{"version":"1efcaa13b1dd8738ba7261f7be898b2d80516e3b9aa091a790b2818179f2cf78","impliedFormat":1},{"version":"111a4c948e8a448d677bfc92166f8a596de03f66045bc1bec50a2f36edb710d2","impliedFormat":1},{"version":"9d7437397cb58f2410f4d64d86a686a6281c5811b17d41b077d6ec0c45d0312e","impliedFormat":1},{"version":"2fdde32fbf21177400da4d10665802c5b7629e2d4012df23d3f9b6e975c52098","impliedFormat":1},{"version":"a8e6ea80509b241d29a62b478b1eb5f8cd2ef9f531056ffc62127ee68e3692f8","impliedFormat":1},{"version":"bbffb20bab36db95b858d13591b9c09e29f76c4b7521dc9366f89eb2aeead68d","impliedFormat":1},{"version":"61b25ce464888c337df2af9c45ca93dcae014fef5a91e6ecce96ce4e309a3203","impliedFormat":1},{"version":"1ac6ead96cc738705b3cc0ba691ae2c3198a93d6a5eec209337c476646a2bce3","impliedFormat":1},{"version":"d5c89d3342b9a5094b31d5f4a283aa0200edc84b855aba6af1b044d02a9cf3b2","impliedFormat":1},{"version":"9863cfd0e4cda2e3049c66cb9cd6d2fd8891c91be0422b4e1470e3e066405c12","impliedFormat":1},{"version":"c8353709114ef5cdaeea43dde5c75eb8da47d7dce8fbc651465a46876847b411","impliedFormat":1},{"version":"0c55d168d0c377ce0340d219a519d3038dd50f35aaadb21518c8e068cbd9cf5e","impliedFormat":1},{"version":"356da547f3b6061940d823e85e187fc3d79bd1705cb84bd82ebea5e18ad28c9c","impliedFormat":1},{"version":"6ee8db8631030efcdb6ac806355fd321836b490898d8859f9ba882943cb197eb","impliedFormat":1},{"version":"e7afb81b739a7b97b17217ce49a44577cfd9d1de799a16a8fc9835eae8bff767","impliedFormat":1},{"version":"ca7c244766ad374c1e664416ca8cc7cd4e23545d7f452bbe41ec5dc86ba81b76","impliedFormat":1},{"version":"46e3a0dfd8cf0e36d14ceaf852d8483bfccbfebe0245debffac0a3b227933c51","impliedFormat":1},{"version":"61e92305d8e3951cc6692064f222555acf25fe83d5313bc441d13098a3e1b4fe","impliedFormat":1},{"version":"dcb3c5cb5cdb73bdf62ffd2808468824ea91a5c258371c32991b97773a20b13e","impliedFormat":1},{"version":"41cf6213c047c4d02d08cdf479fdf1b16bff2734c2f8abbb8bb71e7b542c8a47","impliedFormat":1},{"version":"0c1083e755be3c23e2aab9620dae8282de8a403b643bd9a4e19fe23e51d7b2d3","impliedFormat":1},{"version":"0810e286e8f50b4ead6049d46c6951fe8869d2ea7ee9ea550034d04c14c5d3e2","impliedFormat":1},{"version":"ead36974e944dcbc1cbae1ba8d6de7a1954484006f061c09f05f4a8e606d1556","impliedFormat":1},{"version":"afe05dc77ee5949ccee216b065943280ba15b5e77ac5db89dfc1d22ac32fc74c","impliedFormat":1},{"version":"2030689851bc510df0da38e449e5d6f4146ae7eac9ad2b6c6b2cf6f036b3a1ea","impliedFormat":1},{"version":"25cd596336a09d05d645e1e191ea91fb54f8bfd5a226607e5c0fd0eeeded0e01","impliedFormat":1},{"version":"d95ac12e15167f3b8c7ad2b7fa7f0a528b3941b556a6f79f8f1d57cce8fba317","impliedFormat":1},{"version":"cab5393058fcb0e2067719b320cd9ea9f43e5176c0ba767867c067bc70258ddc","impliedFormat":1},{"version":"c40d5df23b55c953ead2f96646504959193232ab33b4e4ea935f96cebc26dfee","impliedFormat":1},{"version":"cbc868d6efdbe77057597632b37f3ff05223db03ee26eea2136bd7d0f08dafc1","impliedFormat":1},{"version":"a0e027058a6ae83fba027952f6df403e64f7bd72b268022dbb4f274f3c299d12","impliedFormat":1},{"version":"a986ec442c12bed15d981ebd3a193f864d39f017a1f11a0c2e7afaca64288e28","impliedFormat":1},{"version":"83e8fd527d4d28635b7773780cc95ae462d14889ba7b2791dc842480b439ea0b","impliedFormat":1},{"version":"00121d48e941209d282cd87847c665686b77e12e2c3534f20059ece8df0cb84e","impliedFormat":1},{"version":"2f344849d706d5d602830833092bfca2825d87742e2e77908a7d0a6c3d08fdd9","impliedFormat":1},{"version":"cb007806a535d04e11aefff0ce8cd5c8454cad1a5ed774b5fc94e5fc575a8b29","impliedFormat":1},{"version":"b25e13b5bb9888a5e690bbd875502777239d980b148d9eaa5e44fad9e3c89a7e","impliedFormat":1},{"version":"38af232cb48efae980b56595d7fe537a4580fd79120fc2b5703b96cbbab1b470","impliedFormat":1},{"version":"4c76af0f5c8f955e729c78aaf1120cc5c24129b19c19b572e22e1da559d4908c","impliedFormat":1},{"version":"c27f313229ada4914ab14c49029da41c9fdae437a0da6e27f534ab3bc7db4325","impliedFormat":1},{"version":"ff8a3408444fb94122191cbfa708089a6233b8e031ebd559c92a90cb46d57252","impliedFormat":1},{"version":"8c25b00a675743d7a381cf6389ae9fbdce82bdc9069b343cb1985b4cd17b14be","impliedFormat":1},{"version":"cd057861569fb30fea931a115767e6fa600f50e33fadb428c8dd16f2b6ca2567","impliedFormat":1},{"version":"f9ec7b8b285db6b4c51aa183044c85a6e21ea2b28d5c4337c1977e9fe6a88844","impliedFormat":1},{"version":"b4d9fae96173bbd02f2a31ff00b2cb68e2398b1fec5aaab090826e4d02329b38","impliedFormat":1},{"version":"9d0f5034775fb0a6f081f3690925602d01ba16292989bfcac52f6135cf79f56f","impliedFormat":1},{"version":"f5181fff8bba0221f8df77711438a3620f993dd085f994a3aea3f8eaac17ceff","impliedFormat":1},{"version":"9312039b46c4f2eb399e7dd4d70b7cea02d035e64764631175a0d9b92c24ec4b","impliedFormat":1},{"version":"9ddacc94444bfd2e9cc35da628a87ec01a4b2c66b3c120a0161120b899dc7d39","impliedFormat":1},{"version":"a8cb7c1e34db0649edddd53fa5a30f1f6d0e164a6f8ce17ceb130c3689f02b96","impliedFormat":1},{"version":"0aba2a2ff3fc7e0d77aaf6834403166435ab15a1c82a8d791386c93e44e6c6a4","impliedFormat":1},{"version":"c83c86c0fddf1c1d7615be25c24654008ae4f672cff7de2a11cfa40e8c7df533","impliedFormat":1},{"version":"348e5b9c2ee965b99513a09ef9a15aec8914609a018f2e012d0c405969a39a2e","impliedFormat":1},{"version":"49d62a88a20b1dbff8bcf24356a068b816fb2cc2cac94264105a0419b2466b74","impliedFormat":1},{"version":"a04c6362fd99f3702be24412c122c41ed2b3faf3d9042c970610fcd1b1d69555","impliedFormat":1},{"version":"aa6f8f0abe029661655108bc7a0ecd93658bf070ce744b2ffaee87f4c6b51bca","impliedFormat":1},{"version":"5ef75e07b37097e602b73f82e6658b5cbb0683edf35943f811c5b7735ec4a077","impliedFormat":1},{"version":"8c88ce6a3db25803c86dad877ff4213e3f6d26e183d0cde08bc42fbf0a6ddbbe","impliedFormat":1},{"version":"02dabdfe5778f5499df6f18916ff2ebe06725a4c2a13ee7fb09a290b5df4d4b2","impliedFormat":1},{"version":"d67799c6a005603d7e0fd4863263b56eecde8d1957d085bdbbb20c539ad51e8c","impliedFormat":1},{"version":"21af404e03064690ac6d0f91a8c573c87a431ed7b716f840c24e08ea571b7148","impliedFormat":1},{"version":"e919a39dc55737a39bbf5d28a4b0c656feb6ec77a9cbdeb6707785bb70e4f2db","impliedFormat":1},{"version":"b75fca19de5056deaa27f8a2445ed6b6e6ceca0f515b6fdf8508efb91bc6398a","impliedFormat":1},{"version":"ce3382d8fdb762031e03fe6f2078d8fbb9124890665e337ad7cd1fa335b0eb4c","impliedFormat":1},{"version":"fe2ca2bde7e28db13b44a362d46085c8e929733bba05cf7bf346e110320570d1","impliedFormat":1},{"version":"c58afb303be3d37d9969d6aa046201b89bb5cae34d8bafc085c0444f3d0b0435","impliedFormat":1},{"version":"a42d7e73a19bcab1212b419862293fc5ea80293523f08d6ff1f4d013cc6e9409","impliedFormat":1},{"version":"23b93ebd1a1014d6892f417137a0873826b8c21f6460e68d93cef9c0163e2914","impliedFormat":1},{"version":"3e1c36055eeb72af70e6435d1e54cdc9546bb6aa826108ef7fdb76919bc18172","impliedFormat":1},{"version":"e00ca18e9752fbd9aaeedb574e4799d5686732516e84038592dbbe2fa979da3f","impliedFormat":1},{"version":"b8e11b2ffb5825c56f0d71d68d9efa2ea2b62f342a2731467e33ae2fc9870e19","impliedFormat":1},{"version":"1a4e3036112cf0cebac938dcfb840950f9f87d6475c3b71f4a219e0954b6cab4","impliedFormat":1},{"version":"ec4245030ac3af288108add405996081ddf696e4fe8b84b9f4d4eecc9cab08e1","impliedFormat":1},{"version":"6f9d2bd7c485bea5504bc8d95d0654947ea1a2e86bbf977a439719d85c50733f","impliedFormat":1},{"version":"1cb6b6e4e5e9e55ae33def006da6ac297ff6665371671e4335ab5f831dd3e2cd","impliedFormat":1},{"version":"dbd75ef6268810f309c12d247d1161808746b459bb72b96123e7274d89ea9063","impliedFormat":1},{"version":"175e129f494c207dfc1125d8863981ef0c3fb105960d6ec2ea170509663662da","impliedFormat":1},{"version":"5c65d0454be93eecee2bec78e652111766d22062889ab910cbd1cd6e8c44f725","impliedFormat":1},{"version":"f5d58dfc78b32134ba320ec9e5d6cb05ca056c03cb1ce13050e929a5c826a988","impliedFormat":1},{"version":"b1827bed8f3f14b41f42fa57352237c3a2e99f3e4b7d5ca14ec9879582fead0f","impliedFormat":1},{"version":"1d539bc450578c25214e5cc03eaaf51a61e48e00315a42e59305e1cd9d89c229","impliedFormat":1},{"version":"c0ee0c5fe835ba82d9580bff5f1b57f902a5134b617d70c32427aa37706d9ef8","impliedFormat":1},{"version":"738058f72601fffe9cad6fa283c4d7b2919785978bd2e9353c9b31dcc4151a80","impliedFormat":1},{"version":"3c63f1d97de7ec60bc18bebe1ad729f561bd81d04aefd11bd07e69c6ac43e4ad","impliedFormat":1},{"version":"7b8d3f37d267a8a2deb20f5aa359b34570bf8f2856e483dd87d4be7e83f6f75b","impliedFormat":1},{"version":"761745badb654d6ff7a2cd73ff1017bf8a67fdf240d16fbe3e43dca9838027a6","impliedFormat":1},{"version":"e4f33c01cf5b5a8312d6caaad22a5a511883dffceafbb2ee85a7cf105b259fda","impliedFormat":1},{"version":"a368b04888b71c4475a667754b740f4aca7f55db2b7553eacaed36e6962ec48c","impliedFormat":1},{"version":"5b49365103ad23e1c4f44b9d83ef42ff19eea7a0785c454b6be67e82f935a078","impliedFormat":1},{"version":"a664ab26fe162d26ad3c8f385236a0fde40824007b2c4072d18283b1b33fc833","impliedFormat":1},{"version":"193337c11f45de2f0fc9d8ec2d494965da4ae92382ba1a1d90cc0b04e5eeebde","impliedFormat":1},{"version":"4a119c3d93b46bead2e3108336d83ec0debd9f6453f55a14d7066bf430bb9dca","impliedFormat":1},{"version":"02ba072c61c60c8c2018bba0672f7c6e766a29a323a57a4de828afb2bbbb9d54","impliedFormat":1},{"version":"88fe3740babbaa61402a49bd24ce9efcbe40385b0d7cceb96ac951a02d981610","impliedFormat":1},{"version":"1abe3d916ab50524d25a5fbe840bd7ce2e2537b68956734863273e561f9eb61c","impliedFormat":1},{"version":"2b44bc7e31faab2c26444975b362ece435d49066be89644885341b430e61bb7e","impliedFormat":1},{"version":"06763bb36ab0683801c1fa355731b7e65d84b012f976c2580e23ad60bccbd961","impliedFormat":1},{"version":"6a6791e7863eb25fa187d9f323ac563690b2075e893576762e27f862b8003f30","impliedFormat":1},{"version":"bd90f3a677579a8e767f0c4be7dfdf7155b650fb1293fff897ccada7a74d77ff","impliedFormat":1},{"version":"fa28c1f081aa3b9fe872f759f1eb95ced4e4d935b534d7f91797433aee9cd589","impliedFormat":1},{"version":"c1cefd1eccda6d3277d556202450d947a1c88dd8194aabe6fbb101f0149fafaf","impliedFormat":1},{"version":"47008c9a4f168c2490bebc92653f4227accb55fe4b75f06cd0d568bd6370c435","impliedFormat":1},{"version":"b5203823f084dcfaae1f506dfe9bd84bf8ea008a2a834fdd5c5d7d0144418e0b","impliedFormat":1},{"version":"76c2ad2b6e3ec3d09819d8e919ea3e055c9bd73a90c3c6994ba807fd0e12ab15","impliedFormat":1},{"version":"03eb569fd62a9035cac5ac9fd5d960d73de56a6704b7988c13ce6593bec015d1","impliedFormat":1},{"version":"f77ca1843ec31c769b7190f9aa4913e8888ffdfbc4b41d77256fad4108da2b60","impliedFormat":1},{"version":"2ce435b7150596e688b03430fd8247893013ec27c565cd601bba05ea2b97e99d","impliedFormat":1},{"version":"4ea6ab7f5028bedbbc908ab3085dc33077124372734713e507d3d391744a411b","impliedFormat":1},{"version":"909ecbb1054805e23a71612dd50dff18be871dcfe18664a3bcd40ef88d06e747","impliedFormat":1},{"version":"26309fe37e159fdf8aed5e88e97b1bd66bfd8fe81b1e3d782230790ea04603bd","impliedFormat":1},{"version":"dd0cf98b9e2b961a01657121550b621ecc24b81bbcc71287bed627db8020fe48","impliedFormat":1},{"version":"60b03de5e0f2a6c505b48a5d3a5682f3812c5a92c7c801fb8ffa71d772b6dd96","impliedFormat":1},{"version":"224a259ffa86be13ba61d5a0263d47e313e2bd09090ef69820013b06449a2d85","impliedFormat":1},{"version":"c260695b255841fcfbc6008343dae58b3ea00efdfc16997cc69992141f4728c6","impliedFormat":1},{"version":"c017165fe60c647f2dbd24291c48161a616e0ab220e9bd00334ef54ff8eff79d","impliedFormat":1},{"version":"88f46a47b213f376c765ef54df828835dfbb13214cfd201f635324337ebbe17f","impliedFormat":1},{"version":"3ce1188fd214883b087e7feb7bd95dd4a8ce9c1e148951edd454c17a23d54b41","impliedFormat":1},{"version":"a23cc04238f0b8a3805ddb406ee6d69bda510aee5f3c4aa85dbe52cb598cbb04","impliedFormat":1},{"version":"003502d5a8ec5d392a0a3120983c43f073c6d2fd1e823a819f25029ce40271e8","impliedFormat":1},{"version":"1fdbd12a1d02882ef538980a28a9a51d51fd54c434cf233822545f53d84ef9cf","impliedFormat":1},{"version":"419bad1d214faccabfbf52ab24ae4523071fcc61d8cee17b589299171419563c","impliedFormat":1},{"version":"74532476a2d3d4eb8ac23bac785a9f88ca6ce227179e55537d01476b6d4435ea","impliedFormat":1},{"version":"bf33e792a3bc927a6b0d84f428814c35a0a9ca3c0cc8a91246f0b60230da3b6c","impliedFormat":1},{"version":"71c99cd1806cc9e597ff15ca9c90e1b7ad823b38a1327ccbc8ab6125cf70118e","impliedFormat":1},{"version":"6170710f279fffc97a7dd1a10da25a2e9dac4e9fc290a82443728f2e16eb619b","impliedFormat":1},{"version":"3804a3a26e2fd68f99d686840715abc5034aeb8bcbf970e36ad7af8ab69b0461","impliedFormat":1},{"version":"67b395b282b2544f7d71f4a7c560a7225eac113e7f3bcd8e88e5408b8927a63e","impliedFormat":1},{"version":"fe301153d19ddb9e39549f3a5b71c5a94fec01fc8f1bd6b053c4ef42207bef2a","impliedFormat":1},{"version":"4b09036cb89566deddca4d31aead948cf5bdb872508263220582f3be85157551","impliedFormat":1},{"version":"c61d09ae1f70d3eed306dc991c060d57866127365e03de4625497de58a996ffc","impliedFormat":1},{"version":"16a64f8bdaa16d75f9523120f260fcfece9218471062bcc33c4ccb52aa2945b0","impliedFormat":1},{"version":"39e31b902b6b627350a41b05f9627faf6bb1919ad1d17f0871889e5e6d80663c","impliedFormat":1},{"version":"282fd78a91b8363e120a991d61030e2186167f6610a6df195961dba7285b3f17","impliedFormat":1},{"version":"ec571ed174e47dade96ba9157f972937b2e4844a85c399e26957f9aa6d288767","impliedFormat":1},{"version":"16ce742a2199b12a6498dee9f832e27ac5e523064d41f951a8b27cdf3c6b702f","impliedFormat":1},{"version":"bd1162d66a709d4adc49725f4a997925a5472b94a4ff376ed4c2c2428132d5e7","signature":"2835abdf7222fabc24b8bdd15e36271565a15fd5310a1ff67711cbcea7e3c6cd"},{"version":"198ab99660ad169e1d9c39ad9f70113dedf856756a5cd0e7dc88fb8e3b8b9b52","signature":"ef43830056524a915e12eee76024b778a8d4e97f76e2d46beb369b274029ae25"},{"version":"1d0628911b56f83b654ca0ba826d503b3605926e73b985e754513832eeab5592","signature":"45b43c38bc20ca8c0edcee887ed49ed8b771dbe78e1cb5b4e3ba794e853fa887"},{"version":"310c820b803950d18c0ed9376df2cd73def2f56cfcc993f9012008403cdd4843","signature":"6fb95390f4022e0327e4a170917a06de5caad8c8c563c8b00be3cd40a71c759e"},{"version":"74e32298683879740a1cc330261f61c04c3ccda08704f777deeddf964ae09c82","signature":"6c541d3147352266cdef8ea5cb869560c0621ef078e7285701798c9a0d71ad48"},{"version":"4ed96213860296593b569b425eec8dfac37cb5bdaffbce2206c000dc673007c7","signature":"0fbe920fa2bb3439dfa680647a4ea264b7a8ea9bfa75e4cdd9ff2507d69df783"},"3ad057f8e9ea10148199b0260170d3f97a855616141017f58a50907d6b661724",{"version":"4720053f6a578540743ee8f3c02278636ada05dfc7184b43433262c4aebbbff5","signature":"20f656d6480d8146a5128b53fee43e77e2851f98fd61b3da28f2d8a5560578b1"},{"version":"9d90361f495ed7057462bcaa9ae8d8dbad441147c27716d53b3dfeaea5bb7fc8","impliedFormat":1},{"version":"799003c0ab928582fca04977f47b8d85b43a8de610f4eef0ad2d069fbb9f9399","impliedFormat":1},{"version":"d998eea476c695d8e4ff9d007d5b46d49ca2ffa052f74dc20ca516425abd57b1","impliedFormat":1},{"version":"f4e8f4151c3490cf7b68c685aabe901cbab19f962aaa2f118a97550e22689a76","impliedFormat":1},{"version":"0345bc0b1067588c4ea4c48e34425d3284498c629bc6788ebc481c59949c9037","impliedFormat":1},{"version":"e30f5b5d77c891bc16bd65a2e46cd5384ea57ab3d216c377f482f535db48fc8f","impliedFormat":1},{"version":"f113afe92ee919df8fc29bca91cab6b2ffbdd12e4ac441d2bb56121eb5e7dbe3","impliedFormat":1},{"version":"49d567cc002efb337f437675717c04f207033f7067825b42bb59c9c269313d83","impliedFormat":1},{"version":"1d248f707d02dc76555298a934fba0f337f5028bb1163ce59cd7afb831c9070f","impliedFormat":1},{"version":"5d8debffc9e7b842dc0f17b111673fe0fc0cca65e67655a2b543db2150743385","impliedFormat":1},{"version":"5fccbedc3eb3b23bc6a3a1e44ceb110a1f1a70fa8e76941dce3ae25752caa7a9","impliedFormat":1},{"version":"f4031b95f3bab2b40e1616bd973880fb2f1a97c730bac5491d28d6484fac9560","impliedFormat":1},{"version":"dbe75b3c5ed547812656e7945628f023c4cd0bc1879db0db3f43a57fb8ec0e2b","impliedFormat":1},{"version":"b754718a546a1939399a6d2a99f9022d8a515f2db646bab09f7d2b5bff3cbb82","impliedFormat":1},{"version":"2eef10fb18ed0b4be450accf7a6d5bcce7b7f98e02cac4e6e793b7ad04fc0d79","impliedFormat":1},{"version":"c46f471e172c3be12c0d85d24876fedcc0c334b0dab48060cdb1f0f605f09fed","impliedFormat":1},{"version":"7d6ddeead1d208588586c58c26e4a23f0a826b7a143fb93de62ed094d0056a33","impliedFormat":1},{"version":"7c5782291ff6e7f2a3593295681b9a411c126e3736b83b37848032834832e6b9","impliedFormat":1},{"version":"3a3f09df6258a657dd909d06d4067ee360cd2dccc5f5d41533ae397944a11828","impliedFormat":1},{"version":"ea54615be964503fec7bce04336111a6fa455d3e8d93d44da37b02c863b93eb8","impliedFormat":1},{"version":"2a83694bc3541791b64b0e57766228ea23d92834df5bf0b0fcb93c5bb418069c","impliedFormat":1},{"version":"b5913641d6830e7de0c02366c08b1d26063b5758132d8464c938e78a45355979","impliedFormat":1},{"version":"46c095d39c1887979d9494a824eda7857ec13fb5c20a6d4f7d02c2975309bf45","impliedFormat":1},{"version":"f6e02ca076dc8e624aa38038e3488ebd0091e2faea419082ed764187ba8a6500","impliedFormat":1},{"version":"4d49e8a78aba1d4e0ad32289bf8727ae53bc2def9285dff56151a91e7d770c3e","impliedFormat":1},{"version":"63315cf08117cc728eab8f3eec8801a91d2cd86f91d0ae895d7fd928ab54596d","impliedFormat":1},{"version":"a14a6f3a5636bcaebfe9ec2ccfa9b07dc94deb1f6c30358e9d8ea800a1190d5e","impliedFormat":1},{"version":"21206e7e81876dabf2a7af7aa403f343af1c205bdcf7eff24d9d7f4eee6214c4","impliedFormat":1},{"version":"cd0a9f0ffec2486cad86b7ef1e4da42953ffeb0eb9f79f536e16ff933ec28698","impliedFormat":1},{"version":"f609a6ec6f1ab04dba769e14d6b55411262fd4627a099e333aa8876ea125b822","impliedFormat":1},{"version":"6d8052bb814be030c64cb22ca0e041fe036ad3fc8d66208170f4e90d0167d354","impliedFormat":1},{"version":"851f72a5d3e8a2bf7eeb84a3544da82628f74515c92bdf23c4a40af26dcc1d16","impliedFormat":1},{"version":"59692a7938aab65ea812a8339bbc63c160d64097fe5a457906ea734d6f36bcd4","impliedFormat":1},{"version":"8cb3b95e610c44a9986a7eab94d7b8f8462e5de457d5d10a0b9c6dd16bde563b","impliedFormat":1},{"version":"f571713abd9a676da6237fe1e624d2c6b88c0ca271c9f1acc1b4d8efeea60b66","impliedFormat":1},{"version":"16c5d3637d1517a3d17ed5ebcfbb0524f8a9997a7b60f6100f7c5309b3bb5ac8","impliedFormat":1},{"version":"ca1ec669726352c8e9d897f24899abf27ad15018a6b6bcf9168d5cd1242058ab","impliedFormat":1},{"version":"bffb1b39484facf6d0c5d5feefe6c0736d06b73540b9ce0cf0f12da2edfd8e1d","impliedFormat":1},{"version":"f1663c030754f6171b8bb429096c7d2743282de7733bccd6f67f84a4c588d96e","impliedFormat":1},{"version":"dd09693285e58504057413c3adc84943f52b07d2d2fd455917f50fa2a63c9d69","impliedFormat":1},{"version":"d94c94593d03d44a03810a85186ae6d61ebeb3a17a9b210a995d85f4b584f23d","impliedFormat":1},{"version":"c7c3bf625a8cb5a04b1c0a2fbe8066ecdbb1f383d574ca3ffdabe7571589a935","impliedFormat":1},{"version":"7a2f39a4467b819e873cd672c184f45f548511b18f6a408fe4e826136d0193bb","impliedFormat":1},{"version":"f8a0ae0d3d4993616196619da15da60a6ec5a7dfaf294fe877d274385eb07433","impliedFormat":1},{"version":"2cca80de38c80ef6c26deb4e403ca1ff4efbe3cf12451e26adae5e165421b58d","impliedFormat":1},{"version":"0070d3e17aa5ad697538bf865faaff94c41f064db9304b2b949eb8bcccb62d34","impliedFormat":1},{"version":"53df93f2db5b7eb8415e98242c1c60f6afcac2db44bce4a8830c8f21eee6b1dd","impliedFormat":1},{"version":"d67bf28dc9e6691d165357424c8729c5443290367344263146d99b2f02a72584","impliedFormat":1},{"version":"932557e93fbdf0c36cc29b9e35950f6875425b3ac917fa0d3c7c2a6b4f550078","impliedFormat":1},{"version":"e3dc7ec1597fb61de7959335fb7f8340c17bebf2feb1852ed8167a552d9a4a25","impliedFormat":1},{"version":"b64e15030511c5049542c2e0300f1fe096f926cf612662884f40227267f5cd9f","impliedFormat":1},{"version":"1932796f09c193783801972a05d8fb1bfef941bb46ac76fbe1abb0b3bfb674fa","impliedFormat":1},{"version":"d9575d5787311ee7d61ad503f5061ebcfaf76b531cfecce3dc12afb72bb2d105","impliedFormat":1},{"version":"5b41d96c9a4c2c2d83f1200949f795c3b6a4d2be432b357ad1ab687e0f0de07c","impliedFormat":1},{"version":"38ec829a548e869de4c5e51671245a909644c8fb8e7953259ebb028d36b4dd06","impliedFormat":1},{"version":"20c2c5e44d37dac953b516620b5dba60c9abd062235cdf2c3bfbf722d877a96b","impliedFormat":1},{"version":"875fe6f7103cf87c1b741a0895fda9240fed6353d5e7941c8c8cbfb686f072b4","impliedFormat":1},{"version":"c0ccccf8fbcf5d95f88ed151d0d8ce3015aa88cf98d4fd5e8f75e5f1534ee7ae","impliedFormat":1},{"version":"1b1f4aba21fd956269ced249b00b0e5bfdbd5ebd9e628a2877ab1a2cf493c919","impliedFormat":1},{"version":"939e3299952dff0869330e3324ba16efe42d2cf25456d7721d7f01a43c1b0b34","impliedFormat":1},{"version":"f0a9b52faec508ba22053dedfa4013a61c0425c8b96598cef3dea9e4a22637c6","impliedFormat":1},{"version":"d5b302f50db61181adc6e209af46ae1f27d7ef3d822de5ea808c9f44d7d219fd","impliedFormat":1},{"version":"19131632ba492c83e8eeadf91a481def0e0b39ffc3f155bc20a7f640e0570335","impliedFormat":1},{"version":"4581c03abea21396c3e1bb119e2fd785a4d91408756209cbeed0de7070f0ab5b","impliedFormat":1},{"version":"ebcd3b99e17329e9d542ef2ccdd64fddab7f39bc958ee99bbdb09056c02d6e64","impliedFormat":1},{"version":"4b148999deb1d95b8aedd1a810473a41d9794655af52b40e4894b51a8a4e6a6d","impliedFormat":1},{"version":"1781cc99a0f3b4f11668bb37cca7b8d71f136911e87269e032f15cf5baa339bf","impliedFormat":1},{"version":"33f1b7fa96117d690035a235b60ecd3cd979fb670f5f77b08206e4d8eb2eb521","impliedFormat":1},{"version":"01429b306b94ff0f1f5548ce5331344e4e0f5872b97a4776bd38fd2035ad4764","impliedFormat":1},{"version":"c1bc4f2136de7044943d784e7a18cb8411c558dbb7be4e4b4876d273cbd952af","impliedFormat":1},{"version":"5470f84a69b94643697f0d7ec2c8a54a4bea78838aaa9170189b9e0a6e75d2cf","impliedFormat":1},{"version":"36aaa44ee26b2508e9a6e93cd567e20ec700940b62595caf962249035e95b5e3","impliedFormat":1},{"version":"f8343562f283b7f701f86ad3732d0c7fd000c20fe5dc47fa4ed0073614202b4d","impliedFormat":1},{"version":"a53c572630a78cd99a25b529069c1e1370f8a5d8586d98e798875f9052ad7ad1","impliedFormat":1},{"version":"4ad3451d066711dde1430c544e30e123f39e23c744341b2dfd3859431c186c53","impliedFormat":1},{"version":"8069cbef9efa7445b2f09957ffbc27b5f8946fdbade4358fb68019e23df4c462","impliedFormat":1},{"version":"cd8b4e7ad04ba9d54eb5b28ac088315c07335b837ee6908765436a78d382b4c3","impliedFormat":1},{"version":"d533d8f8e5c80a30c51f0cbfe067b60b89b620f2321d3a581b5ba9ac8ffd7c3a","impliedFormat":1},{"version":"33f49f22fdda67e1ddbacdcba39e62924793937ea7f71f4948ed36e237555de3","impliedFormat":1},{"version":"710c31d7c30437e2b8795854d1aca43b540cb37cefd5900f09cfcd9e5b8540c4","impliedFormat":1},{"version":"b2c03a0e9628273bc26a1a58112c311ffbc7a0d39938f3878837ab14acf3bc41","impliedFormat":1},{"version":"a93beb0aa992c9b6408e355ea3f850c6f41e20328186a8e064173106375876c2","impliedFormat":1},{"version":"efdcba88fcd5421867898b5c0e8ea6331752492bd3547942dea96c7ebcb65194","impliedFormat":1},{"version":"a98e777e7a6c2c32336a017b011ba1419e327320c3556b9139413e48a8460b9a","impliedFormat":1},{"version":"ea44f7f8e1fe490516803c06636c1b33a6b82314366be1bd6ffa4ba89bc09f86","impliedFormat":1},{"version":"c25f22d78cc7f46226179c33bef0e4b29c54912bde47b62e5fdaf9312f22ffcb","impliedFormat":1},{"version":"d57579cfedc5a60fda79be303080e47dfe0c721185a5d95276523612228fcefc","impliedFormat":1},{"version":"a41630012afe0d4a9ff14707f96a7e26e1154266c008ddbd229e3f614e4d1cf7","impliedFormat":1},{"version":"298a858633dfa361bb8306bbd4cfd74f25ab7cc20631997dd9f57164bc2116d1","impliedFormat":1},{"version":"921782c45e09940feb232d8626a0b8edb881be2956520c42c44141d9b1ddb779","impliedFormat":1},{"version":"06117e4cc7399ce1c2b512aa070043464e0561f956bda39ef8971a2fcbcdbf2e","impliedFormat":1},{"version":"daccf332594b304566c7677c2732fed6e8d356da5faac8c5f09e38c2f607a4ab","impliedFormat":1},{"version":"4386051a0b6b072f35a2fc0695fecbe4a7a8a469a1d28c73be514548e95cd558","impliedFormat":1},{"version":"78e41de491fe25947a7fd8eeef7ebc8f1c28c1849a90705d6e33f34b1a083b90","impliedFormat":1},{"version":"3ccd198e0a693dd293ed22e527c8537c76b8fe188e1ebf20923589c7cfb2c270","impliedFormat":1},{"version":"2ebf2ee015d5c8008428493d4987e2af9815a76e4598025dd8c2f138edc1dcae","impliedFormat":1},{"version":"0dcc8f61382c9fcdafd48acc54b6ffda69ca4bb7e872f8ad12fb011672e8b20c","impliedFormat":1},{"version":"9db563287eb527ead0bcb9eb26fbec32f662f225869101af3cabcb6aee9259cf","impliedFormat":1},{"version":"068489bec523be43f12d8e4c5c337be4ff6a7efb4fe8658283673ae5aae14b85","impliedFormat":1},{"version":"838212d0dc5b97f7c5b5e29a89953de3906f72fce13c5ae3c5ade346f561d226","impliedFormat":99},{"version":"2223d68f66fbab4dcff52f2ccf81e8c487392288b2974cb2862721e9dbf9551d","impliedFormat":1},{"version":"b07047a60f37f65427574e262a781e6936af9036cf92b540311e033956fd49be","impliedFormat":1},{"version":"25ba804522003eb8212efb1e6a4c2d114662a894b479351c36bd9c7491ceb04f","impliedFormat":1},{"version":"6445fe8e47b350b2460b465d7df81a08b75b984a87ee594caf4a57510f6ec02e","impliedFormat":1},{"version":"425e1299147c67205df40ce396f52ff012c1bf501dcfbf1c7123bbd11f027ab0","impliedFormat":1},{"version":"3abf6b0a561eed97d2f2b58f2d647487ba33191c0ecb96764cc12be4c3dd6b55","impliedFormat":1},{"version":"01cc05d0db041f1733a41beec0ddaeea416e10950f47e6336b3be26070346720","impliedFormat":1},{"version":"e21813719193807d4ca53bb158f1e7581df8aa6401a6a006727b56720b62b139","impliedFormat":1},{"version":"f4f9ca492b1a0306dcb34aa46d84ca3870623db46a669c2b7e5403a4c5bcbbd6","impliedFormat":1},{"version":"492d38565cf9cce8a4f239d36353c94b24ef46a43462d3d411e90c8bef2f8503","impliedFormat":1},{"version":"9f94dc8fb29d482f80aec57af2d982858a1820a8c8872910f89ae2f7fd9bee7f","impliedFormat":1},{"version":"a23f14db3212d53b6c76c346caca80c3627bf900362ce7a896229675a67ae49b","impliedFormat":1},{"version":"f317cf0107576c3e70d3fc9040d767272e4eb5940a1a22666cc81ae491b69d12","impliedFormat":1},{"version":"f317cf0107576c3e70d3fc9040d767272e4eb5940a1a22666cc81ae491b69d12","impliedFormat":1},{"version":"eedb957064af583258d82b6fd845c4df7d0806868cb18cbc2c6a8b0b51eb00bd","impliedFormat":1},{"version":"b6967a67f087fd77eb1980a8abb701ad040679404ed62bd4d6b40406a621fc45","impliedFormat":1},{"version":"092f99777813f42f32abf6f2e4ef1649b6e74cd94db499f2df64fc78d3f969e4","impliedFormat":1},{"version":"3d86c7feb4ee3862d71fe42e3fc120131decf6aa4a21bdf8b3bb9f8c5228aed2","impliedFormat":1},{"version":"ab70ea5d6d02c8631da210783199dc0f6c51ac5dfbc4265fdb8f1526fa0fdc7f","impliedFormat":1},{"version":"427acaa3bbea7c0b1f57d7d9190bedbbb49c147ef36b9088f8f43d1c57974d6e","impliedFormat":1},{"version":"bbd32da0338c47c74e40436d262d787e9a61c11de6d70d431b830babe79aa679","impliedFormat":1},{"version":"cb852ce7eb0ab4281cd3c5a1710d819f54f58fba0f0e9d4b797195416f254883","impliedFormat":1},{"version":"34465f88f94a4b0748055fa5702528e54ef9937c039e29a6bcde810deefd73d0","impliedFormat":1},{"version":"c451606558ca4e1e71e38396f94778b7c9a553a3b33f376ab5e4991dd3633e28","impliedFormat":1},{"version":"22986fb5b95b473335e2bbcc62a9438e8a242ca3d1b28c220d8b99e0d5874678","impliedFormat":1},{"version":"838dc2c15fe68509985a94d1853e96b1e519992a711a7a0cd8568dfd36bf757e","impliedFormat":1},{"version":"bb894fb593532cd9819c43f747cc7b0901136a93758e78482a9f675563beacdf","impliedFormat":1},{"version":"9575c608269abe4889b7c1382762c09deb7493812284bde0a429789fa963838b","impliedFormat":1},{"version":"c8c57e8f7e28927748918e0420c0d6dd55734a200d38d560e16dc99858710f2b","impliedFormat":1},{"version":"64903d7216ed30f8511f03812db3333152f3418de6d422c00bde966045885fb7","impliedFormat":1},{"version":"8ff3e2f7d218a5c4498a2a657956f0ca000352074b46dbaf4e0e0475e05a1b12","impliedFormat":1},{"version":"498f87ea2a046a47910a04cf457a1b05d52d31e986a090b9abc569142f0d4260","impliedFormat":1},{"version":"5ac05c0f6855db16afa699dccfd9e3bd3a7a5160e83d7dce0b23b21d3c7353b9","impliedFormat":1},{"version":"7e792c18f8e4ac8b17c2b786e90f9e2e26cf967145ad615f5c1d09ab0303241f","impliedFormat":1},{"version":"a528a860066cc462a9f0bddc9dbe314739d5f8232b2b49934f84a0ce3a86de81","impliedFormat":1},{"version":"81760466a2f14607fcacf84be44e75ef9dcc7f7267a266d97094895a5c37cbac","impliedFormat":1},{"version":"ee05b32eccbf91646cb264de32701b48a37143708065b74ed0116199d4774e86","impliedFormat":1},{"version":"60f3443b1c23d4956fb9b239e20d31859ea57670cd9f5b827f1cd0cac24c9297","impliedFormat":1},{"version":"648eacd046cfe3e9cba80da0cf2dc69c68aa749be900d7ee4b25ce28099ffa72","impliedFormat":1},{"version":"6a69d5ec5a4ed88455753431cf4d72411d210f04bce62475f9f1a97c4cf4294e","impliedFormat":1},{"version":"11fb88d11384bea44dc08b42b7341a39e36719a68a6be5fed5da575cdaeb1ad8","impliedFormat":1},{"version":"2936dcfaf4b4d1585b73c5ae7ac6395f143e136474bc091cc95033aface47e5e","impliedFormat":1},{"version":"4719ef9fe00fb18f2c3844a1939111ebca55e64f1fa93b14ddcea050865b63f0","impliedFormat":1},{"version":"86edb0b4f12ce79243d5e6ca4bed776bdd7e7a774ce4961578905e775c994ea8","impliedFormat":1},{"version":"b4a4433d4d4601efe2aa677164dee3754e511de644080147421a8cac8d6aae68","impliedFormat":1},{"version":"09a2e34f98a73581d1fd923f2eafaf09bb3ebde6ea730779af09da35dffebbcd","impliedFormat":1},{"version":"f5b5545691bd2e4ca7cf306f99a088ba0ec7e80f3dfca53b87167dbbb44cd836","impliedFormat":1},{"version":"3bd5bd5fabd0b2c646e1413e4d4eb9bbca4bd5a9ffdc53c5375f50078c20c2e2","impliedFormat":1},{"version":"3bd5bd5fabd0b2c646e1413e4d4eb9bbca4bd5a9ffdc53c5375f50078c20c2e2","impliedFormat":1},{"version":"d5003e54842f82de63a808473357de001162f7ca56ab91266e5d790b620f6fdb","impliedFormat":1},{"version":"aa0761c822c96822508e663d9b0ee33ad12a751219565a12471da3e79c38f0ba","impliedFormat":1},{"version":"8338db69b3c23549e39ecf74af0de68417fcea11c98c4185a14f0b3ef833c933","impliedFormat":1},{"version":"85f208946133e169c6a8e57288362151b2072f0256dbed0a4b893bf41aab239a","impliedFormat":1},{"version":"e6957055d9796b6a50d2b942196ffece6a221ec424daf7a3eddcee908e1df7b0","impliedFormat":1},{"version":"e9142ff6ddb6b49da6a1f44171c8974c3cca4b72f06b0bbcaa3ef06721dda7b5","impliedFormat":1},{"version":"3961869af3e875a32e8db4641d118aa3a822642a78f6c6de753aa2dbb4e1ab77","impliedFormat":1},{"version":"4a688c0080652b8dc7d2762491fbc97d8339086877e5fcba74f78f892368e273","impliedFormat":1},{"version":"c81b913615690710c5bcfff0845301e605e7e0e1ebc7b1a9d159b90b0444fccf","impliedFormat":1},{"version":"2ced4431ecdda62fefcf7a2e999783759d08d802962adcff2b0105511f50056d","impliedFormat":1},{"version":"2ced4431ecdda62fefcf7a2e999783759d08d802962adcff2b0105511f50056d","impliedFormat":1},{"version":"e4c6c971ce45aef22b876b7e11d3cd3c64c72fcd6b0b87077197932c85a0d81d","impliedFormat":1},{"version":"7fd1258607eddcc1cf7d1fef9c120a3f224f999bba22da3a0835b25c8321a1d3","impliedFormat":1},{"version":"da3a1963324e9100d88c77ea9bec81385386dbb62acd45db8197d9aeb67284f7","impliedFormat":1},{"version":"f14deef45f1c4c76c96b765e2a7a2410c5e8ae211624fb99fe944d35da2f27c1","impliedFormat":1},{"version":"04dc76c64d88e872fafce2cceb7e25b00daa7180a678600be52c26387486a6d7","impliedFormat":1},{"version":"18c19498e351fb6f0ddbfa499a9c2c845a4d06ed076a976deb4ac28d7c613120","impliedFormat":1},{"version":"5738df287f7e6102687a9549c9b1402941632473e0423ef08bd8af6f394b2662","impliedFormat":1},{"version":"c67e42d11d442babad44a7821e5a18d55548271fdbe9dceb34e3f794e4e2c045","impliedFormat":1},{"version":"407bd942087ec965acd69dfb8f3196838337b07ce9bb3b6939b825bf01f6fb82","impliedFormat":1},{"version":"3d6e4bf3459c87e9cdf6016f51479c5f1e2535ef6b1e9d09ac5826c53d1f849c","impliedFormat":1},{"version":"c583b7e6c874476a42f22fb8afa7474f7ddedac69733e5e28fed9bde08418a3b","impliedFormat":1},{"version":"faf7c4d1fafaed99f524a1dc58b2c3f5602aebfb1a7cac119f279361bae6a0aa","impliedFormat":1},{"version":"d3ded63f1110dc555469fc51ce9873be767c72bff2df976e3afb771c34e91651","impliedFormat":1},{"version":"b0a1098565684d1291020613947d91e7ae92826ffbc3e64f2a829c8200bc6f05","impliedFormat":1},{"version":"1a5bbfae4f953a5552d9fa795efca39883e57b341f0d558466a0bf4868707eb4","impliedFormat":1},{"version":"fe542d91695a73fd82181e8d8898f3f5f3bec296c7480c5ff5e0e170fa50e382","impliedFormat":1},{"version":"891becf92219c25433153d17f9778dec9d76185bc8a86ca5050f6971eaf06a65","impliedFormat":1},{"version":"267f93fbddff4f28c34be3d6773ee8422b60c82f7d31066b6587dffa959a8a6a","impliedFormat":1},{"version":"276d36388f1d029c4543c0ddd5c208606aedcbaed157263f58f9c5016472057e","impliedFormat":1},{"version":"b018759002a9000a881dbb1f9394c6ef59c51fa4867705d00acba9c3245428ea","impliedFormat":1},{"version":"20bbf42534cbacbd0a8e1565d2c885152b7c423a3d4864c75352a8750bb6b52c","impliedFormat":1},{"version":"0ce3dbc76a8a8ed58f0f63868307014160c3c521bc93ed365de4306c85a4df33","impliedFormat":1},{"version":"d9a349eb9160735da163c23b54af6354a3e70229d07bb93d7343a87e1e35fd40","impliedFormat":1},{"version":"9bd17494fcb9407dcc6ace7bde10f4cf3fc06a4c92fe462712853688733c28a3","impliedFormat":1},{"version":"ba540f8efa123096aa3a7b6f01acb2dc81943fa88e5a1adb47d69ed80b949005","impliedFormat":1},{"version":"c6b20a3d20a9766f1dded11397bdba4531ab816fdb15aa5aa65ff94c065419cf","impliedFormat":1},{"version":"91e4a5e8b041f28f73862fb09cd855cfab3f2c7b38abe77089747923f3ad1458","impliedFormat":1},{"version":"2cebda0690ab1dee490774cb062761d520d6fabf80b2bd55346fde6f1f41e25d","impliedFormat":1},{"version":"bcc18e12e24c7eb5b7899b70f118c426889ac1dccfa55595c08427d529cc3ce1","impliedFormat":1},{"version":"6838d107125eeaf659e6fc353b104efd6d033d73cfc1db31224cb652256008f1","impliedFormat":1},{"version":"97b21e38c9273ccc7936946c5099f082778574bbb7a7ab1d9fc7543cbd452fd5","impliedFormat":1},{"version":"ae90b5359bc020cd0681b4cea028bf52b662dff76897f125fa3fe514a0b6727a","impliedFormat":1},{"version":"4596f03c529bd6c342761a19cf6e91221bee47faad3a8c7493abff692c966372","impliedFormat":1},{"version":"6682c8f50bd39495df3042d2d7a848066b63439e902bf8a00a41c3cfc9d7fafa","impliedFormat":1},{"version":"1b111caa0a85bcfd909df65219ecd567424ba17e3219c6847a4f40e71da9810b","impliedFormat":1},{"version":"b8df0a9e1e9c5bd6bcdba2ca39e1847b6a5ca023487785e6909b8039c0c57b16","impliedFormat":1},{"version":"2e26ca8ed836214ad99d54078a7dadec19c9c871a48cb565eaac5900074de31c","impliedFormat":1},{"version":"2b5705d85eb82d90680760b889ebedade29878dbb8cab2e56a206fd32b47e481","impliedFormat":1},{"version":"d131e0261dc711dd6437a69bac59ed3209687025b4e47d424408cf929ca6c17c","impliedFormat":1},{"version":"86c7f05da9abdecf1a1ea777e6172a69f80aec6f9d37c665bd3a761a44ec177b","impliedFormat":1},{"version":"840fe0bc4a365211bae1b83d683bfd94a0818121a76d73674ee38081b0d65454","impliedFormat":1},{"version":"1b6e2a3019f57e4c72998b4ddeea6ee1f637c07cc9199126475b0f17ba5a6c48","impliedFormat":1},{"version":"69920354aa42af33820391f6ec39605c37a944741c36007c1ff317fc255b1272","impliedFormat":1},{"version":"054186ff3657c66e43567635eed91ad9d10a8c590f007ba9eae7182e5042300b","impliedFormat":1},{"version":"1d543a56cb8c953804d7a5572b193c7feb3475f1d1f7045541a227eced6bf265","impliedFormat":1},{"version":"67374297518cf483af96aa68f52f446e2931b7a84fa8982ab85b6dd3fc4accce","impliedFormat":1},{"version":"cf9bfdf581e8998f45f486fdb1422edd7fc05cc9bc39a0bf45c293805176bf7d","impliedFormat":1},{"version":"cf9bfdf581e8998f45f486fdb1422edd7fc05cc9bc39a0bf45c293805176bf7d","impliedFormat":1},{"version":"849d09d5dc6836815767c3f8e2e4c561c8c1986d5398a8e876208aed2cc691c3","impliedFormat":1},{"version":"849d09d5dc6836815767c3f8e2e4c561c8c1986d5398a8e876208aed2cc691c3","impliedFormat":1},{"version":"0dd43d0e8bc78b0c73b1bd20ad29dac4c82163ab92744551bf2ab46512c33b6c","impliedFormat":1},{"version":"0dd43d0e8bc78b0c73b1bd20ad29dac4c82163ab92744551bf2ab46512c33b6c","impliedFormat":1},{"version":"54a527b58cf10aae5525481b5446b81a28b2ae459ce27dc97bd56b13508ea11c","impliedFormat":1},{"version":"54a527b58cf10aae5525481b5446b81a28b2ae459ce27dc97bd56b13508ea11c","impliedFormat":1},{"version":"d1880d157445fdbf521eead6182f47f4b3e5405afd08293ed9e224c01578e26a","impliedFormat":1},{"version":"ed2f74c2566e99295f366f820e54db67d304c3814efcb4389ce791410e9178b0","impliedFormat":1},{"version":"4f7f0dd2d715968cbc88f63784e3323ef0166566fbd121f0ebeb0d07d1ef886b","impliedFormat":1},{"version":"b45e4210d7ffd6339cc7c44484a287bd6578440e4885610067d44d6a084e6719","impliedFormat":1},{"version":"86c931b4aaddf898feee19e37ebdc9f29715bc71e39717138a8dbfb7b56e964d","impliedFormat":1},{"version":"b23d3623bbd2371f16961b7a8ab48f827ee14a0fc9e64aace665e4fc92e0fabe","impliedFormat":1},{"version":"95742365fd6f187354ad59aa45ec521f276b19acfb3636a065bc53728ede2aa6","impliedFormat":1},{"version":"4ac7cb98cbdde71287119827a1ec79c75e4b31847e18b7522cc8ff613f37d0d7","impliedFormat":1},{"version":"ae46812138452a8bf885321878a4f3f66060843b136322cf00e5bdd291596f5a","impliedFormat":1},{"version":"dd708604a523a1f60485ff5273811ff5a2581c0f9d0ccaa9dd7788b598c3e4cb","impliedFormat":1},{"version":"dbdd0616bc8801c73ded285458dddbc468bbae511e55a2b93db71a6fca9fc8fa","impliedFormat":1},{"version":"7682d3f8f04441f516ce74f85733583138039097779b0ac008785e4ecd440ca3","impliedFormat":1},{"version":"7619775d1c3f0bf6c49df7f1cf46bb0729b2f217e84c05e452ce4bb4c50347ba","impliedFormat":1},{"version":"2bd5ad36a78749bf88e7405712ad6cec774fd7646458612e80992a023f3a4da2","impliedFormat":1},{"version":"29a9495b4092f60dd5f079e664be6be1b967b8c2d600bfbf3986104e1d936e77","impliedFormat":1},{"version":"b966a1ceb3c4e8cc5a195ea43a962a6383d55d528ed3c33e97e65e14d2926e8e","impliedFormat":1},{"version":"524138093155f10c138b3ee9cc07284697bf6ba6d90a072106a1f0f7a23f8bea","impliedFormat":1},{"version":"4d44be7af68c7b5a537781bd4f28d48f2262dfd846ff5167f67f665aa93c342b","impliedFormat":1},{"version":"b5534cd11582a3025fb774fbda25a5bfb3a310befb36df425a954b23e2f1872a","impliedFormat":1},{"version":"1eb50ff7cef891bb6f7970802d061dbeb460bde39aef2690937e4e5dbadd74f7","impliedFormat":1},{"version":"b65353223b43764d9ac3a5b3f6bc80ac69b4bb53dfb733dca5dbe580cb2c95ee","impliedFormat":1},{"version":"a843a1a722ebd9a53aeb0823d40190907bde19df318bd3b0911d2876482bd9fa","impliedFormat":1},{"version":"c587631255497ef0d8af1ed82867bfbafaab2d141b84eb67d88b8c4365b0c652","impliedFormat":1},{"version":"b6d3cd9024ab465ec8dd620aeb7d859e323a119ec1d8f70797921566d2c6ac20","impliedFormat":1},{"version":"c5ccf24c3c3229a2d8d15085c0c5289a2bd6a16cb782faadf70d12fddcd672ff","impliedFormat":1},{"version":"a7fc49e0bee3c7ecdcd5c86bc5b680bfad77d0c4f922d4a2361a9aa01f447483","impliedFormat":1},{"version":"3dab449a3c849381e5edb24331596c46442ad46995d5d430c980d7388b158cf8","impliedFormat":1},{"version":"5886a079613cbf07cf7047db32f4561f342b200a384163e0a5586d278842b98e","impliedFormat":1},{"version":"9dae0e7895da154bdc9f677945c3b12c5cc7071946f3237a413bbaa47be5eaa3","impliedFormat":1},{"version":"2d9f27cd0e3331a9c879ea3563b6ad071e1cf255f6b0348f2a5783abe4ec57fb","impliedFormat":1},{"version":"8e6039bba2448ceddd14dafcefd507b4d32df96a8a95ca311be7c87d1ea04644","impliedFormat":1},{"version":"9466d70d95144bf164cd2f0b249153e0875b8db1d6b101d27dce790fd3844faf","impliedFormat":1},{"version":"223ff122c0af20e8025151f11100e3274c1e27234915f75f355881a5aa996480","impliedFormat":1},{"version":"e89a09b50458d1a1ef9992d4c1952d5b9f49f8cfdf82cada3feb4f906d290681","impliedFormat":1},{"version":"2d46726ef0883e699242f2f429b09605beb94ec2ed90d4cccdee650cfd38e9bf","impliedFormat":1},{"version":"a5d3817a1198f3c0f05501d3c23c37e384172bc5a67eaaccbf8b22e7068b607e","impliedFormat":1},{"version":"4ff787695e6ab16b1516e7045d9e8ecf6041c543b7fbed27e26d5222ee86dc7b","impliedFormat":1},{"version":"2b04c4f7b22dfa427973fa1ae55e676cbef3b24bd13e80266cf9e908d1911ce4","impliedFormat":1},{"version":"e89136e2df173f909cb13cdffbc5241b269f24721fe7582e825738dbb44fd113","impliedFormat":1},{"version":"88cf175787ba17012d6808745d3a66b6e48a82bb10d0f192f7795e9e3b38bee0","impliedFormat":1},{"version":"415f027720b1fd2ef33e1076d1a152321acb27fd838d4609508e60280b47ad74","impliedFormat":1},{"version":"1b4034b0a074f5736ae3ec4bf6a13a87ec399779db129f324e08e7fff5b303f2","impliedFormat":1},{"version":"dcd22923b72f9a979a1cea97be236b10fc1fa3ba592c587807bfe3e10d53dbb2","impliedFormat":1},{"version":"dcd22923b72f9a979a1cea97be236b10fc1fa3ba592c587807bfe3e10d53dbb2","impliedFormat":1},{"version":"f34f40704ea9f38ee0c7e1d8f28dfde5a2720577bfdfcd5c6566df140dbe0f7a","impliedFormat":1},{"version":"ea4034d0a7d4878f0710457807ae81cc00529a5f343594bc6e5fe3337561960a","impliedFormat":1},{"version":"2d3dbed1071ac8188a9d210ec745547bc4df0a6c7f4271ac28a36865bb76ee18","impliedFormat":1},{"version":"f71430f4f235cf6fe3ab8f30b763853fe711d186fc9dc1a5f4e11ba84f2000ad","impliedFormat":1},{"version":"5c4dac355c9c745a43de2b296ec350af4ee5548639728f238996df8e4c209b68","impliedFormat":1},{"version":"e8f5dbeb59708cde836d76b5bc1ff2fff301f9374782ffd300a0d35f68dce758","impliedFormat":1},{"version":"04967e55a48ca84841da10c51d6df29f4c8fa1d5e9bd87dec6f66bb9d2830fac","impliedFormat":1},{"version":"22f5e1d0db609c82d53de417d0e4ee71795841131ad00bbd2e0bd18af1c17753","impliedFormat":1},{"version":"afd5a92d81974c5534c78c516e554ed272313a7861e0667240df802c2a11f380","impliedFormat":1},{"version":"d29b6618f255156c4e5b804640aec4863aa22c1e45e7bd71a03d7913ab14e9e2","impliedFormat":1},{"version":"3f8ac93d4f705777ac6bb059bbe759b641f57ae4b04c8b6d286324992cb426e8","impliedFormat":1},{"version":"ba151c6709816360064659d1adfc0123a89370232aead063f643edf4f9318556","impliedFormat":1},{"version":"7957745f950830ecd78ec6b0327d03f3368cfb6059f40f6cdfc087a2c8ade5c0","impliedFormat":1},{"version":"e864f9e69daecb21ce034a7c205cbea7dfc572f596b79bcd67daab646f96722a","impliedFormat":1},{"version":"ebfba0226d310d2ef2a5bc1e0b4c2bc47d545a13d7b10a46a6820e085bc8bcb2","impliedFormat":1},{"version":"dac79c8b6ab4beefba51a4d5f690b5735404f1b051ba31cd871da83405e7c322","impliedFormat":1},{"version":"1ec85583b56036da212d6d65e401a1ae45ae8866b554a65e98429646b8ba9f61","impliedFormat":1},{"version":"8a9c1e79d0d23d769863b1a1f3327d562cec0273e561fd8c503134b4387c391a","impliedFormat":1},{"version":"b274fdc8446e4900e8a64f918906ba3317aafe0c99dba2705947bab9ec433258","impliedFormat":1},{"version":"ecf8e87c10c59a57109f2893bf3ac5968e497519645c2866fbd0f0fda61804b8","impliedFormat":1},{"version":"fe27166cc321657b623da754ca733d2f8a9f56290190f74cc72caad5cb5ef56f","impliedFormat":1},{"version":"74f527519447d41a8b1518fbbc1aca5986e1d99018e8fcd85b08a20dc4daa2e1","impliedFormat":1},{"version":"63017fb1cfc05ccf0998661ec01a9c777e66d29f2809592d7c3ea1cb5dab7d78","impliedFormat":1},{"version":"d08a2d27ab3a89d06590047e1902ee63ca797f58408405729d73fc559253bbc0","impliedFormat":1},{"version":"30dc37fb1af1f77b2a0f6ea9c25b5dc9f501a1b58a8aae301daa8808e9003cf6","impliedFormat":1},{"version":"2e03022de1d40b39f44e2e14c182e54a72121bd96f9c360e1254b21931807053","impliedFormat":1},{"version":"c1563332a909140e521a3c1937472e6c2dda2bb5d0261b79ed0b2340242bdd7b","impliedFormat":1},{"version":"4f297b1208dd0a27348c2027f3254b702b0d020736e8be3a8d2c047f6aa894dd","impliedFormat":1},{"version":"db4d4a309f81d357711b3f988fb3a559eaa86c693cc0beca4c8186d791d167d2","impliedFormat":1},{"version":"67cd15fcb70bc0ee60319d128609ecf383db530e8ae7bab6f30bd42af316c52c","impliedFormat":1},{"version":"c9ecba6a0b84fd4c221eb18dfbae6f0cbf5869377a9a7f0751754da5765e9d3f","impliedFormat":1},{"version":"394a9a1186723be54a2db482d596fd7e46690bda5efc1b97a873f614367c5cea","impliedFormat":1},{"version":"4fb9545dbfaa84b5511cb254aa4fdc13e46aaaba28ddc4137fed3e23b1ae669a","impliedFormat":1},{"version":"b265ebd7aac3bc93ba4eab7e00671240ca281faefddd0f53daefac10cb522d39","impliedFormat":1},{"version":"feadb8e0d2c452da67507eb9353482a963ac3d69924f72e65ef04842aa4d5c2e","impliedFormat":1},{"version":"46beac4ebdcb4e52c2bb4f289ba679a0e60a1305f5085696fd46e8a314d32ce6","impliedFormat":1},{"version":"1bf6f348b6a9ff48d97e53245bb9d0455bc2375d48169207c7fc81880c5273d6","impliedFormat":1},{"version":"1b5c2c982f14a0e4153cbf5c314b8ba760e1cd6b3a27c784a4d3484f6468a098","impliedFormat":1},{"version":"894ce0e7a4cfe5d8c7d39fab698da847e2da40650e94a76229608cb7787d19e6","impliedFormat":1},{"version":"7453cc8b51ffd0883d98cba9fbb31cd84a058e96b2113837191c66099d3bb5a6","impliedFormat":1},{"version":"25f5fafbff6c845b22a3af76af090ddfc90e2defccca0aa41d0956b75fe14b90","impliedFormat":1},{"version":"41e3ec4b576a2830ff017112178e8d5056d09f186f4b44e1fa676c984f1cb84e","impliedFormat":1},{"version":"5617b31769e0275c6f93a14e14774398152d6d03cc8e40e8c821051ef270340e","impliedFormat":1},{"version":"60f19b2df1ca4df468fae1bf70df3c92579b99241e2e92bc6552dfb9d690b440","impliedFormat":1},{"version":"52cac457332357a1e9ea0d5c6e910b867ca1801b31e3463b1dcbaa0d939c4775","impliedFormat":1},{"version":"cf08008f1a9e30cd2f8a73bc1e362cad4c123bd827058f5dffed978b1aa41885","impliedFormat":1},{"version":"582bf54f4a355529a69c3bb4e995697ff5d9e7f36acfddba454f69487b028c66","impliedFormat":1},{"version":"d342554d650b595f2e64cb71e179b7b6112823b5b82fbadf30941be62f7a3e61","impliedFormat":1},{"version":"f7bfc25261dd1b50f2a1301fc68e180ac42a285da188868e6745b5c9f4ca7c8a","impliedFormat":1},{"version":"61d841329328554af2cfa378a3e8490712de88818f8580bde81f62d9b9c4bf67","impliedFormat":1},{"version":"be76374981d71d960c34053c73d618cad540b144b379a462a660ff8fbc81eabe","impliedFormat":1},{"version":"8d9629610c997948d3cfe823e8e74822123a4ef73f4ceda9d1e00452b9b6bbf3","impliedFormat":1},{"version":"0c15ca71d3f3f34ebf6027cf68c8d8acae7e578bb6cc7c70de90d940340bf9bd","impliedFormat":1},{"version":"e5d0a608dca46a22288adac256ec7404b22b6b63514a38acab459bf633e258e0","impliedFormat":1},{"version":"c6660b6ccec7356778f18045f64d88068959ec601230bab39d2ad8b310655f99","impliedFormat":1},{"version":"aaca412f82da34fb0fd6751cea6bbf415401f6bb4aed46416593f7fcfaf32cb5","impliedFormat":1},{"version":"5e283ec6c1867adf73635f1c05e89ee3883ba1c45d2d6b50e39076e0b27f7cd9","impliedFormat":1},{"version":"2712654a78ad0736783e46e97ce91210470b701c916a932d2018a22054ee9751","impliedFormat":1},{"version":"347872376770cb6222066957f9b1ab45083552d415687f92c8b91cb246fd5268","impliedFormat":1},{"version":"24ecb13ea03a8baa20da7df564b4ba48505b396cd746cd0fe64b1f891574a0c9","impliedFormat":1},{"version":"1ded976e25a882defb5c44c3cf0d86f6157aadc85ff86b3f1d6b0796d842e861","impliedFormat":1},{"version":"c15bc8c0b0d3c15dec944d1f8171f6db924cc63bc42a32bc67fbde04cf783b5f","impliedFormat":1},{"version":"5b0c4c470bd3189ea2421901b27a7447c755879ba2fd617ab96feefa2b854ba5","impliedFormat":1},{"version":"08299cc986c8199aeb9916f023c0f9e80c2b1360a3ab64634291f6ff2a6837b1","impliedFormat":1},{"version":"1c49adea5ebea9fbf8e9b28b71e5b5420bf27fee4bf2f30db6dfa980fdad8b07","impliedFormat":1},{"version":"24a741caee10040806ab1ad7cf007531464f22f6697260c19d54ea14a4b3b244","impliedFormat":1},{"version":"b08dfe9e6da10dd03e81829f099ae983095f77c0b6d07ffdd4e0eaf3887af17e","impliedFormat":1},{"version":"40bd28334947aab91205e557963d02c371c02dc76a03967c04ae8451c3702344","impliedFormat":1},{"version":"62e9943dc2f067bda73b19fe8bcf20b81459b489b4f0158170dd9f3b38c68d30","impliedFormat":1},{"version":"267c58ef692839390c97bbb578bdd64f8a162760b4afbd3f73eacacf77d6ea6e","impliedFormat":1},{"version":"6d2496f03c865b5883deee9deda63b98d41f26d60b925204044cd4b78f0f8596","impliedFormat":1},{"version":"02988c4a472902b6ec5cb00809ef193c8a81ffde90b1759dfc34eb18674e0b02","impliedFormat":1},{"version":"7b2b386bb8e6842a4406164027fb53ab4bfef3fbc0eca440f741555dc212d0e8","impliedFormat":1},{"version":"35d669220fc1b97204dc5675e124932294d45b021feb425a9aa16888df44716d","impliedFormat":1},{"version":"bb7b865996627537dbaba9f2fd2f4195003370b02022937cd9eb57c0a0e461d0","impliedFormat":1},{"version":"28a2b8c6566e5a25119829e96a0ac0f0720df78ff55553f1a7529fbce5a87749","impliedFormat":1},{"version":"a1bb9a53774db78ea94042f996663ccac2ba1a1f695dd3e9931ff8ee898cbd06","impliedFormat":1},{"version":"0875537e7be2600acd9e872204840dcfadcc1fe4092a08bd0172a1b766019513","impliedFormat":1},{"version":"4227776f77e27c7d441fd5b8777d16b527928a7b62a0ef86ab8b9c67014cb81c","impliedFormat":1},{"version":"fbf3b2da9b15b5636cbc84578e26ce32e09ddbbac273d1af0313134858ada13e","impliedFormat":1},{"version":"af6f476584c7f0cc7840d26bd53b8f2cb2d297fdfbbce545f054f6098c156760","impliedFormat":1},{"version":"e0dcee233f86aa9a287c8e5021568a9d141faf5f312f348742d77e0a3e57e57d","impliedFormat":1},{"version":"feb50e2e786d7ffebe305337c5fcfe0a8cb2e9eb86542eafffaaf765526075c3","impliedFormat":1},{"version":"154c7aa0bb4266ec1ba8cbc132a6d6f4f5a501c6f557e42fab1551f12d7aadb4","impliedFormat":1},{"version":"ff580bb5932bafb0e88770659100ebb12da80897ed6cc7ffbdf3687048e46555","impliedFormat":1},{"version":"ef2c75a07f97f5214fb2da7bf59bbe82cbaeb6b9cc081e39b674aed5ebdf7905","impliedFormat":1},{"version":"d0c05fadcba345577656a05bf79d4b39a1f00acf76f22c8e4cf18ff30467750e","impliedFormat":1},{"version":"d0c05fadcba345577656a05bf79d4b39a1f00acf76f22c8e4cf18ff30467750e","impliedFormat":1},{"version":"7014093354b80dd4a938ea58d26de184454c4a08bd0500ae00e80eb9a4c19739","impliedFormat":1},{"version":"d06d271d2c714876d2e99a3e91426ed486ef86e92a46d7bd6183bd7849495162","impliedFormat":1},{"version":"da0fb569b713681bfa283495f9f53de3da5a0934fd1794baa99d83686f0eb243","impliedFormat":1},{"version":"1af351fa79e3f56d6ad665ffcd9c19e13d66a76e6d87e1889047729411c34105","impliedFormat":1},{"version":"97b738457d2e1311435022a93b7fa0105d54d3cab2a9557da6df6c3578b9cbdb","impliedFormat":1},{"version":"4cd82c54df6351d625a16e533463ed589155ca392257d5d5d29908be9f6c6ab0","impliedFormat":1},{"version":"c1a3b064d216c0d2503265a68444cd07638b9894575ebcd28fb3ed87ef401641","impliedFormat":1},{"version":"11ddb81d72d7c1e9b70bdec8d887f5d6737c78448477f34b0e66b9d38c5fe960","impliedFormat":1},{"version":"7f2db8b69950287573e65133460d6d0c55afcf99d415f18b00024bd5f55c4941","impliedFormat":1},{"version":"f279cd82f0d7a8c257e9750beafdd375085419733539e6d5ede1ab242de8957f","impliedFormat":1},{"version":"3bd004b8e866ef11ced618495781fd2c936a2a5989927137bdebb3e4755741fd","impliedFormat":1},{"version":"6d34100e5393cbee1869db0f370436d583045f3120c85c7c20bf52377ab6d548","impliedFormat":1},{"version":"92d7ba36531ea86b2be88729546129e1a1d08e571d9d389b859f0867cf26432a","impliedFormat":1},{"version":"f3a6050138891f2cdfdeacf7f0da8da64afc3f2fc834668daf4c0b53425876fb","impliedFormat":1},{"version":"9f260829b83fa9bce26e1a5d3cbb87eef87d8b3db3e298e4ea411a4a0e54f1f5","impliedFormat":1},{"version":"1c23a5cd8c1e82ded17793c8610ca7743344600290cedaf6b387d3518226455b","impliedFormat":1},{"version":"152d05b7e36aac1557821d5e60905bff014fcfe9750911b9cf9c2945cac3df8d","impliedFormat":1},{"version":"6670f4292fc616f2e38c425a5d65d92afc9fb1de51ea391825fa6d173315299a","impliedFormat":1},{"version":"c61a39a1539862fbd48212ba355b5b7f8fe879117fd57db0086a5cbb6acc6285","impliedFormat":1},{"version":"ae9d88113c68896d77b2b51a9912664633887943b465cd80c4153a38267bf70b","impliedFormat":1},{"version":"5d2c41dad1cb904e5f7ae24b796148a08c28ce2d848146d1cdf3a3a8278e35b8","impliedFormat":1},{"version":"b900fa4a5ff019d04e6b779aef9275a26b05794cf060e7d663c0ba7365c2f8db","impliedFormat":1},{"version":"5b7afd1734a1afc68b97cc4649e0eb8d8e45ee3b0ccb4b6f0060592070d05b6d","impliedFormat":1},{"version":"0c83c39f23d669bcb3446ce179a3ba70942b95ef53f7ba4ce497468714b38b8c","impliedFormat":1},{"version":"e9113e322bd102340f125a23a26d1ccf412f55390ae2d6f8170e2e602e2ae61b","impliedFormat":1},{"version":"456308ee785a3c069ec42836d58681fe5897d7a4552576311dd0c34923c883be","impliedFormat":1},{"version":"31e7a65d3e792f2d79a15b60b659806151d6b78eb49cb5fc716c1e338eb819b5","impliedFormat":1},{"version":"a9902721e542fd2f4f58490f228efdad02ebafa732f61e27bb322dbd3c3a5add","impliedFormat":1},{"version":"6e846536a0747aa1e5db6eafec2b3f80f589df21eea932c87297b03e9979d4bf","impliedFormat":1},{"version":"8bd87605aca1cb62caeca63fa442590d4fc14173aa27316ff522f1db984c5d37","impliedFormat":1},{"version":"0ecce2ac996dc29c06ed8e455e9b5c4c7535c177dbfa6137532770d44f975953","impliedFormat":1},{"version":"e2ddd4c484b5c1a1072540b5378b8f8dd8a456b4f2fdd577b0e4a359a09f1a5a","impliedFormat":1},{"version":"db335cb8d7e7390f1d6f2c4ca03f4d2adc7fc6a7537548821948394482e60304","impliedFormat":1},{"version":"b8beb2b272c7b4ee9da75c23065126b8c89d764f8edc3406a8578e6e5b4583b2","impliedFormat":1},{"version":"71e50d029b1100c9f91801f39fd02d32e7e2d63c7961ecb53ed17548d73c150f","impliedFormat":1},{"version":"9af2013e20b53a733dd8052aa05d430d8c7e0c0a5d821a4f4be2d4b672ec22ae","impliedFormat":1},{"version":"8fbe1bc4365212d10f188649f6f8cc17afb5bb3ff12336eb1a9bd5f966d23ad2","impliedFormat":1},{"version":"8fbe1bc4365212d10f188649f6f8cc17afb5bb3ff12336eb1a9bd5f966d23ad2","impliedFormat":1},{"version":"7c2ad9924e9d856fbefbe4ada292bfbf8ffa9b75c419934ad54c7480ef974255","impliedFormat":1},{"version":"7c2ad9924e9d856fbefbe4ada292bfbf8ffa9b75c419934ad54c7480ef974255","impliedFormat":1},{"version":"8033abdbffc86e6d598c589e440ab1e941c2edf53da8e18b84a2bef8769f0f31","impliedFormat":1},{"version":"e88eb1d18b59684cd8261aa4cdef847d739192e46eab8ea05de4e59038401a19","impliedFormat":1},{"version":"834c394b6fdac7cdfe925443170ecdc2c7336ba5323aa38a67aaaf0b3fd8c303","impliedFormat":1},{"version":"831124f3dd3968ebd5fac3ede3c087279acb5c287f808767c3478035b63d8870","impliedFormat":1},{"version":"21d06468c64dba97ef6ee1ccffb718408164b0685d1bff5e4aadd61fcc038655","impliedFormat":1},{"version":"967e26dd598db7de16c9e0533126e624da94bd6c883fd48fbccc92c86e1163c5","impliedFormat":1},{"version":"e2bb71f5110046586149930b330c56f2e1057df69602f8051e11475e9e0adcb0","impliedFormat":1},{"version":"54d718265b1257a8fa8ebf8abe89f899e9a7ae55c2bbeb3fbe93a9ee63c27c08","impliedFormat":1},{"version":"52d09b2ffcfe8a291d70dd6ec8c301e75aff365b891241e5df9943a5bd2cd579","impliedFormat":1},{"version":"c4c282bd73a1a8944112ec3501b7aed380a17a1e950955bb7e67f3ef2ae3eacd","impliedFormat":1},{"version":"b68bffb8ec0c31f104751b7783ea3fca54a27e5562dc6a36467a59af2b9f45d0","impliedFormat":1},{"version":"5f5befc12e7070c00db287c98ebff95b1978d57c94e5eb7f1dc2cdc4351a132a","impliedFormat":1},{"version":"a1fb885801e6a1b76618c7db3dd88d547d696c34b54afb37c6188fdc5c552495","impliedFormat":1},{"version":"d72c555ebec376d349d016576506f1dc171a136206fe75ef8ee36efe0671d5c3","impliedFormat":1},{"version":"e48eda19a17d77b15d627b032d2c82c16dbe7a8714ea7a136919c6fd187a87e9","impliedFormat":1},{"version":"64f38f3e656034d61f6617bff57f6fce983d33b96017a6b1d7c13f310f12a949","impliedFormat":1},{"version":"044028281a4a777b67073a9226b3a3a5f6720083bb7b7bab8b0eeafe70ccf569","impliedFormat":1},{"version":"0dac330041ba1c056fe7bacd7912de9aebec6e3926ff482195b848c4cef64f1c","impliedFormat":1},{"version":"302de1a362e9241903e4ebf78f09133bc064ee3c080a4eda399f6586644dab87","impliedFormat":1},{"version":"940851ac1f3de81e46ea0e643fc8f8401d0d8e7f37ea94c0301bb6d4d9c88b58","impliedFormat":1},{"version":"afab51b01220571ecff8e1cb07f1922d2f6007bfa9e79dc6d2d8eea21e808629","impliedFormat":1},{"version":"0a22b9a7f9417349f39e9b75fb1e1442a4545f4ed51835c554ac025c4230ac95","impliedFormat":1},{"version":"11b8a00dbb655b33666ed4718a504a8c2bf6e86a37573717529eb2c3c9b913ad","impliedFormat":1},{"version":"c4f529f3b69dfcec1eed08479d7aa2b5e82d4ab6665daa78ada044a4a36638c2","impliedFormat":1},{"version":"56fb9431fdb234f604d6429889d99e1fec1c9b74f69b1e42a9485399fd8e9c68","impliedFormat":1},{"version":"1abfd55d146ec3bfa839ccba089245660f30b685b4fdfd464d2e17e9372f3edc","impliedFormat":1},{"version":"5ea23729bee3c921c25cd99589c8df1f88768cfaf47d6d850556cf20ec5afca8","impliedFormat":1},{"version":"0def6b14343fb4659d86c60d8edb412094d176c9730dc8491ce4adabdbe6703a","impliedFormat":1},{"version":"7871d8a4808eab42ceb28bc7edefa2052da07c5c82124fb8e98e3b2c0b483d6c","impliedFormat":1},{"version":"f7e0da46977f2f044ec06fd0089d2537ff44ceb204f687800741547056b2752f","impliedFormat":1},{"version":"586e954d44d5c634998586b9d822f96310321ee971219416227fc4269ea1cdaf","impliedFormat":1},{"version":"33a7a07bc3b4c26441fa544f84403b1321579293d6950070e7daeee0ed0699d8","impliedFormat":1},{"version":"4d000e850d001c9e0616fd8e7cc6968d94171d41267c703bd413619f649bd12a","impliedFormat":1},{"version":"a2d30f0ed971676999c2c69f9f7178965ecbe5c891f6f05bc9cbcd9246eda025","impliedFormat":1},{"version":"f94f93ce2edf775e2eeb43bc62c755f65fb15a404c0507936cc4a64c2a9b2244","impliedFormat":1},{"version":"b4275488913e1befb217560d484ca3f3bf12903a46ade488f3947e0848003473","impliedFormat":1},{"version":"b173f8a2bd54cee0ae0d63a42ca59a2150dce59c828649fc6434178b0905bc05","impliedFormat":1},{"version":"613afe0af900bad8ecb48d9d9f97f47c0759aaebd7975aab74591f5fe30cf887","impliedFormat":1},{"version":"7c43dd250932457013546c3d0ed6270bfe4b9d2800c9a52ad32ece15fc834ef4","impliedFormat":1},{"version":"d0875863f16a9c18b75ef7eab23a1cf93c2c36677c9bb450307b1fa5b7521746","impliedFormat":1},{"version":"37154c245da711d32d653ad43888aac64c93d6f32a8392b0d4635d38dd852e57","impliedFormat":1},{"version":"9be1d0f32a53f6979f12bf7d2b6032e4c55e21fdfb0d03cb58ba7986001187c1","impliedFormat":1},{"version":"6575f516755b10eb5ff65a5c125ab993c2d328e31a9af8bb2de739b180f1dabc","impliedFormat":1},{"version":"5580c4cc99b4fc0485694e0c2ffc3eddfb32b29a9d64bba2ba4ad258f29866bc","impliedFormat":1},{"version":"3217967a9d3d1e4762a2680891978415ee527f9b8ee3325941f979a06f80cd7b","impliedFormat":1},{"version":"430c5818b89acea539e1006499ed5250475fdda473305828a4bb950ada68b8bd","impliedFormat":1},{"version":"a8e3230eab879c9e34f9b8adee0acec5e169ea6e6332bc3c7a0355a65fbf6317","impliedFormat":1},{"version":"62563289e50fd9b9cf4f8d5c8a4a3239b826add45cfb0c90445b94b8ca8a8e46","impliedFormat":1},{"version":"e1f6516caf86d48fd690663b0fd5df8cf3adf232b07be61b4d1c5ba706260a56","impliedFormat":1},{"version":"c5fd755dac77788acc74a11934f225711e49014dd749f1786b812e3e40864072","impliedFormat":1},{"version":"672ed5d0ebc1e6a76437a0b3726cb8c3f9dd8885d8a47f0789e99025cfb5480d","impliedFormat":1},{"version":"e15305776c9a6d9aac03f8e678008f9f1b9cb3828a8fc51e6529d94df35f5f54","impliedFormat":1},{"version":"4da18bcf08c7b05b5266b2e1a2ac67a3b8223d73c12ee94cfa8dd5adf5fdcd5e","impliedFormat":1},{"version":"a4e14c24595a343a04635aff2e39572e46ae1df9b948cc84554730a22f3fc7a3","impliedFormat":1},{"version":"0f604aef146af876c69714386156b8071cdb831cb380811ed6749f0b456026bd","impliedFormat":1},{"version":"4868c0fb6c030a7533deb8819c9351a1201b146a046b2b1f5e50a136e5e35667","impliedFormat":1},{"version":"8a1cfeb14ca88225a95d8638ee58f357fc97b803fe12d10c8b52d07387103ff1","impliedFormat":1},{"version":"fac0f34a32af6ff4d4e96cd425e8fefb0c65339c4cb24022b27eb5f13377531f","impliedFormat":1},{"version":"7ec5a106f7a6de5a44eac318bb47cdece896e37b69650dd9e394b18132281714","impliedFormat":1},{"version":"a015f74e916643f2fd9fa41829dea6d8a7bedbb740fe2e567a210f216ac4dcad","impliedFormat":1},{"version":"4dbabbde1b07ee303db99222ef778a6c2af8362bc5ce185996c4dc91cba6b197","impliedFormat":1},{"version":"0873baae7b37627c77a36f8ead0ab3eb950848023c9e8a60318f4de659e04d54","impliedFormat":1},{"version":"dc7d167f4582a21e20ac5979cb0a9f58a0541d468b406fd22c739b92cd9f5eec","impliedFormat":1},{"version":"edeec378c31a644e8fa29cfcb90f3434a20db6e13ae65df8298163163865186f","impliedFormat":1},{"version":"12300e3a7ca6c3a71773c5299e0bca92e2e116517ab335ab8e82837260a04db7","impliedFormat":1},{"version":"2e6128893be82a1cbe26798df48fcfb050d94c9879d0a9c2edece4be23f99d9f","impliedFormat":1},{"version":"2819f355f57307c7e5a4d89715156750712ea15badcb9fbf6844c9151282a2b8","impliedFormat":1},{"version":"4e433094ed847239c14ae88ca6ddaa6067cb36d3e95edd3626cec09e809abc3b","impliedFormat":1},{"version":"7c592f0856a59c78dbfa856c8c98ba082f4dafb9f9e8cdd4aac16c0b608aaacd","impliedFormat":1},{"version":"9fb90c7b900cee6a576f1a1d20b2ef0ed222d76370bc74c1de41ea090224d05d","impliedFormat":1},{"version":"c94cfa7c0933700be94c2e0da753c6d0cf60569e30d434c3d0df4a279df7a470","impliedFormat":1},{"version":"b208e4729b03a250bc017f1231a27776db6e5396104c4a5cfe40a8de4d3ab33e","impliedFormat":1},{"version":"b208e4729b03a250bc017f1231a27776db6e5396104c4a5cfe40a8de4d3ab33e","impliedFormat":1},{"version":"83624214a41f105a6dd1fef1e8ebfcd2780dd2841ce37b84d36d6ae304cba74e","impliedFormat":1},{"version":"bc63f711ce6d1745bb9737e55093128f8012d67a9735c958aaaf1945225c4f1d","impliedFormat":1},{"version":"951404d7300f1a479a7e70bca4469ea5f90807db9d3adc293b57742b3c692173","impliedFormat":1},{"version":"e93bba957a27b85afb83b2387e03a0d8b237c02c85209fde7d807c2496f20d41","impliedFormat":1},{"version":"4537c199f28f3cd75ab9d57b21858267c201e48a90009484ef37e9321b9c8dbb","impliedFormat":1},{"version":"faae84acef05342e6009f3fa68a2e58e538ef668c7173d0fc2eacac0ad56beef","impliedFormat":1},{"version":"7e19092d64b042f55f4d7b057629159a8167ee319d4cccc4b4bdd12d74018a6c","impliedFormat":1},{"version":"39196b72ec09bdc29508c8f29705ce8bd9787117863ca1bcf015a628bed0f031","impliedFormat":1},{"version":"3f727217522dabc9aee8e9b08fccf9d67f65a85f8231c0a8dbcc66cf4c4f3b8d","impliedFormat":1},{"version":"bbeb72612b2d3014ce99b3601313b2e1a1f5e3ce7fdcd8a4b68ff728e047ffcd","impliedFormat":1},{"version":"c89cc13bad706b67c7ca6fca7b0bb88c7c6fa3bd014732f8fc9faa7096a3fad8","impliedFormat":1},{"version":"2272a72f13a836d0d6290f88759078ec25c535ec664e5dabc33d3557c1587335","impliedFormat":1},{"version":"1074e128c62c48b5b1801d1a9aeebac6f34df7eafa66e876486fbb40a919f31a","impliedFormat":1},{"version":"87bba2e1de16d3acb02070b54f13af1cb8b7e082e02bdfe716cb9b167e99383b","impliedFormat":1},{"version":"a2e3a26679c100fb4621248defda6b5ce2da72943da9afefccaf8c24c912c1cb","impliedFormat":1},{"version":"3ee7668b22592cc98820c0cf48ad7de48c2ad99255addb4e7d735af455e80b47","impliedFormat":1},{"version":"643e9615c85c77bc5110f34c9b8d88bce6f27c54963f3724ab3051e403026d05","impliedFormat":1},{"version":"35c13baa8f1f22894c1599f1b2b509bdeb35f7d4da12619b838d79c6f72564bb","impliedFormat":1},{"version":"7d001913c9bf95dbdc0d4a14ffacf796dbc6405794938fc2658a79a363f43f65","impliedFormat":1},{"version":"9906fbdb7d8e18b0105f61569701a07c8aaa7ea0ef6dc63f8f9fbba7de8e044e","impliedFormat":1},{"version":"9906fbdb7d8e18b0105f61569701a07c8aaa7ea0ef6dc63f8f9fbba7de8e044e","impliedFormat":1},{"version":"6a0840f6ab3f97f9348098b3946941a7ca67beb47a6f2a75417376015bde3d62","impliedFormat":1},{"version":"24c75bd8d8ba4660a4026b89abc5457037ed709759ca1e9e26bd68c610817069","impliedFormat":1},{"version":"8cc6185d8186c7fefa97462c6dd9915df9a9542bd97f220b564b3400cdf3ad82","impliedFormat":1},{"version":"2cad19f3eae8e3a9176bf34b9cffa640d55a3c73b69c78b0b80808130d5120c6","impliedFormat":1},{"version":"a140d8799bc197466ac82feef5a8f1f074efc1bb5f02c514200269601279a6ff","impliedFormat":1},{"version":"48bda2797d1005604d21de42a41af85dfe7688391d28f02b90c90c06f6604781","impliedFormat":1},{"version":"1454f42954c53c719ae3f166a71c2a8c4fbc95ee8a5c9ddba3ec15b792054a3d","impliedFormat":1},{"version":"ae4890722031fcaa66eed85d5ce06f0fc795f21dedbe4c7c53f777c79caf01dd","impliedFormat":1},{"version":"1a6ff336c6c59fa7b44cf01dc0db00baa1592d7280be70932110fe173c3a3ed6","impliedFormat":1},{"version":"95fa82863f56a7b924814921beeab97aa064d9e2c6547eb87492a3495533be0f","impliedFormat":1},{"version":"248cdafd23df89eee20f1ef00daef4f508850cfcbad9db399b64cdb1c3530c06","impliedFormat":1},{"version":"936579eb15fe5cf878d90bddaf083a5dce9e8ca7d2222c2d96a2e55b8022e562","impliedFormat":1},{"version":"1bd19890e78429873f6eb45f6bd3b802743120c2464b717462ec4c9668ce7b89","impliedFormat":1},{"version":"756c0802bc098388018b4f245a15457083aee847ebcd89beb545d58ccbf29a9f","impliedFormat":1},{"version":"8e00226014fc83b74b47868bfac6919b2ca51e1dc612ea3f396a581ba7da8fdd","impliedFormat":1},{"version":"27930087468a6afd3d42fd75c37d8cc7df6a695f3182eb6230fcea02fce46635","impliedFormat":1},{"version":"b6d0a876f84484d9087e8eadde589e25b3f1975d32a11d188f6da0bc5dcf1d1d","impliedFormat":1},{"version":"5a282b327e397cf1637717c454d71f5dff2af2514d7f3766562bd51721d5eaab","impliedFormat":1},{"version":"fba971f62ec18b0de02357aba23b11c19aeb512eb525b9867f6cc2495d3a9403","impliedFormat":1},{"version":"69334948e4bc7c2b5516ed02225eaf645c6d97d1c636b1ef6b7c9cfc3d3df230","impliedFormat":1},{"version":"4231544515c7ce9251e34db9d0e3f74fc38365e635c8f246f2d8b39461093dea","impliedFormat":1},{"version":"963d469b265ce3069e9b91c6807b4132c1e1d214169cf1b43c26bfbcb829b666","impliedFormat":1},{"version":"387616651414051e1dd73daf82d6106bbaefcbad21867f43628bd7cbe498992f","impliedFormat":1},{"version":"f3b6f646291c8ddfc232209a44310df6b4f2c345c7a847107b1b8bbde3d0060a","impliedFormat":1},{"version":"8fbbfbd7d5617c6f6306ffb94a1d48ca6fa2e8108c759329830c63ff051320e1","impliedFormat":1},{"version":"9912be1b33a6dfc3e1aaa3ad5460ee63a71262713f1629a86c9858470f94967d","impliedFormat":1},{"version":"57c32282724655f62bff2f182ce90934d83dc7ed14b4ac3f17081873d49ec15b","impliedFormat":1},{"version":"fabb2dcbe4a45ca45247dece4f024b954e2e1aada1b6ba4297d7465fac5f7fb3","impliedFormat":1},{"version":"449fa612f2861c3db22e394d1ad33a9544fe725326e09ec1c72a4d9e0a85ccf1","impliedFormat":1},{"version":"5e80786f1a47a61be5afde06ebd2eae0d1f980a069d34cea2519f41e518b31e8","impliedFormat":1},{"version":"565fbcf5374afdcb53e1bf48a4dd72db5c201551ec1cdf408aab9943fec4f525","impliedFormat":1},{"version":"8334934b3c4b83da15be9025d15b61fdada52adfb6b3c81e24bf61e33e4a8f56","impliedFormat":1},{"version":"0bf7ddc236561ac7e5dcd04bcbb9ac34ea66d1e54542f349dc027c08de120504","impliedFormat":1},{"version":"329b4b6fb23f225306f6a64f0af065bc7d5858024b2b04f46b482d238abe01ef","impliedFormat":1},{"version":"c70a7411a384063543b9703d072d38cfec64c54d9bdcc0916a24fcb7945907c3","impliedFormat":1},{"version":"d74eccab1a21737b12e17a94bacff23954496ccad820ee1bd4769353825ea1f0","impliedFormat":1},{"version":"5a169268ac5488e3555a333964a538ce27a8702b91fffa7f2f900b67bf943352","impliedFormat":1},{"version":"85931e79bdd6b16953de2303cebbe16ba1d66375f302ffe6c85b1630c64d4751","impliedFormat":1},{"version":"ad9da00aa581dca2f09a6fec43f0d03eff7801c0c3496613d0eb1d752abf44d9","impliedFormat":1},{"version":"28ea9e12e665d059b80a8f5424e53aa0dd8af739da7f751cc885f30440b64a7f","impliedFormat":1},{"version":"cdc22634df9ab0cd1e1ab5a32e382d034bba97afd7c12db7862b9079e5e3c4c0","impliedFormat":1},{"version":"73940b704df78d02da631af2f5f253222821da6482c21cd96f64e90141b34d38","impliedFormat":1},{"version":"76e64c191fe381ecbbb91a3132eaf16b54e33144aee0e00728d4f8ba9d3be3c1","impliedFormat":1},{"version":"de49fed066a921f1897ca031e5a3d3c754663b9a877b01362cc08fb6a250a8b6","impliedFormat":1},{"version":"833b691a43b7b18f4251fdb305babad29234dd6c228cf5b931118301c922283d","impliedFormat":1},{"version":"a5f925f6ad83aa535869fb4174e7ef99c465e5c01939d2e393b6f8c0def6d95e","impliedFormat":1},{"version":"db80344e9c5463e4fb49c496b05e313b3ebcc1b9c24e9bcd97f3e34429530302","impliedFormat":1},{"version":"f69e0962918f4391e8e5e50a1b3eb1e3fd40f63ed082da8242b34dda16c519ba","impliedFormat":1},{"version":"012dcd1847240a35fd1de3132d11afab38bb63e99ce1ca2679c2376567f5ef74","impliedFormat":1},{"version":"c4e34c7b331584cd9018fb2d51d602d38cf9f2aeec0bad092b61dd10ff602bd5","impliedFormat":1},{"version":"06675fa918f0abfe5632adbfae821517a34af861cadab135d4240f0b0fd975a5","impliedFormat":1},{"version":"a4919817b89aadcc8fb7121d41c3924a30448d017454cb3d1e3570f8413f74a6","impliedFormat":1},{"version":"2a37bd0673e5f0b487f05880d143883abcbdc9682d0ed54d550eb44e775dab46","impliedFormat":1},{"version":"8ed0765cafa7e4b10224672c29056e8ee4a9936df65ba4ea3ffd841c47aa2393","impliedFormat":1},{"version":"a38694615d4482f8b6556f6b0915374bbf167c3e92e182ae909f5e1046ebbc97","impliedFormat":1},{"version":"a0ff175b270170dd3444ee37fdd71e824b934dcdae77583d4cdea674349f980e","impliedFormat":1},{"version":"99391c62be7c4a7dc23d4a94954973e5f1c1ca0c33fdd8f6bb75c1ddc7ffc3ad","impliedFormat":1},{"version":"ea58d165e86c3e2e27cf07e94175c60d1672810f873e344f7bc85ad4ebe00cef","impliedFormat":1},{"version":"85c8e99f8cd30d3a742c4c0fe5500db8561e0028b8153dc60c3d1e64ef2a507f","impliedFormat":1},{"version":"e272f75b77cffbfbb88ba377d7892d55e49f67378a8ffa7bddce1be53634ca3b","impliedFormat":1},{"version":"67448f432a710a322eac4b9a56fd8145d0033c65206e90fca834d9ed6601a978","impliedFormat":1},{"version":"7a319bad5a59153a92e455bebcfce1c8bc6e6e80f8e6cc3b20dd7465662c9c8e","impliedFormat":1},{"version":"2d7bed8ff2044b202f9bd6c35bf3bda6f8baad9e0f136a9c0f33523252de4388","impliedFormat":1},{"version":"308786774814d57fc58f04109b9300f663cf74bd251567a01dc4d77e04c1cdc1","impliedFormat":1},{"version":"68af14958b6a2faf118853f3ecb5c0dbee770bd1e0eb6c2ef54244b68cecf027","impliedFormat":1},{"version":"1255747e5c6808391a8300476bdb88924b13f32287270084ebd7649737b41a6e","impliedFormat":1},{"version":"37b6feaa304b392841b97c22617b43f9faa1d97a10a3c6d6160ca1ea599d53ce","impliedFormat":1},{"version":"79adb3a92d650c166699bb01a7b02316ea456acc4c0fd6d3a88cdd591f1849b0","impliedFormat":1},{"version":"0dc547b11ab9604c7a2a9ca7bf29521f4018a14605cc39838394b3d4b1fbaf6d","impliedFormat":1},{"version":"31fedd478a3a7f343ee5df78f1135363d004521d8edf88cd91b91d5b57d92319","impliedFormat":1},{"version":"88b7ed7312f01063f327c5d435224e137c6a2f9009175530e7f4b744c1e8957f","impliedFormat":1},{"version":"3cf0c7a66940943decbf30a670ab6077a44e9895e7aea48033110a5b58e86d64","impliedFormat":1},{"version":"11776f5fa09779862e18ff381e4c3cb14432dd188d30d9e347dfc6d0bda757a8","impliedFormat":1},{"version":"a7c12ec0d02212110795c86bd68131c3e771b1a3f4980000ec06753eb652a5c4","impliedFormat":1},{"version":"8d6b33e4d153c1cc264f6d1bb194010221907b83463ad2aaaa936653f18bfc49","impliedFormat":1},{"version":"4e0537c4cd42225517a5cdec0aea71fdaaacbf535c42050011f1b80eda596bbd","impliedFormat":1},{"version":"cf2ada4c8b0e9aa9277bfac0e9d08df0d3d5fb0c0714f931d6cac3a41369ee07","impliedFormat":1},{"version":"3bdbf003167e4dffbb41f00ddca82bb657544bc992ef307ed2c60c322f43e423","impliedFormat":1},{"version":"9d62d820685dfbed3d1da3c5d9707ae629eac65ee42eeae249e6444271a43f79","impliedFormat":1},{"version":"9fc1d71181edb6028002b0757a4de17f505fb538c8b86da2dabb2c58618e9495","impliedFormat":1},{"version":"895c35a7b8bdd940bda4d9c709acfc4dd72d302cc618ec2fd76ae2b8cd9fd534","impliedFormat":1},{"version":"e7eb43e86a2dfcb8a8158b2cc4eff93ff736cfec1f3bf776c2c8fb320b344730","impliedFormat":1},{"version":"7d2f0645903a36fe4f96d547a75ea14863955b8e08511734931bd76f5bbc6466","impliedFormat":1},{"version":"4d88daa298c032f09bc2453facf917d848fcd73b9814b55c7553c3bf0036ac3d","impliedFormat":1},{"version":"7e46cd381a3ac5dbb328d4630db9bf0d76aae653083fc351718efba4bd4bf3b3","impliedFormat":1},{"version":"23cca6a0c124bd1b5864a74b0b2a9ab12130594543593dc58180c5b1873a3d16","impliedFormat":1},{"version":"286c428c74606deaa69e10660c1654b9334842ef9579fbfbb9690c3a3fd3d8c5","impliedFormat":1},{"version":"e838976838d7aa954c3c586cd8efc7f8810ec44623a1de18d6c4f0e1bc58a2b6","impliedFormat":1},{"version":"fe7b3e4b7b62b6f3457f246aa5b26181da0c24dc5fc3a3b4f1e93f66c41d819f","impliedFormat":1},{"version":"ea15abd31f5884334fa04683b322618f1f4526a23f6f77839b446dbeee8eb9a1","impliedFormat":1},{"version":"e55b5d8322642dda29ae2dea9534464e4261cb8aa719fe8cec26ce2d70753db5","impliedFormat":1},{"version":"6074dbe82ec2c1325ecda241075fa8d814e6e5195a6c1f6315aa5a582f8eb4cf","impliedFormat":1},{"version":"c044c7f653a4aff233adfdee4c3d4e05da4fc071dfb6f8f32f5a8cd30e8aacaa","impliedFormat":1},{"version":"2f5f95be086b3c700fe1c0f1b20a5ff18a26a15ae9924b495231555a3bed7f05","impliedFormat":1},{"version":"fb4de4bc74a1997282181648fecd3ec5bb19d39cdb0ff3a4fb8ac134b2e03eb8","impliedFormat":1},{"version":"ada6919a8c3d26712dac8469dbe297980d97258fd7927aa4b4f68d8a0efeb20b","impliedFormat":1},{"version":"b1f2367947cf2dfba2cd6cc0d1ed3c49e55059f4ee0e648590daafecd1b49e63","impliedFormat":1},{"version":"e7aee498fe1438535033fdfe126a12f06874e3608cd77d8710ff9542ebb7ba60","impliedFormat":1},{"version":"0017e3bbd2f7b139daf97c0f27bef8531a6f44572ba9387f5451e417b62ecd55","impliedFormat":1},{"version":"91dda5226ec658c3c71dfb8689231f6bfea4d559d08f27237d0d02f4eb3e4aa6","impliedFormat":1},{"version":"e1e2ee6fc32ea03e5e8b419d430ea236b20f22d393ba01cc9021b157727e1c59","impliedFormat":1},{"version":"8adfd735c00b78c24933596cd64c44072689ac113001445a7c35727cb9717f49","impliedFormat":1},{"version":"999bfcbaae834b8d00121c28de9448c72f24767d3562fc388751a5574c88bd45","impliedFormat":1},{"version":"110a52db87a91246f9097f284329ad1eedd88ff8c34d3260dcb7f4f731955761","impliedFormat":1},{"version":"8929df495a85b4cc158d584946f6a83bf9284572b428bb2147cc1b1f30ee5881","impliedFormat":1},{"version":"22c869750c8452121f92a511ef00898cc02d941109e159a0393a1346348c144a","impliedFormat":1},{"version":"d96e2ff73f69bc352844885f264d1dfc1289b4840d1719057f711afac357d13e","impliedFormat":1},{"version":"a01928da03f46c245f2173ced91efd9a2b3f04a1a34a46bc242442083babaab9","impliedFormat":1},{"version":"c175f6dd4abdfac371b1a0c35ebeaf01c745dffbf3561b3a5ecc968e755a718b","impliedFormat":1},{"version":"d3531db68a46747aee3fa41531926e6c43435b59cd79ccdbcb1697b619726e47","impliedFormat":1},{"version":"c1771980c6bcd097876fe8b78a787e28163008e3d6d46885e9506483ac6b9226","impliedFormat":1},{"version":"8c2cc0d0b9b8650ef75f186f6c3aeeb3c18695e3cd3d0342cf8ef1d6aea27997","impliedFormat":1},{"version":"0a9bcf65e6abc0497fffcb66be835e066533e5623e32262b7620f1091b98776b","impliedFormat":1},{"version":"235a1b88a060bd56a1fc38777e95b5dda9c68ecb42507960ec6999e8a2d159cc","impliedFormat":1},{"version":"dde6b3b63eb35c0d4e7cc8d59a126959a50651855fd753feceab3bbad1e8000a","impliedFormat":1},{"version":"1f80185133b25e1020cc883e6eeadd44abb67780175dc2e21c603b8062a86681","impliedFormat":1},{"version":"f4abdeb3e97536bc85f5a0b1cced295722d6f3fd0ef1dd59762fe8a0d194f602","impliedFormat":1},{"version":"9de5968f7244f12c0f75a105a79813539657df96fb33ea1dafa8d9c573a5001a","impliedFormat":1},{"version":"87ab1102c5f7fe3cffbbe00b9690694cba911699115f29a1e067052bb898155d","impliedFormat":1},{"version":"a5841bf09a0e29fdde1c93b97e9a411ba7c7f9608f0794cbb7cf30c6dcd84000","impliedFormat":1},{"version":"e9282e83efd5ab0937b318b751baac2690fc3a79634e7c034f6c7c4865b635b4","impliedFormat":1},{"version":"7469203511675b1cfb8c377df00c6691f2666afb1a30c0568146a332e3188cb3","impliedFormat":1},{"version":"86854a16385679c4451c12f00774d76e719d083333f474970de51b1fd4aeaa9a","impliedFormat":1},{"version":"eb948bd45504f08e641467880383a9d033221c92d5e5f9057a952bbb688af0f2","impliedFormat":1},{"version":"8ad3462b51ab1a76a049b9161e2343a56a903235a87a7b6fb7ed5df6fc3a7482","impliedFormat":1},{"version":"c5e3f5a8e311c1be603fca2ab0af315bb27b02e53cd42edc81c349ffb7471c7e","impliedFormat":1},{"version":"0785979b4c5059cde6095760bc402d936837cbdeaa2ce891abe42ebcc1be5141","impliedFormat":1},{"version":"224881bef60ae5cd6bcc05b56d7790e057f3f9d9eacf0ecd1b1fc6f02088df70","impliedFormat":1},{"version":"3d336a7e01d9326604b97a23d5461d48b87a6acf129616465e4de829344f3d88","impliedFormat":1},{"version":"27ae5474c2c9b8a160c2179f2ec89d9d7694f073bdfc7d50b32e961ef4464bf0","impliedFormat":1},{"version":"e5772c3a61ac515bdcbb21d8e7db7982327bca088484bf0efdc12d9e114ec4c4","impliedFormat":1},{"version":"37d515e173e580693d0fdb023035c8fb1a95259671af936ea0922397494999f1","impliedFormat":1},{"version":"9b75d00f49e437827beeec0ecd652f0e1f8923ff101c33a0643ce6bed7c71ce1","impliedFormat":1},{"version":"bca71e6fb60fb9b72072a65039a51039ac67ea28fd8ce9ffd3144b074f42e067","impliedFormat":1},{"version":"d9b3329d515ac9c8f3760557a44cbca614ad68ad6cf03995af643438fa6b1faa","impliedFormat":1},{"version":"66492516a8932a548f468705a0063189a406b772317f347e70b92658d891a48d","impliedFormat":1},{"version":"20ecc73297ec37a688d805463c5e9d2e9f107bf6b9a1360d1c44a2b365c0657b","impliedFormat":1},{"version":"8e5805f4aab86c828b7fa15be3820c795c67b26e1a451608a27f3e1a797d2bf0","impliedFormat":1},{"version":"bb841b0b3c3980f91594de12fdc4939bb47f954e501bd8e495b51a1237f269d6","impliedFormat":1},{"version":"c40a182c4231696bd4ea7ed0ce5782fc3d920697866a2d4049cf48a2823195cc","impliedFormat":1},{"version":"c2f1079984820437380eba543febfb3d77e533382cbc8c691e8ec7216c1632ae","impliedFormat":1},{"version":"8737160dbb0d29b3a8ea25529b8eca781885345adb5295aa777b2f0c79f4a43f","impliedFormat":1},{"version":"78c5ee6b2e6838b6cbda03917276dc239c4735761696bf279cea8fc6f57ab9b7","impliedFormat":1},{"version":"11f3e363dd67c504e7ac9c720e0ddee8eebca10212effe75558266b304200954","impliedFormat":1},{"version":"ca53a918dbe8b860e60fec27608a83d6d1db2a460ad13f2ffc583b6628be4c5c","impliedFormat":1},{"version":"b278ba14ce1ea93dd643cd5ad4e49269945e7faf344840ecdf3e5843432dc385","impliedFormat":1},{"version":"f590aedb4ab4a8fa99d5a20d3fce122f71ceb6a6ba42a5703ea57873e0b32b19","impliedFormat":1},{"version":"1b94fcec898a08ad0b7431b4b86742d1a68440fa4bc1cd51c0da5d1faaf8fda4","impliedFormat":1},{"version":"a6ca409cb4a4fb0921805038d02a29c7e6f914913de74ab7dc02604e744820f7","impliedFormat":1},{"version":"9e938bdb31700c1329362e2246192b3cd2fac25a688a2d9e7811d7a65b57cd48","impliedFormat":1},{"version":"22ab05103d6c1b0c7e6fd0d35d0b9561f2931614c67c91ba55e2d60d741af1aa","impliedFormat":1},{"version":"aeebcee8599e95eb96cf15e1b0046024354cc32045f7e6ec03a74dcb235097ec","impliedFormat":1},{"version":"6813230ae8fba431d73a653d3de3ed2dcf3a4b2e965ca529a1d7fefdfd2bfc05","impliedFormat":1},{"version":"2111a7f02e31dd161d7c62537a24ddcbd17b8a8de7a88436cb55cd237a1098b2","impliedFormat":1},{"version":"dcac554319421fbc60da5f4401c4b4849ec0c92260e33a812cd8265a28b66a50","impliedFormat":1},{"version":"69e79a58498dbd57c42bc70c6e6096b782f4c53430e1dc329326da37a83f534d","impliedFormat":1},{"version":"6f327fc6d6ffcf68338708b36a8a2516090e8518542e20bb7217e2227842c851","impliedFormat":1},{"version":"5d770e4cc5df14482c7561e05b953865c2fdd5375c01d9d31e944b911308b13a","impliedFormat":1},{"version":"80ad25f193466f8945f41e0e97b012e1dafe1bd31b98f2d5c6c69a5a97504c75","impliedFormat":1},{"version":"30e75a9da9cd1ff426edcf88a73c6932e0ef26f8cbe61eed608e64e2ec511b6c","impliedFormat":1},{"version":"9ee91f8325ece4840e74d01b0f0e24a4c9b9ec90eeca698a6884b73c0151aa11","impliedFormat":1},{"version":"7c3d6e13ac7868d6ff1641406e535fde89ebef163f0c1237c5be21e705ed4a92","impliedFormat":1},{"version":"13f2f82a4570688610db179b0d178f1a038b17403b3a8c80eaa89dbdc74ddfd6","impliedFormat":1},{"version":"f805bae240625c8af6d84ac0b9e3cf43c5a3574c632e48a990bcec6de75234fb","impliedFormat":1},{"version":"fa3ce6af18df2e1d3adca877a3fe814393917b2f59452a405028d3c008726393","impliedFormat":1},{"version":"274b8ce7763b1a086a8821b68a82587f2cb1e08020920ae9ec8e28db0a88cd24","impliedFormat":1},{"version":"ea5e168745ac57b4ee29d953a42dc8252d3644ad3b6dab9d2f0c556f93ce05b4","impliedFormat":1},{"version":"830020b6fe24d742c1c3951e09b8b10401a0e753b5e659a3cbdea7f1348daeac","impliedFormat":1},{"version":"b1f68144e6659b378f0e02218f3bd8dfa71311c2e27814ab176365ed104d445a","impliedFormat":1},{"version":"a7a375e4436286bc6e68ce61d680ffeb431dc87f951f6c175547308d24d9d7ab","impliedFormat":1},{"version":"e41845dbc0909b2f555e7bcb1ebc55321982c446d58264485ca87e71bf7704a8","impliedFormat":1},{"version":"546291fd95c3a93e1fc0acd24350c95430d842898fc838d8df9ba40fdc653d6a","impliedFormat":1},{"version":"a6e898c90498c82f5d4fd59740cb6eb64412b39e12ffeca57851c44fa7700ed4","impliedFormat":1},{"version":"c8fb0d7a81dac8e68673279a3879bee6059bf667941694de802c06695f3a62a9","impliedFormat":1},{"version":"0a0a0bf13b17a7418578abea1ddb82bf83406f6e5e24f4f74b4ffbab9582321f","impliedFormat":1},{"version":"c4ea3ac40fbbd06739e8b681c45a4d40eb291c46407c04d17a375c4f4b99d72c","impliedFormat":1},{"version":"0f65b5f6688a530d965a8822609e3927e69e17d053c875c8b2ff2aecc3cd3bf6","impliedFormat":1},{"version":"443e39ba1fa1206345a8b5d0c41decfe703b7cdab02c52b220d1d3d8d675be6f","impliedFormat":1},{"version":"eaf7a238913b3f959db67fe7b3ea76cd1f2eedc5120c3ba45af8c76c5a3b70ad","impliedFormat":1},{"version":"8638625d1375bbb588f97a830684980b7b103d953c28efffa01bd5b1b5f775d2","impliedFormat":1},{"version":"ee77e7073de8ddc79acf0a3e8c1a1c4f6c3d11164e19eb725fa353ce936a93b0","impliedFormat":1},{"version":"ac39c31661d41f20ca8ef9c831c6962dc8bccbfca8ad4793325637c6f69207a3","impliedFormat":1},{"version":"80d98332b76035499ccce75a1526adcf4a9d455219f33f4b5a2e074e18f343fe","impliedFormat":1},{"version":"0490b6e27352ca7187944d738400e1e0ccb8ad8cc2fb6a939980cec527f4a3f9","impliedFormat":1},{"version":"7759aad02ab8c1499f2b689b9df97c08a33da2cb5001fbf6aed790aa41606f48","impliedFormat":1},{"version":"cb3c2b54a3eb8364f9078cfbe5a3340fa582b14965266c84336ab83fa933f3c7","impliedFormat":1},{"version":"7bc5668328a4a22c3824974628d76957332e653f42928354e5ac95f4cd00664d","impliedFormat":1},{"version":"b1905e68299346cc9ea9d156efb298d85cdb31a74cef5dbb39fda0ba677d8cfc","impliedFormat":1},{"version":"3ab80817857677b976b89c91cd700738fc623f5d0c800c5e1d08f21ac2a61f2a","impliedFormat":1},{"version":"cab9fb386ad8f6b439d1e125653e9113f82646712d5ba5b1b9fd1424aa31650c","impliedFormat":1},{"version":"20af956da2baefb99392218a474114007f8f6763f235ae7c6aae129e7d009cb6","impliedFormat":1},{"version":"6bfc9175ea3ade8c3dce6796456f106eb6ddc6ac446c41a71534a4cdce92777a","impliedFormat":1},{"version":"c8290d0b597260fd0e55016690b70823501170e8db01991785a43d7e1e18435f","impliedFormat":1},{"version":"002dfb1c48a9aa8de9d2cbe4d0b74edd85b9e0c1b77c865dcfcacd734c47dd40","impliedFormat":1},{"version":"17638e7a71f068c258a1502bd2c62cd6562e773c9c8649be283d924dc5d3bada","impliedFormat":1},{"version":"4b5e02a4d0b8f5ab0e81927c23b3533778000d6f8dfe0c2d23f93b55f0dcf62e","impliedFormat":1},{"version":"7bcdcafce502819733dc4e9fbbd97b2e392c29ae058bd44273941966314e46b1","impliedFormat":1},{"version":"39fefe9a886121c86979946858e5d28e801245c58f64f2ae4b79c01ffe858664","impliedFormat":1},{"version":"e68ec97e9e9340128260e57ef7d0d876a6b42d8873bfa1500ddead2bef28c71a","impliedFormat":1},{"version":"b944068d6efd24f3e064d341c63161297dc7a6ebe71fd033144891370b664e6d","impliedFormat":1},{"version":"9aee6c3a933af38de188f46937bdc5f875e10b016136c4709a3df6a8ce7ce01d","impliedFormat":1},{"version":"c0f4cd570839560ba29091ce66e35147908526f429fcc1a4f7c895a79bbbc902","impliedFormat":1},{"version":"3d44d824b1d25e86fb24a1be0c2b4d102b14740e8f10d9f3a320a4c863d0acad","impliedFormat":1},{"version":"f80511b23e419a4ba794d3c5dadea7f17c86934fa7a9ac118adc71b01ad290e3","impliedFormat":1},{"version":"633eabeec387c19b9ad140a1254448928804887581e2f0460f991edb2b37f231","impliedFormat":1},{"version":"f7083bbe258f85d7b7b8524dd12e0c3ee8af56a43e72111c568c9912453173a6","impliedFormat":1},{"version":"067a32d6f333784d2aff45019e36d0fc96fff17931bb2813b9108f6d54a6f247","impliedFormat":1},{"version":"0c85a6e84e5e646a3e473d18f7cd8b3373b30d3b3080394faee8997ad50c0457","impliedFormat":1},{"version":"f554099b0cfd1002cbacf24969437fabec98d717756344734fbae48fb454b799","impliedFormat":1},{"version":"1c39be289d87da293d21110f82a31139d5c6030e7a738bdf6eb835b304664fdd","impliedFormat":1},{"version":"5e9da3344309ac5aa7b64276ea17820de87695e533c177f690a66d9219f78a1e","impliedFormat":1},{"version":"1d4258f658eda95ee39cd978a00299d8161c4fef8e3ceb9d5221dac0d7798242","impliedFormat":1},{"version":"7df3bac8f280e1a3366ecf6e7688b7f9bbc1a652eb6ad8c62c3690cc444932e3","impliedFormat":1},{"version":"816c71bf50425c02608c516df18dfcb2ed0fca6baef0dbb30931c4b93fb6ab28","impliedFormat":1},{"version":"a32e227cdf4c5338506e23f71d5464e892416ef6f936bafa911000f98b4f6285","impliedFormat":1},{"version":"215474b938cc87665c20fe984755e5d6857374627953428c783d0456149c4bda","impliedFormat":1},{"version":"6b4915d3c74438a424e04cd4645b13b8b74733d6da8e9403f90e2c2775501f49","impliedFormat":1},{"version":"780c26fecbc481a3ef0009349147859b8bd22df6947990d4563626a38b9598b8","impliedFormat":1},{"version":"41a87a15fdf586ff0815281cccfb87c5f8a47d0d5913eed6a3504dc28e60d588","impliedFormat":1},{"version":"0973d91f2e6c5e62a642685913f03ab9cb314f7090db789f2ed22c3df2117273","impliedFormat":1},{"version":"082b8f847d1e765685159f8fe4e7812850c30ab9c6bd59d3b032c2c8be172e29","impliedFormat":1},{"version":"63033aacc38308d6a07919ef6d5a2a62073f2c4eb9cd84d535cdb7a0ab986278","impliedFormat":1},{"version":"f30f24d34853a57aed37ad873cbabf07b93aff2d29a0dd2466649127f2a905ff","impliedFormat":1},{"version":"1828d9ea4868ea824046076bde3adfd5325d30c4749835379a731b74e1388c2a","impliedFormat":1},{"version":"4ac7ee4f70260e796b7a58e8ea394df1eaa932cdaf778aa54ef412d9b17fe51a","impliedFormat":1},{"version":"9ddbe84084a2b5a20dd14ca2c78b5a1f86a328662b11d506b9f22963415e7e8d","impliedFormat":1},{"version":"871e5cd964fafda0cd5736e757ba6f2465fd0f08b9ae27b08d0913ea9b18bea1","impliedFormat":1},{"version":"95b61511b685d6510b15c6f2f200d436161d462d768a7d61082bfba4a6b21f24","impliedFormat":1},{"version":"3a0f071c1c982b7a7e5f9aaea73791665b865f830b1ea7be795bc0d1fb11a65e","impliedFormat":1},{"version":"6fcdac5e4f572c04b1b9ff5d4dace84e7b0dcccf3d12f4f08d296db34c2c6ea7","impliedFormat":1},{"version":"04381d40188f648371f9583e3f72a466e36e940bd03c21e0fcf96c59170032f8","impliedFormat":1},{"version":"5b249815b2ab6fdfe06b99dc1b2a939065d6c08c6acf83f2f51983a2deabebce","impliedFormat":1},{"version":"93333bd511c70dc88cc8a458ee781b48d72f468a755fd2090d73f6998197d6d4","impliedFormat":1},{"version":"1f64a238917b7e245930c4d32d708703dcbd8997487c726fcbadaa706ebd45dc","impliedFormat":1},{"version":"17d463fd5e7535eecc4f4a8fd65f7b25b820959e918d1b7478178115b4878de0","impliedFormat":1},{"version":"10d5b512f0eeab3e815a58758d40abe1979b420b463f69e8acccbb8b8d6ef376","impliedFormat":1},{"version":"e3c6af799b71db2de29cf7513ec58d179af51c7aef539968b057b43f5830da06","impliedFormat":1},{"version":"fbd151883aa8bb8c7ea9c5d0a323662662e026419e335a0c3bd53772bd767ec5","impliedFormat":1},{"version":"7b55d29011568662da4e570f3a87f61b8238024bc82f5c14ae7a7d977dbd42b6","impliedFormat":1},{"version":"1a693131491bf438a4b2f5303f4c5e1761973ca20b224e5e9dcd4db77c45f09b","impliedFormat":1},{"version":"09181ba5e7efec5094c82be1eb7914a8fc81780d7e77f365812182307745d94f","impliedFormat":1},{"version":"fb5a59f40321ec0c04a23faa9cf0a0640e8b5de7f91408fb2ecaaec34d6b9caf","impliedFormat":1},{"version":"0e2578d08d1c0139ba788d05ef1a62aa50373e0540fd1cad3b1c0a0c13107362","impliedFormat":1},{"version":"65f22fbb80df4ffdd06b9616ec27887d25b30fd346d971ced3ab6e35d459e201","impliedFormat":1},{"version":"adf56fbfbd48d96ff2525dae160ad28bcb304d2145d23c19f7c5ba0d28d1c0cf","impliedFormat":1},{"version":"e972d127886b4ba51a40ef3fa3864f744645a7eaeb4452cb23a4895ccde4943e","impliedFormat":1},{"version":"5af6ea9946b587557f4d164a2c937bb3b383211fef5d5fd33980dc5b91d31927","impliedFormat":1},{"version":"bffa47537197a5462836b3bb95f567236fa144752f4b09c9fa53b2bf0ac4e39a","impliedFormat":1},{"version":"76e485bb46a79126e76c8c40487497f5831c5faa8d990a31182ad5bf9487409c","impliedFormat":1},{"version":"34c367f253d9f9f247a4d0af9c3cfcfaabb900e24db79917704cd2d48375d74c","impliedFormat":1},{"version":"1b7b16cceca67082cd6f10eeaf1845514def524c2bc293498ba491009b678df3","impliedFormat":1},{"version":"81ad399f8c6e85270b05682461ea97e3c3138f7233d81ddbe4010b09e485fce0","impliedFormat":1},{"version":"8baaf66fecb2a385e480f785a8509ac3723c1061ca3d038b80828e672891cccf","impliedFormat":1},{"version":"6ed1f646454dff5d7e5ce7bc5e9234d4e2b956a7573ef0d9b664412e0d82b83e","impliedFormat":1},{"version":"6777b3a04a9ff554b3e20c4cb106b8eb974caad374a3d2651d138f7166202f59","impliedFormat":1},{"version":"cc2a85161dab1f8b55134792706ecf2cf2813ad248048e6495f72e74ecb2462c","impliedFormat":1},{"version":"c994de814eca4580bfad6aeec3cbe0d5d910ae7a455ff2823b2d6dce1bbb1b46","impliedFormat":1},{"version":"a8fdd65c83f0a8bdfe393cf30b7596968ba2b6db83236332649817810cc095b6","impliedFormat":1},{"version":"2cc71c110752712ff13cea7fb5d9af9f5b8cfd6c1b299533eeaf200d870c25db","impliedFormat":1},{"version":"07047dd47ed22aec9867d241eed00bccb19a4de4a9e309c2d4c1efb03152722f","impliedFormat":1},{"version":"ce8f3cd9fd2507d87d944d8cdb2ba970359ea74821798eee65fd20e76877d204","impliedFormat":1},{"version":"5e63289e02fb09d73791ae06e9a36bf8e9b8b7471485f6169a2103cb57272803","impliedFormat":1},{"version":"16496edeb3f8f0358f2a9460202d7b841488b7b8f2049a294afcba8b1fce98f7","impliedFormat":1},{"version":"5f4931a81fac0f2f5b99f97936eb7a93e6286367b0991957ccd2aa0a86ce67e8","impliedFormat":1},{"version":"0c81c0048b48ba7b579b09ea739848f11582a6002f00c66fde4920c436754511","impliedFormat":1},{"version":"2a9efc08880e301d05e31f876eb43feb4f96fa409ec91cd0f454afddbedade99","impliedFormat":1},{"version":"8b84db0f190e26aeed913f2b6f7e6ec43fb7aeec40bf7447404db696bb10a1aa","impliedFormat":1},{"version":"3faa4463234d22b90d546925c128ad8e02b614227fb4bceb491f4169426a6496","impliedFormat":1},{"version":"83dc14a31138985c30d2b8bdf6b2510f17d9c1cd567f7aadd4cbfd793bd320b8","impliedFormat":1},{"version":"4c21526acf3a205b96962c5e0dc8fa73adbce05dd66a5b3960e71527f0fb8022","impliedFormat":1},{"version":"8de35ab4fcd11681a8a7dae4c4c25a1c98e9f66fbd597998ca3cea58012801a8","impliedFormat":1},{"version":"40a50581f3fa685fda5bbd869f6951272e64ccb973a07d75a6babf5ad8a7ec51","impliedFormat":1},{"version":"5575fd41771e3ff65a19744105d7fed575d45f9a570a64e3f1357fe47180e2a2","impliedFormat":1},{"version":"ea94b0150a7529c409871f6143436ead5939187d0c4ec1c15e0363468c1025cc","impliedFormat":1},{"version":"b8deddcf64481b14aa88489617e5708fcb64d4f64db914f10abbd755c8deb548","impliedFormat":1},{"version":"e2e932518d27e7c23070a8bbd6f367102a00107b7efdd4101c9906ac2c52c3f3","impliedFormat":1},{"version":"1a1a8889de2d1c898d4e786b8edf97a33b8778c2bb81f79bcf8b9446b01663dd","impliedFormat":1},{"version":"bb66806363baa6551bd61dd79941a3f620f64d4166148be8c708bf6f998c980b","impliedFormat":1},{"version":"23b58237fc8fbbcb111e7eb10e487303f5614e0e8715ec2a90d2f3a21fd1b1c0","impliedFormat":1},{"version":"c63bb5b72efbb8557fb731dc72705f1470284093652eca986621c392d6d273ab","impliedFormat":1},{"version":"9495b9e35a57c9bfec88bfb56d3d5995d32b681317449ad2f7d9f6fc72877fd0","impliedFormat":1},{"version":"8974fe4b0f39020e105e3f70ab8375a179896410c0b55ca87c6671e84dec6887","impliedFormat":1},{"version":"7f76d6eef38a5e8c7e59c7620b4b99205905f855f7481cb36a18b4fdef58926d","impliedFormat":1},{"version":"a74437aba4dd5f607ea08d9988146cee831b05e2d62942f85a04d5ad89d1a57a","impliedFormat":1},{"version":"65faea365a560d6cadac8dbf33953474ea5e1ef20ee3d8ff71f016b8d1d8eb7c","impliedFormat":1},{"version":"1d30c65c095214469a2cfa1fd40e881f8943d20352a5933aa1ed96e53118ca7e","impliedFormat":1},{"version":"342e05e460b6d55bfbbe2cf832a169d9987162535b4127c9f21eaf9b4d06578b","impliedFormat":1},{"version":"8bfced5b1cd8441ba225c7cbb2a85557f1cc49449051f0f71843bbb34399bbea","impliedFormat":1},{"version":"9388132f0cb90e5f0a44a5255f4293b384c6a79b0c9206249b3bcf49ff988659","impliedFormat":1},{"version":"a7e8f748de2465278f4698fe8656dd1891e49f9f81e719d6fc3eaf53b4df87ce","impliedFormat":1},{"version":"1ef1dcd20772be36891fd4038ad11c8e644fe91df42e4ccdbc5a5a4d0cfddf13","impliedFormat":1},{"version":"3e77ee3d425a8d762c12bb85fe879d7bc93a0a7ea2030f104653c631807c5b2e","impliedFormat":1},{"version":"e76004b4d4ce5ad970862190c3ef3ab96e8c4db211b0e680e55a61950183ff16","impliedFormat":1},{"version":"b959e66e49bfb7ff4ce79e73411ebc686e3c66b6b51bf7b3f369cc06814095f7","impliedFormat":1},{"version":"3e39e5b385a2e15183fc01c1f1d388beca6f56cd1259d3fe7c3024304b5fd7aa","impliedFormat":1},{"version":"3a4560b216670712294747d0bb4e6b391ca49271628514a1fe57d455258803db","impliedFormat":1},{"version":"f9458d81561e721f66bd4d91fb2d4351d6116e0f36c41459ad68fdbb0db30e0a","impliedFormat":1},{"version":"c7d36ae7ed49be7463825d42216648d2fb71831b48eb191bea324717ba0a7e59","impliedFormat":1},{"version":"5a1ae4a5e568072f2e45c2eed8bd9b9fceeb20b94e21fb3b1cec8b937ea56540","impliedFormat":1},{"version":"acbbea204ba808da0806b92039c87ae46f08c7277f9a32bf691c174cb791ddff","impliedFormat":1},{"version":"055489a2a42b6ece1cb9666e3d68de3b52ed95c7f6d02be3069cc3a6c84c428c","impliedFormat":1},{"version":"3038efd75c0661c7b3ff41d901447711c1363ef4aef4485f374847a8a2fcb921","impliedFormat":1},{"version":"0022901e655f49011384f960d6b67c5d225e84e2ea66aa4aae1576974a4e9b40","impliedFormat":1},{"version":"0022901e655f49011384f960d6b67c5d225e84e2ea66aa4aae1576974a4e9b40","impliedFormat":1},{"version":"9d2106024e848eccaeaa6bd9e0fd78742a0c542f2fbc8e3bb3ab29e88ece73a9","impliedFormat":1},{"version":"668a9d5803e4afcd23cd0a930886afdf161faa004f533e47a3c9508218df7ecd","impliedFormat":1},{"version":"dd769708426135f5f07cd5e218ac43bf5bcf03473c7cbf35f507e291c27161e7","impliedFormat":1},{"version":"6067f7620f896d6acb874d5cc2c4a97f1aa89d42b89bd597d6d640d947daefb8","impliedFormat":1},{"version":"8fd3454aaa1b0e0697667729d7c653076cf079180ef93f5515aabc012063e2c1","impliedFormat":1},{"version":"f13786f9349b7afc35d82e287c68fa9b298beb1be24daa100e1f346e213ca870","impliedFormat":1},{"version":"5e9f0e652f497c3b96749ed3e481d6fab67a3131f9de0a5ff01404b793799de4","impliedFormat":1},{"version":"1ad85c92299611b7cd621c9968b6346909bc571ea0135a3f2c7d0df04858c942","impliedFormat":1},{"version":"08ef30c7a3064a4296471363d4306337b044839b5d8c793db77d3b8beefbce5d","impliedFormat":1},{"version":"b700f2b2a2083253b82da74e01cac2aa9efd42ba3b3041b825f91f467fa1e532","impliedFormat":1},{"version":"0edbad572cdd86ec40e1f27f3a337b82574a8b1df277a466a4e83a90a2d62e76","impliedFormat":1},{"version":"cc2930e8215efe63048efb7ff3954df91eca64eab6bb596740dceb1ad959b9d4","impliedFormat":1},{"version":"1cf8615b4f02bbabb030a656aa1c7b7619b30da7a07d57e49b6e1f7864df995f","impliedFormat":1},{"version":"2cbd0adfb60e3fed2667e738eba35d9312ab61c46dbc6700a8babed2266ddcf2","impliedFormat":1},{"version":"bed2e48fefb5a30e82f176e79c8bd95d59915d3ae19f68e8e6f3a6df3719503f","impliedFormat":1},{"version":"032a6c17ee79d48039e97e8edb242fe2bd4fc86d53307a10248c2eda47dbd11d","impliedFormat":1},{"version":"83b28226a0b5697872ea7db24c4a1de91bbf046815b81deaa572b960a189702a","impliedFormat":1},{"version":"8c08bc40a514c6730c5e13e065905e9da7346a09d314d09acc832a6c4da73192","impliedFormat":1},{"version":"b95a07e367ec719ecc96922d863ab13cce18a35dde3400194ba2c4baccfafdc0","impliedFormat":1},{"version":"36e86973743ca5b4c8a08633ef077baf9ba47038002b8bbe1ac0a54a3554c53e","impliedFormat":1},{"version":"b8c19863be74de48ff0b5d806d3b51dc51c80bcf78902a828eb27c260b64e9f1","impliedFormat":1},{"version":"3555db94117fb741753ef5c37ffdb79f1b3e64e9f24652eecb5f00f1e0b1941c","impliedFormat":1},{"version":"52b3bc9c614a193402af641bee64a85783cd2988a46a09bdfe4bddd33410d1b8","impliedFormat":1},{"version":"52b3bc9c614a193402af641bee64a85783cd2988a46a09bdfe4bddd33410d1b8","impliedFormat":1},{"version":"deb25b0ec046c31b288ad7f4942c83ad29e5e10374bdb8af9a01e669df33d59d","impliedFormat":1},{"version":"deb25b0ec046c31b288ad7f4942c83ad29e5e10374bdb8af9a01e669df33d59d","impliedFormat":1},{"version":"a3eb808480fe13c0466917415aa067f695c102b00df00c4996525f1c9e847e4f","impliedFormat":1},{"version":"5d5e54ce407a53ac52fd481f08c29695a3d38f776fc5349ab69976d007b3198e","impliedFormat":1},{"version":"6f796d66834f2c70dd13cfd7c4746327754a806169505c7b21845f3d1cabd80a","impliedFormat":1},{"version":"bde869609f3f4f88d949dc94b55b6f44955a17b8b0c582cdef8113e0015523fa","impliedFormat":1},{"version":"9c16e682b23a335013941640433544800c225dc8ad4be7c0c74be357482603d5","impliedFormat":1},{"version":"622abbfd1bb206b8ea1131bb379ec1f0d7e9047eddefcfbe104e235bfc084926","impliedFormat":1},{"version":"3e5f94b435e7a57e4c176a9dc613cd4fb8fad9a647d69a3e9b77d469cdcdd611","impliedFormat":1},{"version":"f00c110b9e44555c0add02ccd23d2773e0208e8ceb8e124b10888be27473872d","impliedFormat":1},{"version":"0be282634869c94b20838acba1ac7b7fee09762dbed938bf8de7a264ba7c6856","impliedFormat":1},{"version":"a640827fd747f949c3e519742d15976d07da5e4d4ce6c2213f8e0dac12e9be6c","impliedFormat":1},{"version":"56dee4cdfa23843048dc72c3d86868bf81279dbf5acf917497e9f14f999de091","impliedFormat":1},{"version":"7890136a58cd9a38ac4d554830c6afd3a3fbff65a92d39ab9d1ef9ab9148c966","impliedFormat":1},{"version":"9ebd2b45f52de301defb043b3a09ee0dd698fc5867e539955a0174810b5bdf75","impliedFormat":1},{"version":"cbad726f60c617d0e5acb13aa12c34a42dc272889ac1e29b8cb2ae142c5257b5","impliedFormat":1},{"version":"009022c683276077897955237ca6cb866a2dfa2fe4c47fadcf9106bc9f393ae4","impliedFormat":1},{"version":"b03e6b5f2218fd844b35e2b6669541c8ad59066e1427f4f29b061f98b79aceeb","impliedFormat":1},{"version":"8451b7c29351c3be99ec247186bb17c8bde43871568488d8eb2739acab645635","impliedFormat":1},{"version":"2c2e64c339be849033f557267e98bd5130d9cb16d0dccada07048b03ac9bbc79","impliedFormat":1},{"version":"39c6cc52fed82f7208a47737a262916fbe0d9883d92556bd586559c94ef03486","impliedFormat":1},{"version":"5c467e74171c2d82381bb9c975a5d4b9185c78006c3f5da03e368ea8c1c3a32e","impliedFormat":1},{"version":"ef1e298d4ff9312d023336e6089a93ee1a35d7846be90b5f874ddd478185eac6","impliedFormat":1},{"version":"d829e88b60117a6bc2ca644f25b6f8bbaa40fc8998217536dbbbfd760677ae60","impliedFormat":1},{"version":"e922987ed23d56084ec8cce2d677352355b4afb372a4c7e36f6e507995811c43","impliedFormat":1},{"version":"9cca233ee9942aaafcf19a8d1f2929fed21299d836f489623c9abfb157b8cd87","impliedFormat":1},{"version":"0dc1aac5e460ea012fe8c67d885e875dbdc5bf38d6cb9addf3f2a0cc3558a670","impliedFormat":1},{"version":"1e350495bd8b33f251c59539c7aef25287ea4907feb08dab5651b78a989a2e6a","impliedFormat":1},{"version":"1e350495bd8b33f251c59539c7aef25287ea4907feb08dab5651b78a989a2e6a","impliedFormat":1},{"version":"4181ed429a8aac8124ea36bfc716d9360f49374eb36f1cc8872dcbbf545969eb","impliedFormat":1},{"version":"948b77bdc160db8025bf63cc0e53661f27c5c5244165505cc48024a388a9f003","impliedFormat":1},{"version":"b3ae4b9b7ec83e0630ce00728a9db6c8bb7909c59608d48cded3534d8ed8fa47","impliedFormat":1},{"version":"c2fa2cba39fcabec0be6d2163b8bc76d78ebe45972a098cca404b1a853aa5184","impliedFormat":1},{"version":"f98232fe7507f6c70831a27ddd5b4d759d6c17c948ed6635247a373b3cfee79e","impliedFormat":1},{"version":"61db0df9acc950cc1ac82897e6f24b6ab077f374059a37f9973bf5f2848cfa56","impliedFormat":1},{"version":"c185ceb3a4cd31153e213375f175e7b3f44f8c848f73faf8338a03fffb17f12b","impliedFormat":1},{"version":"bfa04fde894ce3277a5e99b3a8bec59f49dde8caaaa7fb69d2b72080b56aedbd","impliedFormat":1},{"version":"f4405ec08057cd8002910f210922de51c9273f577f456381aeb8671b678653c9","impliedFormat":1},{"version":"631f50cc97049c071368bf25e269380fad54314ce67722072d78219bff768e92","impliedFormat":1},{"version":"c88a192e6d7ec5545ad530112a595c34b2181acd91b2873f40135a0a2547b779","impliedFormat":1},{"version":"ddcb839b5b893c67e9cc75eacf49b2d4425518cfe0e9ebc818f558505c085f47","impliedFormat":1},{"version":"d962bdaac968c264a4fe36e6a4f658606a541c82a4a33fe3506e2c3511d3e40a","impliedFormat":1},{"version":"549daccede3355c1ed522e733f7ab19a458b3b11fb8055761b01df072584130a","impliedFormat":1},{"version":"2852612c7ca733311fe9443e38417fab3618d1aac9ba414ad32d0c7eced70005","impliedFormat":1},{"version":"f86a58fa606fec7ee8e2a079f6ff68b44b6ea68042eb4a8f5241a77116fbd166","impliedFormat":1},{"version":"434b612696740efb83d03dd244cb3426425cf9902f805f329b5ff66a91125f29","impliedFormat":1},{"version":"e6edb14c8330ab18bdd8d6f7110e6ff60e5d0a463aac2af32630d311dd5c1600","impliedFormat":1},{"version":"f5e8edbedcf04f12df6d55dc839c389c37740aa3acaa88b4fd9741402f155934","impliedFormat":1},{"version":"794d44962d68ae737d5fc8607c4c8447955fc953f99e9e0629cac557e4baf215","impliedFormat":1},{"version":"8d1fd96e52bc5e5b3b8d638a23060ef53f4c4f9e9e752aba64e1982fae5585fa","impliedFormat":1},{"version":"4881c78bd0526b6e865fcf38e174014645e098ac115cacd46b40be01ac85f384","impliedFormat":1},{"version":"56e5e78ff2acc23ad1524fc50579780bc2a9058024793f7674ec834759efc9de","impliedFormat":1},{"version":"13b9d386e5ee49b2f5caff5e7ed25b99135610dcda45638027c5a194cc463e27","impliedFormat":1},{"version":"631634948d2178785c3a707d5567ae0250a75bf531439381492fc26ef57d6e7f","impliedFormat":1},{"version":"1058b9b3ba92dd408e70dd8ea75cdde72557204a8224f29a6e4a8e8354da9773","impliedFormat":1},{"version":"997c112040764089156e67bab2b847d09af823cc494fe09e429cef375ef03af9","impliedFormat":1},{"version":"9ddf7550e43329fa373a0694316ddc3d423ae9bffa93d84b7b3bb66cf821dfae","impliedFormat":1},{"version":"fdb2517484c7860d404ba1adb1e97a82e890ba0941f50a850f1f4e34cfd6b735","impliedFormat":1},{"version":"5116b61c4784252a73847f6216fdbff5afa03faaab5ff110d9d7812dff5ddc3f","impliedFormat":1},{"version":"f68c1ecd47627db8041410fcb35b5327220b3b35287d2a3fcca9bf4274761e69","impliedFormat":1},{"version":"9d1726afaf9e34a7f31f3be543710d37b1854f40f635e351a63d47a74ceef774","impliedFormat":1},{"version":"a3a805ec9621188f85f9d3dda03b87b47cd31a92b76d2732eba540cc2af9612d","impliedFormat":1},{"version":"0f9e65ffa38ea63a48cf29eb6702bb4864238989628e039a08d2d7588be4ab15","impliedFormat":1},{"version":"3993a8d6d3068092ed74bb31715d4e1321bf0bbb094db0005e8aa2f7fbab0f93","impliedFormat":1},{"version":"bcc3756f063548f340191869980e14ded6d5cb030b3308875f9e6e0ce52071ed","impliedFormat":1},{"version":"7da3fcacec0dc6c8067601e3f2c39662827d7011ea06b61e06af2d253b55a363","impliedFormat":1},{"version":"d101d3030fb8b29ed44f999d0d03e5ec532f908c58fefb26c4ecd248fe8819c5","impliedFormat":1},{"version":"2898bf44723a97450bf234b9208bce7c524d1e7735a1396d9aabcba0a3f48896","impliedFormat":1},{"version":"3f04902889a4eb04ef34da100820d21b53a0327e9e4a6ef63cd6a9682538dc6f","impliedFormat":1},{"version":"67b0df47d30dad3449ba62d2f4e9c382ee25cb509540eb536ded3f59fb3fdf41","impliedFormat":1},{"version":"526e0604ed8cf5ec53d629c168013d99f06c0673108281e676053f04ee3afc6d","impliedFormat":1},{"version":"79f84d0bccc2f08c62a74cc4fcf445f996ef637579191edfc8c7c5bf351d4bd2","impliedFormat":1},{"version":"26694ee75957b55b34e637e9752742c6eee761155e8b87f8cdec335aee598da4","impliedFormat":1},{"version":"017b4f63bafe1e29d69dc2fecc5c3e1f119e8aa8e3c7a0e82c2f5b572dbc8969","impliedFormat":1},{"version":"74faaea9ae62eea1299cc853c34404ac2113117624060b6f89280f3bc5ed27de","impliedFormat":1},{"version":"3b114825464c5cafc64ffd133b5485aec7df022ec771cc5d985e1c2d03e9b772","impliedFormat":1},{"version":"c6711470bc8e21805a45681f432bf3916e735e167274e788120bcef2a639ebef","impliedFormat":1},{"version":"ad379db2a69abb28bb8aaf09679d24ac59a10b12b1b76d1201a75c51817a3b7c","impliedFormat":1},{"version":"3be0897930eb5a7ce6995bc03fa29ff0a245915975a1ad0b9285cfaa3834c370","impliedFormat":1},{"version":"0d6cf8d44b6c42cd9cd209a966725c5f06956b3c8b653ba395c5a142e96a7b80","impliedFormat":1},{"version":"0242e0818acc4d6b9da05da236279b1d6192f929959ebbd41f2fc899af504449","impliedFormat":1},{"version":"dbf3580e00ea32ec07da17de068f8f9aa63ad02e225bc51057466f1dfed18c32","impliedFormat":1},{"version":"e87ad82343dae2a5183ef77ab7c25e2ac086f0359850af8bfaf31195fb51bebe","impliedFormat":1},{"version":"0659ac04895ce1bfb7231fe37361e628f616eb48336dad0182860c21c8731564","impliedFormat":1},{"version":"627ec421b4dfad81f9f8fcbfe8e063edc2f3b77e7a84f9956583bdd9f9792683","impliedFormat":1},{"version":"d428bae78f42e0a022ca13ad4cdf83cc215357841338c8d4d20a78e100069c49","impliedFormat":1},{"version":"4843347a4d4fc2ebbdf8a1f3c2c5dc66a368271c4bddc0b80032ed849f87d418","impliedFormat":1},{"version":"3e05200e625222d97cf21f15793524b64a8f9d852e1490c4d4f1565a2f61dc4d","impliedFormat":1},{"version":"5d367e88114f344516c440a41c89f6efb85adb953b8cc1174e392c44b2ac06b6","impliedFormat":1},{"version":"22dc8f5847b8642e75b847ba174c24f61068d6ad77db8f0c23f4e46febdb36bb","impliedFormat":1},{"version":"7350c18dd0c7133c8d2ec272b1aa10784a801104d28669efc90071564750da6d","impliedFormat":1},{"version":"45bd73d4cb89c3fb2003257a4579cbce04c01a19b01fda4b5f1a819bcea71a2e","impliedFormat":1},{"version":"6684e81b54855f813639599aa847578f51c78b9933ff7eee306b6ce1b178bc0c","impliedFormat":1},{"version":"36ecc67bce3e36e22ea8af1a17c3bfade5bf1119fb87190f47366a678e823129","impliedFormat":1},{"version":"dbcc536b6bc9365e611989560eb30b81a07140602a9db632cc4761c66228b001","impliedFormat":1},{"version":"cb0b26b99104ec6b125c364fe81991b1e4fb7acdcb0315fff04a1f0c939d5e5d","impliedFormat":1},{"version":"e77adac69fbf0785ad1624a1dbaf02794877f38d75c095facd150bfef9cb0cc5","impliedFormat":1},{"version":"44710cf3db1cc8d826e242d2e251aff0d007fd9736a77d449fbe82b15a931919","impliedFormat":1},{"version":"44710cf3db1cc8d826e242d2e251aff0d007fd9736a77d449fbe82b15a931919","impliedFormat":1},{"version":"0d216597eed091e23091571e8df74ed2cb2813f0c8c2ce6003396a0e2e2ea07d","impliedFormat":1},{"version":"b6a0d16f4580faa215e0f0a6811bdc8403306a306637fc6cc6b47bf7e680dcca","impliedFormat":1},{"version":"9b4b8072aac21a792a2833eb803e6d49fd84043c0fd4996aa8d931c537fe3a36","impliedFormat":1},{"version":"9b4b8072aac21a792a2833eb803e6d49fd84043c0fd4996aa8d931c537fe3a36","impliedFormat":1},{"version":"67bcfdec85f9c235e7feb6faa04e312418e7997cd7341b524fb8d850c5b02888","impliedFormat":1},{"version":"519f452d81a2890c468cca90b9b285742b303a9b9fd1f88f264bb3dda4549430","impliedFormat":1},{"version":"519f452d81a2890c468cca90b9b285742b303a9b9fd1f88f264bb3dda4549430","impliedFormat":1},{"version":"d58d25fa1c781a2e5671e508223bf10a3faf0cde1105bc3f576adf2c31dd8289","impliedFormat":1},{"version":"376bc1793d293b7cd871fe58b7e58c65762db6144524cb022ffc2ced7fcc5d86","impliedFormat":1},{"version":"40bd62bd598ec259b1fa17cf9874618efe892fa3c009a228cb04a792cce425c8","impliedFormat":1},{"version":"8f5ac4753bd52889a1fa42edefab3860a07f198d67b6b7d8ac781f0d8938667b","impliedFormat":1},{"version":"962287ca67eb84fe22656190668a49b3f0f9202ec3bc590b103a249dca296acf","impliedFormat":1},{"version":"3dab1e83f2adb7547c95e0eec0143c4d6c28736490e78015ac50ca0e66e02cb0","impliedFormat":1},{"version":"7f0cfb5861870e909cc45778f5e22a4a1e9ecdec34c31e9d5232e691dd1370c8","impliedFormat":1},{"version":"8c645a4aa022e976b9cedd711b995bcff088ea3f0fb7bc81dcc568f810e3c77a","impliedFormat":1},{"version":"4cc2d393cffad281983daaf1a3022f3c3d36f5c6650325d02286b245705c4de3","impliedFormat":1},{"version":"f0913fc03a814cebb1ca50666fce2c43ef9455d73b838c8951123a8d85f41348","impliedFormat":1},{"version":"a8cfdf77b5434eff8b88b80ccefa27356d65c4e23456e3dd800106c45af07c3c","impliedFormat":1},{"version":"494fdf98dfa2d19b87d99812056417c7649b6c7da377b8e4f6e4e5de0591df1d","impliedFormat":1},{"version":"989034200895a6eaae08b5fd0e0336c91f95197d2975800fc8029df9556103c4","impliedFormat":1},{"version":"0ac4c61bb4d3668436aa3cd54fb82824d689ad42a05da3acb0ca1d9247a24179","impliedFormat":1},{"version":"c889405864afce2e14f1cffd72c0fccddcc3c2371e0a6b894381cc6b292c3d32","impliedFormat":1},{"version":"6d728524e535acd4d13e04d233fb2e4e1ef2793ffa94f6d513550c2567d6d4b4","impliedFormat":1},{"version":"14d6af39980aff7455152e2ebb5eb0ab4841e9c65a9b4297693153695f8610d5","impliedFormat":1},{"version":"44944d3b25469e4c910a9b3b5502b336f021a2f9fe67dd69d33afc30b64133b3","impliedFormat":1},{"version":"7aa71d2fa9dfb6e40bdd2cfa97e9152f4b2bd4898e677a9b9aeb7d703f1ca9ad","impliedFormat":1},{"version":"1f03bc3ba45c2ddca3a335532e2d2d133039f4648f2a1126ff2d03fb410be5dd","impliedFormat":1},{"version":"8b6fadc7df773879c30c0f954a11ec59e9b7430d50823c6bfb36fcc67b59eb42","impliedFormat":1},{"version":"689cb95de8ea23df837129d80a0037fe6fbadba25042199d9bb0c9366ace83b7","impliedFormat":1},{"version":"eeb6c806376b9c3464f29b6058aecf113328f9ce290af0375e520f1a844529cf","signature":"61f11ef9f7b473f14c872a139f0a329251738f177edfdcaefb3745adf8967036"},{"version":"5128c2a5fb4f7ed3fbc1941daf38be2f46d4d254602742f9082764730d2b10f8","signature":"11ef15e6c437548d908fba2917027940aebd6d68599d4e848dd559f1a8b2c8b2"},{"version":"88cdbc3bcb4689a70130597de7c941b2450bb2760674a02ae816e0667a1958f6","signature":"6dbaf13dab6dc2db0cb7312fba7996ca7f548c7929bb627315cc89b43bf93ada"},{"version":"42b8fa71b5a9f74f951ff7dc8e56f2bdd153828422806d3448cc4befae1099a4","signature":"ca5fc69e2b35182c5f563ad51094b9d8b3653d7d86beba04cb2cb9985518930f"},{"version":"0ec42581e4a23b521960e65c4be272a7a0ee95de6781984ad8f0a83cf343a436","signature":"9476a3afb022179c2f02f17ca6bfcf8a1a9dd5d56bd0fb97be3df694d3eb96a7"},{"version":"ed0300836934be9970c950c0d485e8276fac87cf1fec92f07e5f13fb7203604d","signature":"e3d48af43b4af0455edee6944467120f4272a8306e90d504935da490b053cafd"},{"version":"30af16a8cc19021a7a377c1a600de0a200f8b9cfcbe2515ae94b53bb7a36b6db","signature":"646d3971a94a1d0471f10da8b101d27e1c7f67d1da535a2073edf0cfad37af47"},{"version":"76de4cd29d83c839bb06e4b88bfd90489ecc4886a893a42026b49d030398dce2","signature":"27b609c42a7a19cb3fb9b39d724cd22fea65d6b10c2e3a6e97df5633ede083e4"},{"version":"d4e9258acb020b007d2a76f0d9870d5d10f0a2d6bc8d24bcaa0f65931892b736","signature":"345eb0a009f9b07377ff2e8bcbd390da1648e549914b3bd027bd6b4987f92481"},{"version":"c91ebf7986b077157f3375872d8b99925a2da8f69b0a6c7d38a3ee7f4983e81f","signature":"75d118b3f69f6d7a0e404f9878839e937ce9aebd2d9454bd77fa102a519a92eb"},{"version":"93c88804801702c2ebf4d7e282ff71d90f118253ee206e7f0ba03305cc581546","signature":"0a7f51c3fb4b7c9a30745a92c15a4cb4eb88aa3ea69dec8f6286491fdfb99dab"},{"version":"447809300abe6967b66fe4226b18bcd8513258b0139a8fd1ac516767bc510372","signature":"6d31cb09b5e87e0588c937c73aff7673a15865e355903fe16ac3bdcb3d2894d6"},{"version":"c4f3d9c6228f744351b3f3d6ac2593edba9e7cf0a965cc6ccb1805595f44f275","signature":"96dacaf48c43f86fe63578c47b008b14f31ea3b26ba6604869be23386548a0af"},{"version":"af01c8571f73ce9045720d7d072e5f223ac4173b24ddc2cb7bb1e836d5909d06","signature":"390d1c24cbc050e4a28b87c36bb0e1c0529633d31415c218a6488bb9f66ca318"},{"version":"d689a82dec12ec02e1c558870a50f71000e06f173151fcdff0458c587dbf016f","impliedFormat":99},{"version":"e58c0b5226aff07b63be6ac6e1bec9d55bc3d2bda3b11b9b68cccea8c24ae839","affectsGlobalScope":true,"impliedFormat":99},{"version":"5a88655bf852c8cc007d6bc874ab61d1d63fba97063020458177173c454e9b4a","impliedFormat":99},{"version":"7e4dfae2da12ec71ffd9f55f4641a6e05610ce0d6784838659490e259e4eb13c","impliedFormat":99},{"version":"c30a41267fc04c6518b17e55dcb2b810f267af4314b0b6d7df1c33a76ce1b330","impliedFormat":1},{"version":"72422d0bac4076912385d0c10911b82e4694fc106e2d70added091f88f0824ba","impliedFormat":1},{"version":"da251b82c25bee1d93f9fd80c5a61d945da4f708ca21285541d7aff83ecb8200","impliedFormat":1},{"version":"64db14db2bf37ac089766fdb3c7e1160fabc10e9929bc2deeede7237e4419fc8","impliedFormat":1},{"version":"98b94085c9f78eba36d3d2314affe973e8994f99864b8708122750788825c771","impliedFormat":1},{"version":"c371ea3efec17691f019bca670c161bee2f4787b1f464bdbbb10c4117bf0a55d","impliedFormat":99},{"version":"46eb5f0f3e155a84e67633dc5a558035505837b6f69dc7d17092ca495db0f424","signature":"e3918bfa940462ace5ebe24793f762776037b80274178b5bfe4b5353ba287c9b"},{"version":"e69b9766df423e1550c2ff84daf2fa7a0efb8585593dda3a548892f7bc003d6a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f22a38020548019be436f7d33fae243aecf68c9e068dd7fb5d88ec31fcfce2c5","signature":"990a86a4c51ffd7c3c146bc5b5e4f2a6eb31f8a08186da00d699ec62be38d14c"},{"version":"41a4b706f190423fb86f0fe568b685dc59c4a76760b506b02c10a3eb70ff880d","impliedFormat":1},{"version":"e26d25ac80157bf4dfaf1b15917520d7d5aa515389c7c0464911ab0129e58835","impliedFormat":1},{"version":"e305ba550c25a1807b5c432b31563f897c82111c8bce166e560029c94df204c3","impliedFormat":1},{"version":"a3e0936d6b795d2fc46850dc9c590152942ee297f3271dbd3d8588c3983592c6","impliedFormat":1},{"version":"2013af14579369e7822fb5f20f01268f3338141d4a382d5969144fe9c6a25ccb","impliedFormat":1},{"version":"6b7fcb23322518af97092aafb777aefa9196a1db215071fe8f96ae6f6101b498","impliedFormat":1},{"version":"a98d81cd9207ec9f1bd54d454809a60b267043dd56b165bdb6912bf9d63d4759","impliedFormat":1},{"version":"cd1aff7bdfa3af19aae3a21c958b932af6941ed89af069bd789f9ba5ad43377c","impliedFormat":1},{"version":"33e9caf10988726e5f7be53d2a4ebaad8db16e51b5f81e5ed4dbb0068f3a88fb","impliedFormat":1},{"version":"cc0d535d54cfec869fe4d28b9acda7e3427b25d8d84fdfe495021ad4cdee301e","impliedFormat":1},{"version":"f3e743e40c9a3b22de28624c8428e1df4cedc2fa3b3d99c546ebb95592ba1978","impliedFormat":1},{"version":"f8dab311b48db5e3b4361d76a629201098efdaa1b785120353f0ad221b263682","impliedFormat":1},{"version":"261b2daf69470a9e9d1ef26aa5e0c23f1611af380eb5e5f031c776ba57367741","impliedFormat":1},{"version":"e181f3ee5845726fa8077b39742a875d4fbaeb35f036765f95a77afcf982f989","impliedFormat":1},{"version":"9227c7d1182bc93c52d39d65472fef3f850a0d0145e1fac7388758d995878ccb","impliedFormat":1},{"version":"d90fecb86a618f308931b2efa45d7beeb73185db71ff184c1066b4e5f5900771","impliedFormat":1},{"version":"69af21e2252e417f1f26d2e6b1ded1f20dba11f066ce0690c53946f4369e00cf","impliedFormat":1},{"version":"fd4ae6100e700ea9e36ab622968f12ec03792f98f7da286cc44649395022453f","impliedFormat":1},{"version":"1adede2d971ad167525195ed811b8e0af2d17c728350689c470b6d5c5a3aa60c","impliedFormat":1},{"version":"4486b61dcb644ea4527e90b5bfbe7600b9ed50371d334a0c01e8fe661a99d952","impliedFormat":1},{"version":"dae0c9cc1c106f7b91a726813cfafe489dec4fb1e3f4f7c684d14e5f99f96b95","impliedFormat":1},{"version":"bf5fe5a9da7069bca0bb83fdc31ba9f624ac778c3ce25a06a3099d83a92a4858","impliedFormat":1},{"version":"7a0958a789740f9ddba6b7d80fbb1ae4d97da910970c1d59965ebc80304ad22c","impliedFormat":1},{"version":"54c9f10d962306a39448b924e73e631bba8221b5797ca4b2dcfb00dca78827b8","impliedFormat":1},{"version":"7125d9240eb556853fdaf7c892b60c630f6d01bd11580a32e9f299b0b91ca4f5","impliedFormat":1},{"version":"b339079a6ce4d7e86dc68ea8af379ee8700dfb205da5546ffdd3cdfe4be43df4","impliedFormat":1},{"version":"a97af3b565a5649c8cd983df40878da9a4bc080cf1a918199574f82a25fa727f","impliedFormat":1},{"version":"6e5996003f6555309d988ceae5b9952f3de47fc6e5110e5d64c612d4f37cb490","impliedFormat":1},{"version":"f07a1939b55120050d3fb95633ac6f8ebdb594e63eaf8f14c123480cc4bec03a","impliedFormat":1},{"version":"325e48e1f58a88830852e00f4fe7c515afef925a1254975a3ba9c54c9156f3fb","impliedFormat":1},{"version":"79caaa6f2db6cd3a1362f50b48ebae937a234b421c66516b8c98282b7d713d12","impliedFormat":1},{"version":"e7f0aa1f6c72a395ef5ac8749c03a31b00ea527abca2db5aea63804fef2dbb72","impliedFormat":1},{"version":"edca8fd9f3960acb4223f2a596d1f944320193afb076cb8b96afc19fb83144ec","impliedFormat":1},{"version":"511a250228acb3131a8c9eabaf45a53c3015c8a5abffc1c2e8b37942b779ef31","impliedFormat":1},{"version":"496927b9d1a6a7bea58453b9749b878ee1040ddc22de6db6e689db11ec111ebb","impliedFormat":1},{"version":"d17c142d0bbf9f20c696ed0224d1740fbbda0b0211225f2e48732809e15f2505","impliedFormat":1},{"version":"67e2a7d5facca25f1df14bc4fa4167337405fdc98b47df49b98aa2dcdaff5696","impliedFormat":1},{"version":"e16f1bf72fcbea703f1b9dba81e6faaf65e29d24128f9e09504c9a862f41c9d1","impliedFormat":1},{"version":"2e47b85f55b2c6606b137b4be24ea386c58bd9621370d2373e95b530c955102e","impliedFormat":1},{"version":"1f9fcdb7e24f1f9b391bb9200e32f6353781a1c604159a6257e9d8f1df7b9bee","impliedFormat":1},{"version":"3c788378c8c8d9525c168ce0578d536607896146b88bb5f6670ab19834be30b3","impliedFormat":1},{"version":"d16aba532f5391ccb1a61f90cf8e904ecabf3105641c398df0f4a79cac27d472","impliedFormat":1},{"version":"8d3a30c921fa2ea91d1bffc7fd9b8c42dc74bc819c503208122bd2e84cbffb5f","impliedFormat":1},{"version":"e4fb93fd2ec72ebaaa1e657435d607ed155da0005e6e30c440257d9fa877d834","impliedFormat":1},{"version":"ad103c0c76b809a8830ca4f9a8c8cb43b53d578bc63dc8c63b1342f1de90f8d8","impliedFormat":1},{"version":"7e15e2f23da806eabcf4bd0f1e48d7a5baeface210f05837171b50eed7d2b894","impliedFormat":1},{"version":"fb5e2afe7c4f988b615085166421f8d88464f6234abcc740ed861eccc0e17ba4","impliedFormat":1},{"version":"652af46b250ee5144ecb0eef7bcfa1647c88fd170cca974bdff9bb315f349f52","impliedFormat":1},{"version":"790daabde36636c46a264b1628f488ae49b2b378810383a1059ee722e82ceab8","impliedFormat":1},{"version":"89a58d0ab59eec78a6b6532e5d748f9942568891619633c890638a2912224ad5","impliedFormat":1},{"version":"9af24ffe92056dea7acff1dee779be364ad35e5f9861ca417d17bfb447a0a230","impliedFormat":1},{"version":"8fab0b106acb9de629cc3f7bf784187cd59d506d734917c4f140d02f0dcd167d","impliedFormat":1},{"version":"d0ba3b6aaef0c96be907938b6fb2a3a04a5db59de34a40f7e426bc7f10bb46d6","impliedFormat":1},{"version":"d91c919538e393ed3c649270a73f239ea7cd9f312dcee7dad037869a6eb0eee0","impliedFormat":1},{"version":"fcf5f4ff294643e6ea5100d09f40668a3a8744b73b8f1c397fac4b17ccecc72f","impliedFormat":1},{"version":"af3bebc2d30fe79abc9a505bc890d16af72f8ea21ec59009e9d57c2d8f6e0b01","impliedFormat":1},{"version":"784c657f85bebb1a7d94ef05e10f1cad4abdf32798203ef8631f7c3aca2390dd","impliedFormat":1},{"version":"e488fdf1efc9112b9ae08aaf2be027c3cc5603b916582c45d73bb3885728543f","impliedFormat":1},{"version":"a43b695758408470608b548841c97ad3827e453fe81ad835e29b9871129785f7","impliedFormat":1},{"version":"96dd9a7f52627f94b64da26c1ce05d2350941487861c8c27a0014c67273c8a40","impliedFormat":1},{"version":"0e754d4ed9a6cd6c131515ba94f3f1095fb10ac3cb0c20c2cbeef9e895f924c9","impliedFormat":1},{"version":"72cac4a4359c6a5e2e5c0ece767455797e46871350324dfe42ab14238f675729","impliedFormat":1},{"version":"cd091878f6b6994d9307156bda8a4419c7c41c524228d9e830f5fa618d70672e","impliedFormat":1},{"version":"bd3bbe444bc7cc28757c7669fb186a9ba326d4b65dbf99e18b2b5b9ca66edeb9","impliedFormat":1},{"version":"b10c73b3d7703d2d870d35631428cdec737d4bbc06706b6fcc6f6e058b8e1594","impliedFormat":1},{"version":"2543b46883befbefe10c2de9f3a0e7809de7baa09e192edda748443fd15d38d3","impliedFormat":1},{"version":"01cdf83ab596024078a6ce08ff990770326ceaf16f9081a8e369b9bc5110cadd","impliedFormat":1},{"version":"bbb9a17f2654caa1d34f49428c0e48ca0fd0d9550f5f82da6f544c924afa17b3","impliedFormat":1},{"version":"a9d8ba2c15cd99f51aa291034a1afff2f67f1f88259d2162eddaa25e3644032a","impliedFormat":1},{"version":"affa88c9484982a9aa35b30480059dadfe98c3bbd92f76513ae2d1d7e68096d2","impliedFormat":1},{"version":"3436c4b40e71c333e253578f6f3176870d4963d5f4ec14862ba5e40794bef8bc","impliedFormat":1},{"version":"a02f786598d002e2e42854d9bb4fc5a4ac03589538055a0eca03c9ec7ad35457","impliedFormat":1},{"version":"348863ce75f819f43557d134e5e7ff11a8ae582ac879349cdf9156bb696012f7","impliedFormat":1},{"version":"235ede03bdeeb87ca21b68fb1398ebc4749a924e2a7219977b71bfc9c51574a7","impliedFormat":1},{"version":"ec83a163716e8a8c2324e3f6b64c907ba7e5247b43df47f52edef954232c0211","impliedFormat":1},{"version":"15a76d1390ae38fe474023a51778887a6e39cc4204f65519a448ed7d1931275f","impliedFormat":1},{"version":"d7a9a81362adcd395f9db48531a89df84461595d189a234796e85ef983399042","impliedFormat":1},{"version":"7ac3584d37571a5ba61326f50860d843072ea95673e53d23b0b684635db9db00","impliedFormat":1},{"version":"96354248b7b7fe792c9545b7b153ef20763677692e9baa9aa6d1afbb17376ba7","impliedFormat":1},{"version":"5dce9f1eea7d40ad9f10295dff47b7de6fbb24b33858c0ff91aa75043e9899d3","impliedFormat":1},{"version":"d324bb068a3a98f3d7ff92eed388935a5ed4bd46b7678c5bf057b5a2ee9416d3","impliedFormat":1},{"version":"fe6e76ab5933ca777c6ce422c7023d44799d4832a8c5ce35e3592c8430867329","impliedFormat":1},{"version":"cc5beca247a7da7286a82c0f3b84686a922d0a402ead5d11b9ddd0dcdef5c762","impliedFormat":1},{"version":"24661a3d44d16268a8ac8260f35651526c54496ed5f29e559c066b7b7e6776c9","impliedFormat":1},{"version":"7c7ddd5cfbbab70612c216ab1d1f982468118fead1d57948ed31b41cd692c2fb","impliedFormat":1},{"version":"461ffe558e162cb3e451654eed59d0090a267818fde655088616be907007d654","impliedFormat":1},{"version":"829df07ac748fd372b8bcace5b46e6ce0420d2fd65c23f64b86e8a099b69a21b","impliedFormat":1},{"version":"162ff76724612621b2623b71139fae21981b276595c5e93a909b464c6eaf6310","impliedFormat":1},{"version":"4ec0abadee52b5287cba7929ce1c34e81a67a046c0299908158497ee85ff20b7","impliedFormat":1},{"version":"3b60b6d30d8be5b573e75d148abd155fb74cbce0795055965ad505afa4b181f9","impliedFormat":1},{"version":"5ea0f9746d216da9d45885462fdad43ed26dc4473ac6d289a94661c9a1e7bbbf","impliedFormat":1},{"version":"96198fa503333a1856039319ad4bc45c6e32afe2ede6a050e23fee2127139a40","impliedFormat":1},{"version":"e9dbaa3c845b96ebd98a7a3c59296fad4f5cbe4c2e471d0c54a248dab6d575f0","impliedFormat":1},{"version":"63c5fe7a04273a69125008737aa3c18212a1276b3a2f3892080c346cb589a716","impliedFormat":1},{"version":"adca096d8a06e8fe1f6f8a1d95dd176e0ec2216f5dce683c9c3656a9bb1e1f10","impliedFormat":1},{"version":"fe5cfccf2b757c44e7251d6ac822f4892d63f0dbbab920da4f24b893e655e836","impliedFormat":1},{"version":"e2a5e2a231048f1b0a8c6c123d524adeb3eda1464ac2413fd039cf5afa57bc54","impliedFormat":1},{"version":"8701130ab14da66b4e908e13c3ece584b420399cc543bafca971c414059ee5e8","impliedFormat":1},{"version":"811e9e98ddaacadbbcc92015fafd5f5ce0dbebc14f3536cbe225094b1d61f885","impliedFormat":1},{"version":"f7c8e5f19d7159bde8f8e9c6561c6e517953457faa86018a7f963b72863380fe","impliedFormat":1},{"version":"c0bc4b28c78bccbb158fb2e8b3e37a86fee5f26b6098a857befd864790da7cd8","impliedFormat":1},{"version":"e7b604762369c8fa5ffafb6e238a2c7af296e5a25bfa25cb33191b525f064cd0","impliedFormat":1},{"version":"dcabfb44bd25183c919819f87428fc589b20c3b9586825ec456f94cdb67bd316","impliedFormat":1},{"version":"41c39405eb8d94777d8b30d2bb295c258391ac4a45deef8d2f569b29bb82938c","impliedFormat":1},{"version":"f5786f9b0a39c790d245b33436e75576990e41e995e1fff1b0919833a57f4357","impliedFormat":1},{"version":"970025f12906d26ea3c1c381199eb6702b9c8cb0bec44edc02e86e004cf95eb1","impliedFormat":1},{"version":"8dafc03ad3ee7acabdb9254c702b80755bdafa7d7548cc6ffc21814e83055abd","impliedFormat":1},{"version":"e0dba6e973edfd7a5d8a7307ad1e6ec014b51fb7dac507ae132d1f9429016252","impliedFormat":1},{"version":"cc2f61e4781ad29f2aa93d4850de1b1d8313f242631f10ca17cc99411eb63022","impliedFormat":1},{"version":"6679676c1dc90d9c371f3de8430bf070ed36d2677d9ce3d2a336b54c5c40c2d7","impliedFormat":1},{"version":"cf2b168364792895c95f8f98f8fb662f07787e518e6d25b0f7c4aca9927a1bb5","impliedFormat":1},{"version":"db115e097d9ccc281414e7cedebfb0435d5bb14022f147331fb1bdad09404885","impliedFormat":1},{"version":"aa78c2b93bce87b73fccd6726cac3cac4f62927460ff3495f2a05553b4c04d3e","impliedFormat":1},{"version":"298104d50f65103c256ad79ff5128141000e4544a6afaa998d3099bee3975b84","impliedFormat":1},{"version":"a99f5ced5c95d7603c94c66a4619dfd0e737351bf20757de516b7f8ca193cce9","impliedFormat":1},{"version":"341b20f291eecbfefa6760a69f7f3f18b2094edcc794f4e78e903a5f0dd86fa6","impliedFormat":1},{"version":"238dd354909fc4a682e0cc4bd0d1eee8ba03197a4efa3cb284e502355eaec8de","impliedFormat":1},{"version":"8a3156a33e38b19f00d543a9f7a96054a0a4b051533449fb04267b4e533f55ef","impliedFormat":1},{"version":"bfd14082a4db87c2847135aab3d617ad7b488b3e65ac82f1620742548ed630f3","impliedFormat":1},{"version":"e2aa5b5cbc067b485de95616efb852886f4a1a43685ed7ea0ee8e08fec961cb2","impliedFormat":1},{"version":"3d6d27c275808a7e8540b1778a5d3808542518acda03f5c1ea4c9c5831058ea0","impliedFormat":1},{"version":"f534c1a02cd756679611ca2b36431b51715a0c59a070d413e292dfa23b9b5c6d","impliedFormat":1},{"version":"e26cccd0ef5654714877908c1674bee29a8e53d60c8c2d82bdffc12cac6b0fb5","impliedFormat":1},{"version":"ccf581fb8928f37fbd6509a7d8fa0d32156fc4eb414f434bf81cf7bd6849f7b8","impliedFormat":1},{"version":"69b227120a5245cddb0805eea82a0bea405872bcf595d2fce9fc03dd16133291","impliedFormat":1},{"version":"d6f495bfd0020102c67a6f80e411b00b913a001c468001b7cbe8c592c748f301","impliedFormat":1},{"version":"eb4463ba66be74eb04aeba3bded1f485a6ee90bed8c28a2c2573f0c983834790","impliedFormat":1},{"version":"a76187885d25a8aed20f71760c116bfb89ed1612d125bc190ab25a1a7a87ed91","impliedFormat":1},{"version":"76d36099aa1a0c7ea690ee1675ac4dd86cc62e2643cd40c097899268f8d2f7a8","impliedFormat":1},{"version":"815d7ee4cb5383f94b88688b8f2d70ce3e5df4de147d3669d225f8bfcffad673","impliedFormat":1},{"version":"afc6a3b94e405b3ae5d5038fe66f3b2412300c93ba1250805ee1a7ad19964ff6","impliedFormat":1},{"version":"b168bf198df3af94f54863a77ca14dcdb67af689d4cd876328f7c70bbb7b985f","impliedFormat":1},{"version":"54a40fe6e389146cb444299ef2d7d6e4ec83b05a9df2e7611fd1d1d862b2743a","impliedFormat":1},{"version":"65ec140c7cde7edee0f611bc84ce505cfa71916571e76f5cc197ba1dcde32f2f","impliedFormat":1},{"version":"969a96cb343d30bd8a28bad66c77049aeb0fabd9f608ad82caee751082685eeb","impliedFormat":1},{"version":"b30e161d3bbbe3f8d15b0bc5d9b47d1935ffffc7ced1099fcd84536c906af711","impliedFormat":1},{"version":"3eaea558e36977f924d85b3207406594568053d7a52950081b7603d112edefce","impliedFormat":1},{"version":"4918bfaa32bc0f63380d84b19bf5adff3188797b17b055417bbfdb09bd531d1d","impliedFormat":1},{"version":"5750305060905aff115cc0a6f295357099856fe72d76a1193204c23b4b9417f9","impliedFormat":1},{"version":"98195f663b1c09572bf794bf2fd7351b05d5895ed471589c0c79ae2e7c7d6b97","impliedFormat":1},{"version":"355e8226f1a83a02c2c5dc22781defbcf171df314f6f3205318851fd4550132f","impliedFormat":1},{"version":"8abeb772b7a7763686fd699980b74d9895863a758f2a6b824edb3d5e5c235078","impliedFormat":1},{"version":"dc1e2e53c9935f62739ca37d9acd484e83fd25bc051e5a330c9be0acbe774253","impliedFormat":1},{"version":"71a542cb540a3c0d4d954d311937a4df56157b0797489ea5cb8a9e57f449aefe","impliedFormat":1},{"version":"49fbd0ae0b53936499ca6293450230273cf299e017ac4a1cc8de936d50d8e696","impliedFormat":1},{"version":"d1a6aec1239a47bbcbbc55324d6ed293f79d4554f6bb0e411e206a9e22c50aa6","impliedFormat":1},{"version":"4e81f6b7048f1022bd8dd7dc18e43b4aca8967c4ffbfc8fe80bd4277936e5be3","impliedFormat":1},{"version":"5ff6328b404fb34d2828b501bd16f75bf17590d2b03a66a469a0359da07a06fa","impliedFormat":1},{"version":"149fbdfda86091cc37779a6eb3f01ac5c73d6e6d34a71e76bb3702a1ebdd8bf6","impliedFormat":1},{"version":"3c1e92d9a7a0c81d02f016c47de63a39706bb0e36231f2e6727a08cbbef6cfa2","impliedFormat":1},{"version":"ebec459a7d4933732cb453254997b9b9e7d17319dd40a29946f985719e297927","impliedFormat":1},{"version":"394dec85f81a33891c71f4e6a1b9a40afdbe93210d5dec749a2791deb57df5e4","impliedFormat":1},{"version":"19d2786de07b0dd973e5515d84182d2734f1c1ecf602929f75b30081fb20fcce","impliedFormat":1},{"version":"728d3db3ffdbd649c96fd64cb5766993cc8cb10b4ef207403fb98304eba04f57","impliedFormat":1},{"version":"8e4ae5371abcf89ca3059e621b666f1a340db0575f0c8635431417528f2d6367","impliedFormat":1},{"version":"de4f5917a7bf2c62cd3ab4c171620fd6e88a4a92902f02c0808ae67793e6baad","impliedFormat":1},{"version":"8f09f0abdd346cdbb1e545a44c85dcefeb047d07080628562a328d3e88160beb","impliedFormat":1},{"version":"4557c1259460570a893f9adb1547b5bdd19948497740c53fbaf654b20ded5855","impliedFormat":1},{"version":"ddf8a6692f74ae9ebdece687ec1cd9ff63811c5af27381b40c7996053a1b0504","impliedFormat":1},{"version":"de735626154dab7ceb24b208728b7461aaa5ad8848152ab0b39192e7bc0aa4be","impliedFormat":1},{"version":"c6296acd69aca14033c815aebc1b4c7fe72b92e44145dfe6432fe855c8c7b463","impliedFormat":1},{"version":"fe7df3fef3d25c0455e7cd4f36fdff7ad7b4d2163e7285a74d6a98a6cb48a282","impliedFormat":1},{"version":"7e9bf7c76b60c4402bd48996bfb0d1fa552a576991f9f73dbb856d15b0346793","impliedFormat":1},{"version":"29199bf01375b374d516e5c8d5d8ce1dac3c07cc52b00eebc0a7ba7b05baeb1c","impliedFormat":1},{"version":"7696d51d4ce2f2f21e9f73214396bfd823bee6c57f65d39e6a2264c40dd021f2","impliedFormat":1},{"version":"4b74f4072dacae8ba4f21abf5d042e5da499d027b0aac8e2d8f42f5f591453a8","impliedFormat":1},{"version":"d3b884fb07719c9457497fa9d5146f7977fed29df303347ff4175f630b903fcf","impliedFormat":1},{"version":"665320db7cce83346ce47ab3baa657b3115d796d28d8f778a98e7f23962b1247","impliedFormat":1},{"version":"3f471b5d1f378519b8276a869cb746d5cfc9b2bf4d7e5cbd0bdec3f9b5f81fe7","impliedFormat":1},{"version":"4afef388856e350489141ad7d90ab0767a02954d53d953f558237e04c89b5d0c","impliedFormat":1},{"version":"7b16f7f12771f8e25117321d1cb607e06be688ed8db002fe2cb13b716d0662b7","impliedFormat":1},{"version":"9412339ec64000a6dce5898fd848f025d31ec3b446872070cc041ade876c0c1f","impliedFormat":1},{"version":"726bc5d2505bf6051e80ede05a576e21d65118c077a8407ef4f9eca8d5445464","impliedFormat":1},{"version":"05f2af853ef135671b9482077d19a2935e3d924debbcf2f4803110bde114f113","impliedFormat":1},{"version":"134078b75b0105535f6164680bd73f88f9ddaa84b7e0126aeddd2af7ea27bcb0","impliedFormat":1},{"version":"264f0b10beaa4141a6bf228d2e22b19ff7baa76b39388da319bffcd1ced741e5","impliedFormat":1},{"version":"98682512d496bb618315de173d7a25ec363676da2457939f42b75a23c5acab87","impliedFormat":1},{"version":"1471ca4439a9dc9787976d1c9ca2b913908f4029465c87372d2efe2069c375f0","impliedFormat":1},{"version":"54b8abfc4713160ce97f91758ee1e8a547ba67c7328177d5e4c40613a3e87f79","impliedFormat":1},{"version":"60894b9993d7e1a7be37933c9bfe96b228ebc206cada93a44bca9d30e12d8d8f","impliedFormat":1},{"version":"236ec9d640fd6438a08dd2be3fb739352f147de529b4aa1953e4d4f74d6638b8","impliedFormat":1},{"version":"e75ea841bf22a156a7ae8f95eb7b1d4479da8c291019148d0a643027356b76c4","impliedFormat":1},{"version":"4111a7444998538da1f6f76378412547281e30cc5a7249b32e7402f66f83a492","impliedFormat":1},{"version":"4b9a4b3456012104409fde7f7631be98068daaafe1c49b627b8d92f033960b67","impliedFormat":1},{"version":"7f7840713032b2ad3bbc379ee2d401489b4e563294f7c87dbaf2424a6682beb2","impliedFormat":1},{"version":"b52b80732805d494ffee704b80f689772e1db9440c1728b907f7b25b3328d5ea","impliedFormat":1},{"version":"a805a58a4c72c7d513295fa7102284dd9cf76c1470e1845a6d7e9afa4bafa609","impliedFormat":1},{"version":"724e0ca25a06f553306e33a45951c368346cf1fc4b26b8bf4bf88b1479131659","impliedFormat":1},{"version":"b0880e598b7256855af6b9ea2aebdf47372444114264b56da9d25ea5f95d064e","impliedFormat":1},{"version":"3ff6607fc3c3a85814ec3d6e05e358d99773ee7c7b5e9deed5e086e39f5372a6","impliedFormat":1},{"version":"62247290540b91ed85258d7e8c67c7786e38bc1111429da9fa42b1b34e4ffdd2","impliedFormat":1},{"version":"656ee3f8184b5dfb87200f73350e57827dba056130d15064406487c0993f0e6b","impliedFormat":1},{"version":"b14c19907984b69ce25e011e6391ff6a150bb31f344d643f2a3d5af9aeb4ba73","impliedFormat":1},{"version":"2b77a0e88653109c708203a50fa23bb50406a9c8c7f61883c92e009f778387d6","impliedFormat":1},{"version":"2aa50966f709107108e7ad733c129c81f9e42731492948a9c23d4f8a0de5ae1e","impliedFormat":1},{"version":"080afc7aa193ecf03c78afe2e3d81cc3b18fa482f1bd955b4dca67bcbf220eb1","impliedFormat":1},{"version":"7282df0d72afd1c283644f5827159ffe0c899850fe121811e6e7e3eb77416868","impliedFormat":1},{"version":"07c70d4602003ffd9f23f8f6fc2693b7cf024a323a6d6146b11659105ae588fa","impliedFormat":1},{"version":"79eb7464a0215c82cf6fdc55b2378dc0a3aed416f03dc647fb6956975d446ec1","impliedFormat":1},{"version":"8cd341d72d1ce25d33dbe1681a9a5f27fecfcf65d426a0d0bb80ce97a1e37d50","impliedFormat":1},{"version":"4f750b488d0d1019bf8b6651e68689debd6106312ca7f4fca22627fc2d0acc04","impliedFormat":1},{"version":"4307994ad4d3a8d842a7d7da76f45f84e5eeaf1580e9b6071dd5fa6b8b21de19","impliedFormat":1},{"version":"87b54711b1c9791dd95b4ff88f814b489089f5b128f29e8a5fb7f6b8123f739e","impliedFormat":1},{"version":"4763437e8a65ec15310aa20a4ec288eae3de1b94b9426336ac423fddd482d70f","impliedFormat":1},{"version":"e2398ace0c73a5da036dbd6cab98008a251c709c56f1b665b6202e99ae3450dd","impliedFormat":1},{"version":"1a59a1ed95ac47cb6d1798a4dee9088b847f41491e57545e788ede35ed1204e5","impliedFormat":1},{"version":"c89ec75e2ebb2f5c2dce2d5f85ab59951cdd217748a49a6e0bd102fe69f5eb75","impliedFormat":1},{"version":"e653e5173f22f243892807fcd85800dfa4efe84be40e0ed1cccd15d62e1c9da4","impliedFormat":1},{"version":"973c290e94835130e51e934d078ab80e975a7bdf5b9563f5fa8de080a06c588c","impliedFormat":1},{"version":"5abc40134600b35d15fcc7305d5bc9e29942c64dbec6413137b55e77b689da1d","impliedFormat":1},{"version":"85c3303460c6339a77ec37ed9b7e06974f6d8e1351181b3f6f0c5180e0e7a76f","impliedFormat":1},{"version":"88d761b9b53ee5aa4ffe94d18e1c05c31ba8778beddcb8418f303c184f4f61f4","impliedFormat":1},{"version":"8ad1059fc2cf09ab5208fdc50a974a1a0c0f3737d81c2aa0718c583716b49f7b","impliedFormat":1},{"version":"ded7cab1c0c297958efedb1c4569674117693e0ebb6e8a1366cf7a629ba490c6","impliedFormat":1},{"version":"997c088bb8c6e1d869a689a3db0157d3efea26fa3fe49ece5f01044321949769","impliedFormat":1},{"version":"aa4d9968e228a15f4f93ba782c05f19de5aab2598ef8acd819ce72d6f4c9953e","impliedFormat":1},{"version":"1c4420d12393decf20734fff3f09f44daea5433869633ed11e9fed9b316523ec","impliedFormat":1},{"version":"25e74c23ca9d123ad4726803694ca249b958270fa3e98eb3de7f339f15241a45","impliedFormat":1},{"version":"87537c5c41597d3355cd28531f715bcfa77b73f0622d49b28b6064b3c0ba0ab3","impliedFormat":1},{"version":"56acbddbfb96d3e9502c73d87f216fd16b9954ce7fc345adaa51a054bbd548cd","impliedFormat":1},{"version":"ea5f8a7e470eec67d9b3a57a311393d9b8146d59e7d3965fc2a07745aabb50d1","impliedFormat":1},{"version":"d75a15490d5dc9b8bdd209200429a4cd31c139b1503e22e1ef743e6d4fd160f2","impliedFormat":1},{"version":"fb238a904625a2a0942f8f0aad2c96d5ba7b684b59890599a10d73c1bfd3f771","impliedFormat":1},{"version":"35c91fef4484772dedb9c253a07ad91912a8e349838bef1e7c85b93b6acd39c8","impliedFormat":1},{"version":"b92d1f42b729031910871b073bbf05b8d68994a91dc43a7fc64f88abff16db54","impliedFormat":1},{"version":"beaeb7cae58c1b074e1e5c4c0cc4e205b1763f0e9f413d7062951e1cf539b450","impliedFormat":1},{"version":"2059b7deab3beb764a2368200ead025358b48fe470bd6850500785d94c8fb5cf","impliedFormat":1},{"version":"da08b48bf74446b6f988e95f501693844d59d445487d89d2364b8bf431c8a21e","impliedFormat":1},{"version":"17c4db14970964450f5bdcd1349e6f3035419db7f7f9f88ee966b3b34cbaa8b3","impliedFormat":1},{"version":"a9e6f34151c8629364892059c1689e2f99775b3642a24854d0330112d6892cff","impliedFormat":1},{"version":"bf5fb8cea51021d395c45a092c1e97534d498c91812b99fdb07c658cf5990585","impliedFormat":1},{"version":"c4c7f14ebace079c50bb480c726a6acb914dff63ce2f4b267ef00a0e8e23ab85","signature":"c6e9b1f6d690ffae1f2c5f84c90f6879a049337ef380218e28f39908db853522"},{"version":"e2a380252967e3f1391d4feaa160773908e754ddf0b4c32e0863a34b444dd25c","signature":"7546aea101d084a3039eec017b4629ef261e36c012a024daeb0f8170d86d192f"},{"version":"bd05d9d2f247106dcf30ea8f49cc5cf72303dd0c693f949cdeb688bc70304043","signature":"00b79d5b56c3be6d869d13f8d045f4090e40761d05f340d037b5fa3e5051c97f"},{"version":"e8119fab6e9c4673fad5586b10109e28a20e43201f234df52c820b1daffd2cf6","signature":"8a996fb34f36d80fa98002a255626b4beceb48c0003273aa5cbcd21ccee92eea"},{"version":"6db6daae1fa96a5c10aa7bfa20710a9cc63b80455cb98dca5dc02a47d10a56f6","signature":"d86c8b7a6c6edbeb6a14c73aad61eec9d13cec0040264ef13c78a8c2f7ecbf43"},{"version":"3f39ac0c0f7eb8fb6c80208f5076718843a55292d912d754652c229d56da2253","signature":"7c56fc0ecedef369430a6cc78f797e1f72ac6aa30cf3861bea222fc9efe93d49"},{"version":"a64477bdabb3df8cd4ba8a62ece72ec42ca2c9b5e97fa768e2c6f2f6727fb5d9","signature":"d1d2efd128275df07279bf887018192c1b38c0cc2aea96243de78a8e92bc30ad"},{"version":"a0ac870c9aa54df50eb41fa26020b49bea9a2dbee0de8efd2380c2be57dc34a2","signature":"29afa7f4d2f64a222d590227109f01361ddb9c6588355096f6aaa036b9d67d05"},{"version":"0cdab5351929ad0e2d94ecea2b8d60d11fcdb153ff2355ab9c5109e84a697ddb","signature":"91537516c066b5bda3446b1dbd01a6b3ff342925cde014d5acb7b6f8b99ed12c"},{"version":"02f299b9b66512f92cb7b80adc13b0a9bef33e9afee5f2e2efc3d2b635588462","signature":"0342e61cdf2eadde061c53e1c6fc7907ad69390beddb2aa50e656dc2a45a632b"},{"version":"d09b5dbb314f199a0d7ae3c2c2a9c7258b611f3f28d1cc83c2685d3c738aec3e","signature":"068d0597a17af822c2ec3af9b1c2a9b9a26c0a4387eb66f655a0f1d26e36ba84"},{"version":"29baa50d188f4ca03d95d58ef52bc20faed12a5500b4080d56c7588e207b6e5c","signature":"942546eaf5ae2d0c5948c6d25a748fba25b6f4d760911ed595b40f043fbae102"},{"version":"60a911c7fcb40590e60a32ce6358e81baf0ab0b58fbb9e15ba9b5d235decf534","signature":"65bb767048368601ad35597c54f6b112e3147dc84fc338199931af5d57b8fe95"},{"version":"1ac040d4f47be4c1b44b4a521c7bc1264c97371b3c51bc2170326827fc8a5406","signature":"dd24f7d41609a7eb1c990ec2f7d7cdd63a419e355e6294050a67c029bdad0d78"},{"version":"a59e6af4854abb1a7f69231f6252836dc64035f9247f7976507926a66bd5e998","signature":"ab04dac1f806941027328926be16232e21366ff829c3be737572abc646b0ff3e"},{"version":"829b5cb87df9dfb327efb8a4e55644d809f3e03de209067122b99ffebf284f00","impliedFormat":1},{"version":"49f6637b8bd2a9d085cc337a1000e673285dad9bfdb3fdb2cdce03f5ceb7421b","signature":"519c584866e4d804355422ce52d8088fda0124969a9668779f10b0d50e114153"},{"version":"3cef134032da5e1bfabba59a03a58d91ed59f302235034279bb25a5a5b65ca62","affectsGlobalScope":true,"impliedFormat":1},{"version":"c9fbc7d96e67dfaf8156b6aad26bedf9b6d699ebdec4175c3c47227e55822d21","signature":"daf6a8dc2319ee3b3da8a84c408542688ca901aecabb3c195e2dc54dfb44b8aa"},{"version":"e23af6623aa17309bc7b7c5f187c234a14925bb2374bdc57460c538d20919628","signature":"5c975df906b720e560dc80cf99f12cb2763329a0d5c42ffe3039256137dc3a70"},{"version":"6b08b7e30913633a10a34d5ac57b0e527294a478200f2657c3bec1d46ee99d57","signature":"b4698bce6f7a4a17593cff994a72d565662855439386bcfabf5f0335ea8d4be1"},{"version":"fb5e02e193477e7b30cf17532c9cbadab056e8bd9a3adbe0ee4ead02f0d91cf7","signature":"97ebafc9d89ce29d62958a732cda28a5cd408a1257cfcac0d253584fcc850e6d"},{"version":"ee1bdf809dfc51b730cfc096b89e880918f54ac17ed7c268f5403da7b8efbcef","signature":"7cb1b9a080742123f5c7fe01bbe8cdebedc24c3fc0b6df33da4fa42bc7211a12"},{"version":"7eef79ddd85a0027752c88244f98b88e668146165c857e653e9850fbdbd18473","signature":"4e69e7b65fb23298ec08b6e0cc86692fb6602df5473948f46baf219a07967697"},{"version":"828355649d02e47c03fde21cd7af40ea1311f41a93f27b89cb6e8a8b4e0ab1dc","signature":"5dfdc8e2a88f5126407c0af9050602ac7306bad7cc807e5f5b23b6fd104dbd48"},{"version":"ea9b6d7b086000b7a58fc664e7fe41a5d57c5c5a465c932c1761af38ab3b0be6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"05321b823dd3781d0b6aac8700bfdc0c9181d56479fe52ba6a40c9196fd661a8","impliedFormat":1},{"version":"ae77d81a5541a8abb938a0efedf9ac4bea36fb3a24cc28cfa11c598863aba571","impliedFormat":1},{"version":"3cfb7c0c642b19fb75132154040bb7cd840f0002f9955b14154e69611b9b3f81","impliedFormat":1},{"version":"8387ec1601cf6b8948672537cf8d430431ba0d87b1f9537b4597c1ab8d3ade5b","impliedFormat":1},{"version":"d16f1c460b1ca9158e030fdf3641e1de11135e0c7169d3e8cf17cc4cc35d5e64","impliedFormat":1},{"version":"a934063af84f8117b8ce51851c1af2b76efe960aa4c7b48d0343a1b15c01aedf","impliedFormat":1},{"version":"e3c5ad476eb2fca8505aee5bdfdf9bf11760df5d0f9545db23f12a5c4d72a718","impliedFormat":1},{"version":"462bccdf75fcafc1ae8c30400c9425e1a4681db5d605d1a0edb4f990a54d8094","impliedFormat":1},{"version":"5923d8facbac6ecf7c84739a5c701a57af94a6f6648d6229a6c768cf28f0f8cb","impliedFormat":1},{"version":"d0570ce419fb38287e7b39c910b468becb5b2278cf33b1000a3d3e82a46ecae2","impliedFormat":1},{"version":"3aca7f4260dad9dcc0a0333654cb3cde6664d34a553ec06c953bce11151764d7","impliedFormat":1},{"version":"a0a6f0095f25f08a7129bc4d7cb8438039ec422dc341218d274e1e5131115988","impliedFormat":1},{"version":"b58f396fe4cfe5a0e4d594996bc8c1bfe25496fbc66cf169d41ac3c139418c77","impliedFormat":1},{"version":"45785e608b3d380c79e21957a6d1467e1206ac0281644e43e8ed6498808ace72","impliedFormat":1},{"version":"bece27602416508ba946868ad34d09997911016dbd6893fb884633017f74e2c5","impliedFormat":1},{"version":"2a90177ebaef25de89351de964c2c601ab54d6e3a157cba60d9cd3eaf5a5ee1a","impliedFormat":1},{"version":"82200e963d3c767976a5a9f41ecf8c65eca14a6b33dcbe00214fcbe959698c46","impliedFormat":1},{"version":"b4966c503c08bbd9e834037a8ab60e5f53c5fd1092e8873c4a1c344806acdab2","impliedFormat":1},{"version":"b598deb1da203a2b58c76cf8d91cfc2ca172d785dacd8466c0a11e400ff6ab2d","impliedFormat":1},{"version":"34a8a5b4c21e7a6d07d3b6bce72371da300ec1aed58961067e13f1f4dc849712","impliedFormat":1},{"version":"82aff380d236a39d03d4efd371dfea87a3c6b788231f8c5c9dd73c98355619d5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2e07a01b444d1e1fde30fb0aaf882a2d3b441476ce1283393e3e3d6e95e17f87","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"00e644a0e3dfdd1461176b0143129c9a12a077507c114185c51e1b9aaad14652","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c6b47f0ea0108d741abf731f728b357a8370c238ffe73fcee007619484c24356","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5a2cdf6adeec348bbc876221be4367e8adff0bb78a5680ebd7d71e5c3bad6cc0","impliedFormat":99},{"version":"e004826eac62081f867c66dabd92d3ef7d126d93a70430a2c88429228c3ecc50","impliedFormat":99},{"version":"38d6857b58d2ac42442e396311c542062d4f0dad40f2adb496dd5fd0756ee400","impliedFormat":99},{"version":"34b7d1e2d15845cf08bcf5e3c01adbb92cea1ec27564ee249ba486cdfb28526c","impliedFormat":99},{"version":"6d575d93896c413b308c3726eed99ddd17e821a00bdd2cc5929510b46fe64de4","impliedFormat":99},{"version":"1beebd50610b0c9701d2de263e0183ec22aad6c051d0e15ce9e6cce295c6a40b","signature":"3c49b34b1c62e5d74c637b63276c9acacb605689334f5450cc3f67f560ac0ecf"},{"version":"c3966037c4406549f831cfce69030dadf21e1b393806ee1444a6e07f2ab46716","signature":"75d57bc24316ee41d516041387f8a150f5776774790bd72285671179c8652070"},{"version":"bd2c7ada3dee03653d3f601011d30072194bc3970cd93208f9588fbdc0c69347","impliedFormat":1},{"version":"e480da45d32313e7174b265674da504f075f59ef326852f0c5a5d863b438ae85","impliedFormat":1},{"version":"ad54850f61fcf5d014e11be80d2f46fea9265cfa7e77456da876f7833ef81769","impliedFormat":1},{"version":"6f7c9e8bd2b5b6a080b07080065f94900bd3c7e5ebbd3047bc33fcce2fab1dd8","impliedFormat":1},{"version":"f42d5fed19610d485c646a0c430e768115567d078c7fc855c57b0c578b3d6cd3","impliedFormat":1},{"version":"0c05e9842ec4f8b7bfebfd3ca61604bb8c914ba8da9b5337c4f25da427a005f2","impliedFormat":1},{"version":"da5950ee2a90721df6f3fba45f5d05308f7e4c35835392215dd2cd404505e2de","impliedFormat":1},{"version":"faed7a5153215dbd6ebe76dfdcc0af0cfe760f7362bed43284be544308b114cf","impliedFormat":1},{"version":"7029e566b8df176f703fb59fd437a38670c7a0e02c58b2d66dfb5b2e2b2defdb","impliedFormat":1},{"version":"7f2aa4d4989a82530aaac3f72b3dceca90e9c25bee0b1a327e8a08a1262435ad","impliedFormat":1},{"version":"d96b39301d0ded3f1a27b47759676a33a02f6f5049bfcbde81e533fd10f50dcb","impliedFormat":1},{"version":"e9f147ecca73d9346a4c073432843c159ccbe50bdcb678a78f6da10eae2cecf4","impliedFormat":1},{"version":"de061f7d72bd65c06fc1419f841dfdcb29a8e22fe6fa527d1e6eb20b897d4de0","impliedFormat":1},{"version":"663beafc2446079574570cba86e9b15f986f908ddb1b01274509970126fee945","impliedFormat":1},{"version":"a3102887d5058bf4cb5b37fa6964c09e9527c42053b3b5c642b89878620748de","impliedFormat":1},{"version":"0aaaa1727edd29673d85c9b26d7ca4d54e5407a48586903c51b48b7f7d196f61","impliedFormat":1},{"version":"d35bca0b261bff02635758c48e8ab99c61c420d0dfabbcf467e847171d876b7d","impliedFormat":1},{"version":"3bc12c40d90c342ff88a3d876996c555ed5cbee5fe8c3308a240b321f401ee46","impliedFormat":1},{"version":"ba130768aae855a5477e9e148e5c879548e6e7ccbcc56fd1934c8a18ea5b7569","impliedFormat":1},{"version":"9f9bb6755a8ce32d656ffa4763a8144aa4f274d6b69b59d7c32811031467216e","impliedFormat":1},{"version":"5c32bdfbd2d65e8fffbb9fbda04d7165e9181b08dad61154961852366deb7540","impliedFormat":1},{"version":"2e4f37ffe8862b14d8e24ae8763daaa8340c0df0b859d9a9733def0eee7562d9","impliedFormat":1},{"version":"bc9ee0192f056b3d5527bcd78dc3f9e527a9ba2bdc0a2c296fbc9027147df4b2","impliedFormat":1},{"version":"db036c56f79186da50af66511d37d9fe77fa6793381927292d17f81f787bb195","impliedFormat":1},{"version":"a6805fcafed712aea7759f8bc731014f9d22738c1d6ef9d43b8091d1d48346d5","impliedFormat":1},{"version":"b62381cae176db34f003cc6172ee8f3e0122014889d66391aa73698105cf4934","impliedFormat":1},{"version":"1d9c0a9a6df4e8f29dc84c25c5aa0bb1da5456ebede7a03e03df08bb8b27bae6","impliedFormat":1},{"version":"84380af21da938a567c65ef95aefb5354f676368ee1a1cbb4cae81604a4c7d17","impliedFormat":1},{"version":"1af3e1f2a5d1332e136f8b0b95c0e6c0a02aaabd5092b36b64f3042a03debf28","impliedFormat":1},{"version":"30d8da250766efa99490fc02801047c2c6d72dd0da1bba6581c7e80d1d8842a4","impliedFormat":1},{"version":"03566202f5553bd2d9de22dfab0c61aa163cabb64f0223c08431fb3fc8f70280","impliedFormat":1},{"version":"41eb514d9ce0a6e87957f08a4b7af70d93f87637f37dee706e2d92a6601c25a9","impliedFormat":1},{"version":"e7765aa8bcb74a38b3230d212b4547686eb9796621ffb4367a104451c3f9614f","impliedFormat":1},{"version":"1de80059b8078ea5749941c9f863aa970b4735bdbb003be4925c853a8b6b4450","impliedFormat":1},{"version":"b06424097632400755c0257c3fa4786544fd132335045fc791a815d383543c08","signature":"1a04d84d03600646a3956356d88f8d8594881b47182f488f5eb011454858f9d8"},{"version":"f13cc653015347688208b6a2817d6a024cae82cea1b2be422061ae1fb40e86a3","signature":"9d34eaf37fb26f7e3b5d52527e1cb097956ecec3deece2590b99f360ab4428a7"},{"version":"cc319ff8f06a331d4b359aa39f43dc18f8d4402f1f3c446b22a197389c4067a6","signature":"a6d8aa22b2e3abe3192321c687b18ff88b15d42a8c3165a2ceef83a58045e9dd"},{"version":"d2f50145db5e30a8fe6c651d9be96d8b58fa8137f885bb29a0f5bf726ec89689","signature":"6670f738aff6aa9e79d8bfa6f042ec32f827f1b7316a794eb69f95c6393dfed6"},{"version":"c81430ff84e021f8e86715979190ec96946d8a5ee69b5d4bd1c23ca188c1a562","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2c4beeb10c123c02ff09c390cb92953e811cb0e846e2d040c65a8a0e73746894","signature":"5431108d0a4a15cc5f6d78abd5a358d13bcabb849877e09f3efc265eba21e6e2"},{"version":"5559d4fcad759cc07a71aca5a792755409db3b683788828abad2a84da3dcd7fc","signature":"c367bae6e0535dda7431e73df32e233511c1e9b1181082d551efc822fcbaae83"},{"version":"46ea0bba2f2e36762dae6bd527f342a19fe9f46c653dc4e8061d9c8530ba8dca","signature":"19467dd75b0a6bae42afc2ac103445b6ddc4a3e259599ac20b2ec70e75fff499"},{"version":"40f8a5ff101ec9d2a6a08af84db2d4865c35e2deb3da94075a482d94612ca24e","signature":"57e73f014bbd5a960cd0a3b39a240cddbf1842f7f06a09757be73de97a234a79"},{"version":"3d64c2914a71ab3af9fd253eda13ea735a784923284005d07af763636578b46e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5112478f7f7dcb622157981c8ac9a0fb3cad40c5eb87a19ff2e37674c75c0fd5","signature":"8ce6788984fbc5caf642946b8dc8a405629def762f166473ae6389aab4822034"},{"version":"178dc732f2d61a4fd094b6672b6c438d1b1d6cfd5489564206a84e3df486beff","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8e4c902d324edc1b873a1b0bc0f07f760268b57392266df84c01faeea3ee033d","signature":"0bd103c19e9fac90503e61110a3b59fc4e9c05dc79b9dc093b704c354ea17577"},{"version":"32666fa32e6247fe6f50ce32cfb0aac3f2bcb2ca0eaca635ae065948028f7254","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ffb9f584394403c5e07c9058c803383a5f127228e6fa911a35df6557108809b3","signature":"cb195125eeb33a1ec87e9a694af8449e518de894df290a34714e043053b883e8"},{"version":"7f8e8b3d1d986e5be4c13b7a642a6f4899f1af03978448c01183a00e828c12bb","signature":"2cca07a66a88e9bd88fba730dd137253950afbf99138a1bd5d5272b2c5d41b56"},{"version":"e1ae660c622a39b34c87f6dd244d59846a5d110d2c39bcf4316a499b166b6e7a","signature":"d65014fad921da41cfd383514c098293dbe40fba77dd7ded291edcf4e04b001a"},{"version":"7606ef9eeff41c0616d32c7f6fc2086c38b34c3d7221598ed9291aaf126eb178","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ec62117264f15406ca6734f497618d2971956185bba9d16fe336973ba99f2554","signature":"8f4e72fe2a5fa527cc58af1827fb63977ad7aa7ea54cd31f3adc523371e0c562"},{"version":"695e9a27d875aae3f404cbd6ff901fedf0d77c193cfd9552edba781658ed0e85","signature":"a299ef368d46feb485cacc1257882c710c962c3835c15268544a95d9385c6641"},{"version":"d0608ad5086ad006c7c2a10e4fce58cbdac946d73cda4c270717bbc11751afcb","signature":"79f1952bf72196b817faf37634b6a85b9c271443bee5e0d1e40c42d210fba354"},{"version":"3b896dc3e1c4ad480362e095a8ed235d8104bdae208d59a6d3506da72d5d097f","signature":"310aa38b81febf19711c6ea15b8b40176d3c29011cebdd81b7f980b7497adc8f"},{"version":"42b18867a7543fec221e4f0321e077538e596cbacfec0595872df4876635dccb","signature":"baa8a117328606a5a80729fa29d3b99e604d1c58274ce6c705b1dd17550d4173"},{"version":"c07f3037b31e0bd7e1384c41a6fd6524ac141e8f56b93a4a12ec63d75a704edf","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6e925ea850d5c27ac93c1d0a26203779a347ded9a658643ac71febb557086d09","signature":"faf83f25a2e1c4c2cdd395f86877ce483031a5fde85d0bfa74cd27548f3139ff"},{"version":"6aeba978327f4645908d22a4f61b4af811f7776469b9bdf7cd1adfda1fad67e1","signature":"19078cea578aad4cbba3e9086c295aa7b2fc6029dc5df2ecbb802dd45d19f8ec"},{"version":"3d1ac90c29f450b8b90705d05264fd29f1034b5ebb6c2d2e9807489969e0a33f","signature":"140c9f01bd8744fc1fbb72ee2a7747039b9637b3976f2284bf1423d1bcbc045c"},{"version":"6cdf25b7999cac1327ad91005d4aba65743aee71a2972eba11f5b1164e39fa4d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0d96088dc89a49f328fc47dd75713536556aca01e187da7dce124fbd2f395f09","signature":"77e82444cebc04e9edeb4d759ea3c8be067ac6bbc3b652d668a3f483b0d5f7fc"},{"version":"ef05bfe2ed3c6fc7575cca3a8ee25f4a4aa28878b83c5d986ee47f4483f73b3f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"dcdb66da2fdcdf0e80f084c8347e2114f573d97619a5e37f680a9ad5656f614b","signature":"f090f8e7e1db58d734c2c7434bcd43e2ea1c30e049be3443fa3a83a063e59324"},{"version":"d322344da57346fad32f22627e0028bdfe12501d7de9eb4a103d47c9a075a85b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2d5bb04855694db19d13174de99509d4d4799d6bf3468318bb74b54ffe994129","signature":"9a6473983696c0401765d2ba2558ef9b0670592e8d74239b4d5623c42d686600"},{"version":"dbbdee1f403eb2a952f5e8ea724cb1a20a2b4dd63d8232e375a750d4929baa88","signature":"7f1336dec949b3008a181a8873c5aebe07ea42b6730e6a5c6efaeff90abd09dc"},{"version":"28c7612edce38076988a57695e970654fd09467b806a8e37b38a26946afafabc","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2a40fc7affa68ec88b30b2d2d19639d8fa8dadee567da499d460257372e04ab3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7f13d1469822b3ce57bd440274f4a8d9c2d9925fa644d284fdeeb520f0ab43fa","signature":"248bf8fb49df3283090f3d2a137e3d74dbe93490ca12bcca8686a3e1004e5c40"},{"version":"0ed05108ce0f4538b5351bcf9d523912d200abe5b804951cf18b2e12feff6b70","signature":"f453c01ca04957da00f261867fea88fe674b34dde9b1da183dc55f2bed19f364"},{"version":"fe55ccd61eb15dd0480443215f5556cd56d9a68075a79a1294e2d9cae200d70d","signature":"9992a93411c1d80cef73f32ab5ac10acddd25700903cf8b5b47925eae8be2a60"},{"version":"7fd1557a212fb2a671abe96de2e1b6a3e5110a07c49076705cdaf9c3e0f81f7c","signature":"dc57421ea686f59b81eeb7885916d7a5cfcf6aac9113b8908c5edbe4d4a7a296"},{"version":"7ad085c66c15e665b43f1055b09ddc1d9c11a3d8f21174c9d3d9205d7dbc03c8","signature":"5b361261b9d93e4a2c0d2e02bf9f0dfc60fd8c761ef6fccdabf563bd3aebb419"},{"version":"68dbb99a0ef2ffb046b1385fca8116795a7b4bf5700193b7bf2fe9cfb62e72e6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"47bcc33a6a3e7c2ffc449508e70b7b42e55afb95ad4cb7b51ada3e48e59bf877","signature":"f6717ce9971f40f30af260a3e42d09f3edefdd725b5ea43006eeaa85fb176b57"},{"version":"dc39c5b409e677273ae4825a5b092506dbbb0130318e90cc1a51ee22333a3915","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4a96c42ed30bf77b8c127453fda697a212495a055f6dcafda400e42eb233d029","signature":"c4e5d6b5f65bfd77c192b36ab608481de02288d42739f978b0c01a812dc94321"},{"version":"f911a52096fa219660557bc3c9ccb5745889d8a357674e8ae67585678891e106","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3a2cadf5506cf6c268f65051dd63ddd5cdc82f5effcf658778af2b12e497726b","signature":"3d3c808c01d46ac6f212b5bf9a3780af30c9ac93fabf3f754e01eece8478207d"},{"version":"3f2074814adeb10d5270e703ae3d2ce2fb333c69ea292c4bb7a7374fc97b3293","signature":"63f9cc5e173cdf605d8378b64d795974920e6fea9b3c515807be08f4cd21667a"},{"version":"2e1f498ba2e37492111a97c6048e07af342ff8df126eeaac6cb99f94372f8ca7","signature":"4a997dea3de3d148c650f5ad6c57d75d5adb6655108e0af42e57f9661d5a9297"},{"version":"759a24229c02dafd2cd73ad6e3325c043d16ad85081d2e76296664bd96226ae2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"72907ed9e40d9903383897f19bfb3aa63f9693e3a70e01f25cbbc4937c6f0c39","signature":"94b4375a697a083461e014a6a855c1669858d797c2bb6c88ea167ed408723eb5"},{"version":"26b692cceb67ab44563761e4c5701f66b58f7ee354393088e3b338aae9918ee3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"89f0ba8c9a02ad55e23f296ff35f0d1d6361a190811401c3f0d767a1aeeefec1","signature":"11a904b87a71e58a2283da4755e0042d78cd7f56395ab9a1b6ecb09290f3672b"},{"version":"ca3da68186289d0f9bd93dc9a7e5c3eb30dae2526ca55be4b106af239b63d0fd","signature":"d539ac9920f9a947cd986cb61772e65f48bc0442d1d94e2ce8d6e25f394cedac"},{"version":"fe17ac85f4e0b305bf44f3a4d81cb75397d9d7776aa2649ead1caf2291f0d416","signature":"144a45096b9c6347512fcbb22cffa4da301feeb608b7fd45e0adc364e49ea255"},{"version":"22c8ef3ac26c1ded1d40c6e1c48b6382fdf0d56a5a3e77f8bcb3bb198a1f769c","signature":"d394730410e0700f09bd96049137fcf096ceed2f5e7bce00a2937aebc9bf4240"},{"version":"c7f6c31638211891b8f6ea106d4d9ca0cfe249adba1528b2d1ebd0f267fa324e","signature":"b70605e01f0bebcb73e3de21b8c1dfa27372859c9a142d66d8d69f1f91e99adc"},{"version":"5d513a6a908bdec9f0c3a72c4fce232063a7365983847c1ade848a9970e97aa9","signature":"302704f3830a828ac25e5b3d330b257810004892d2acf61a6a656b05978d7a2c"},{"version":"bcf829509dc2bdb8f3851efb4451010e917abcdd8a52e2ae302886045c0e38f1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"10ebed90f6cf7c3e637d5595d4dedc3377575aad097fd0b28e15312572c09622","signature":"7d26b77586708051f6f1735b57756edf0be83ca4670c4af58a8e28b965a33a08"},{"version":"0ba02535f72f0cc1712b1a073897c522f3460eb86620046f1f3b250ea79fa567","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2b4b00a99c93370ce060e954393ebac6d299d87a0400b9a69307c97e8021abcc","signature":"1af687725c0895163ae338b1c94acf5819a042e98cfac2dd6e83b993c57d5623"},{"version":"1f1d8292770ec1d17820eda3e3ca60550d3c356d4f4832e211e9ab2ae5d5c9f7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"07e108a85e9c41db3bfca18812565985fe9c4185c09e44cbfd365364daf67e80","signature":"7231ddbad84b7265695458d181b33e24e857a11dcf40f694a4dd42b3e265293d"},{"version":"7236d482e2ca1a6307e2182466f18dc8ee373209e5588f156b019f949cd9ece0","signature":"c92738c8f42ef530edebc3a1912a4ba2ec85ad86494839d23b6084782f9f2e91"},{"version":"da35015d12dac52832201a4900b07e4b0f4fc08f282eee6e7131d895db1930c5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f900c92458d8931cdba89748cadf96d7b35c86ccd7b45d7c17188ef8f8a8dcc7","signature":"75250e46b1120d83de8762a83126b17415f4c942668bddb0180b7674cb2464f1"},{"version":"272ea1da19723a68f172a2408bdf7b5627c1188f9707896c15a3be6a7a68be87","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"24ac88c5cd809281eca45ae09b8b16dec4318221ea50d1b47888254798a046f9","signature":"c97c4207c753de5cccfb48d3488e193f8846f302690bd3ff73f4de951675b01a"},{"version":"63bb37a1d958427795e2e6ca7fb9451aff711e665b45bd4878ba693da9a140cc","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"81aa2ba3522bc10222837dca4556efb07a6628de274c94545a536c1955684ef4","signature":"daf649274b917c1d7d6b8e8488d04d7e47f3bbbb09842c2a9899b4ec507fb243"},{"version":"d8f785b87f0a4f596648fb2e5fb351d34d4bffa5288980a8eb52ee82ed27180b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b581577a6affe98a790ea707971843ad284ae4cbd4bd1dc17642196e3c0de158","signature":"840d3e2bdea7d5a418436aafedac9749b1d9de78bda0f825a48869cfbb3e7f83"},{"version":"004d3bd387fc646ba3d76c6880c06461caa0b5bc15a184ae7605ee1f130f6ef7","signature":"212fdca7769790ac75031f925478591057411842339e39988f1ffe769ab88da5"},{"version":"ac94b15e69603d8aa96f6871176b4bf3b70b295f60ed7190fc1deb835a328605","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a3be9843036cf2c658821504c4931c7e1055db71db2a607b9bc5d8aa7be679a2","signature":"3a526554ad06e5c46700e8b1ac5e6f817fdca923787a3c9344acba81e8d17ff1"},{"version":"ae3566ec3d167f05176112f609460a77863016fec3216a87a15c36bef01d370d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c914982336f64f67076a86d5569fd1b878a42f415962a07d1d151a4d65d187d2","signature":"33831d2be2fefd1ecc0b722e8270094b857a42109f3eb3bdb5c5e666233c588c"},{"version":"4d5caf53ba49b41ff378943f6b1dad1ea8e042426fa713b57c9524219bbbf573","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"bd703e7c27cc5dd4780eba552a51a698b459c9f12dbddfeafa9e0d216a4e66b2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5946158af389cbe762eee6869f0a5fa5c93e87e633a1c1bba333e8b0af7be82e","signature":"f8fd457e54594676a0106e9e40e7de3217ab284fd52a60aa51406d5c35a53222"},{"version":"71e7240e131e0e0f5fa6b5102179bbcb4ec0aa0f969cd3c07a715f6729a2aa22","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1e4dd6a5a91d2798fa4dd70d7e6f682475942bedb36b9f870cbb15e5c1f1a54b","signature":"a8a191cc7d792c8cc2d87c992ffee823187689960dc717e122e158f24b77a242"},{"version":"0a5fe133b4ef41f0f2443b3cc82c4b99be11738a93d613fd12014e9d632c2fcd","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b7278ff0b0dfd9e3a3f9f92785d7166d8c50c34ad80da47abd946c08cae1461d","signature":"eb63e897dbc8b27643106520c69e2f49993ccf53af48ccf8c02f999bde56ea31"},{"version":"e9dc912bf8a7678feb40d7d996c9263bad3b7fdab9ad9ac95d4e4ed023403d29","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c7aa0820877b8341f78e794fde60d464746eec82b0e2624982000ebb19fb8f8c","signature":"488c8eb8a1054444f74a12eb49f8e21ae583aa2ba59ac9cfe4ffc71754c3b1f7"},{"version":"7866820b43b8c2b53cf5a1d2f4de019e059e681e9ec7cff7b5972800a8d78732","signature":"b26c1138cc869467f57022e668f1499192e359d44f7cfaaf0e72a576d79c491c"},{"version":"4b3f5e2463bca0844de6802fe4ee8e1e29a3c11d14fdd3fa7c96ae69fba8d74e","signature":"053cec7f0a8bd24eeddfee887cbc9883f56cab39ebed9e143de9f0a6cf34d202"},{"version":"d878f9fb504fbde395cd7c61e48f2fc7bbc7d0cf14828004f95e5f9fe64f238c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"12564f17d21c31e77240293e7434c0c933945a3a3a142130eaa166d0042f5529","signature":"ed4aed28c29ff0fefa86143fc6824969cb43f6bde467d4f9254c84372fa63cfc"},{"version":"3e8a4a82fa1baa0281a0ccf309c22c954488a949c13a7ab6fd7c171b1bafb361","signature":"13428a7125f291070d1c531f233c9e8719c80160dd3587d51adf86a3e41bb62f"},{"version":"b51fb5ae439f311990f5a5c2fc4914fda89e5ebd585b33d22d0251afe382d391","signature":"f49a7f528e4e42999b277a5ea73799e735e349e562a0ac6c97b99644a13a3ed0"},{"version":"d9df7df9ddd168648cb6e27223b325763738bccb1b57e965ba0d8443cd166fe4","signature":"d786daad1509af6e601e8de4259a2c6abae27fd33287b4936fd079fbfd1f0ce0"},{"version":"99f67ae9774e4bb88839948649b26a55dfbe8b99ec80ecab4c548102d2ddcaf3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1537c350b11115c0e713596a8dcd004151573eeae99ed1d2fe81049ca29857c8","signature":"17e770a9f59f622dfe33762933a978e74b5fe1c1bc65fc6c1c9d15f1c4ffe4a0"},{"version":"53ed7ccce63fb30e129e73dd0abff74d68929d92fbe3b20f8ade965780b2353c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3abcb2afd6c10eadcd9e5cfcffe3f93172b891ac80b079ad1421105d1574b983","signature":"2cdedb09674dadec42708ff08cf53e8ebfb3dc9402a0aa42464a061d228c7ef2"},{"version":"ade0a7d50f110afaed90ffd5f24f1617bf9b8a0d2f118530892d139ad67f29fe","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6aaeb7779ddef5bf76d08b6144956966d645f4218ab113e7e2527b4c618d0878","signature":"cfccf5311c906745df21e8fdc5a854d294f481fe2338cc95d94a01f35f67a784"},{"version":"a3311c2d4225d8eacfa5a9e662811aed57fdb4de78b824f1a702311b3f99b09a","signature":"651d947dcd8fb9009c702ecf43eea7c50c9ebe342b6e0619cbcda9d8a8b64e10"},{"version":"d7e17a1b90344a6d9c26f1462f77d6350a6882706064a36e5d640f5726ce49a6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"11d6ff0c97c5df3d8ff0df31b01b360626608daec64b5958c846b6387bfe7590","signature":"222650725f03127837c627c2802cab1acb1838aa85c8ca49197b40c277f36683"},{"version":"2229be080ce75a9cdceb42f1a2e47390d2ab68fd5946b02c8b324b602c2b3a01","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6a073d10bc03e721d0b1ce2620253ae9b69daa32fbebb2f7a8c67e7e1579c6f2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e2c28116d0d3256a3ecadc6580d9d76a5c612eb9a86dd1d9d17909c59fd1753f","signature":"cd59b71cce3988ac1c5f91fc2d0b5489ee69e560df6e7987b497fcc1abb6e9fe"},{"version":"4025a1efab8877af2ed8d8edda349736055a800404134c6b24ee95cdfb0c0ba4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0a46d4afc023db18a3c9d7816e3f90d960122319e2765c5d686f76da661864bb","signature":"b5a97abd79ec3360bbef597b4f34eab1b9f0d3545d0c3f46e3b3e2ec6e91771b"},{"version":"4f649bd1b169333de666f079f7989d184c27109ce26544354957f5be00abd232","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0e30e16f547666851c4fe51505a9feeff6508b2d720b7b8c60691e5158db10d6","signature":"cc094a8b2d9686d5ff268266e02eedf9d66e2389a04c46fc49cc819d23134a39"},{"version":"1c03c99ee1b5f5bcf0704dac9398e922805929cd23a7ad246ad0d67cfc3826db","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b0d66f4c2b58973e10b5778d9fb2f6795790e8ba20ad97e6004c41178bccbf52","signature":"0c5260f26c1eaeb4ed1a23b60d9809144f6c842b66fb2acaabad8a73fdec11b2"},{"version":"56e64e37cd8e352a8312c8f90b2acbe1eee2a5bce1cbe2721b473afea5186eef","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"34ef539c1384da9fe858fb9c16368ddf1624a4914dc3f0fabc7a39811bcd2668","signature":"ed6cfc1cf330cbbc602b7a0305aebcef220219fcd5cd3e4493e5afe79058fcee"},{"version":"f7409e1093e57b3f7be327a71c71087e1f7a767333fc10cff7c2504af7222f88","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8a530f7f3cf74dd313415c551d5e2c52ea22949866ac2616b8e8a2cbdeaed8b5","signature":"d9d073863d3d0cb154331182ae4ef77da4413a6ac9fcc52d2357bdb51b6dbfca"},{"version":"24803211766a60a0fca7ca541d6a158d376c65f7ae4827a8661063c2e70c06e5","signature":"6e2681371e356de1c4fa982616bd1d26b7c525b5ca9f75e1f688b83332e8a81f"},{"version":"579a472a70260bf6b40d68aeffa65bf00fa5c59a44bf214f2385dce399f5898c","signature":"fb4582d6a3a9b2152a49c918d6c98ace7ce35978ebe850af889e7aaa526551eb"},{"version":"3fa0c656d89d31fa67f25c8b3eb9974b1aab6fd8fe5ae0fafd521e7d6808f71c","signature":"cf64d4b205595fbd260e6fced4216298b35c82faba7dce73a9e205add66ef85d"},{"version":"ad5ab4bc064063efa662328e47c8eb39c80888a25467fefd231a32e874162d6f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3de88511ad7fa251f77f93515beba64b330124d0c2eaf22032cd2dffb6c6dc7d","signature":"e36e8e0a80ee26a2398c86a0385012146b409e679800b2f59b0742c0b16b6d08"},{"version":"843ed25b4bb1b7debf9b796c9376a43d4399c0d64cb4146ca9a7ea0c541e8f9b","signature":"22a84c5708b83fc36ae54c9f73605a8bb70e435ad4cac23af334d61a88136828"},{"version":"71890e72c9a66a34b8617c1755fba9f68a9ef1915182c02220d6e6217b2d6bf8","signature":"14084429a03e8974f38aaa1890d6a80ea62d5c8f0b627e0f824afb0ac621a65c"},{"version":"63ce74c6a697650a7b1748618ecd898eca5b14018b8d3a9a5b7eb29f95d11a07","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2d6a819db1ca73bc3e3b29597b18ca5c36e2dafa02b171e8bb92208b2d50b353","signature":"1571b1b7546d0267d42d0c0b3e1e4593b2ef990541b260a9652427dd82758bb7"},{"version":"a62a7e45347ca1dc80dd4df8d2094790870c03b1c890960cce9628934f695295","signature":"af0b9205baeb5b5d2e4470da33f26673b5c363db0ccfe761276f8af34cfe3e9b"},{"version":"380b919bfa0516118edaf25b99e45f855e7bc3fd75ce4163a1cfe4a666388804","impliedFormat":1},{"version":"0d89e5c4ce6e3096e64504e1fa45a8ddccf488cb5fdc1980ea09db2a451f0b91","impliedFormat":1},{"version":"fcf79300e5257a23ed3bacaa6861d7c645139c6f7ece134d15e6669447e5e6db","impliedFormat":1},{"version":"187119ff4f9553676a884e296089e131e8cc01691c546273b1d0089c3533ce42","impliedFormat":1},{"version":"aa2c18a1b5a086bbcaae10a4efba409cc95ba7287d8cf8f2591b53704fea3dea","impliedFormat":1},{"version":"5a0b15210129310cee9fa6af9200714bb4b12af4a04d890e15f34dbea1cf1852","impliedFormat":1},{"version":"0244119dbcbcf34faf3ffdae72dab1e9bc2bc9efc3c477b2240ffa94af3bca56","impliedFormat":1},{"version":"00baffbe8a2f2e4875367479489b5d43b5fc1429ecb4a4cc98cfc3009095f52a","impliedFormat":1},{"version":"a873c50d3e47c21aa09fbe1e2023d9a44efb07cc0cb8c72f418bf301b0771fd3","impliedFormat":1},{"version":"7c14ccd2eaa82619fffc1bfa877eb68a012e9fb723d07ee98db451fadb618906","impliedFormat":1},{"version":"49c36529ee09ea9ce19525af5bb84985ea8e782cb7ee8c493d9e36d027a3d019","impliedFormat":1},{"version":"df996e25faa505f85aeb294d15ebe61b399cf1d1e49959cdfaf2cc0815c203f9","impliedFormat":1},{"version":"4f6a12044ee6f458db11964153830abbc499e73d065c51c329ec97407f4b13dd","impliedFormat":1},{"version":"a5f9563c1315cffbc1e73072d96dcd42332f4eebbdffd7c3e904f545c9e9fe24","impliedFormat":1},{"version":"97980df4d75192f66df770bb4f658000cdfa1956eb313a9137e9a3a8646fe258","signature":"7a49a822cb790c72be6db966c7f0d69c641479732f211b020cbb08bc4f30a3d1"},{"version":"75c7c6a3935fab83ee2b92b10dbdd927889c56480a322dc18e59996c86ace2bf","signature":"058ecacd85566ea678127a31760ea37e7d03ac2f56d6a73322e873d0aa6b0a9f"},{"version":"56d890ddcdbd24fe7922ae61d25e20c13841e7a7b081f200c414878238c35d03","signature":"c9d08e1a10a3fb2493a80388d30df61b48cf36492d1a38baee14f64bad2aa184"},{"version":"a84bbd7d67d78a825c4c8086203db73b5501a383c71d441a2662118f25058a60","signature":"16c726346c6d566cc00aced3a44414807649395d858aca64fc34569583709690"},{"version":"415128b10509c030be557c34519c906bc7c29f55afabf3ac0c280dad98e3302a","signature":"228a47d85e97c163450a668ba3439510b6038b3531e2666256a1bac7e69539d1"},{"version":"f540c84532540fd78edff637ff86fc732dbd976b473b5a497e0555319b3d65ba","signature":"78f739f5b91e1135aadb4752b0fbd6b6bad0fa86b3f0e889900982b12176fc2e"},{"version":"d915818ed7e7ae46bad36fff5456aeb1bcaf2d402db2c094302731488536fde3","signature":"9d01797abc1ce5d2b2ca095bee592fa4887661c3cb1603e9f126b767d68bb57a"},{"version":"ad7787dd126b76b2148468f7a3e9945aa76f6e109e4be609dbef35404c9bb334","signature":"cf533088d48a0208786aa83c93a31f571bf9ba04190f0706322adc45cdbad20b"},{"version":"1bafd63c35d51b2d91755295abd9787a4a3ed1e8c96b440b27f3409ce9b20b6b","signature":"c54d0d991ecd2bc4626bcbcf9d32169b09174c3d6cb7bd174dc944bedd504989"},{"version":"1ff55a79605c140b9251c461efe09534925af2ac9e1753a58a17a96711588611","signature":"63b2f936da9faea8c52723d6b78ca09ee0bb769833160bbcb000be8b7cb456ac"},{"version":"b9a896843e293ae4e9560af9ef4c7cb999eb2ba47c629b4b73f81f83e085eea4","signature":"1ba2728a760e3d34d737964dc465092e51239587b874db79e71539eb8d271ca8"},{"version":"72155a0464029e06986ff956599c76a2ffc09c1636810a0ebf798e1207d1f4d3","signature":"63d31ca52e6e6071c1d33b659cc3550fca4657ccac514119f05bb30996f9b18d"},{"version":"994e51755e33de4e85d180542261adb695ddd7653d76f07934746be31196a091","signature":"b4e98fe21b2b7cca7ccabb5169df346479a0e3bb6bf46c25946174977917d316"},{"version":"6a7823e1c997de5b18f6f0b2d30b784692f0a6345a5e4a6662999bc1512f9f80","signature":"80923ea73f37baf4c06eb870a02060c68149fc8a8e47daddf6b55af1047d2b0c"},{"version":"7bcdf5a55ddf85072339f1f2af726763b75c3426e0c8e1ed104e24990883b3e1","signature":"6179a86622a28cdafe5d99fb99e1ab06b1a06011bb9a8ae9d65d4697e61d5316"},{"version":"fa698e0418b205926b7fbbec8b5c2c4ec37ddce72fefc7777e8904dc5c3cc2c3","signature":"37a718acb4d240ce0d45b7082a821b9bc8d9c523df47980aed0547887beafcb0"},{"version":"04e2d30a62563c91cf725e1ce85cfa64e2bf937bcba6501b156d625a017fffa0","signature":"e724140889de1a68b6fac45652942a37dbe94d0951da3249196817e9004181c8"},{"version":"11182196acb1c4e02a0046f0551a6096a06e24c04596d685580f54208c715e73","signature":"bdc9efa668395fb9851321350d62d507b1d77bd0ac73b9008ab116707a384821"},{"version":"bfdba17abf2629fac7cb2365811611ee4c58fda307ae7a3e12bafcdf470a4d05","signature":"8c8cfeee741fffa0d501b737893870e334e8ce8ffb111206364cb4e78cb489f4"},{"version":"8be3f833458178dbcb0d5025dbd09a888944448d51a08211f6a2d7cee0498edc","signature":"bb43b720c161d7aa620d5d68b8bd9769b9252d5711cb29454694e6fcbd8040ae"},{"version":"3fe46f792104ebc7973970f90aa5f014fa1276843c3fd0be4ca19f4974ba9142","signature":"5dd6a27d74b6c75f710ee5c79a87d1ece333000b10b6f96d00feacc190924798"},{"version":"904a442eea43f370d28ff0e40044ce07e25caae4901b194781723e610c601aeb","signature":"70325771fa3fddf64123a4f0246466cc124fd4e95e1c9099811d3819b9b5c3cb"},{"version":"02448cbf2ab203ced15be88a14165899f06b45543dce72b0c9be68c62ad4d3ff","impliedFormat":99},{"version":"8ab646541fcf5c09c55e4e1440a5310ce72de13b8a473e6bc775fd9531d1ab80","impliedFormat":99},{"version":"7cf75d220713bc4c2437cad80fdfb94fa2ac2d23b34643a5fdf2cafcb037b969","impliedFormat":99},{"version":"a716a3392219b2febd2b291d43921cf2eae7f9aa794d45da388d51ef2d659473","impliedFormat":99},{"version":"6d1b22dad9078bcf671d5ff5d03c9645ccecedb9816869aec74778489faa52f0","impliedFormat":99},{"version":"95c893fbe6896bc4d41408222e601cb1accd34d5d4148c37351bceb68beacc32","impliedFormat":99},{"version":"fdac0d6a0a042a2930afed2f017f5c5df5da9ed97495574b2c15e6592e9cb9ed","impliedFormat":99},{"version":"3c8ea23396d23cc136984dba37c7317f87f5f93e61060c09a2d82325d57ee261","signature":"3db3dc1fe56ab55e5bf0641e0e5e74032a2008ebbc61082463a131a2926f85e4"},{"version":"fec74e459caae4f2284b67d7225202c16a59efadf2c45d5f418de2963ce64ddc","signature":"3787c7ffb670ae4e74506253c65fd0c50cfc2495ec02f3916b192317b9012fb9"},{"version":"b25b281e70937b1c7a33e77309ed1f78117d95f1cde60e49703ce36c8b777b11","signature":"8463aea741cf53ec7f3722308bcbfaf4db65f71c46c2f23f0dbd142576f5d83e"},{"version":"fa8dbed00530fb4114906cd93f7fb55512c8eb9551d2f2e9796c69a4da4b594f","impliedFormat":1},{"version":"6d9e1b7a1fa967fb8505a5fa33073efb38aec5e7b75f2dc6383c9f84f3b5c0ba","impliedFormat":1},{"version":"0c6168215a3bba7a7a07119c23d3c5163a7c1aa36065abdbdb7c45643c859cbf","signature":"35f2abe6c86b8ee3741319a9d7a8c3eb0230e9b42f7bd7d543db7a94dc4e9051"},{"version":"e4b2435883ebce06d89737096a8831f47f14afd97a7a77ee67a10e6b21cdee81","signature":"443b3d66214796d6fda04e0fa046dad726466a02057743ee694d2486b1efc4b8"},{"version":"2163a878e5f82fd49b893bd69074ef3fb52b74a9fdf90c63067f2769a38d92ab","signature":"1fd2c5095ed136c58a63e6ecac817212c6cc3f773b662b473c7ce944790f3cf9"},{"version":"87b3c4492ce251073dcb09e5637c230238773ef858b87ad431a2308abc1003af","signature":"b0312121e01123f510e034bdf1a40c38b0ef4e0d64cbdd4bb34d65d203c73a3c"},{"version":"c604f168b38aecfbff9cc74225fcbc33ad057d1435a4f21c07228064a8d77240","signature":"2bccdddb2549b99dc756946217f3261b7e72c8974136c205dad3ed48b185ab1b"},{"version":"dab8790811b360ea1d3a69831c2cde589afc83729e3c1ea537edf629881e5004","signature":"a4d7376b6ce00df8eae10620748535a019f90134cec3b7c1028f067bff0e5025"},{"version":"fa2c05739d7236ea17571662ee9ab1793fb9acd285fec7f63b9622cbc6c01a27","signature":"f12acaa6f04cc3698628891d95a523a4bf0c03d03fb103edc7e4929709f1baf9"},{"version":"d6f4b9a418add129da3898008b6036a68dab54ec3997b04dd0bee2534d59a2fc","signature":"2bde30c5f8e10d5820a401c68e63e2b81a23077352c01977a10cc10a15f266f9"},{"version":"8de6508c8f5b0e9342779f0d1cb3999ee4dd84afd0061c51539ad0a047de094a","signature":"4b3f4c9228ab5427308c651bd192f9f627d6e7465d7e3eaf63422d09e1a2e187"},{"version":"66450277f3b147b473d04080b525dbba940cf75bce8aec50d0bbcc487321c317","signature":"37c0b6b7e7724598b96189a0153a958a908b1b73546dbebb7fceef0986e3ed3a"},{"version":"c4f363b279a3f41e59b61659cfd26a36497131139e59c0a6308c594c2ff54426","signature":"419996c73365008124de6ed63224ae81323de45079174bcac6b0dbdfc0108b44"},{"version":"c83b7f75cb77196d9dbb5ba8cf04f98da7fb4c6ce1fa3671d9fa0a2e34b01289","signature":"611acc6aabb75529a70459d172d44ad46a29d7f4b560fd049c201d05b6d0e698"},{"version":"c92c5036b82435bfc5084da97ea7e487c377b8d823a08450321c743e81faeab6","signature":"dc1097eff258d192d1b76e71f09eb7a5a8c9b4776c6e8ce8af855e41ce73a274"},{"version":"92dc4e8b3d0e8dea1f5abbe30adfd3910a7be441c12ed6fadc738adb59f9bb2c","signature":"322baceb1c9f45aedb9e5100ebbbedd07a164fc03ab9e98c462e32b41cbdc90a"},{"version":"b000b76af49edeca5e36dacb1027db1ad93c27ec097e2e2e2f17c53058062091","signature":"fe0cea76d1c6a718447bcd594059ac0a4c7f60b9452227e9bd6af7d564519f45"},{"version":"20a5bbe03c428dd68decc6d79d27beb557b46b556f756c5359825c0ddb22f503","signature":"3f3f0fb51d3c7c9fbab033f6757b786168283559ba1e6649a99010ef60aada5a"},{"version":"bc766171f81681d21c4ace62fe0a93a878ec92c0b1e11a87da0cb9e9f15ebf94","signature":"93799ea217ffac697e3222caa0d5c60771c1cfea1136666c2963797f10d09ce4"},{"version":"7cc0ca04ac330f9f0808e33e4595a1f1961b10fe6b3c8beb0ac0c45967598564","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6d870a6ebc4af392ccd2875f99d1dd4c90db167636e4b267de2e1d3ff14a6104","signature":"d02c70b928a2b77ba435d387595ed3b27e4466e3115d7b71a79c36c592238798"},{"version":"2f79a63626adba271a461cbaa34c976c26be8e474095b1bc8a80ce6e4fc68536","signature":"019c10e3a4d1413779d87a055ffea70d5dfd127b4b65a51cb2e20fca9e8f1f66"},{"version":"ef68d70baf9635137535142e9df63e515a9b8bdcf9906d6edaaf0e93313ac3aa","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d1d17705658519c7d57ce6c5cf95f0c894c2dd754ad1fcaeb1e2c4207d54e6e5","signature":"0bdd8b0d06cc0910bf6a36f2bdf87794634ab7014e178c7a7eb928437c6e5b76"},{"version":"7fb4c5b72e0a9a54c13085462b88f4d5f40a54a69a0578a8a391c0814d78d5d0","signature":"122cee24c6792a6328d79fb0ef76cc48d82112306037e5fb7bf78e3a2f4367c0"},{"version":"cefbd80acdccce73354b332cf8182bf72bfd81d1f4fc9ad2b57de314d79fc53d","signature":"5ff40a8d87e993b7d9798cfd183cad9e5cc58f9e4334ce2b76f69ef9294744d0"},{"version":"e219f6386da776b95ebcbf13d890d795dee10fd649006ff995814016fa85c77f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"99f539d1ed9cb203201dad7ea4bcee81d6f6ff0658e7543a3014e1dfa2cf3f87","signature":"12ee1ad5a651c4484cbbdc6ea7d594fe1f8adc9684006988275bd671f510f581"},{"version":"92a902847173b9c651ac667f7d47785536063a1987da48ca414939218a4042a2","signature":"8124c31de224c31a76019e9eb48d1c002aa3746e3c25d24a7c61a06b41ba0787"},{"version":"453957dcd68b2ce4ca9e3964669137141e3a6b66be1438775183fae9bf4f0b1f","impliedFormat":1},{"version":"51954e948be6a5b728fcfaf561f12331b4f54f068934c77adfc8f70eea17d285","impliedFormat":1},{"version":"1bcbe4a313d5cef449c393b331b0fe95fcb5ceacfa069c4208758d6c8a958db6","signature":"884c9b05c8b1f9cd07539bbd9db5f8ecf669a81e93b60c0d5045b99cd8916cc0"},{"version":"237b612f7714c0cb839b8e99b5d9feb721c4205ab762452d316d60bf28fdcea1","signature":"50f602bb3c9cd89a1879bd9432e5bf3cb44f916ea27e4975977792b9bbd1b9c6"},{"version":"3d8fe120bbb7ca42a0738c6e10855f6c6c0f18d288b42902eeaf7f7ba3c2d1ee","signature":"f210f5de32a60d365af7f0526213011dc0732700cd3678164918318dcb1a9da8"},{"version":"e1f90140453b0fd52c46cc6ebfec2ebe754483c05e50dc74caae321d41e7a9ac","signature":"da215cd8311e3d53ac952d9a12e0fcebedf7d76b9f5692525046d7bb0ecb1cc5"},{"version":"5563052849dd3f93ba63a20568ac06acf6b8e15c0b57aee1bd452e7e4b1d5d95","signature":"91db33413af7e79f8f5639385fa3fa68c2595993f3f5bd6d7efaa4fe19dc94bd"},{"version":"908b7b3f2c71140accb3333fa49485ce1ad10ac4e14faab10ed51c0c28c68782","signature":"d19e4f9294c2efb124088d0e5dc71b2faceaed9cb614ef974ab0c3b925374891"},{"version":"b15d5906d9090803407e10ef7858d2c7470dd78c7ed9a115d960ef9017904629","signature":"2e24bca723fca55268895430a62f030a00435b910ee7875844d127090673f98c"},{"version":"a645cfc27245e2a1f3282f0a93a86cd43c81edcf4795cf1f0545bdb28235bd3f","signature":"98d26fed15c1969891bc73c9dedc7278cdbd15f3afe3e34efe01c27dde514ba5"},{"version":"4f9ac21c4ded5c60695b528b367d889f2407d13378e2cd989219a5e85d1a1037","signature":"2c8f9281a7a4bb4a77894f0c4f76c50888be09b23ec04fefe9bf84c63513524e"},{"version":"80a3a9561b1e7ed1b11869acdbd73d0b751388fbe37d6ffa75cb7fd7808157a1","signature":"db1016666977bab29ab1854fb90c9ed76f0632bdf412c73c6fa81412a02bc5b6"},{"version":"6097e2be9bf4e2f5c98f779ac44dd9eff8aa047c065acdcaa8cf9bbc722a6164","signature":"d4438e83c3a3e41f54007253c009f863e491bb7eef87d4d3d46991f8ea62ec23"},{"version":"7437a1f294d03c63c49ddbf214e25ab9410424b79b6dc01fd9cb3b23e0c0be06","signature":"d73e7d9f551a968dcbd471ca03440ca263efb053defa3a163b94c429ac47729c"},{"version":"e2e250a24ea41932c838b2d5ccf4bdf34e0676a21805a6c60827f4abd4afa641","signature":"ff960dfb3d25c7584dbd000c154da20bc32aaf43a0b47f2e05e12628fda1e805"},"6958e241f880588015372a690454d0f7c0727b78e0a9882f493e2ac0fada857e",{"version":"b3d8ca2e78ed8245ecb17d7d2e0222330d32152bc328c9d7243d686f3c02d97b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7a732b03d5a0bd9c071d5887794887796b8a5cc30decef607a565735d8cdfc0d","signature":"f1983b8ef21692b453a2ef5ee21b5f8e5c32fea1e87bc833c55c781c109e45bf"},{"version":"c2d4dfa9bb5bbafa31b4423a78c2df02ccb51ad3f4abe7dcbbfaeb8dcf2cb82f","signature":"90b39c231c33d05240cbaabcfc21d94f68e05b5d9d2e972b644363be2133bebe"},{"version":"94adbb305113a8e6572989713200d1eba425e7a01443f1d02f4bd9a66f7f4fa3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3cdbef5d6bb0c6201d2edacc2f930322c36b9798b90131787398324b67bd2130","signature":"b84e31232011f6ac9ded7e727871f7c1ece606d8179950f791bcc03ac3c03b6e"},{"version":"3f8860ee39a7c1ca7824f29d948eb3f2160caa4cf4b1df9380a9b9b0c1ea184f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2d7cfcb10c36e23dcd60cc5371dd2c4716bba7950da92d836072ccedca2594db","signature":"de8b89e8f7e1489acfbf39531ee1eb5807be79db548a2ae53c4eaf740c0acb35"},{"version":"cbcd7e3639eae69860983466b671c160570ff8bef3295eb9d9cf3a918f7c3924","signature":"22039e2144d09a04262d19b5f0e4237ee40bec8322b5c9574403a9103af52044"},{"version":"775de23fb7da19fc3785f7649402d6d4e64f02b73819c2afe1c22e88e2cba6e6","signature":"cc9a2738a0b247ef64248e8bca32129c46b94dd155f2cd961eb59033964022ae"},{"version":"8cfea1c345d6bc3ba4d7d7ebda9ba3ca2186b0db9838efe6c79f4059a9618f53","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"298d271a03732dc27842cd85a0bb7f015147d6cf6cfd741579b1c40a8460ed68","signature":"d273813f1a71341c5f482788561acb719f12c65fcafb4f36423a6d409856d472"},{"version":"1fca48a9c511929eb58026762cf0bb7fac7a48488ca78ad1adc8414e2dcb1060","signature":"9e2bb88f173d3209e25d8856088cd88006b416949bf633f766578ae5b18f8488"},{"version":"fd9ff018f992e9f8f9f9fa2dfc37b89647cdc422a9220feac46a28d0c34ded90","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5e9142cb5f88305d5b5db601df9fe78244144630b03316f24b123c2bdaec5cd8","signature":"72a4b4bcd25bb33acac0c8d83f0d4d198714a06e03c49046690f380b9736998f"},{"version":"89a59cf51385bc46238630d496c8954cb98857545ac63ef595665d048965d71c","signature":"bbdcb92189d07c0439c3828e5aea552bfc8a01d782608d85d96264fe292d96c7"},{"version":"123af1de4ea296966265ebcf6b6ca3c5c4ac8e3548f0c3fc88e9fc4d24f6ce7b","signature":"0cdaaf51916fc5c085e3fa90eb61001fdae7ce8819cae1adb20ebeecb582795a"},{"version":"90453ef456c9284945d132c4d1c29753bcc06b0b7b1df7910768f748d4630160","signature":"d4d3b854dee0def611af8377422b5caef70f3b8c2c2d10ee3bb9ffb97d51cd45"},{"version":"404d9825e0fe3cc10db20060d068fd4f33c85c245ec9d2f99a20d05b02291b20","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d7382f620923bf13382b5aa1ed1d439617c8f6c916c1d7a645a6f5005dcddf8e","signature":"32159b615fba8ba0c76d071b40e35822660f8317b107f16b5d40ce3a8d6a5bbd"},{"version":"47928a15ba8a058a746a3fe7113775a2ae59003fb2ef6a6b82abcb72660f5717","signature":"20450b8a83fe349aea0ec611e1cfc508218d7b04eaa6e5d7e54a5e68efe7b174"},{"version":"1318f5ed0496352fb48c4c03ed01567db651e1794260df1b2da1e146a1aefab7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8732b365c97738e1e4b1da68affb78510c3b221043a2d7bb70ed76e2cbc476f5","signature":"ad2341da1c8562c9efb6dab3ec28be204fb5619a186320a26a6a65630800dd87"},{"version":"1a3e431b2f35ad9227aaf10b60ab4e0d1d736c45750dea729ebc84a96eaddd6e","signature":"1b1efaebf9198894a414851e919942c6ac8e03143ee26fc0f1ede061b98b492d"},"e6a0402ea87bfb937cea0e710472da29626189d13dbc6467c9a6814d7eb8fa43",{"version":"5e375dd5811641f19e1d39189456db4bad92e32135084b5f69ddf5ea77f66cd8","signature":"d6a98119ba90f6f7583274d639f20d153cb98faf1abe1a7f75d7db6743bf5acf"},{"version":"528b6d63b62cc0a5ee45454b35da231bfa50c93a0837a69f901a6342749a356f","signature":"487d2f1959a1a9b08e0e5fd4b3ca65700343665b40b76a066358b8cd3592aacc"},{"version":"ef20d9124a12e824c20d04362452ecbbdc6c9f3de2c3c4b231222870b9586e27","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"43746f41da19ceffd2f6d3d399d5380c1c7691798a22d0e786e7b457891da09b","signature":"5e2979e02d36c29b3adcd2555348ffe278cd2c1ea5bed57fb1c9661c7a30fc7d"},{"version":"5a10e3f7923e6063f54617980b79548b38ebcdd567d2b4a252b6ab6d133228c2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"161f871f8102ec12fb0f8b16aa90544c4056ee4f5eda4c6b8b8bba67cf5ee451","signature":"c4c000f5db2334ea4e2bc0b9bc437d27c292ba078ea53202378b878846840865"},"9eed204f26aed45ba513a001aaa78dffd4bf0194ed42fb59fa4a5b48dc382767",{"version":"232bb7f0d7da586fcca52950af3c64e626f0312d42f221ef8fd4ffa5b46a3a9a","signature":"f592c7e333a33b4e5dba58516b31a9ba2c3f5639c989fce88351556ba49606fc"},{"version":"5bd8925fb6477020ccab4924d24f09fbe5e76dfa970291207e66316575833179","signature":"571d3448b7e5dbb700ec919745d70b84c0859909793935026a8331b7666d91ed"},{"version":"8289d00aef316131b835c6fef2227e2b247641f3ecb434a3c93bb5692c7809e5","signature":"9ab6693aeebded592e13762f5108b1964d6edaaf634cb9a189df79643f88f7ad"},{"version":"d11c1ab2e722cfe3053c9800aec58a336665f3e6894272b8bacffe55a36ce729","signature":"03da542307af2869e7b2c1de8d1237d05b584cf0acf36c9d40f8e994f21adb2e"},"5f1ab4340b3a3f3d2c88167d0b98d1d8ae6c6d4b1ed845f25c3069d7d1b902d1",{"version":"34d7855e8328561594808d2203cee5dbe16a446120ff63ecb9dfa2529fafcaff","signature":"181c39a0a8a88631f8d29f5abffa3d154ca1a5fa46b87bda27b690a424404325"},{"version":"75b1f4a95f21e55792f113a90848a777b5d93357d59f30940fb87bd5cd3e6c47","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"eaf4a58ef586c168ad73bf0bf2e4fc7b50b0207046cb03e70f064e23cc7410a2","signature":"dab2cd6f392f32d0e94793582857c06a2d1fc79ebafc631e2b80e4b0c40778c9"},{"version":"08e69d30c063db86ea2221adf2282b1c118723cec16131cbd40024f3f43e5a90","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"44108382db49a9dfc5c2179601e376cac4d5fdc0eeb71f5ba93f7e591e412166","signature":"34eba88feaa79ccd50d2896998b69e4f85ea940a1553c14888466591bca44323"},{"version":"5e84fb45249b0704489777ad0ac4a54c20bf8495652edf9dd56322b28f9171a9","signature":"2b2185f188d84775508e17e3a98d216c3334d0c6890feee1f05e79be97dfa888"},{"version":"ea6c4aa3d6cb71e5cf5fad3f2bb57a7bf65198836bf1f4992f0e3a9aa56282c5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a8dcef5aa8dc0cc898d702364b72088c97994fde70660b8fe22d2ad622beb007","signature":"2cf5e020f8143231e08aca82ea1647287a23472ef15d1b54bebad2064c0ccd10"},{"version":"b8e47815afbb0381e41b1580893fb527078db40eb65cabf8fdae4b59202d3ad6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"95f053ad6f9e8f22fb9a0309e14a768302ff5f8072b9bc24b8decdcbdaaad0ff","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ec9e46ec9ebcf2563a7710cc683300002129826c6892b86fbc905153728fe0be","signature":"48c60fa731386066e73339c81976306d6734f7e3f9040b52a34ef418c51c4280"},{"version":"615a4c247058e88e2b3c498fde704dfda190dacf8117a05a74268a9767b9df24","signature":"d4b8a67fd5df8d739582126306c6899bcf7429238696abe8b6f87915d81a64f0"},{"version":"afaea598c95b1ad2bd0ed30bfb6ad867d78bf021b73a048b8fdaeb90cff22d88","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8d06badc9283290aaeaa6ceca270d68b55942af80fba3bd8ba1f4e3803850c2c","signature":"77307295274cc402aca163afe863f0ae8a1d2e94588f2acd35ec24d77af97b75"},{"version":"d579bdba0db63d615c541195af19b783527c13bbcc870e83ea61d198e3be56c6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"bb040b86775b25fc0848d43e3cc03e1b5cf4f00c40cbd350dbce010c06404df1","signature":"6d46cfeb3c048590784e9c099f0dfb8de954141c9fbf25f6d9ec87981ebc6fe7"},{"version":"d20eaead87726a364899203678c3e81614bfc368630e7a38ed0008acf7f7c1f4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7859b822b52969cf5421d8d07df464fabd68c67579733fd51cc994c6f3b9452e","signature":"91d869ffb8dc40ecfa1ed7675197ed893ea5501d5eab07a48f22dbe192cbd9b0"},{"version":"90bdcbca0e0bf6c2b0bbb00673a340a12647ee9e9c0b7ffa08c1cbee51683f6a","signature":"081dc84b2cfc91d910771f621446c86f99ef16cba361a195bc5b6415671e3940"},{"version":"7ca17bf6e4a1ac849f8d3165958db7bf74d3943a537ce3eda4c9f351a0ed66ae","signature":"9a83a4a37c134d0c0680968460af0b9f548c4c0aeca4a8d7551fc76a66bbd66e"},{"version":"ee1f2990fc6dacd47567a6075f7266b0af2628940d4f39a14f2aacf7a0fd4e85","signature":"7deb1227fcff5b438b3dc694ab262f7801f66701f3a0824ca0ac060c5bf8c39d"},{"version":"50040e211ffeffee56ae1c7ab65e604dc0dbe3f91dbf53ff5d6d31d0953da70d","signature":"bc3902ec251a79518aa6ad225a42563566db06b0b76a0fc66fb0456b2e5cd332"},{"version":"f3094aca88df33c32dd8c3bf7a2d6b00cbd62824f56d83d5d5420cc0bffbb3c1","signature":"81497943bee616a246679fdfba6e2afd14a1357f5a39f36e50aabc90970f594e"},{"version":"4051f6311deb0ce6052329eeb1cd4b1b104378fe52f882f483130bea75f92197","impliedFormat":1},{"version":"7b6e8d32728e05107c573c5dc2b6fe9cb14332dd7c82fb530093a840d6b59dc7","signature":"22146890ab30bea45bc289ccc48192249fe1cead53510eda7d9af2b09e065189"},{"version":"e7c2f40dc99121500ad108a4f86541d29cac105ed018f994c7c5a2836e77b257","impliedFormat":1},{"version":"90e930283286ab117ab89f00589cf89ab5e9992bc57e79f303b36ee14649bdd9","impliedFormat":1},{"version":"6d48a6c907c668a6d6eda66acec4242e367c983e073100e35c1e234c424ad1a4","impliedFormat":1},{"version":"68a0e898d6c39160f1326ef922508914498c7a2d0b5a0d9222b7928d343214eb","impliedFormat":1},{"version":"69d96a8522b301a9e923ac4e42dd37fc942763740b183dffa3d51aca87f978d5","impliedFormat":1},{"version":"ff2fadad64868f1542a69edeadf5c5519e9c89e33bec267605298f8d172417c7","impliedFormat":1},{"version":"2866ae69517d6605a28d0c8d5dff4f15a0b876eeb8e5a1cbc51631d9c6793d3f","impliedFormat":1},{"version":"f8c4434aa8cbd4ede2a75cbc5532b6a12c9cac67c3095ed907e54f3f89d2e628","impliedFormat":1},{"version":"0b8adc0ae60a47acf65575952eee568b3d497f9975e3162f408052a99e65f488","impliedFormat":1},{"version":"ede9879d22f7ce68a8c99e455acab32fc45091c6eed9625549742b03e1f1ac1a","impliedFormat":1},{"version":"0e8c007c6e404da951c3d98a489ac0a3e9b6567648b997c03445ac69d7938c1c","impliedFormat":1},{"version":"f2a4866bed198a7c804b58ee39efe74c66ecdcf2dfebef0b9895d534a50790c4","impliedFormat":1},{"version":"ad72538d0c5e417ee6621e1b54691c274bcacaa1807c9895c5fa6d40b45fb631","impliedFormat":1},{"version":"4f851c59f3112702f6178e76204f839e3156daa98b5b7d7e3fc407a6c5764118","impliedFormat":1},{"version":"57511f723968d2f41dd2d55b9fbc5d0f3107af4e4227db0fb357c904bd34e690","impliedFormat":1},{"version":"9585df69c074d82dda33eadd6e5dccd164659f59b09bd5a0d25874770cf6042d","impliedFormat":1},{"version":"f6f6ce3e3718c2e7592e09d91c43b44318d47bca8ee353426252c694127f2dcb","impliedFormat":1},{"version":"4f70076586b8e194ef3d1b9679d626a9a61d449ba7e91dfc73cbe3904b538aa0","impliedFormat":1},{"version":"6d5838c172ff503ef37765b86019b80e3abe370105b2e1c4510d6098b0e84414","impliedFormat":1},{"version":"1876dac2baa902e2b7ebed5e03b95f338192dc03a6e4b0731733d675ba4048f3","impliedFormat":1},{"version":"8086407dd2a53ce700125037abf419bddcce43c14b3cf5ea3ac1ebded5cad011","impliedFormat":1},{"version":"c2501eb4c4e05c2d4de551a4bace9c28d06a0d89b228443f69eb3d7f9049fbd6","impliedFormat":1},{"version":"1829f790849d54ea3d736c61fdefd3237bede9c5784f4c15dfdafb7e0a9b8f63","impliedFormat":1},{"version":"5392feeda1bf0a1cc755f7339ea486b7a4d0d019774da8057ddc85347359ed63","impliedFormat":1},{"version":"c998117afca3af8432598c7e8d530d8376d0ca4871a34137db8caa1e94d94818","impliedFormat":1},{"version":"4e465f7e9a161a5a5248a18af79dbfbf06e8e1255bfdc8f63ab15475a2ba48bd","impliedFormat":1},{"version":"e0353c5070349846fe9835d782a8ce338d6d4172c603d14a6b364d6354957a4e","impliedFormat":1},{"version":"323133630008263f857a6d8350e36fb7f6e8d221ec0a425b075c20290570c020","impliedFormat":1},{"version":"c04e691d64b97e264ca4d000c287a53f2a75527556962cdbe3e8e2b301dac906","impliedFormat":1},{"version":"3733dba5107de9152f98da9bcb21bf6c91ac385f3b22f30ed08d0dc5e74c966f","impliedFormat":1},{"version":"d3ec922ddd9677696ee0552f10e95c4e59f85bb8c93fd76cd41b2dd93988ff39","impliedFormat":1},{"version":"0492c0d35e05c0fdd638980e02f3a7cdec18b311959fc730d85ed7e1d4ff38a7","impliedFormat":1},{"version":"c7122ba860d3497fa04a112d424ee88b50c482360042972bcf0917c5b82f4484","impliedFormat":1},{"version":"838f52090a0d39dce3c42e0ccb0db8db250c712c1fa2cd36799910c8f8a7f7bf","impliedFormat":1},{"version":"116ec624095373939de9edb03619916226f5e5b6e93cd761c4bda4efecb104fc","impliedFormat":1},{"version":"8e6b8259bfd8c8c3d6ed79349b7f2f69476d255aede2cd6c0acb0869ad8c6fdd","impliedFormat":1},{"version":"14d95b7d7f7b5a779d493d060f53e163b1a74787d6f9b4ccbe8936ca01dafb5f","signature":"5727ceb9e1b0c8cb49fbc478c9bfc4e9ed07b9dd137121f1c09debf15bb37b59"},{"version":"bb496dc8024d753c28f375a4c0df0002dbad2facb8e548f27062a2655414db19","signature":"1da3635633f03cbe281630d2314ae81655a7a61783520e93b82b0bfe25d8e15a"},{"version":"dd6fcbc92559e404786bc671fed5a37516d9c55471b871dbba9a8b7f28f82753","signature":"15c15f737b3fc3aecd1b523378681a613ca49e3b4ebec59c974a5581a795916e"},{"version":"33cc9af95f90390a2cf1999a546260afe12f17c98a75f7c600f4da4b1e1ead00","signature":"d559519162225e563c30f461537f694d6c3258441a87a2ac34625c0dedb50598"},{"version":"15e52670d2ddfa335fd8d1268d14002d1b45a022411cdd99c6a9eca28b9ddcab","signature":"4e05515e35960fd9a549c915fa8c2a1aba9c27cf769d8e029b45ff8c8c50508a"},{"version":"9930757cf814a58885f76cf8399341c6b0ed7721d9bb14811134d6659419fcb3","signature":"dcf266c1eab20ad321e5bf1bb72699681899711d3a908e179f939d1edf24e013"},{"version":"5c6fad27889b29555ec4270fa34f6d318520fc36e3c368efef6988c2240f943d","signature":"e569b4fa591b1a8f2e8bc65df628ea7b8314ea9e9fc4de877bae1db7073f82d3"},{"version":"cfc1433bebaa05a9984117bbb336b30130bb234601f9a9cd92a2ed1e789afc54","signature":"2bb5816f801dc67b86549dcb0c662be56b248c5dd1662c1042691c7329148f98"},{"version":"4ecad7680faf9f6da12ba4db55dbc6df9eaae54bd199f145686ef897dc7d2ef1","signature":"de8287721228df0725bb5775da05878176c0b7788985dd9784efabbf520e15ed"},{"version":"2fc9963541991db69ae93c38f5170a229671b0f0e7170734e4428b4a8a2abd38","signature":"1f957907589ccd8879d7a77c1d5b0d478a64d866f3cca8de4ae23c1fe26940d1"},{"version":"67598ed7b69f803aabffe4f3cf9f85568f40656f48c71b0ecafe20d1b1f81eea","signature":"fa7a41ca696b949f45f852191cb2f159ae3039d65354e0595606e496012b1168"},{"version":"d6ad5279f8d296ebb50264aabcbd50a5296edfef7f23ae4c159579028935d119","signature":"48cf5a32fd77ba27774c29824c8d1bf27cfe16081169b1a013e0874292c3709b"},{"version":"eb8fcd3ac7e251b9d845d1d6cba5c742f034427219cc1df07307cb4c75adbd06","signature":"f350851978868a72a6438216754895a618bb6e28e72c468cd95b38b6e7df88e6"},{"version":"2c756fb2f6f8670edcaf04b280d669868830c93bb2ad97d04a6bac3e188a4213","signature":"aaeb521b6f9317f1358efeed044f7b8c9da2de643c9c444c86efa4b5974707ac"},{"version":"54c5cc433b64453256e2c017dc860876095fd30ab8f04798deb579cce34bfd17","signature":"7edc93fe90f8fabb25092054cd2ba3454b5665dc1470229cd49300d2787f9256"},{"version":"97e528c0766eec3cc10ee8900c37ed68075c925dcfa650bf71315532d34e3f1d","signature":"bb1bc2267b12d61504f42bb52c6aa47c88776574ed3150f2bb819226113d9d14"},{"version":"9c3809d98729933f6f435861b5538d484fbd667793d2089b8e2682c285141735","signature":"6905f829492addc100db593a31f563dd47f1c0c3f1a2b9fd5a35e2464c2aaa24"},{"version":"3776d3191d90a04a9fccade21da9929412ba450cc0be0a177c8b8e14e554adc2","signature":"0fafd7d6cde3e37c7c4045f298b00355745dcb2147c0e8d0f1240fba867274de"},{"version":"ae373dd89c07e2b635108407db8d0df2014029bdf7d51fd8c7838be770d81fa4","signature":"e017494fbef6d93da248b22d25416f95d8412cb4a4e8cb12c7e64495f16de3ec"},{"version":"bb6ac242b9c592dc784ef0d5c2e62a9c10e1546320aff1446d7c6d266dc35e85","signature":"5405216cffa69c9f9a5fcd8feced66b22d58045299c9fcab1c802d538e8bcc2b"},{"version":"227c62ec248e9072b199f9bbb88e10cc2e57b7c0a36c07587a063b2fb8191b97","signature":"c2f157d50cb6cd3bb53df17f7e4b15a6597c8a8544ce36976307b698b45d15af"},{"version":"4d5f0b37853e5b348cc7f4a50c7e62f3aabeb59eab7b15280e61a2e2e95b3d94","signature":"d3e65013cbd33328df76d080bb674401fc80b1880b3adea79fe4f49569c3767c"},{"version":"4d8d7e049c7a369a07b41963903b7041bd8c88560b55af2b4b6c4fd7be645cd5","impliedFormat":1},{"version":"83f6b233e11c9f2855f7f318f608570e9a45db007ae924278e7a581d7ef99b35","impliedFormat":1},{"version":"d5f6b57c733aa6afac7ab670974709fc2809a70450bb673b530a19f346c52836","signature":"e8b8e503a66283a53cb5197650eb1a6db822606f5e7216e19bb41047a2092bcf"},{"version":"0da8f1531846a6ca595707187e5a9e2ae7193ba426bdf3738a707ead043e4fb2","signature":"35444513a0600f3a35f1e67267dff8913a3cd02d8542c3da1ac90014dd905d8c"},{"version":"deb4df42f640706245617d22c38500d0d24e34689f405224004477c47b30a287","signature":"84225d531b0d673c7dee0a7abb7592e937c207fb0393e85d8e9808505e415642"},{"version":"05196723f295c088f92bd93f9edf2f9d72a0e6fb7a468f55aa270214519857a5","signature":"1c7c3f06b140f7f31f69c3f2a6f87659c1a487c552bc733f9de6ed963779a17a"},{"version":"01c55cc84a9a595b413f1fc1b25fb370b01de9098ff3d4d893451b6f33202b8e","signature":"93b8ca9c414deedbabc6f291b8129ec289fb392e499d0c4df2d9fb0d91263a10"},{"version":"60baecf2ee0b36e0b6f81536d77774d964bde3e3975c00328874e3b564a97e9e","signature":"a37134dd3223c23184711cd39086b2d518c984efc22d9e205d8155a9544847ed"},{"version":"c9e33faf41a15688f6a3d27f53167aa8238b5719e63ac75ce0f9bc608c7a429d","signature":"699f3f4cc048530f0e94b4e6c2c41e762eecdb2817c939de22b59d49b0029a4e"},{"version":"9394d990a82ad3db2079ea7b8f2d820c9e15e8b5131f7650a814ffa3c43f82c2","signature":"6ee7940135a66f481d7ffda0b6abc844e5d61fe14b9dc7866f9e0d7457d41d87"},{"version":"ac085a41f1a3d75c54f580b18f3cd5f34cc8e2b62279d70881808d4040f3ccd1","signature":"e4c5858df5ad3636f5bf6e13c2cc3a879e778ba55daca10f807d9f349e3e077c"},{"version":"f060e1946eb32ff62b101bbac21a6cd02835440c0892554566d0dde5d4838cec","signature":"036240f98ae8d5e07ad6f648996ff0630d6112eaee5b53fc3b309a1acd7c0721"},{"version":"7149bf3befb8b34de676bb8151a6c452b899500e81ff5991570e87187410ab6f","signature":"a2a12a3503d5bf9d004de85d49c7707a8c46b30953343089b0c7b14f804a11c7"},{"version":"a7e388ffc0396227e03c0960655d92a75783a699359d12ca645ce5648fe0863b","signature":"81b5b8de19882f9eb71c2f0021647ccb258b831a32404136811492d2fc71ce34"},{"version":"72382689d6ed60f25f6db3887f8f6df7be429d8e7533e4309b9ddbedd5deefed","signature":"a00dfd5786abefb744f2a2083e60a1a18cfefc11400b1cab42e63040430dd27f"},{"version":"f655ee0bf0f6a46b13b8dbea184cf25547a6328ad29d2382b081ebacd88e501e","signature":"d4f22b5386cf23e091c22e4f0e33a7a9c0ff3a245afeaa97840bf05e7bf91984"},{"version":"c993179a2129274a21e8926cc3b0281338a695ee0e8dd93df185a1ed66c1d401","signature":"1cb18627b01c1cd32263f3d582b16ce629a2bad80e2ef8a1c9d3263b05d0544c"},{"version":"d0ac529320dc415e66f66077248585f8a33f093de52186c296698451b5b1e712","signature":"c745247621d6425e3a4bd08dcb43b23754d9b3c6f3ff8072775566b93a15da6b"},{"version":"9c6cf6f3f66814d3d66592523e2047cd3e6f2430f6ac1694eb01c70d0f51d079","signature":"66247c65872f191626b989b5400c0c1f13547591eb4dc827ec3dd8c8e768fd82"},{"version":"29074a158418a682faf7fd1fa514ed1cb23122b05dd41ab14e45e6384f11fe96","signature":"3f41de67b26fe2b45e304db927cd0877c998a2a42704d358d026d7468cc5984f"},{"version":"d77b8be301421fa907ffc98763d96bb894ee9c3f3ad5f9e51fa36af0a3cb4b22","signature":"b1f49412c86f3f892d4693c31da6947a22602259778385e69a3a989c6ad1eb2d"},{"version":"30817ea9d19c62648cef33b7404ce06d1da3edec3d5b90534e1807ce403c2b49","signature":"de13b48db3d00144030014260f98c37af7af4e2126b419f1d26a2b213fd85824"},{"version":"735af8f14d6e3866b5863742bec289903bdde848ba8754fb628afb54af74d5bf","signature":"cf08e90dd518ced00aafe1c8036b90d927e9355e98d870a177e4420702925830"},{"version":"5369f40b97674e7b0dc8a3e9d66e4c98a4f6c9d41000ce110441af449d483df1","signature":"f874d87c06c9a63a1dc1754d69442198119d9b1686d12764d23d4abe9c6329c9"},{"version":"7901809357c2ac81b845f15716ba3eacdcf19ff9c5f2843d9e40b540a39afd90","signature":"723c9dcf67e44fec32f209501750f322febe472b3b91dc090bc3dccd6cac5718"},"98ebfeb0805807ae08415404af1b664e76353e70e5e71e6f086c7ba264b76fba",{"version":"ec213e4001fcd5e8446fea02e4a123873df592e8205987405c0e6664886104fd","signature":"f5ff573f4a6451fc2e8c7e1e6ceb8537cd0f25338065baae8a5735c24f22d431"},{"version":"c38e3b9be1619f15bec612db3c9867c9e3435bcc9798dd0d8f6342ab0d8ae946","signature":"e8f50d40694344b6b22ef6d4c3d5fe9347601c7a435fb7e60f820bf37d881d0a"},{"version":"1d25e7af5af3830d0cbc77493b047838dc152a775920710be72c3c8472b980a6","signature":"3560dc1bdf53f078348f0e499ab1e3339be7659310d92861fba7a9277024ffa7"},{"version":"cb83a4611b876ddcf649c18d80609654bb2c80fc160da1e7539cd3be15811a60","signature":"4220ee655e09296874f3ae3d3145efdd76398677bfa27fbef8e133c1b09cf50d"},{"version":"51a8c66060d8e08fa1607e33758afebaa47c542b40cf7091a791218255e7f769","signature":"475b03ccd54597a3115e13c07c1fe8ac417f6f6857d485114a00039d5d1ea179"},{"version":"817f663192fcc81412090c3e7167fa81c63eab9ea977e6a6ae8eeafb8742001e","signature":"77ce1243fed91f1a9f7be9c18192af18f4ea1603611a3dac2ba4f726c298de4c"},{"version":"4c31fd07fd4e530b6da7febec131d259907d5e179ef3bdac0a4a43cfa56b6935","signature":"b1eb5e11871638368fc4cab710e40fe4b23e0f7e9d4a6e22052edfa50d185fa9"},{"version":"9c5d15efba9e25be56ae6fe11057225c74316fb0c90e7d34b81ec8f633a9810e","signature":"a68280c12af5525ec8003356652c9ce50a24a6b3b6fc83bf793fafe60909fdbb"},{"version":"0172224d148517f7cf90527aa73f03cb436b3fe7f06ac70c039d6886b8f9dfdf","signature":"d01168279f1f9a417a578d3768c58eb5170a5b3445e7b2dedf880dbf0d1fe873"},{"version":"963c32aa76aee7050bf4b5749bec7c775046f03bcd791d54992b3345f7b80e2e","signature":"66be142f9806a1a6a875064f7a0416e1ccedcdcf6a9a209b1c633e475b975dd2"},{"version":"70047c5f97553530141aacef27e3dbff138c7606d2dd0934032bba2e84bc8dc5","signature":"219dfcb98664c09e2a901a0bebd0a1990dece13622fa81b99a4fd16e6352c936"},{"version":"aa2bf606eaeec4bbccb9f5fca0a068cf864b0fe287f7cf37b1aa785c945ddd2d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0d264c10a2c5205c38a41763017bf769e758b5f252f8c98d51d5c62d8a3515e5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6a91c1a0055a5d6d43e17b5f31de67b4050162cff0d7e45f82d767f9be56a774","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"69a81ee2b65949687067f23e7d5ca0fec2110620ff93ca8b92bc56740340b760","signature":"bc160a1badf1d1e0e9b92666b4848dfdc428b553d00ac5d2304b7ad80ac4cbea"},{"version":"eab5c45ccad2406d6da2068d7a2ea33a8738f39853674280f462718b3e2c9d54","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4dfaa3f58af09d51630ec76f6930e860fa49a21befda6568c9c9abd859df867b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ad7e4e17c67808e01a84f46591f2d4b763173ecc84a4863a727b7058b5786945","signature":"b7902bfba5bc8b901152f1ed5f8d9c2cbf2ba2790d351e5e5d61e00bdebbc624"},{"version":"3e5b494cda4d0ac2bce024928f0eacc77f8d9e4ac3d51eadde967ce542efc27e","signature":"b94b0bc72e5a26387c9314dc4f72106bddc0e5056469dcf4a43a351c1523c49c"},{"version":"1c230948c8120cd778cb258a0b70eec899b458db58229de16a2951a7ebdc57b8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0fef5a24cad5c4948eb776b00fa114e2560ce3dded3abc27db2e2b2b66832331","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a0bd46d587005aad4819980f6cf2dbcd80ebf584ed1a946202326a27158ba70e","impliedFormat":1},{"version":"07fcbb61a71bd69a92a5bbde69e60654666cf966b5675c2010c3bf9f436f056a","impliedFormat":1},{"version":"88b2eb23d36692162f2bf1e50577ebcde26de017260473e03ed9a0e61e2726a4","impliedFormat":1},{"version":"23ffbd8c0e20a697d2ea5a0cf7513fb6e42c955a7648f021da12541728f62182","impliedFormat":1},{"version":"43fba5fc019a4ce721a6f53ddb97fdc34c55049cfb793bc544d5c864ee5560b9","impliedFormat":1},{"version":"f4e12292c9a7663a13d152195019711c427c552eb0fa02705e0f61370cd5547a","impliedFormat":1},{"version":"c127ebf14d1b59d1604865008fb072865c5ca52277621f566092fe1f42ce0954","impliedFormat":1},{"version":"def638da26d84825a312113a20649d3086861de7c06a18ea13121278702976fd","impliedFormat":1},{"version":"fbaf86f8ba11298dea2727ce0da84b4ab6ae6c265e1919d44aff7d9b2bbc578a","impliedFormat":1},{"version":"c1010caaeaca8e420c6e040c2e822dbe18702459c93a7d2d5de38597d477b8cd","impliedFormat":1},{"version":"e1f0d8392efd9d71f2644eb97d3f33d90827e30ea8051d93b6f92bb11dff520a","impliedFormat":1},{"version":"085211167559ca307d4053bb8d2298d5ad83cbc3d2ae9bb4c8435a4cabf59369","impliedFormat":1},{"version":"55fc49198d8a85a73cdb79e596d9381cfdc9de93c32c77d42e661c1c1e7268ef","impliedFormat":1},{"version":"6a53fb3df8dd32ed1a65502ca30aeae19cfe80990e78ba68162d6cb2a7fed129","impliedFormat":1},{"version":"b5dcc18d7902597a5584a43c1146ca4fe0295ceb5125f724c1348f6a851dd6ed","impliedFormat":1},{"version":"0c6b0f3fbe6eb6a3805170b3766a341118c92ed7b6d1f193b9f35aa82f594846","impliedFormat":1},{"version":"60eaadb36cf157c5cae9c40e84fa367d04f52a150db3920dbe35139780739143","impliedFormat":1},{"version":"4680a32b1098c49dc87881329af1e68af9af94e051e1b9e19fed555a786f6ce6","impliedFormat":1},{"version":"89fcd129ec37f321cddcdb6b258ffe562de4281e90ec3ccbe7c1199ba39359ca","impliedFormat":1},{"version":"4313011f692861c2c1f5205d7f9a473e763adab6444f9853b96937b187fb19f7","impliedFormat":1},{"version":"caa57157e7bdb8d5f1efe56826fb84a6c8f22a1927bba7fa21fd54e2a44ccba2","impliedFormat":1},{"version":"6b74700abfe4a9b88be957fd8e373cfd998efb1a5f6ad122da49a92997e183ad","impliedFormat":1},{"version":"9ef1342f193bd8bae86c64e450c3ac468ef08652110355e1f3cdd45362eb95c4","impliedFormat":1},{"version":"6853c91662c36a2bf4c8371a87177c819007c76a23c293ef3f686ce9157ae4c8","impliedFormat":1},{"version":"9be1c5dabce43380d13fc621100676b03d420b5687b08d1288f479bee68ab7a8","impliedFormat":1},{"version":"8996d218010896712678e6a0337d8ef8b81c1066ab76f637dd8253f0d6ff838d","impliedFormat":1},{"version":"a15603bf387fc45defe28a68f405a6c29105e135c4e8538eeb6d0a1ef5b69a81","impliedFormat":1},{"version":"84e2532e4d42949a2775cdd8bb7b2b97370dd6ddb683d0c199b21bf6978b152d","impliedFormat":1},{"version":"22bf5f19f620db3b8392cfece44bdd587cdbed80ba39c88a53697d427135bf37","impliedFormat":1},{"version":"23ebbd8d484d07e1c1d8783169c20570ed8409966b28f6be6cf8e970d76ef491","impliedFormat":1},{"version":"18b6fa2c778cad6489f2febf76433453f5e2432ec3535f2d45ae7d803b93cc17","impliedFormat":1},{"version":"609d0d7419999cf44529e6ba687e2944b2fc7ad2570d278fd4e6b1683c075149","impliedFormat":1},{"version":"249cf421b8878a3fe948d9c02f6b0bae65491b3bb974c2ffc612341406fa78ff","impliedFormat":1},{"version":"b4aa22522d653428c8148ddbf1dcc1fb3a3471e15eb1964429a67c390d8c7f38","impliedFormat":1},{"version":"30b2cee905b1848b61c7d28082ebfa2675dd5545c0d25d1c093ce21a905cdccc","impliedFormat":1},{"version":"0a2a2eed4137368735205de97c245f2a685af1a7f1bf8d636b918a0ee4ff4326","impliedFormat":1},{"version":"69f342ce86706aa2835a62898e93ea7a1f21b1d89c70845da69371441bb6cd56","impliedFormat":1},{"version":"b5ab4282affcfd860dd1cc3201653f591509a586d110f8e5b1b010508ba79b2c","impliedFormat":1},{"version":"d396233f6cd3edf0d33c2fbfc84ded029c3ea4a05af3c94d09d31a367cced111","impliedFormat":1},{"version":"bc41a726c817624a5136ae893d7aac7c4dc93c771e8d243a670324bccf39b02b","impliedFormat":1},{"version":"710728600e4b3197f834c4dd1956443be787d2e647a72f190bf6519f235aaadd","impliedFormat":1},{"version":"a45097e01ef30ba26640fed365376ab3ccd5faf97d03f20daff3355a7e60286a","impliedFormat":1},{"version":"763cbb7c22199f43fd5c2b1566af5ba96bf7366f125dd31a038a2291cbc89254","impliedFormat":1},{"version":"031933bf279b7563e11100b5e1746397caf3a278596796a87bc0db23cf68dc9e","impliedFormat":1},{"version":"a4a54c1f58fc6e25a82e2c0f651bf680058bd7f72cfb2d43b85ee0ab5fe2e87e","impliedFormat":1},{"version":"9613d789b6f1037f2523a8f70e1b736f1da4566b470593da062be5c9e13dac57","impliedFormat":1},{"version":"0d2a320763a0c9c71493f8f1069971018c8720a6e7e5a8f10c26b6de79aa2f7d","impliedFormat":1},{"version":"817e0df27a237a268dc16e5acffc19f9a74467093af7a0ba164ee927007a4d25","impliedFormat":1},{"version":"43102521b5ca50ff1865188c3c60790feaed94dc9262b25d4adec4dbc76f9035","impliedFormat":1},{"version":"f99947f8d873b960b0115e506ef9c43f4e40c2071b1d20375564538af4a6023b","impliedFormat":1},{"version":"c1e5ad5ca89d18d2a36d25e8ec105623648cf35615825e202c7d8295a49d61ab","impliedFormat":1},{"version":"2b6c9cb81da4e0a2e32a58230e8c0dec49fc5b345efb7f7a3648b98956be4b13","impliedFormat":1},{"version":"99e34af3ede50062dcc826a1c3ce2d45562060dfd0f29f8066381a6ef548bf2a","impliedFormat":1},{"version":"49f5c2a23ea5fc4b2cdb4426f09d1c8b83f8409fa2af13ef38845cc9b9d4bc3d","impliedFormat":1},{"version":"e935227675144b64ecde3489e4a5e242eeb25fdd6b7464b8c21ad1f7a0faa88b","impliedFormat":1},{"version":"b42e6bbe88dc79c2d6dc5605fb9c15184e70f64bdd7b8d4069b802b90ce86df6","impliedFormat":1},{"version":"b9cd712399fdc00fdae07e96c9b39c3cb311e2a8a5425f1bd583f13cab35e44b","impliedFormat":1},{"version":"5a978550ae131b7fef441d67372fd972abab98ea9fdb9fa266e8bdc89edcb8d6","impliedFormat":1},{"version":"4f287919cfc1d26420db9f0457cd5c8780b1ef0a9f949570936abe48d3a43d91","impliedFormat":1},{"version":"496b23b2fd07e614bc01d90dd4388996cb18cd5f3a612d98201e9f683e58ad2e","impliedFormat":1},{"version":"dcfbe42824f37c5fb6dc7b9427ef2500791ec0d30825ecb614f15b8d5bf5a667","impliedFormat":1},{"version":"390124ad2361b46bf01851d25e331cd7eed355d04451d8b2a4aa985c9de4f8ce","impliedFormat":1},{"version":"14d94f17772c3a58eda01b6603490983d845ee2012cd643f7497b4e22566aacb","impliedFormat":1},{"version":"03ef2386c683707ce741a1c30cb126e8c51a908aa0acc01c3471fafb9baaacd5","impliedFormat":1},{"version":"66a372e03c41d2d5e920df5282dadcec2acae4c629cb51cab850825d2a144cea","impliedFormat":1},{"version":"5b48ba9a30a93176a93c87f9e0abf26a9df457eeb808928009439ca578b56f27","impliedFormat":1},{"version":"4707625392316d3c16edbd0716f4ac310e8ff5d346d58f4d01a2b7e0533a23df","impliedFormat":1},{"version":"154d58a4b2d9c552dc864ea39c223d66efd0ed2dd8b55bd13db5225d14322915","impliedFormat":1},{"version":"6a830433fa072931b4ea3eb9aa5fa7d283f470080586a27bfe69837a0f12de9a","impliedFormat":1},{"version":"d25e930e181f4f69b2b128514538f2abb54ef1d48a046ad776ac6f1cda885a72","impliedFormat":1},{"version":"0259b4c21bc93b52ca82c755f97fc90481072bcc44a8010131b2ea7326cf03fe","impliedFormat":1},{"version":"bea43a13a1104a640da0cb049db85c6993f484a6cc03660496b97824719ecc91","impliedFormat":1},{"version":"0224239d61fe66d4900544d912b2e11c2cca24b4707d53fdb94b874a01e29f48","impliedFormat":1},{"version":"2bce8fd2d16a9432110bbe0ba1e663fd02f7d8b8968cd10178ea7bc306c4a5df","impliedFormat":1},{"version":"9c4ad63738346873d685e5c086acbf41199e7022eff5b72bb668931e9ca42404","impliedFormat":1},{"version":"cfb6329bf8ce324e83fe4bbdee537d866a0d5328246f149a0958b75d033de409","impliedFormat":1},{"version":"efc3816f19ea87a7050c84271ea3d3aad9631a517c168013c4f4b6724c287ce0","impliedFormat":1},{"version":"f99f6737336140047e8dd4ade3859f08331aa4b17bc2bd5f156a25c54e0febbc","impliedFormat":1},{"version":"12a2b25c7c9c05c8994adf193e65749926acfcc076381f7166c2f709a97bdf0a","impliedFormat":1},{"version":"0f93a3fdd517c1e45218cd0027c1d6b82237e379dc6b66d693aab1fe74c82e81","impliedFormat":1},{"version":"03c753da0bee80ad0d0f1819b9b42dfe9bf9f436664caf15325aa426246fd891","impliedFormat":1},{"version":"18f5bf1dae429c451f20171427c9e3223fade4346af4dfd817725cbeb247a09d","impliedFormat":1},{"version":"a4eece5fab202e840dd84f7239e511017a8162edb8fc8b54ff2851c5c844125c","impliedFormat":1},{"version":"c4a94af483a63bf947d89f97553a55df5107c605ec8a26f0b9b8bdcc14bd6d89","impliedFormat":1},{"version":"19de2915ccebc0a1482c2337b34cb178d446def2493bf775c4018a4ea355adb8","impliedFormat":1},{"version":"9be8fc03c8b5392cd17d40fd61063d73f08d0ee3457ecf075dcb3768ae1427bd","impliedFormat":1},{"version":"3b568b63f0e8b3873629a4d7a918dce4266ad41461004ab979f8dcdfd13532bb","impliedFormat":1},{"version":"a5e5223c775fe30d606b8aaa521953c925d5ad176a531c2b69437d2461aaabbd","impliedFormat":1},{"version":"8cbf41d2d1ce8ac2066783ae00613c33feef07493796f638e30beaf892e4354a","impliedFormat":1},{"version":"e22ad737718160df198cd428f18da707177d0467934cecdeed4be6e067b0c619","impliedFormat":1},{"version":"15bf5ed8cb7c1a1e1db53fa9b45bc1a1c73c0497735343a8d0c59fdb596a3744","impliedFormat":1},{"version":"791fce84bce8b6948e4f23422d9cbbd7d08c74b3f91cca12dcae83d96079798b","impliedFormat":1},{"version":"8a2619c8e24305f6b9700b35af178394b995dcb28690a57a71cca87ee7e709ae","impliedFormat":1},{"version":"f95fd2fc3cc164921a891f5d6c935fa0d014a576223dd098fc64677e696b0025","impliedFormat":1},{"version":"8c9cecaaa9caba9a8caa47f46dcf24b524b27899b286d8edcc75a81b370d2ba3","impliedFormat":1},{"version":"2b7a82692ecc877c5379df9653902e23f2d0d0bc9f210ec3cf9e47be54413c5c","impliedFormat":1},{"version":"e2ad09c011cf9d7ee128875406bef787eeb504659495f42656a0098c15fe646c","impliedFormat":1},{"version":"eb518567ea6b0b2623f9a6d37c364e1b1ac9d8b508d79e558f64ac05c17e2685","impliedFormat":1},{"version":"630a48fb8f6b07161588e0aee3f9d301c59c97e1532c884118f89368baf4073b","impliedFormat":1},{"version":"14736c608aa46120f8d6d0bc5e0721b46b927bc7eba20e479600571935f27062","impliedFormat":1},{"version":"7574803692d2230db13205a7749b9c3587dccaccdf9e76f003f9e08078bb6d09","impliedFormat":1},{"version":"f3cc1588e666651c51353b1728460bee8acbc6e0f36be8c025eaaf292dca525d","impliedFormat":1},{"version":"0d4ea8a20527dcf3ad6cf1bd188b8ad4e449df174fad09b9e540ed81080af834","impliedFormat":1},{"version":"aa82876d59912d25becff5a79ed7341af04c71bfeb2221cc0417bc34531125e2","impliedFormat":1},{"version":"6f4b0389f439adc84cba35d45428668eabcfbdd351ba17e459d414ca51ab8eb8","impliedFormat":1},{"version":"d5dd33d15fbb07668c264b38065ac542a07a7650af4917727bbc09b58570e862","impliedFormat":1},{"version":"7d90202d0212e9cdc91a20bfddf04a539c89f09fe1d64db3343546fa2eb37e71","impliedFormat":1},{"version":"1a5d073c95a3a4480b17d2fa7fd41862a9df0cb2afaee86834b13649e96bdb45","impliedFormat":1},{"version":"2092495a5b3116c760527a690c4529748f2d8b126cdd5f56b2ce2230b48aba3f","impliedFormat":1},{"version":"620b29d6adbd4061bc0a8fedf145fcc8e8fc9648fb6e0a39726e33babb4e07bc","impliedFormat":1},{"version":"931eda51b5977f7f3fa7a0d9afde01cfd8b0cc1df0bb66dcf8c2cf6e7090384e","impliedFormat":1},{"version":"b084a412374bdd124048c52c4e8a82d64f3adec6c0a9ad5ecbb7317636039b0f","impliedFormat":1},{"version":"11199daa694c3ced3cc2a382a3fa7bd64e95eb40f9bbc3979fc8fb43f5ba38cc","impliedFormat":1},{"version":"2c86f279d7db3c024de0f21cd9c8c2c972972f842357016bfbbd86955723b223","impliedFormat":1},{"version":"dfb53b9d748df3e140b0fddb75f74d21d7623e800bb1f233817a1a2118d4bb24","impliedFormat":1},{"version":"8cfc293b33082003cacbf7856b8b5e2d6dd3bde46abbd575b0c935dc83af4844","impliedFormat":1},{"version":"7730c538d6d35efe95d2c0d246b1371565b13037e893178033360b4c9d2ac863","impliedFormat":1},{"version":"b256694544b0d45495942720852d9597116979d52f2b53c559fda31f635c60df","impliedFormat":1},{"version":"794e8831c68cc471671430ee0998397ea7a62c3b706b30304efdc3eaff77545a","impliedFormat":1},{"version":"9cfc1b227477e31988e3fb18d26b6988618f4a5da9b7da6bc3df7fc12fb2602e","impliedFormat":1},{"version":"264a292b6024567dd901fdabbf3239a8742bea426432cdbda4cf390b224188e1","impliedFormat":1},{"version":"f1556a28bb8e33862dcfa9da7e6f1dca0b149faf433fe6a50153ae76f3362db1","impliedFormat":1},{"version":"1d321aea1c6a77b2a44e02e5c2aeff290e3f1675ead1a86652b6d77f5fea2b32","impliedFormat":1},{"version":"4910efc2ce1f96d6e71a9e7c9437812ffae5764b33ab3831c614663f62294124","impliedFormat":1},{"version":"e3ceab51a36e8b34ab787af1a7cf02b9312b6651bac67c750579b3f05af646c1","impliedFormat":1},{"version":"baf9f145bcee1b765bed6e79fd45e1ff0ca297a81315944de81eb5d6fff2d13d","impliedFormat":1},{"version":"2afd62362b83db93cd20de22489fe4d46c6f51822069802620589a51ccad4b99","impliedFormat":1},{"version":"9f0cd9bd4ab608123b88328c78814738cbdee620f29258b89ef8cd923f07ff9c","impliedFormat":1},{"version":"801186c9e765583c825f28dab63a7ad12db5609e36dc6d9acbdc97d23888a463","impliedFormat":1},{"version":"96c515141c6135ccd6fb655fb9e3500074a9216ba956fb685dc8edc33f689594","impliedFormat":1},{"version":"416af6d65fc76c9ced6795f255cb1096c9d7947bede75b82289732b74d902784","impliedFormat":1},{"version":"a280c68b128ebba35fb044965d67895201c2f83b6b28281bb8b023ade68bf665","impliedFormat":1},{"version":"6fa118f15723b099a41d3beea98ed059bcd1b3eda708acf98c5eff0c7e88832f","impliedFormat":1},{"version":"dcbf582243e20ea50d283f28f4f64e9990b4ed4a608757e996160c63cff6aa99","impliedFormat":1},{"version":"efa432d8fd562529c4e9f859fd936676dd8fef5d3b4bedb06f754e4740056ea9","impliedFormat":1},{"version":"a59b66720b2ccf2e0150fafb49e8da8dabdf4e1be36244a4ccd92f5bd18e1e9e","impliedFormat":1},{"version":"c657fb1ec3b727d6a14a24c71ea20c41cb7d26a503e8e41b726bb919eb964534","impliedFormat":1},{"version":"50d6d3174868f6e974355bf8e8db8c8b3fcf059315282a0c359ecf799d95514a","impliedFormat":1},{"version":"86bf79091014a1424fc55122caa47f08622b721a4d614b97dd620e3037711541","impliedFormat":1},{"version":"7a63313dff3a57f824a926e49a7262f7bd14e0e833cf45fa5af6da25286769c2","impliedFormat":1},{"version":"36dcaeffe1a1aed1cb84d4feba32895bf442795170edccc874fa32232b2354e5","impliedFormat":1},{"version":"686c6962d04d90edafc174aa5940acb9c9db8949c8d425131c01d796cf9a3aef","impliedFormat":1},{"version":"2b1dbc3d5762d6865744b6e7be94b8b9004097698c37e93e06983e42dd8fe93b","impliedFormat":1},{"version":"eb5e8f74826bdf3a6a0644d37a0f48133f8ad0b5298cc2c574102868542ba4eb","impliedFormat":1},{"version":"c6a82a9673ba517cf04dd0803513257d0adf101aed2e3b162a54d840c9a1a3b2","impliedFormat":1},{"version":"fc9f0f415abaa323efcecc4a4e0b6763bfe576e32043546d44f1de6541b6399b","impliedFormat":1},{"version":"2c4d772ac7ac56a44deef82903364eb7c78dd7bc997701123df0ce4639fe39bb","impliedFormat":1},{"version":"9369ef11eed17c1c223fdea9c0fa39e83f3722914ef390b1448db3d71620c93a","impliedFormat":1},{"version":"aa84130dbc9049bba6095f87932138698f53259b642635f6c9e92dd0ddc7512c","impliedFormat":1},{"version":"084ceadd21efabd4b58667dca00d4f644306099151d2ee18cd28a395855b8009","impliedFormat":1},{"version":"b9503e29f06c99b352b7cae052da19e3599fa42899509d32b23a27c9bb5bebf6","impliedFormat":1},{"version":"75188920fe6ccc14070fe9a65c036049f1141d968c627b623d4a897ec3587e15","impliedFormat":1},{"version":"e2e1df7f45013d2b34f8d08e6ae5a9339724b0ea251b5445fcca3e170e640105","impliedFormat":1},{"version":"af06feb5d18a6ea11c088b683bdb571800d1f76b98d848eecdf41e5ec8f317fd","impliedFormat":1},{"version":"0596af52b95e0c8adc2c07f49f109d746b164739c5866fa8bb394dd6329a3725","impliedFormat":1},{"version":"c3365d08fe7a1ccc3b8e8638edc30123007f3241b4604e2585b9f14422ab97d8","impliedFormat":1},{"version":"a7a3d96b04bb0ec8cb7d2669767c4756f97dd70d08548f9e6522dde4de8e8a03","impliedFormat":1},{"version":"745e960e885a4ba04c872225cbb44bd67a7490d169ceaefab7c0dfc444768676","impliedFormat":1},{"version":"0b1ce1768cde3535493a9daf99e3bbb8c7dcc3a7f9d8cd358cb846af71ce5cdf","impliedFormat":1},{"version":"48b9603f6e8a7c94b727277592a089f94261baa64e6c9d18165da0481663a69e","impliedFormat":1},{"version":"3c20a3bb0c50c819419f44aa55acc58476dad4754a16884cef06012d02b0722f","impliedFormat":1},{"version":"4dc64902cb86e677a928293593658fbf53388f9a30d2b934140c70a7267b07ec","impliedFormat":1},{"version":"cb4fd56539a61d163ea9befe6b0292c32aa68a104c1f68f61416f1bc769bcfba","impliedFormat":1},{"version":"0d852bdc2b72b22393a8eebe374ee3efe3e0d44e630037b5e1b6087985388e62","impliedFormat":1},{"version":"b6c9a2deefb6a57ff68d2a38d33c34407b9939487fc9ee9f32ba3ecf2987a88a","impliedFormat":1},{"version":"f6b371377bab3018dac2bca63e27502ecbd5d06f708ad7e312658d3b5315d948","impliedFormat":1},{"version":"faa72893e85cb8ebb1dafde6b427e5204e60bb5f3ee6576bb64c01db1f255bc8","impliedFormat":1},{"version":"95b7ed47b31a6eaddcdd853ee0871f2bb61e39ce36a01d03dfafb83766f6c10c","impliedFormat":1},{"version":"19287d6b76288c2814f1633bdd68d2b76748757ffd355e73e41151644e4773d6","impliedFormat":1},{"version":"fc4e6ec7dade5f9d422b153c5d8f6ad074bd9cc4e280415b7dc58fb5c52b5df1","impliedFormat":1},{"version":"3aea973106e1184db82d8880f0ca134388b6cbc420f7309d1c8947b842886349","impliedFormat":1},{"version":"765e278c464923da94dda7c2b281ece92f58981642421ae097862effe2bd30fa","impliedFormat":1},{"version":"de260bed7f7d25593f59e859bd7c7f8c6e6bb87e8686a0fcafa3774cb5ca02d8","impliedFormat":1},{"version":"d95c4eaad4df9e564859f0c74a177fa0b2e5f8a155939b52580566ab6b311c3f","impliedFormat":1},{"version":"7192a6d17bfa06e83ba14287907b7c671bef9b7111c146f59c6ea753cfc736b9","impliedFormat":1},{"version":"5156d3d392db5d77e1e2f3ea723c0a8bd3ca8acffe3b754b10c84b12f55a6e10","impliedFormat":1},{"version":"a6494e7833ee04386a9f0c686726f7cb05f52f6e069d9293475ccb1e791ee0da","impliedFormat":1},{"version":"d9af0c89a310256851238f509a22aa1071a464d35dc22ea8c2a0bae42dd81bc5","impliedFormat":1},{"version":"291642a66e55e6ca38b029bc6921c7301f5c7b7acf21ae588a5f352e6c1f6d58","impliedFormat":1},{"version":"43cd7c37298b051d1ce0307d94105bcd792c6c7e017282c9d13f1097c27408e8","impliedFormat":1},{"version":"e00d8cce6e2e627654e49c543b582568ad0bf27c1d4ad1018d26aff78d7599df","impliedFormat":1},{"version":"ed13354f0d96fb6d5878655b1fead51722b54875e91d5e53ef16de5b71a0e278","impliedFormat":1},{"version":"fcb934d0fcdee06a8571bd90aa3a63aa288c784b3ebcecfe7ae90d3104d321f4","impliedFormat":1},{"version":"af682dfabe85688289b420d939020a10eb61f0120e393d53c127f1968b3e9f66","impliedFormat":1},{"version":"0dca04006bf13f72240c6a6a502df9c0b49c41c3cab2be75e81e9b592dcd4ea8","impliedFormat":1},{"version":"7dc0b5e3d7be8e1f451f0545448c2eaa02683f230797d24434b36f9820d5a641","impliedFormat":1},{"version":"247af61cdc3f4ec7876b9e993a2ecdd069e10934ff790c9cee5811842bff49eb","impliedFormat":1},{"version":"4be8c2c63d5cd1381081d90021ddfaef106881df4129eddeeaba906f2d0f75d0","impliedFormat":1},{"version":"012f621d6eb28172afb1b2dc23898d8bc74cf35a6d76b63e5581aa8e50fa71b3","impliedFormat":1},{"version":"3a561fa91097e4580c5349ce72e69d247c31c11d29f39e1d0bd3716042ff2c0b","impliedFormat":1},{"version":"bc9981a79dda3badea61d716d368a280c370267e900f43321f828495f4fef23c","impliedFormat":1},{"version":"2ed3b93d55aea416d7be8d49fe25016430caab0fe64c87d641e4c2c551130d17","impliedFormat":1},{"version":"3d66dfc31dd26092c3663d9623b6fc5cec90878606941a19e2b884c4eacd1a24","impliedFormat":1},{"version":"6916c678060af14a8ce8d78a1929d84184e9507fba7ab75142c1bcb646e1c789","impliedFormat":1},{"version":"3eea74afae095028597b3954bde69390f568afc66d457f64fff56e416ea47811","impliedFormat":1},{"version":"549fb2d19deb7d7cae64922918ddddf190109508cc6c7c47033478f7359556d2","impliedFormat":1},{"version":"e7023afc677a74f03f8ccb567532fe9eedd1f5241ee74be7b75ac2336514f6f6","impliedFormat":1},{"version":"ff55505622eac7d104b9ab9570f4cc67166ba47dd8f3badfb85605d55dd6bdc9","impliedFormat":1},{"version":"102fac015b1eebfa13305cb90fd91a4f0bbcabb10f2343556b3483bbb0a04b62","impliedFormat":1},{"version":"18a1f4493f2dbad5fd4f7d9bfba683c98cf5ed5a4fa704fa0d9884e3876e2446","impliedFormat":1},{"version":"f57e6707d035ab89a03797d34faef37deefd3dd90aa17d90de2f33dce46a2c56","impliedFormat":1},{"version":"cc8b559b2cf9380ca72922c64576a43f000275c72042b2af2415ce0fb88d7077","impliedFormat":1},{"version":"1a337ca294c428ba8f2eb01e887b28d080ee4a4307ae87e02e468b1d26af4a74","impliedFormat":1},{"version":"310fe80ff40a158c2de408efbe9de11e249c53d2de5e33ca32798e6f3fbc8822","impliedFormat":1},{"version":"d6ce96c7bb34945c1d444101f44e0f8ba0bba8ab7587a6cc009a9934b538c335","impliedFormat":1},{"version":"1b10a2715917601939a9288d49beccd45b591723256495b229569cd67bbe48a8","impliedFormat":1},{"version":"7498dfdeed2e003ec49cdf726ff6c293002d1d7fdadbc398ce8aafe6d0688de7","impliedFormat":1},{"version":"8492306a4864a1dc6fc7e0cc0de0ae9279cbd37f3aae3e9dc1065afcdc83dddc","impliedFormat":1},{"version":"9c86abbc4fd0248f56abc12aaecd76854517389af405d5ec2eb187fdb00a606f","impliedFormat":1},{"version":"9ffd906f14f8b059d6b95d6640920f530507e596e548f7a595da58ab66e3ce76","impliedFormat":1},{"version":"1884bccc10ce40adca470c2c371c1c938b36824f169c56f7f43d860416ca0a4c","impliedFormat":1},{"version":"986b55b4f920c99d77c1845f2542df6f746cb5adc9ab93eb1545a7e6ef37590d","impliedFormat":1},{"version":"cd00906068b81fbd8a22d021580ac505e272844408174520fafed0ae00627a5d","impliedFormat":1},{"version":"69fab68a769c17a52a24b868aeb644f3ee14abaa5064115f575ddd59231105ce","impliedFormat":1},{"version":"e181eb86b2caf80fe18c72efce6b913bc226e4a69a5456eaf4f859f1c29c6fd6","impliedFormat":1},{"version":"93f7871380478bc6acf02ad9f3dc7da0c21997caebbe782eb93a11b7bd06a46d","impliedFormat":1},{"version":"d00279ab020713264f570d5181c89ca362b7de8abddf96733de86bce0eca082c","impliedFormat":1},{"version":"f7db473f1d5d2a124f14886ac9dbfeccfbb94a98bbe1610a47c30c2933afa279","impliedFormat":1},{"version":"f44cf6c6d608ef925831e550b19841b5d71bd87195bd346604ff05644fb0d29c","impliedFormat":1},{"version":"154f23902d7a3fcdace4c20b654da7355fee4b7f807d1f77d6c9a24a8756013a","impliedFormat":1},{"version":"562f4f3c75a497d3ad7709381f850bb8c7646a9c6e94fdf8e91928e23d155411","impliedFormat":1},{"version":"4583380b676ee59b70a9696b42acfa986cd5f32430f37672e04f31f40b05df74","impliedFormat":1},{"version":"ad0a13f35a0d88803979f8ea9050ad7441e09d21a509abf2f303e18c1267af17","impliedFormat":1},{"version":"ba9781c718ab3d09cbde1216029072698d2da6135f0d2f856ba387d6caceb13e","impliedFormat":1},{"version":"d7c597c14698ba5fc8010076afa426f029b2d8edabb5073270c070cc645ba638","impliedFormat":1},{"version":"bd2afc69cf1d85cd950a99813bc7eff007d8afa496e7c2142a845cd1181d0474","impliedFormat":1},{"version":"558b462b23ea186d094dbff158d652acd58c0988c9fd53af81a8903412aa5901","impliedFormat":1},{"version":"0e984ae642a15973d652fd7b0d2712a284787d0d7a1db99aa49af0121e47f1df","impliedFormat":1},{"version":"0ad53ee208a23eef2a5cb3d85f2a9dc1019fd5e69179c4b0c02dc56c40d611c4","impliedFormat":1},{"version":"7a6898b26947bd356f33f4efef3eb23e61174d85dca19f41a8780d6bb4bfb405","impliedFormat":1},{"version":"9fe30349d26f34e85209fb06340bac34177f7eae3d6bb69dc12cd179d2c13ddf","impliedFormat":1},{"version":"d568c51d2c4360fd407445e39f4d86891dba04083402602bf5f24fd3969cacbb","impliedFormat":1},{"version":"b2483a924349ec835f4d778dd6787447a2f8bfbb651164851bff29d5b3d990a6","impliedFormat":1},{"version":"aae66889332cff4b2f7586c5c8758abc394d8d1c48f9b04b0c257e58f629d285","impliedFormat":1},{"version":"0f86c85130c64d6dbe6a9090bb3df71c4b0987bce4a08afe1ac4ece597655b9c","impliedFormat":1},{"version":"0ce28ad2671baed24517e1c1f4f2a986029137635bce788ee8fb542f002ac5b8","impliedFormat":1},{"version":"cd12e4fe77d24db98d66049360a4269299bcfb9dc3a1b47078ab1b4afac394cb","impliedFormat":1},{"version":"1589e5ac394b2b2e64264da3e1798d0e103b4f408f5bae1527d9e706f98269c7","impliedFormat":1},{"version":"ff8181aa0fde5ec2d737aecc5ebaa9e881379041f13e5ce1745620e17f78dcf9","impliedFormat":1},{"version":"0b2e54504b568c08df1e7db11c105786742866ba51e20486ab9b2286637d268f","impliedFormat":1},{"version":"bc1ffc3a2dca8ee715571739be3ec74d079e60505e1d0d2446e4978f6c75ba5c","impliedFormat":1},{"version":"770a40373470dff27b3f7022937ea2668a0854d7977c9d22073e1c62af537727","impliedFormat":1},{"version":"a0f8ce72cb02247a112ce4a2fa0f122478a8e99c90a5e6b676b41a68b1891ad2","impliedFormat":1},{"version":"6e957ea18b2bf951cf3995d115ad9bfa439e8d891aeb1afc901d793202c0b90d","impliedFormat":1},{"version":"a1c65bd78725f9172b5846c3c58ddf4bcbb43a30ab19e951f0102552fbfd3d5d","impliedFormat":1},{"version":"04718c7325e7df4bac9a6d026a0a2bd5a8b54501f274aaf93a03b5d1d0635bd1","impliedFormat":1},{"version":"405205f932d4e0ce688a380fa3150b1c7ff60e7fc89909e11a33eab7af240edb","impliedFormat":1},{"version":"566fc1a6616a522f8b45082032a33e6d37ff7df3f7d4d63c3cce9017d0345178","impliedFormat":1},{"version":"3b699b08db04559803b85aa0809748e61427b3d831f77834b8206e9f2ed20c93","impliedFormat":1},{"version":"b27242dd3af2a5548d0c7231db7da63d6373636d6c4e72d9b616adaa2acef7e1","impliedFormat":1},{"version":"e0ee7ba0571b83c53a3d6ec761cf391e7128d8f8f590f8832c28661b73c21b68","impliedFormat":1},{"version":"072bfd97fc61c894ef260723f43a416d49ebd8b703696f647c8322671c598873","impliedFormat":1},{"version":"e70875232f5d5528f1650dd6f5c94a5bed344ecf04bdbb998f7f78a3c1317d02","impliedFormat":1},{"version":"8e495129cb6cd8008de6f4ff8ce34fe1302a9e0dcff8d13714bd5593be3f7898","impliedFormat":99},{"version":"e3c2cc25f470bea78afb3af5ca6f30759cfd44e4b1212e84657fb36e45a3a1d3","signature":"6e0fa1db91df6484a033340dfafa435f2e98844b66e876a01f963ff84a878646"},{"version":"16bed64f9c23abdf4e18425ab522343d5c8f180214832e5b6d40135d838f7841","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5e6e6f01cb776da72cb3ca11adf3fa20c16d0f68841c2f550e485376491fe584","signature":"981a1e9bb280cbad4485d10bfb76e890079fcc75cdf11f502bd995e1065d2616"},{"version":"2e0902468e1a220489a3f33dc82d5eff8f70521cc4ad8eb35abee7a792a14a6f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"964244acf38c094ec67de89656b936d3a3f836b66719afc936249bc1fe097a6c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1652f9e9c84699da6c0050e0818ffbd6093fb9dc67e082bfb5d3df1fc92012b2","signature":"e7ed13b72a29fe7f0c628bb06ed80ba591e327268a8acf0cb66fa1725ce5e930"},{"version":"ccb92614f79729906fc8a9f5c9c18f163c1352d2e549cc49b42c4302fa591f30","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"666382b2c44dfe6f0c2855b47eded1c7834b41302dc74a7e6a67b8fc834bef74","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"eedbc489481913977cc00e055bd0f493c8ed3115928ca006929d1b353411e722","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9344e6e424dfd647c27be85b5ea478753830f7fb31a74747ce6a373b479d51b2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"73a98325658b7d7509ec766ae0f0b34b9a4d9819b388d4e5b3c74ef8af5af185","signature":"211206a1cc64d3623a54c919fe8e7e8cb70032936ce7dd2625ba2f185580334a"},{"version":"938165e53c76725415bd8152fc8363d628ac4a95e04d4c2884f75349e3d337a9","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"469d3ea0db1d18de2755c0524ccd3ce841290f6c070e1e36d5d2a29814698a06","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ccae0e1f81234cd2641d83504765e64e37013fc26faec970ea5931946db96772","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"618bb47ac74b0ab39b90743cfe920a6e670b2d3bfa24f37d106b535c1c18ae37","signature":"4c8a45f845c330caf5dc2bc33da6e8e4f317035d08a20fd5f1da986bea511d60"},{"version":"681ab80103a45e835b91035d733228aa210d75cb0cd45355dbe6e72fcbe1806a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cfc4d1e3e0f3fa0a4a3de8483598fe4ca1f9677de760b092b4919748ca383fff","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f3d8c757e148ad968f0d98697987db363070abada5f503da3c06aefd9d4248c1","impliedFormat":1},{"version":"ac450542cbfd50a4d7bf0f3ec8aeedb9e95791ecc6f2b2b19367696bd303e8c6","impliedFormat":1},{"version":"8a190298d0ff502ad1c7294ba6b0abb3a290fc905b3a00603016a97c363a4c7a","impliedFormat":1},{"version":"5ba4a4a1f9fae0550de86889fb06cd997c8406795d85647cbcd992245625680c","impliedFormat":1},{"version":"57c98d540df72bdc219a9d4e23e7c1f844459414e4e62eda3439067fadf743ba","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7998ce2d8b37f668e88c92daa47b133247d30af42a5bd5c66c87c95cf2e9cbc5","signature":"5c2ec047b44a11691848a7f94ea2485a57dc90298a6b1aac2fa73780645035ca","impliedFormat":99},{"version":"4e9a13db70bb7f18b2806dc10c184927d99d9551ac75b61a40abd179c197c853","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f329dfad7970297cbf07ddc8fce2ad4a24e2a3855917c661922ef86eb24dd1f1","impliedFormat":1},{"version":"841784cfa9046a2b3e453d638ea5c3e53680eb8225a45db1c13813f6ea4095e5","affectsGlobalScope":true,"impliedFormat":1},{"version":"646ef1cff0ec3cf8e96adb1848357788f244b217345944c2be2942a62764b771","impliedFormat":1},{"version":"f8276f80a1e792110e3a21974c6db4821798b9b726a067d2326923ca3b8a111b","signature":"90ec9100c29e008c3d9194acd818e2cfa6dc6e177154bc8e10c5959aa35619ed"},{"version":"acd8fd5090ac73902278889c38336ff3f48af6ba03aa665eb34a75e7ba1dccc4","impliedFormat":1},{"version":"d6258883868fb2680d2ca96bc8b1352cab69874581493e6d52680c5ffecdb6cc","impliedFormat":1},{"version":"1b61d259de5350f8b1e5db06290d31eaebebc6baafd5f79d314b5af9256d7153","impliedFormat":1},{"version":"f258e3960f324a956fc76a3d3d9e964fff2244ff5859dcc6ce5951e5413ca826","impliedFormat":1},{"version":"643f7232d07bf75e15bd8f658f664d6183a0efaca5eb84b48201c7671a266979","impliedFormat":1},{"version":"21da358700a3893281ce0c517a7a30cbd46be020d9f0c3f2834d0a8ad1f5fc75","impliedFormat":1},{"version":"2b2bef0fbee391adb55bcd1fa38edf99e87233a94af47c30951d1b641fc46538","impliedFormat":1},{"version":"f21af9796e3aa1fe83b3d3e3b401ad4e15e39c15e8e0dab3bb946794b4d2e63f","impliedFormat":1},{"version":"17ed71200119e86ccef2d96b73b02ce8854b76ad6bd21b5021d4269bec527b5f","impliedFormat":1},{"version":"1cfa8647d7d71cb03847d616bd79320abfc01ddea082a49569fda71ac5ece66b","impliedFormat":1},{"version":"bb7a61dd55dc4b9422d13da3a6bb9cc5e89be888ef23bbcf6558aa9726b89a1c","impliedFormat":1},{"version":"413df52d4ea14472c2fa5bee62f7a40abd1eb49be0b9722ee01ee4e52e63beb2","impliedFormat":1},{"version":"db6d2d9daad8a6d83f281af12ce4355a20b9a3e71b82b9f57cddcca0a8964a96","impliedFormat":1},{"version":"446a50749b24d14deac6f8843e057a6355dd6437d1fac4f9e5ce4a5071f34bff","impliedFormat":1},{"version":"182e9fcbe08ac7c012e0a6e2b5798b4352470be29a64fdc114d23c2bab7d5106","impliedFormat":1},{"version":"2f4e6b4d39426a1b85ecf4bdeb9dddbf4d9b3397d95d8555d46f925c9519ec7d","impliedFormat":1},{"version":"78a2869ad0cbf3f9045dda08c0d4562b7e1b2bfe07b19e0db072f5c3c56e9584","impliedFormat":1},{"version":"89d5d28d4f57e000b836ac273079be1b75710e28ce14750d081fb420d37e2ca5","impliedFormat":1},{"version":"fd4e24ccff3966390600d7f5d6aa1fed5a512e92ada735ea5fbc933d313ad3d3","impliedFormat":1},{"version":"b7cddfe1aa6b86b5fad3c9ccb30d05b3ccb165aebbf112f48d2d8a5f69dd98b1","impliedFormat":1},{"version":"a86f82d646a739041d6702101afa82dcb935c416dd93cbca7fd754fd0282ce1f","impliedFormat":1},{"version":"ad0d1d75d129b1c80f911be438d6b61bfa8703930a8ff2be2f0e1f8a91841c64","impliedFormat":1},{"version":"3e7efde639c6a6c3edb9847b3f61e308bf7a69685b92f665048c45132f51c218","impliedFormat":1},{"version":"df45ca1176e6ac211eae7ddf51336dc075c5314bc5c253651bae639defd5eec5","impliedFormat":1},{"version":"8a0e762ceb20c7e72504feef83d709468a70af4abccb304f32d6b9bac1129b2c","impliedFormat":1},{"version":"ce75b1aebb33d510ff28af960a9221410a3eaf7f18fc5f21f9404075fba77256","impliedFormat":1},{"version":"ee8df1cb8d0faaca4013a1b442e99130769ce06f438d18d510fed95890067563","impliedFormat":1},{"version":"d5630f2ad9b4541e5ce891648121022f9412ecdca1820baa1f0104f70fd7eff7","impliedFormat":1},{"version":"4d15375ab13497104bc8fe56fdef2b5fd6853f29255737d23a33fa306ff7fd69","impliedFormat":1},{"version":"2cd3fc1d0d6a1e85baffd2d4f50f5efb192b5446eef567e97c94765402f0aad4","impliedFormat":1},{"version":"e4cbf2f1e89ecccaddd2c045e600ae41b732295953fb06247c7dcbc2d281ed30","impliedFormat":1},{"version":"6dcedaef57dff0d79a05ab0ab602cde74db803d1e765468bf91263786a383e1b","impliedFormat":1},{"version":"8c1697d90c394a6fd955b98eae01238eff628e129b987a68aea10f898a48e7da","impliedFormat":1},{"version":"7580e62139cb2b44a0270c8d01abcbfcba2819a02514a527342447fa69b34ef1","impliedFormat":1},{"version":"f374cb24e93e7798c4d9e83ff872fa52d2cdb36306392b840a6ddf46cb925cb6","impliedFormat":1},{"version":"d10d63718e1646c2279e3b33831f82c60e31f622b2b7020f1196409ca4c09242","impliedFormat":1},{"version":"106c6025f1d99fd468fd8bf6e5bda724e11e5905a4076c5d29790b6c3745e50c","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"148679c6d0f449210a96e7d2e562d589e56fcde87f843a92808b3ff103f1a774","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"02436d7e9ead85e09a2f8e27d5f47d9464bced31738dec138ca735390815c9f0","impliedFormat":1},{"version":"f8d5ff8eafd37499f2b6a98659dd9b45a321de186b8db6b6142faed0fea3de77","impliedFormat":1},{"version":"c86fe861cf1b4c46a0fb7d74dffe596cf679a2e5e8b1456881313170f092e3fa","impliedFormat":1},{"version":"a22dd55aa4d39906252000ab8e8a1b83b195eef7f4274eb51e457c1f11cf6580","impliedFormat":1},{"version":"540cc83ab772a2c6bc509fe1354f314825b5dba3669efdfbe4693ecd3048e34f","impliedFormat":1},{"version":"121b0696021ab885c570bbeb331be8ad82c6efe2f3b93a6e63874901bebc13e3","impliedFormat":1},{"version":"612d9da66bb046a9c1e2e8d026245ded881fc4b9f98cbfae714415d57ee0ae0b","impliedFormat":1},{"version":"32c2ad9494dad5d11b0564a619fee18f388db6c1e9e2cd3c360b3122549691eb","impliedFormat":1},{"version":"6c301d40aec56a74ec7bd7324e31a728dadf9bfba3e96def02938d3d973534ec","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881","impliedFormat":1},{"version":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881","impliedFormat":1},{"version":"aa14cee20aa0db79f8df101fc027d929aec10feb5b8a8da3b9af3895d05b7ba2","impliedFormat":1},{"version":"493c700ac3bd317177b2eb913805c87fe60d4e8af4fb39c41f04ba81fae7e170","impliedFormat":1},{"version":"aeb554d876c6b8c818da2e118d8b11e1e559adbe6bf606cc9a611c1b6c09f670","impliedFormat":1},{"version":"acf5a2ac47b59ca07afa9abbd2b31d001bf7448b041927befae2ea5b1951d9f9","impliedFormat":1},{"version":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881","impliedFormat":1},{"version":"d71291eff1e19d8762a908ba947e891af44749f3a2cbc5bd2ec4b72f72ea795f","impliedFormat":1},{"version":"c0480e03db4b816dff2682b347c95f2177699525c54e7e6f6aa8ded890b76be7","impliedFormat":1},{"version":"25a5f6fd3a2243c859eddc99ab5fba11d970af2fe7a5df9c32b7668f76f97b01","impliedFormat":1},{"version":"8d207e1f9d2c30d6f77dfa693f3827c3fbf0d89240297e10bdfe1041d433df68","impliedFormat":1},{"version":"b620391fe8060cf9bedc176a4d01366e6574d7a71e0ac0ab344a4e76576fcbb8","impliedFormat":1},{"version":"6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff","impliedFormat":1},{"version":"2652448ac55a2010a1f71dd141f828b682298d39728f9871e1cdf8696ef443fd","impliedFormat":1},{"version":"d682336018141807fb602709e2d95a192828fcb8d5ba06dda3833a8ea98f69e3","impliedFormat":1},{"version":"6124e973eab8c52cabf3c07575204efc1784aca6b0a30c79eb85fe240a857efa","impliedFormat":1},{"version":"0d891735a21edc75df51f3eb995e18149e119d1ce22fd40db2b260c5960b914e","impliedFormat":1},{"version":"3b414b99a73171e1c4b7b7714e26b87d6c5cb03d200352da5342ab4088a54c85","impliedFormat":1},{"version":"4fbd3116e00ed3a6410499924b6403cc9367fdca303e34838129b328058ede40","impliedFormat":1},{"version":"9c82171d836c47486074e4ca8e059735bf97b205e70b196535b5efd40cbe1bc5","impliedFormat":1},{"version":"8c70ddc0c22d85e56011d49fddfaae3405eb53d47b59327b9dd589e82df672e7","impliedFormat":1},{"version":"2f9c89cbb29d362290531b48880a4024f258c6033aaeb7e59fbc62db26819650","impliedFormat":1},{"version":"a365c4d3bed3be4e4e20793c999c51f5cd7e6792322f14650949d827fbcd170f","impliedFormat":1},{"version":"c5426dbfc1cf90532f66965a7aa8c1136a78d4d0f96d8180ecbfc11d7722f1a5","impliedFormat":1},{"version":"65a15fc47900787c0bd18b603afb98d33ede930bed1798fc984d5ebb78b26cf9","impliedFormat":1},{"version":"9d202701f6e0744adb6314d03d2eb8fc994798fc83d91b691b75b07626a69801","impliedFormat":1},{"version":"de9d2df7663e64e3a91bf495f315a7577e23ba088f2949d5ce9ec96f44fba37d","impliedFormat":1},{"version":"c7af78a2ea7cb1cd009cfb5bdb48cd0b03dad3b54f6da7aab615c2e9e9d570c5","impliedFormat":1},{"version":"1ee45496b5f8bdee6f7abc233355898e5bf9bd51255db65f5ff7ede617ca0027","impliedFormat":1},{"version":"273782b8454e78f6a8b30d2cfbf6860499c930595095fcc1689637115f0eddda","affectsGlobalScope":true,"impliedFormat":1},{"version":"3fbdd025f9d4d820414417eeb4107ffa0078d454a033b506e22d3a23bc3d9c41","affectsGlobalScope":true,"impliedFormat":1},{"version":"dba114fb6a32b355a9cfc26ca2276834d72fe0e94cd2c3494005547025015369","impliedFormat":1},{"version":"a8f8e6ab2fa07b45251f403548b78eaf2022f3c2254df3dc186cb2671fe4996d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fa6c12a7c0f6b84d512f200690bfc74819e99efae69e4c95c4cd30f6884c526e","impliedFormat":1},{"version":"f1c32f9ce9c497da4dc215c3bc84b722ea02497d35f9134db3bb40a8d918b92b","impliedFormat":1},{"version":"b73c319af2cc3ef8f6421308a250f328836531ea3761823b4cabbd133047aefa","affectsGlobalScope":true,"impliedFormat":1},{"version":"e433b0337b8106909e7953015e8fa3f2d30797cea27141d1c5b135365bb975a6","impliedFormat":1},{"version":"ddff7fc6edbdc5163a09e22bf8df7bef75f75369ebd7ecea95ba55c4386e2441","impliedFormat":1},{"version":"d38530db0601215d6d767f280e3a3c54b2a83b709e8d9001acb6f61c67e965fc","impliedFormat":1},{"version":"6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff","impliedFormat":1},{"version":"b499af2054a037a162b3b72cd886f48bbf32a3502c865c6e29fac7d2ab3ce0b5","impliedFormat":1},{"version":"b83cb14474fa60c5f3ec660146b97d122f0735627f80d82dd03e8caa39b4388c","impliedFormat":1},{"version":"48773ca557b0319c2ee62ae249cf52a81709e8be139920d6479a66274de7c4ed","impliedFormat":1},{"version":"7274fbffbd7c9589d8d0ffba68157237afd5cecff1e99881ea3399127e60572f","impliedFormat":1},{"version":"b73cbf0a72c8800cf8f96a9acfe94f3ad32ca71342a8908b8ae484d61113f647","impliedFormat":1},{"version":"bae6dd176832f6423966647382c0d7ba9e63f8c167522f09a982f086cd4e8b23","impliedFormat":1},{"version":"20865ac316b8893c1a0cc383ccfc1801443fbcc2a7255be166cf90d03fac88c9","impliedFormat":1},{"version":"c9958eb32126a3843deedda8c22fb97024aa5d6dd588b90af2d7f2bfac540f23","impliedFormat":1},{"version":"461d0ad8ae5f2ff981778af912ba71b37a8426a33301daa00f21c6ccb27f8156","impliedFormat":1},{"version":"e927c2c13c4eaf0a7f17e6022eee8519eb29ef42c4c13a31e81a611ab8c95577","impliedFormat":1},{"version":"fcafff163ca5e66d3b87126e756e1b6dfa8c526aa9cd2a2b0a9da837d81bbd72","impliedFormat":1},{"version":"70246ad95ad8a22bdfe806cb5d383a26c0c6e58e7207ab9c431f1cb175aca657","impliedFormat":1},{"version":"f00f3aa5d64ff46e600648b55a79dcd1333458f7a10da2ed594d9f0a44b76d0b","impliedFormat":1},{"version":"772d8d5eb158b6c92412c03228bd9902ccb1457d7a705b8129814a5d1a6308fc","impliedFormat":1},{"version":"802e797bcab5663b2c9f63f51bdf67eff7c41bc64c0fd65e6da3e7941359e2f7","impliedFormat":1},{"version":"b01bd582a6e41457bc56e6f0f9de4cb17f33f5f3843a7cf8210ac9c18472fb0f","impliedFormat":1},{"version":"8b4327413e5af38cd8cb97c59f48c3c866015d5d642f28518e3a891c469f240e","impliedFormat":1},{"version":"4cceef18d7f088e797a463e90b7a9dad10c6bc667724b7686e3e740ae00122be","impliedFormat":1},{"version":"7ee86fbb3754388e004de0ef9e6505485ddfb3be7640783d6d015711c03d302d","impliedFormat":1},{"version":"cc1954b539604b1e562319119ac7e888172208b32ca873f9a357a92c826bd046","impliedFormat":1},{"version":"a67b87d0281c97dfc1197ef28dfe397fc2c865ccd41f7e32b53f647184cc7307","impliedFormat":1},{"version":"771ffb773f1ddd562492a6b9aaca648192ac3f056f0e1d997678ff97dbb6bf9b","impliedFormat":1},{"version":"43e96a3d5d1411ab40ba2f61d6a3192e58177bcf3b133a80ad2a16591611726d","impliedFormat":1},{"version":"232f70c0cf2b432f3a6e56a8dc3417103eb162292a9fd376d51a3a9ea5fbbf6f","impliedFormat":1},{"version":"bb8f2dbc03533abca2066ce4655c119bff353dd4514375beb93c08590c03e023","impliedFormat":1},{"version":"706dd95827e7ebaabda91d5db2b755233e0952d98570e9c032b0f066a15c1177","affectsGlobalScope":true,"impliedFormat":1},{"version":"0b103e9abfe82d14c0ad06a55d9f91d6747154ef7cacc73cf27ecad2bfb3afcf","impliedFormat":1},{"version":"cd9304972e6d616197fb44fce00540a904f38b54306a1951b5dbeaf3c01ab5bd","impliedFormat":1},{"version":"77438e2c397a3db78407621cfc57241a305b310ddea2c185f1d555248297f587","impliedFormat":1},{"version":"120599fd965257b1f4d0ff794bc696162832d9d8467224f4665f713a3119078b","impliedFormat":1},{"version":"43ba4f2fa8c698f5c304d21a3ef596741e8e85a810b7c1f9b692653791d8d97a","impliedFormat":1},{"version":"5433f33b0a20300cca35d2f229a7fc20b0e8477c44be2affeb21cb464af60c76","impliedFormat":1},{"version":"c49469a5349b3cc1965710b5b0f98ed6c028686aa8450bcb3796728873eb923e","impliedFormat":1},{"version":"4a889f2c763edb4d55cb624257272ac10d04a1cad2ed2948b10ed4a7fda2a428","impliedFormat":1},{"version":"7bb79aa2fead87d9d56294ef71e056487e848d7b550c9a367523ee5416c44cfa","impliedFormat":1},{"version":"d88ea80a6447d7391f52352ec97e56b52ebec934a4a4af6e2464cfd8b39c3ba8","impliedFormat":1},{"version":"142617b3cdf902b69c6464c9fbd942b60ab3e733ca18c032b19e0f7e2adbefe8","impliedFormat":1},{"version":"0b603555f1881f87256ffd6344d3e3ed6d466c2e701eabf381f28be8c2125892","impliedFormat":1},{"version":"897e4f7662488e3ecc79e743bdd3b78f13bdb69a97851afa5b440c4211e32ea9","impliedFormat":1},{"version":"e2e1c6d3b2d93add5200bd7bc1a8cccb4e446836b2111ece45db8683a2c765de","impliedFormat":1},{"version":"251b03d5cd243854ce870d9a9a39f491faf69898c5d6b5eee28cc7649c57417b","impliedFormat":1},{"version":"27ff4196654e6373c9af16b6165120e2dd2169f9ad6abb5c935af5abd8c7938c","impliedFormat":1},{"version":"2c4de79f406d137390608e8c0a44fba2ff8e00bacfcae7c9d1781fef10e9440d","impliedFormat":1},{"version":"07ba23a10465791be5d22deaf5ef7de7658774ddff53721e5ea17fedea1bc721","impliedFormat":1},{"version":"dca8c645c5afeb03b1ecedbf16323f33e7d0afaa6256c8e047e6e38087a97f53","impliedFormat":1},{"version":"775f181bd4a533d6f8b5e55ec1d9f1624559720ae8a70e9432258da26b38d27c","impliedFormat":1},{"version":"796273b2edc72e78a04e86d7c58ae94d370ab93a0ddf40b1aa85a37a1c29ecd7","impliedFormat":1},{"version":"5df15a69187d737d6d8d066e189ae4f97e41f4d53712a46b2710ff9f8563ec9f","impliedFormat":1},{"version":"7715134a0cf07dd41a9da2895d708625a3a303a0385e355ecaaf0b8bfaef2550","impliedFormat":1},{"version":"6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff","impliedFormat":1},{"version":"622694a8522b46f6310c2a9b5d2530dde1e2854cb5829354e6d1ff8f371cf469","impliedFormat":1},{"version":"cd8ce8d68567f62dd580b3c3c37777ac3f5b81944c7417f5ea83030eab533385","impliedFormat":1},{"version":"e5c939d896565dcac0f6fbdbada11284e7728ef26a069561c09aa5aa4a788393","impliedFormat":1},{"version":"9e2739b32f741859263fdba0244c194ca8e96da49b430377930b8f721d77c000","impliedFormat":1},{"version":"a9e6c0ff3f8186fccd05752cf75fc94e147c02645087ac6de5cc16403323d870","impliedFormat":1},{"version":"49af4b52f0d4d2304c5f2c6fe5fab3e153e0acc38830d0202821b877c097dd02","impliedFormat":1},{"version":"49c346823ba6d4b12278c12c977fb3a31c06b9ca719015978cb145eb86da1c61","impliedFormat":1},{"version":"bfac6e50eaa7e73bb66b7e052c38fdc8ccfc8dbde2777648642af33cf349f7f1","impliedFormat":1},{"version":"92f7c1a4da7fbfd67a2228d1687d5c2e1faa0ba865a94d3550a3941d7527a45d","impliedFormat":1},{"version":"f53b120213a9289d9a26f5af90c4c686dd71d91487a0aa5451a38366c70dc64b","impliedFormat":1},{"version":"e68b8e5a1df7c1be2bc105141456ecba70215806e1c28bfbc5c12bfce4be6e68","impliedFormat":1},{"version":"511c8f02329808d47d00b859c532ae9115590048b17325a946c74dac48428650","impliedFormat":1},{"version":"57d67b72e06059adc5e9454de26bbfe567d412b962a501d263c75c2db430f40e","impliedFormat":1},{"version":"b5f9e66625783eefcbe3d2da074b2e7ba2066d61ce3fc6ef4f22805ad946cab4","impliedFormat":1},{"version":"e37115962d284b9f7a37c2bdd2add50f88365dde41f5e0ff591ffc48a8ec7575","impliedFormat":1},{"version":"6459054aabb306821a043e02b89d54da508e3a6966601a41e71c166e4ea1474f","impliedFormat":1},{"version":"bb37588926aba35c9283fe8d46ebf4e79ffe976343105f5c6d45f282793352b2","impliedFormat":1},{"version":"f89488602bec98a142072fae7ea5ba99431a569ff580c64b7be39896474799d8","impliedFormat":1},{"version":"bbbc47961f39a57df103cf4ca3bb8f8732b4b6678a18225a0aa76d59c466956c","impliedFormat":1},{"version":"2e6114a7dd6feeef85b2c80120fdbfb59a5529c0dcc5bfa8447b6996c97a69f5","impliedFormat":1},{"version":"2ffb043dc5163458e473b7010859f86e01dc4edffcae0a93d885d028b426a546","impliedFormat":1},{"version":"c8f004e6036aa1c764ad4ec543cf89a5c1893a9535c80ef3f2b653e370de45e6","impliedFormat":1},{"version":"dd80b1e600d00f5c6a6ba23f455b84a7db121219e68f89f10552c54ba46e4dc9","impliedFormat":1},{"version":"b064c36f35de7387d71c599bfcf28875849a1dbc733e82bd26cae3d1cd060521","impliedFormat":1},{"version":"05c7280d72f3ed26f346cbe7cbbbb002fb7f15739197cbbee6ab3fd1a6cb9347","impliedFormat":1},{"version":"8de9fe97fa9e00ec00666fa77ab6e91b35d25af8ca75dabcb01e14ad3299b150","impliedFormat":1},{"version":"04b7b2e0832dfd3c31e81df3975e8d8fda28e7ff999b0aa2932608a8f6661d5c","impliedFormat":1},{"version":"ca2d34c6ed5cbd3070b8b6f32f42ae54adcc6499c1e4b99f0a5798b3f27cc653","impliedFormat":1},{"version":"9ec68995e66dd6b9dac834bf5ae85fde802714ea2e82151a5d1d53ef01b463ef","impliedFormat":1},{"version":"5c4d626b4902f2ef8a1cc146d761d276cef988016dc674e3b98fbad70e64bc9f","impliedFormat":1},{"version":"fdfaa0aad899524962e2955287b5b991ffe3be50f64e02eb60c933ca44644a94","impliedFormat":1},{"version":"53c972a0f9bc3a4ec70fff7314123ea8cfcf75b3703046f767d2dc1eea87b2fb","impliedFormat":1},{"version":"f974e4a06953682a2c15d5bd5114c0284d5abf8bc0fe4da25cb9159427b70072","impliedFormat":1},{"version":"50256e9c31318487f3752b7ac12ff365c8949953e04568009c8705db802776fb","impliedFormat":1},{"version":"7d73b24e7bf31dfb8a931ca6c4245f6bb0814dfae17e4b60c9e194a631fe5f7b","impliedFormat":1},{"version":"d130c5f73768de51402351d5dc7d1b36eaec980ca697846e53156e4ea9911476","impliedFormat":1},{"version":"413586add0cfe7369b64979d4ec2ed56c3f771c0667fbde1bf1f10063ede0b08","impliedFormat":1},{"version":"06472528e998d152375ad3bd8ebcb69ff4694fd8d2effaf60a9d9f25a37a097a","impliedFormat":1},{"version":"7303b45138d2511035056a5901a1490ebdcbf055cbb1276f8629c5121cbe733e","impliedFormat":1},{"version":"27f874cd5327507eeff699a74567f60c1215b94509f4308633a7b01922471ed2","impliedFormat":1},{"version":"a401617604fa1f6ce437b81689563dfdc377069e4c58465dbd8d16069aede0a5","impliedFormat":1},{"version":"2c6cf04bc525caf6546e859e8ef10bfb9573837ec0bc5ec7b53a7b1b8ca72781","impliedFormat":1},{"version":"8695dec09ad439b0ceef3776ea68a232e381135b516878f0901ed2ea114fd0fe","impliedFormat":1},{"version":"304b44b1e97dd4c94697c3313df89a578dca4930a104454c99863f1784a54357","impliedFormat":1},{"version":"0a437ae178f999b46b6153d79095b60c42c996bc0458c04955f1c996dc68b971","impliedFormat":1},{"version":"74b2a5e5197bd0f2e0077a1ea7c07455bbea67b87b0869d9786d55104006784f","impliedFormat":1},{"version":"4a7baeb6325920044f66c0f8e5e6f1f52e06e6d87588d837bdf44feb6f35c664","impliedFormat":1},{"version":"87cc05fe13108f02e12da7e3efd8e360fef78d96a0c9e11408ea1b1b9fb3e03d","impliedFormat":1},{"version":"1abbf67c218d23c2ce76887caac2df6c7dab3d97ba2b65348432b876f510002a","impliedFormat":1},{"version":"1a82deef4c1d39f6882f28d275cad4c01f907b9b39be9cbc472fcf2cf051e05b","impliedFormat":1},{"version":"4b20fcf10a5413680e39f5666464859fc56b1003e7dfe2405ced82371ebd49b6","impliedFormat":1},{"version":"c06ef3b2569b1c1ad99fcd7fe5fba8d466e2619da5375dfa940a94e0feea899b","impliedFormat":1},{"version":"f7d628893c9fa52ba3ab01bcb5e79191636c4331ee5667ecc6373cbccff8ae12","impliedFormat":1},{"version":"1d879125d1ec570bf04bc1f362fdbe0cb538315c7ac4bcfcdf0c1e9670846aa6","impliedFormat":1},{"version":"dad97c99382889e9c7d1a9d8275500ff71235130fae9f8916fdbf3641d56e592","impliedFormat":1},{"version":"a6dba407fc287f1e25454e75028c91bbc00675f2d1c4e8b3edcc36c08611a486","impliedFormat":1},{"version":"d663134457d8d669ae0df34eabd57028bddc04fc444c4bc04bc5215afc91e1f4","impliedFormat":1},{"version":"e91f7b1344577a02f051b9b471f33044fef8334a76dc9e1de003d17595a5219b","impliedFormat":1},{"version":"c0723195c85e19656d6b5b9fdb81d3f3403c1ae4679e722c6ea058c516b38d12","impliedFormat":1},{"version":"b55eb9f72166093b5460d34b34f5d8699c968de3bc3fc696e40f2c93f2ebf650","impliedFormat":1},{"version":"71d9eb4c4e99456b78ae182fb20a5dfc20eb1667f091dbb9335b3c017dd1c783","impliedFormat":1},{"version":"cfa846a7b7847a1d973605fbb8c91f47f3a0f0643c18ac05c47077ebc72e71c7","impliedFormat":1},{"version":"1594da19968752a22b2ac48c2d0e60575700e745c577a8a4a676b841238ad5bb","impliedFormat":1},{"version":"e0cee12109e0a10a4c3d6769fcc7644b7c1ea7f52365bea51728f5af29f8a137","impliedFormat":1},{"version":"7d4254b4c6c67a29d5e7f65e67d72540480ac2cfb041ca484847f5ae70480b62","impliedFormat":1},{"version":"3536968defef8a75514f547ead5e2e9c1e984820290ec9b00c5fdfb6ef786535","impliedFormat":1},{"version":"d83773870080c30a230e322ce13a9c6f3398e8dacea4ea8a83e26370f3bac23e","impliedFormat":1},{"version":"dcfeaf98d66314fec29a9076c4290e45d0b196a65827becc19138e9c7b855f37","impliedFormat":1},{"version":"6849fe9210fe4946d5f085bfed36758f33dc6ae15a751338d178dd4daa017c46","impliedFormat":1},{"version":"888cda0fa66d7f74e985a3f7b1af1f64b8ff03eb3d5e80d051c3cbdeb7f32ab7","impliedFormat":1},{"version":"60681e13f3545be5e9477acb752b741eae6eaf4cc01658a25ec05bff8b82a2ef","impliedFormat":1},{"version":"ffae4e1e06aa848a1e4bcef162cd1c48e5909b26223515981310af9c036bdfc7","impliedFormat":1},{"version":"a57b1802794433adec9ff3fed12aa79d671faed86c49b09e02e1ac41b4f1d33a","impliedFormat":1},{"version":"34e16eb7c31768a11a08aebcfb3d70d7b8f0b016197e98d8419e566ceae6d6c8","impliedFormat":1},{"version":"f94ec1f7e4b709d26960306c9082a7a1b728a6e13089346aa48ba57c74cbf47e","impliedFormat":1},{"version":"9a11cb4033405e96c247cd5aa29790212aaffdd127869e8a5219103f0b389fd5","impliedFormat":1},{"version":"01479d9d5a5dda16d529b91811375187f61a06e74be294a35ecce77e0b9e8d6c","impliedFormat":1},{"version":"aff5213585cb72e94054dfe17250ff315f3569b3919d1ef1ad235f37c4ee894e","impliedFormat":1},{"version":"fb2ea35e1be6388d722d7725e2b49c697d34d9c890c3b96758faaeb86d35cef8","impliedFormat":1},{"version":"ce0df82a9ae6f914ba08409d4d883983cc08e6d59eb2df02d8e4d68309e7848b","impliedFormat":1},{"version":"1a4dc28334a926d90ba6a2d811ba0ff6c22775fcc13679521f034c124269fd40","impliedFormat":1},{"version":"f05315ff85714f0b87cc0b54bcd3dde2716e5a6b99aedcc19cad02bf2403e08c","impliedFormat":1},{"version":"5fad3b31fc17a5bc58095118a8b160f5260964787c52e7eb51e3d4fcf5d4a6f0","impliedFormat":1},{"version":"72105519d0390262cf0abe84cf41c926ade0ff475d35eb21307b2f94de985778","impliedFormat":1},{"version":"456006a6975b26c0a1785feddae165f6d307e2d601ffde27e21fc4a790e448a4","impliedFormat":1},{"version":"c857e0aae3f5f444abd791ec81206020fbcc1223e187316677e026d1c1d6fe08","impliedFormat":1},{"version":"ccf6dd45b708fb74ba9ed0f2478d4eb9195c9dfef0ff83a6092fa3cf2ff53b4f","impliedFormat":1},{"version":"1fe0d18b111e1145a7e7601855bccd4ca20f24e3b9a5aba6bb1fa9d1a7059170","impliedFormat":1},{"version":"5632c3c26d420c063eebe64c45b1248b9492a67bf44f1d0c57e9dc8f6cf449bb","impliedFormat":1},{"version":"0df5aa619ab12993a39ea6dae062ee46eadbb4d738916460e636ada52bced75b","impliedFormat":1},{"version":"8fca3039857709484e5893c05c1f9126ab7451fa6c29e19bb8c2411a2e937345","impliedFormat":1},{"version":"35069c2c417bd7443ae7c7cafd1de02f665bf015479fec998985ffbbf500628c","impliedFormat":1},{"version":"10ab7be91f87ebe8916b62cf28af2e45b5601fc7b0e311adf838f912c6b31dd8","impliedFormat":1},{"version":"bc636fbc08e0979ceb7eb0731a33000283d77a33b62e1f71ee65be50394e40ba","impliedFormat":1},{"version":"7e0b7f91c5ab6e33f511efc640d36e6f933510b11be24f98836a20a2dc914c2d","impliedFormat":1},{"version":"045b752f44bf9bbdcaffd882424ab0e15cb8d11fa94e1448942e338c8ef19fba","impliedFormat":1},{"version":"2894c56cad581928bb37607810af011764a2f511f575d28c9f4af0f2ef02d1ab","impliedFormat":1},{"version":"0a72186f94215d020cb386f7dca81d7495ab6c17066eb07d0f44a5bf33c1b21a","impliedFormat":1},{"version":"75bbd3be047d539988a0ff0b56384ef7a6a25f3b676ad96bee547d44c31622a7","impliedFormat":1},{"version":"42960001a776b089ade681ab5cfddc936e0afb0615133ec1841f3dee89d3e1bf","impliedFormat":1},{"version":"0aedb02516baf3e66b2c1db9fef50666d6ed257edac0f866ea32f1aa05aa474f","impliedFormat":1},{"version":"da47712b394d944328245482603bc6f416d3949b67c9392279caab595076b510","affectsGlobalScope":true,"impliedFormat":1},{"version":"37d0071d8f0a06dc55c2c5e0ec3391affd4fd107c53410bf358196ec0bf3923f","impliedFormat":1},{"version":"b213dad76ca37fd552274c9499056e1c0d9c1bd38a55bb7f68b22ba6b84c3ad7","impliedFormat":1},{"version":"2879a055439b6c0c0132a1467120a0f85b56b5d735c973ad235acd958b1b5345","impliedFormat":1},{"version":"d90b9f1520366d713a73bd30c5a9eb0040d0fb6076aff370796bc776fd705943","impliedFormat":1},{"version":"736a8712572e21ee73337055ce15edb08142fc0f59cd5410af4466d04beff0f9","affectsGlobalScope":true,"impliedFormat":1},{"version":"bef86adb77316505c6b471da1d9b8c9e428867c2566270e8894d4d773a1c4dc2","impliedFormat":1},{"version":"5a49adaef698b7ad7e6127949fa1b0bbd3d46b7cbd11c54e392a4dcdd51f5190","impliedFormat":1},{"version":"6ee598cdfdd0fa52039dca135b3dfff7b49035dc13292143e0a93843e3861967","impliedFormat":1},{"version":"27be6622e2922a1b412eb057faa854831b95db9db5035c3f6d4b677b902ab3b7","impliedFormat":1},{"version":"5c634644d45a1b6bc7b05e71e05e52ec04f3d73d9ac85d5927f647a5f965181a","impliedFormat":1},{"version":"2489bf04d77dc025ba67f49f1a56eb24b9db477d5ff88123d887e163ed1776aa","impliedFormat":1},{"version":"63a7595a5015e65262557f883463f934904959da563b4f788306f699411e9bac","impliedFormat":1},{"version":"4ba137d6553965703b6b55fd2000b4e07ba365f8caeb0359162ad7247f9707a6","impliedFormat":1},{"version":"0b77b819b5417775fccb20c678293cf614c054a5b1a65421a5b933a9124ba998","impliedFormat":1},{"version":"eb5acb58487367e502d994b57e2c58255d8241f481ea8efa8e79af23af3f41c2","impliedFormat":1},{"version":"9252d498a77517aab5d8d4b5eb9d71e4b225bbc7123df9713e08181de63180f6","impliedFormat":1},{"version":"b1f1d57fde8247599731b24a733395c880a6561ec0c882efaaf20d7df968c5af","impliedFormat":1},{"version":"6715dc4eb59c8ea9abe2b78c235ed331dc710a06fe56798868dbc4d40cd1b707","impliedFormat":1},{"version":"35e6379c3f7cb27b111ad4c1aa69538fd8e788ab737b8ff7596a1b40e96f4f90","impliedFormat":1},{"version":"1fffe726740f9787f15b532e1dc870af3cd964dbe29e191e76121aa3dd8693f2","impliedFormat":1},{"version":"5a3ea721d03a361ccbdd7390ccd75f6e84cbca3a3f01f4b331ecc9af31890c49","impliedFormat":1},{"version":"e7dfaee4af38d45b1cab8a1ee0b3bc1f85ddcf64545ed391d675d78ae6526274","affectsGlobalScope":true,"impliedFormat":1},{"version":"e8daa443eaf9a27fd382cc1f8ebe30330c0f4d89511cfb469166874806751d35","impliedFormat":1},{"version":"af48e58339188d5737b608d41411a9c054685413d8ae88b8c1d0d9bfabdf6e7e","impliedFormat":1},{"version":"616775f16134fa9d01fc677ad3f76e68c051a056c22ab552c64cc281a9686790","impliedFormat":1},{"version":"65c24a8baa2cca1de069a0ba9fba82a173690f52d7e2d0f1f7542d59d5eb4db0","impliedFormat":1},{"version":"f9fe6af238339a0e5f7563acee3178f51db37f32a2e7c09f85273098cee7ec49","impliedFormat":1},{"version":"1de8c302fd35220d8f29dea378a4ae45199dc8ff83ca9923aca1400f2b28848a","impliedFormat":1},{"version":"77e71242e71ebf8528c5802993697878f0533db8f2299b4d36aa015bae08a79c","impliedFormat":1},{"version":"98a787be42bd92f8c2a37d7df5f13e5992da0d967fab794adbb7ee18370f9849","impliedFormat":1},{"version":"332248ee37cca52903572e66c11bef755ccc6e235835e63d3c3e60ddda3e9b93","impliedFormat":1},{"version":"94e8cc88ae2ef3d920bb3bdc369f48436db123aa2dc07f683309ad8c9968a1e1","impliedFormat":1},{"version":"4545c1a1ceca170d5d83452dd7c4994644c35cf676a671412601689d9a62da35","impliedFormat":1},{"version":"320f4091e33548b554d2214ce5fc31c96631b513dffa806e2e3a60766c8c49d9","impliedFormat":1},{"version":"a2d648d333cf67b9aeac5d81a1a379d563a8ffa91ddd61c6179f68de724260ff","impliedFormat":1},{"version":"d90d5f524de38889d1e1dbc2aeef00060d779f8688c02766ddb9ca195e4a713d","impliedFormat":1},{"version":"07ed3ddab975995eea41b22f3010506fb9f5fb301d04820b07d7a1aee5477d7c","impliedFormat":1},{"version":"969d8b0965849f4bae7cab0ba90bd1e1220e95999c2c6f01117fa7500901c017","impliedFormat":1},{"version":"6ec840ee5e2bc103f557fe38b1d585ee250540468713d7634ee066de372bf332","impliedFormat":1},{"version":"b0309e1eda99a9e76f87c18992d9c3689b0938266242835dd4611f2b69efe456","impliedFormat":1},{"version":"47699512e6d8bebf7be488182427189f999affe3addc1c87c882d36b7f2d0b0e","impliedFormat":1},{"version":"6ceb10ca57943be87ff9debe978f4ab73593c0c85ee802c051a93fc96aaf7a20","impliedFormat":1},{"version":"1de3ffe0cc28a9fe2ac761ece075826836b5a02f340b412510a59ba1d41a505a","impliedFormat":1},{"version":"e46d6cc08d243d8d0d83986f609d830991f00450fb234f5b2f861648c42dc0d8","impliedFormat":1},{"version":"1c0a98de1323051010ce5b958ad47bc1c007f7921973123c999300e2b7b0ecc0","impliedFormat":1},{"version":"ff863d17c6c659440f7c5c536e4db7762d8c2565547b2608f36b798a743606ca","impliedFormat":1},{"version":"5412ad0043cd60d1f1406fc12cb4fb987e9a734decbdd4db6f6acf71791e36fe","impliedFormat":1},{"version":"ad036a85efcd9e5b4f7dd5c1a7362c8478f9a3b6c3554654ca24a29aa850a9c5","impliedFormat":1},{"version":"fedebeae32c5cdd1a85b4e0504a01996e4a8adf3dfa72876920d3dd6e42978e7","impliedFormat":1},{"version":"e297c0a524edee7677939122f90027bfbe5f2698939d9a85728e5044b39c7124","impliedFormat":1},{"version":"cdf21eee8007e339b1b9945abf4a7b44930b1d695cc528459e68a3adc39a622e","impliedFormat":1},{"version":"1d079c37fa53e3c21ed3fa214a27507bda9991f2a41458705b19ed8c2b61173d","impliedFormat":1},{"version":"5bf5c7a44e779790d1eb54c234b668b15e34affa95e78eada73e5757f61ed76a","impliedFormat":1},{"version":"5835a6e0d7cd2738e56b671af0e561e7c1b4fb77751383672f4b009f4e161d70","impliedFormat":1},{"version":"4b7f74b772140395e7af67c4841be1ab867c11b3b82a51b1aeb692822b76c872","impliedFormat":1},{"version":"7bd01f0f28cd3aeb2046274d85208e245965f6f2948edf4f7b2057bcf9f22ccc","impliedFormat":99},{"version":"d2f2cf2b8cc92bea913cda4a076e0f790b23a21e84f989d12f0116a7fe3906e0","impliedFormat":99},{"version":"6de125ea94866c736c6d58d68eb15272cf7d1020a5b459fea1c660027eca9a90","affectsGlobalScope":true,"impliedFormat":1},{"version":"f5b20bc288ee49989c95b20847fc93b96bf61cc0845598897a6a53a967dd7d07","affectsGlobalScope":true,"impliedFormat":1},{"version":"064ac1c2ac4b2867c2ceaa74bbdce0cb6a4c16e7c31a6497097159c18f74aa7c","impliedFormat":1},{"version":"3dc14e1ab45e497e5d5e4295271d54ff689aeae00b4277979fdd10fa563540ae","impliedFormat":1},{"version":"d3b315763d91265d6b0e7e7fa93cfdb8a80ce7cdd2d9f55ba0f37a22db00bdb8","impliedFormat":1},{"version":"fe93c474ab38ac02e30e3af073412b4f92b740152cf3a751fdaee8cbea982341","impliedFormat":1},{"version":"3255b97f3f24af29c79cc1aa88004efb13b6285ebdde0a567bf32e19bb65250d","impliedFormat":1},{"version":"1e00b8bf9e3766c958218cd6144ffe08418286f89ff44ba5a2cc830c03dd22c7","impliedFormat":1},{"version":"1ad1e608b48a5eea7f1d1dd2195c56aabdb5d434ee7a6ea3e4d9bb3f7c19affb","signature":"1172a76e0f08ae2f3ee3945863e405b51be43b053879f519ceff4c565edf1c0f"},{"version":"57966149b133b2cc5be424026c5dec226936774de2993086b3db1d8396b69ca2","signature":"b0fe4ebd89323e0b58b2a06b45292ea27ed1f3a5f2bfd4dc9d7ec82367cd7095"},{"version":"31caf28f1dc08edfe3cb8e7c4011ced09f4bf3f5f725c50e5dc0b5ae573b498a","signature":"bae25bd2065e51d8f2981a602ea5c8510f947bc7d5fa9c8bb9d11573d631e38e"},{"version":"c03fe612af1138dcead8e808241a0ef89ce09eacf11ba92a7c863e164be98d61","signature":"0c6f146bf5402327aa93d97c9e263e92bb63b4d87f2af155416cc7d0490a8224"},{"version":"7afc8ef7ade1f7cb4e4ec2b5d8890649511bb0b684d870da6f25fbeed4cc4e19","signature":"a249fbe38c60a2a707d8381a5cc4246312d803f096870a9d77876f2629601662"},{"version":"ac88c093ae32ac5872660cae2d1453528a9bbac4d3d79e4d40bd0ba8dc11f96c","signature":"138cf0cf601045c4741faaf79f2d7024eca30e59e3b533a4d60e01a99209ad68"},{"version":"0f9e0759d865a9c490413b1211acbce0c29d3ca56d2437060dc1ddff96fd6fbf","signature":"335f66607c072668fef3e0563ad5619a7632e75eef9f85740e84e35523d1ee82"},{"version":"3deadea5c924d495e643f3b3d0db964bbec7b13944b048e2fba2df054f749af5","signature":"d1c85428c55ff1c7d980d04feea74240a8bef90974b07aac3d867b44f91622c6"},{"version":"0c87b5785cf95d6ac48046cd43e9e9af2e8514731e0f61583146ae698d927e4f","signature":"ab21447ef0584cf1fed179cc5df1b9b2c9d86a407e1dff3daa9168eb54366749"},{"version":"eb2ca2e825628da3e61a98cbf8338f9af1da351244346ee4955b09972d60e7c3","signature":"46b6f81029d3463673e8948a07c2b8a45d165f76cffd2e707701ce15ae7ec8ce"},{"version":"8de27f177af3aed70aa91af5df501deac7b92fc72d54cd046c4535a98c58d05c","signature":"4a5aa2e3faf50e9bbd01f52dc7d15f867f4a1b47ee49e9663455c1b05f917cc2"},{"version":"9e9fc0a89103169c53464d456f3bca79cd6fed85398d0c4be0589f91c41aca6c","signature":"410aae1dab008177682aafcfbeeb27bb71cf90e2644309d0473a9f4840d460b3"},{"version":"209209c9edea680aab9bcf39ad4ceaf9700429bf1be0babed06f14489f7a4535","signature":"0cc24adad526e7c075f3223582ca642a555751bddc0088d47a1fe62ac19ebe31"},{"version":"8bffa50dd700f040b86076c6169484967f3d5f78eac8dd5ab8d8704c9d7e7971","signature":"4db2374e885b05cca098ccddbf73603e0c45cd5d27f131f90ffeec6c35eae100"},{"version":"c5ed0796ac973137391ab9755403837f9530f73c5da866798664126c7fa94c83","signature":"b0540a7a4d0339ff0999796b3fbf590929231141c424a21ec85c6477a1e5e176"},{"version":"92d8475989f3d5c9ca06245655566fc52974ff50c29a20735e36c9e384860fc3","signature":"322124f66182b890bc15265b8af6b872b31528ea3e0bf7e431a29a349648f67f"},{"version":"e0dd3aaf08541fa0c17a605ed21d7a6ac704d19595cdb851666e80c138dd4b68","signature":"acaee283946e562a6a4f999558a47c3d5110e5e2ae0581f90b5d2d4e35dd74cf"},{"version":"4bd736647b00fce2c3a6106e09d2dcde3de3b1d579f1d4e194bc4972b1ff8b81","signature":"adcfc27e9fa8c06fe6e25e4dd89fee0a415723a55e88957a020db14a12505abc"},{"version":"299e707704e60bbe0438b5ca2af66f5a06f8d903c82fcd830959bd5b7a3c7142","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3ddde64308409c05a0448cc9dd5103816df5f7c49d707eed3ea6e7e4ab2c9193","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6943db60489e17ed68912aec658d0f893d499ac7053aeb7ed161017151897991","signature":"fdb9f15b09c3f33cbb6b0112e1c7a25d797f32511b1fd8406824a1932be39e6c"},{"version":"2fbb44a0b7b3008a7d77e6e27b803448af81671c11e58a1d48b63811f13a7158","signature":"54b1178ff1aaca40dcedcc4a7553d2c30a2ed187105f013526056f721b816f05"},{"version":"835dc5372073132e66588d9e38e54d65e9a86d191eb850f5cc92a7beb5d1b877","signature":"fbcf0ebeb72ab3ef2c5e91fdb048894dbe46d68c62fd7bc35a8c6476f6f7d6b1"},{"version":"50a97612fd2557f947fff7bc53118552d9eeae0f349814c410151cc6dc305f81","signature":"990ed5af4440089a54368245a9fb7777e60d433d416d7b6d6a2035c4225b4eb4"},{"version":"81f5e70b2efef928bafed1f1ed517b5f3d7dbe1bbae734620b1680b8cdd0bc66","signature":"56311c20d6b70677a8c70f2f96ec4fd60c25decbbc91a3eabbe2a97f70857c49"},{"version":"c0d0f32efeb6b747b535605fbc150723df43935937ad768694508546bb05cfd1","signature":"ac55b99427e8f93d864f62023f10171b091089b07e9b94cb244b45bc926ac00a"},{"version":"532241d3c3502cbf657521d1eae1d1522cd7358d39d71cb58ee1e165774efbdd","signature":"ce3c320aa064afbbdf251b452c532471d0158759a28f4f50bbf3535947492370"},{"version":"0702499aa6384244a89e13df162163ed41949de76518a8580c8044e783876dea","signature":"401f1208590180b74cc9007c8a894d499d48b469bb110c769cb004aff4819b3c"},{"version":"89d9f65ec6270b62ac2297e2b69b0d063b1903f2d7ca02d57492ad83e07cedf8","signature":"dc2305978a758b68bbd20a28ac5a6ba729a6ec2adf9e65998ec0940d397b8e25"},{"version":"5f2da296822afef222b64f2230de59c8b19264e5fbee576bcf923ef127316a59","signature":"32d1aecfc4df9ffbdf41de3abde55daa360fd375bceb4b9f0539b5095efe762e"},{"version":"0e55f17f1022c18e2b88b6fff73f9f4e15121b300a924c4093fe60270803b79e","signature":"b8df9c14d085533e16aaa58dfa061788e5dea6b8d7e3a17ace1562e6d904cd85"},{"version":"b30435ebf6c77ce2d76cbe0bfc2fcc37e5d90e36c68a712301df136549212be5","signature":"8a2590718362dc9587a8e34376a541bbc5e80c3be375550d459cd7793ba5c996"},{"version":"18981392c502332d353be793e0eee6b4b71b92c4cc159879c76c0e412b50166f","signature":"85a5f8ec84196d475ea68d0239a7ee678d96c40274f16d0e940937166e2f9fa7"},{"version":"71d0c93414fce184ed5ab15fecbade0f08f83f949788947e853a6a0827f457e4","signature":"b44400e11517ceddce8ec70b8163280b1b4ba891a19ebc5b2cc2307f291d3b88"},{"version":"abf64f5c05be5dd41016e87e5969ed28600dc0e61aadaa61a00b0c3ef4381ce1","signature":"99a6ba31404d66459a57b93db84c49b16f0690ff7fd7bb07bd04fc192f4b22dd"},{"version":"74555f271e8c624c5229628b3e6355b74e74c80a60069959e5cf0f59ce11e09a","signature":"00a5afb32489ec5937497735f6212357fd2f878a64aa57f4f0c0472d1c2bb8a1"},{"version":"c889f0134aa59775cec73110d33ee4d9987822d469760c909bf1155006199332","signature":"30e753be12067427fdac00849d0620d9f2cd7bf655a80c698ba3bd5671be8e74"},{"version":"3f443fbbf9924ab11703529cfc20eacd32d049779c198633228ba5ddcc7a1ba8","signature":"dd748d8d9eea57557a55a89f7ae5501835c4529678157498752db303f182b509"},{"version":"097c88111fa0b1df7962c1c30db8bae5dff4d0e7ac25a177f0fba84461129017","signature":"59b6b492be4b755e74f3abddc5c586cedddccd3d5dd10a4dbeb4316ec43bc7c3"},{"version":"73b5da2b12b2168d241d77c2efefa0603f96d9356f23a6853d688824ea11c58c","signature":"71e0ae0ab79469ca19dcf4d5566240e5019b5a59f0aa86a6d1b5afee5edac2b7"},{"version":"5abe717e11b3a2dcec527571b041a6df92058148ab7e9db05e514860cdbaf785","signature":"6a891a6cb7835fc4aec6da5acfe7e699a7657630100a6eb7670e371e5a4d2ea4"},{"version":"55a9358103d4d3fc812e7226cbf54ec885ab5d274c3f0fd7c7b89138761420fe","signature":"09ac449d6431aaeea979c72f664e0179ac698d66b9dd695199bcc985f21b10b7"},{"version":"d2de4e8615da1d6da4379f6a5465912982d27321becb97d61cb94d77fa73b0fa","signature":"e8e8633851ccd9b775c85fbb5a1d901b07801430ab76700f960acac4bd7be2b7"},{"version":"3cd5936c627c00a98f30a927218592a0c6b2ce52bd9365360973e8efe9502906","signature":"bc9e14f58deffbbd68bc1bfce57f57b6da9d3251b36290327106cf70252d0c20"},{"version":"d8303f53d7ebdd58c9d9bbf9dcd06aaf3216d423467dc0570895a90609218723","signature":"bec0344dd85fa103d28c0f7a321473a5f2cfe5739a0ca397422764cd6effaeb6"},{"version":"a9d69a7f6ce87d0a295384864fcc7a18cfabcebda2c56f623cd9c728a4c16b9b","signature":"75d79958804ca5a6d738975354f408d4cdbbf0d11c43e4f6d8ad7418d8a2c06c"},{"version":"4e3cef7add4741ef800199b5d9f6f45f3b05b4cbd7b4f9713b680370488856b2","signature":"1ff78963c39443a6899be8b64a99935479779596df02b6ac250b9a164d1ef962"},{"version":"c8ee1e94a242afe1bf0badbe1c104a19f7beef6c2813885168aa9480fbbfb617","signature":"0cdee9fa8afd67592beafd0b7c16e5dfbf1dbc95ac37bcd40a25f67fd4283fd4"},{"version":"2814cc3ada0566ab3ff2381f16f181d036a6ed5c840a9746e12fe4c60b890d29","signature":"536b9131c74c18137261bc2890cda45edf43d51c92fa96fdf5490f80d57c111a"},{"version":"dd893122f52f093bed0e313c60387de819fdd40dde8526ccd542782aad7c28a1","signature":"7f3eb2ab3a52fee353398658cc6cf5eb9f25517d0fb04f928ff743d0fb1fe8c4"},{"version":"7d8ca54716e502790d05cac18eb1e38b6feca001d51da11985f8deb4bc5615ad","signature":"de507d97c27553e4bf35cb8c2bc772fed8687c5538104274cccc9da99aca21c4"},{"version":"74458c6cb8c657a8c32e3fab9e230f71ad697a589b19d78a80941db515bc0af9","signature":"6f56b672249984c6df614b88092538c1086584d913c0b2dae14829f11d7d18a8"},{"version":"078f581084a5d49ebc4bd8ef870414e4647a374051acc46f900e13ad4de0351b","signature":"c5ac587c457088e29a96e148770f8bb6b55738c7bf678956a922877b2f80c226"},{"version":"4afcf731b783a79a27a8d8882d81c4803eccf82c3da968d7d6bd000dec1bb788","signature":"178945bd938cd23e41a3bc633a3c4646f8f5d4891baf31ef66c66ba89aab7aee"},{"version":"3f7f66fc428e37be13c878e7c9165386c703b3c6325f9338d2aed4744bfca26d","signature":"e41be35477d7ffa9f719088ab8bea2bc4bfc86cadc12033d1651903315793c97"},{"version":"c9e97ab16e1b931d6dafb5334e09d0b8f0687df5951b9b5e6ebcb7b959340c5c","signature":"357ff7fa54786ee278632faa1700abe9d222ccdf4a96507de70da3638f97889e"},{"version":"89faf332a5c1dd70c3f6f551c124cca9b66704d83c18763c307726045089ec7d","signature":"b3441ea2d656463bf47dd1981ee9964b8b76f7afb7a1a19b4c071902ce6b2074"},{"version":"da0f84fcd93700b4a5fbf9c6f166a6cc19fc798231bff56dd1e3875bfc6966eb","impliedFormat":1},{"version":"634ff08e0143bec98401c737de7bfc6883bfec09200bd3806d2a4cfc79c62aaa","impliedFormat":1},{"version":"90a86863e3a57143c50fec5129d844ec12cef8fe44d120e56650ed51a6ce9867","impliedFormat":1},{"version":"472c0a98c5de98b8f5206132c941b052f5cc1ae78860cb8712ac4f1ebf4550ca","impliedFormat":1},{"version":"538c4903ef9f8df7d84c6cf2e065d589a2532d152fa44105c7093a606393b814","impliedFormat":1},{"version":"cfcb6acbb793a78b20899e6537c010bfbbf939c77471abcdc2a41faf9682ca1a","impliedFormat":1},{"version":"a7798e86de8e76844f774f8e0e338149893789cdc08970381f0ae78c86e8667f","impliedFormat":1},{"version":"eebc21bb922816f92302a1f9dcefc938e74d4af8c0a111b2a52519d7e25d4868","impliedFormat":1},{"version":"6b359d3c3138a9f4d3a9c9a8fda24be6fd15bd789e692252b53e68ce99db8edc","impliedFormat":1},{"version":"9488b648a6a4146b26c0fd4e85984f617056293092a89861f5259a69be16ca5c","impliedFormat":1},{"version":"e156513655462b5811a8f980e32ccd204c19042f8c9756430fe4e8d6f7c1326e","impliedFormat":1},{"version":"5679b694d138b8c4b3d56c9b1210f903c6b0ca2b5e7f1682a2dd41a6c955f094","impliedFormat":1},{"version":"ca8da035b76fb0136d2c1390dda650b7979202dbe0f5dc7eaefcde1c76dee4f4","impliedFormat":1},{"version":"4b1022a607444684abeee6537e4cace97263d1ef047c31b012c41fdc15838a79","impliedFormat":1},{"version":"dd0271250f1e4314e52d7e0da9f3b25a708827f8a43ceff847a2a5e3fd3283e8","affectsGlobalScope":true,"impliedFormat":1},{"version":"47971d8a8639a2a2dd684091c6e7660ec5909fed540c4479ca24e22ac237194e","affectsGlobalScope":true,"impliedFormat":1},{"version":"e1075312b07671ef1cbf46409a0fa2eb2b90bb59c6215c94f0e530113013eeda","impliedFormat":1},{"version":"1bfd63c3f3749c5dc925bb0c05f229f9a376b8d3f8173d0e01901c08202caf6f","impliedFormat":1},{"version":"da850b4fdbabdd528f8b9c2784c5ba3b3bedc4e2e1e34dcd08b6407f9ec61a25","impliedFormat":1},{"version":"e61c918bb5f4a39b795a06e22bc4d44befcefd22f6a5c8a732c9ed0b565a6128","impliedFormat":1},{"version":"ee56351989b0e6f31fd35c9048e222146ced0aac68c64ce2e034f7c881327d6d","impliedFormat":1},{"version":"f58b2f1c8f4bcf519377d39f9555631b6507977ad2f4d8b73ac04622716dc925","impliedFormat":1},{"version":"4c805d3d1228c73877e7550afd8b881d89d9bc0c6b73c88940cffcdd2931b1f6","impliedFormat":1},{"version":"4aa74b4bc57c535815ae004550c59a953c8f8c3c61418ac47a7dcfefba76d1ba","impliedFormat":1},{"version":"78b17ceb133d95df989a1e073891259b54c968f71f416cd76185308af4f9a185","impliedFormat":1},{"version":"d76e5d04d111581b97e0aa35de3063022d20d572f22f388d3846a73f6ce0b788","impliedFormat":1},{"version":"0a53bb48eba6e9f5a56e3b85529fbbe786d96e84871579d10593d4f3ae0f9dba","impliedFormat":1},{"version":"d34fb8b0a66f0a406c7ce63a36f16dda7ff4500b11b0bd30a491aa0d59336d1f","impliedFormat":1},{"version":"282b31893b18a06114e5173f775dd085597ca220d183b8bd474d21846c048334","impliedFormat":1},{"version":"ed27d5ce258f069acf0036471d1fbb56b4cb3c16d7401b52a51297eca651db62","impliedFormat":1},{"version":"ec203a515afd88589bf1d384535024f5b90ebe6b5c416fb3dcca0abd428a8ba4","impliedFormat":1},{"version":"32a2a1374b57f0744d284ca93b477bd97825922513a24dfe262cbf3497377d96","impliedFormat":1},{"version":"a8b60d24dc1eb26c0e987f9461c893744339a7f48e4496f8077f258a644cffab","impliedFormat":1},{"version":"3f9df27a77a23d69088e369b42af5f95bcb3e605e6b5c2395f0bfcd82045e051","affectsGlobalScope":true,"impliedFormat":1},{"version":"9fd080a9458c6d6f3eb6d4e2b12a3ec498d7d219863e9dca0646bdee9acce875","impliedFormat":1},{"version":"e5d31928bee2ba0e72aeb858881891f8948326e4f91823028d0aea5c6f9e7564","affectsGlobalScope":true,"impliedFormat":1},{"version":"9a9ba9f6fd097bb2f57d68da8a39403bbe4dc818b8ccd155a780e4e23fa556f2","impliedFormat":1},{"version":"e50c4cd1f5cbce3e74c19a5bbf503c460e6ae86597e6d648a98c7f6c90b596dd","impliedFormat":1},{"version":"fa140f881e20591ce163039a7968b54c5e51c11228708b4f9147473d06471cf5","affectsGlobalScope":true,"impliedFormat":1},{"version":"295eca0c47be1191690fd2fe588195fff9d4dc43852aceb8b4cab2aa634579f0","impliedFormat":1},{"version":"59ee7346e19b0050508a592702871dc943083c6dcb69a47d52e888115d840781","impliedFormat":1},{"version":"067712491fb2094c212c733dd8e2d56e74c309a9ce9dac9e919286b7245a1eb4","impliedFormat":1},{"version":"a5eae58ac55bd30c42359e4b01fb2be5eddac336869d3f04ffb4daa54b58f009","impliedFormat":1},{"version":"d12d691ef8933e8db39f2ca81d6973940ff5e37bb421752f5b6e7bc15dea3abf","impliedFormat":1},{"version":"4c5f8bd9b3a1aae4e4fddfee41667e495a045f73ed603993038fa6a8ba92fa14","impliedFormat":1},{"version":"dfb274ab0f319cf18ce7152067c25f984c7fd1924fc72b3f66734588444c934a","impliedFormat":1},{"version":"108c8c05cbc3fbbbd4ff4fc0779c9bef55655c28528eb0f77829795dc9f0b484","impliedFormat":1},{"version":"a7e5444d24cdec45f113f4fb8a687e1c83a5d30c55d2da19a04be71108ad77bd","impliedFormat":1},{"version":"41ec17e218b7358fcff25c719bc419fec8ec98f13e561b9a33b07392d4fec24c","impliedFormat":1},{"version":"23c204326746e981e02d7f0a15ab6f8015f9035998cb3766c9ddbf8ea247aea2","impliedFormat":1},{"version":"25f994b5d76ce6a3186a3319555bbba79706dac2174019915c39ac6080e98c7e","impliedFormat":1},{"version":"dfa4e2c6a612d43851ccbc499598cb006a3a78bc8c7f972c52078f862fa84e47","impliedFormat":1},{"version":"02c1705fa902f172be6e9020d74bcd92ce5db8d2ef3e1b03aabc2ac8eb46c3db","impliedFormat":1},{"version":"99d2d8a0c7bb3dd77459552269a7b5865fa912cedab69db686d40d2586b551f7","impliedFormat":1},{"version":"b47abe58626d76d258472b1d5f76752dd29efe681545f32698db84e7f83517df","impliedFormat":1},{"version":"3a99bbbbbf42e45c3d203e7c74f1319b79f9821c5e5f3cdd03249184d3e003ce","impliedFormat":1},{"version":"aaacc0e12ab4de27bdf131f666e315d8e60abec26c7f87501e0a7806fc824ae6","impliedFormat":1},{"version":"3b4195afd41a9215afc7be0820f8083f6bd2e85e5e0b45bb0061fb041944711e","impliedFormat":1},{"version":"108df8095f5e25d7189dd0d1433ac2df75ec40c779d8faf7d2670f1485beb643","impliedFormat":1},{"version":"ddd3c1d3c9ff67140191a3cf49b09875e20f28f2fc5535ae5ea16e14293a989b","impliedFormat":1},{"version":"7b496e53d5f7e1737adcb5610516476ee055bf547918797348f245c68e7418fe","impliedFormat":1},{"version":"577f44389d7faedd7fc9c0330caf73140e5d0d5f6c968210bff78be569f398a7","impliedFormat":1},{"version":"3046c57724587a59bceefadd30040d418e9df81b9f3cfd680618a3511302ed7a","impliedFormat":1},{"version":"15ccc911ed15397e838471bfe6d476c28deffe976c05cb057e6b1ea7491242c2","impliedFormat":1},{"version":"64b5a5ebdaead77a9a564aa938f4fb7a45e27cda7441d3bee8c9de8a4df5a04f","impliedFormat":1},{"version":"a48037f7af5f80df8973db5e562e17566407541de284b8dadf1879ea3aed8a2f","impliedFormat":1},{"version":"dab97d96ce986857150db03f0d435b44c060d126b4a387c7807f4e9f6c92e531","impliedFormat":1},{"version":"85f39366ea7bc5e34b596fc97de18a7e377856755e789d8e931054f2191d9b8b","impliedFormat":1},{"version":"daf3ea3d49f6e8a2fa70b7ca1f21bd97f1b65021b31fbfccb73dd55f86abb792","impliedFormat":1},{"version":"b15bd260805f9dd06cd4b2b741057209994823942c5696fd835e8a04fb4aab6b","impliedFormat":1},{"version":"6635a824edf99ed52dbd3502d5bce35990c3ed5e2ec5cef88229df8ac0c52b06","impliedFormat":1},{"version":"d6577effa37aae713c34363b7cc4c84851cbabe399882c60e2b70bcbb02bfa01","impliedFormat":1},{"version":"8eaf80ad438890fe5880c39a7bbf2c998ce7d29d4c14dd56d82db63bd871eefb","impliedFormat":1},{"version":"9b3e7f776f312c76ac67e1060e5398d7ac2c69d6a3a928a9daaae2eb05b15f56","impliedFormat":1},{"version":"202042eccb4789b7dee51ba9ecab0b854834ea5c1d6a3946504bfc733d4468c3","impliedFormat":1},{"version":"2b2ef76a9f36094b07ee6f76a5ac6903f2f65c0a20283201814a8d1e752cb592","impliedFormat":1},{"version":"8882e4e087d0bc8cc713cb3d8090c45d33e373e6f5c83e0f8d00fe6a950ef875","impliedFormat":1},{"version":"239bf5ecab7a3e2b5aada92cd7ddbde7f5203668df4a6be6370de467673c0afe","signature":"8bf9fd0097958cd3603d8aec536503bfd3b2738111f439e3faf7c1f49454a18a"},{"version":"4658529914e4785a4ce0213e8cc9af4f2cc86adbc3bef7cb6e9836ca17d8f0fb","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6500eb6b035d7cf336521bfbfd879f50e35037c06d98b1a19cbe5b2c0da63382","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"416a7b9ef1ee628461297313abb875a7747dfc26d9757902caa3c57527d0a15d","signature":"0774366c811ec1c799b0c0922d3a58dd6e81ab902ff9847ef804b1cda0b16cd4"},{"version":"6544a9680839140f348bfda1025386a508ffed8c8039eaaacca135402cf1449e","signature":"d621400249ba8e7421459928f59ca558d53c61417f4d07833f994947592fba99"},{"version":"a51a99c6f12fbd275b7d38f75659f78339793baa8ccaf0dc60a6b3509b307384","signature":"031f80190948b8a395721dbf882796ff5f71390be85d950e6796e851316f59d7"},{"version":"9d6489481686e1d4b12b9063bece5327681251df9aaf6b4815841da91f0c76ff","signature":"4d981d6aa5d8dc5af9b343ecb2c5f4c5a9e1e9890c31158e049df1c31bbf7a72"},{"version":"5da85146f8149cf43a0473f278bda54ec9063f977dacaa43ca157e251399a5ab","signature":"6ff5a08113fb520f023cd78f8c7151bcccc3824aa41cc276419d8f031e790082"},{"version":"9c34fb27be6c13b706eac1d1200745b6e8843d4ec3f0e76f34db7f3e62320f44","signature":"dbde942ed04200173975b3aba7a4b95d3d29638118f4ee6cf3c12bc5c6aae7a4"},{"version":"ef89e15381725b2dec9ad150a75b5ac071ed8d2a67429432cdf996bf6b7dcd49","signature":"2d40ed5b22e817c315e2d541bd1583648872728ce3e1cf92636778fcdcbf78db"},{"version":"47dcc1c11566410ba7ff49baf3ee84445d2c552e90371b147a8cf7608f125d7d","signature":"7a62fccc87f6097e7aef8373169218fe17cec1f7de472cf07a7234b4b298fe94"},{"version":"1e90e0336b6a315bd3241c1ccce81216caaf4fb927dd103a45cf395c15d42b57","signature":"8d8a8295107e2834f955762ff110f8f87cec9211e37d5de2be000e4593fc5af7"},{"version":"f15212ead0a0cbdca75bb858d26ef06276f07891d0ef5469f3712de626379b93","signature":"5d504d7753f7c784bb3aa32ee67d6cccf890afa51afe0058d84acc63c7295e11"},{"version":"4037e9d672f86620f30517ab16631866b429c208b0b997f16e10b9d265e0eadc","signature":"79f1f1e9f52a7c07246a9084b3e5bb6af722523a8325ebf02a0daec62b773448"},{"version":"790d61ef88b26fc99e4fdbbd54f1aa54de701a2d2f036c7791fefae21f0a610a","signature":"d809793dd927943844394da81f4a73e4f930288f6ce94d44008c838d422a0db0"},{"version":"a0396e5824a35489d860bfd826b15a87c25a45be943dda43e179db81d1fe221a","signature":"4c66d74ef56464f8dff370e32297186663f98e047d7b18fe5b797b5d8f37da8b"},{"version":"492f0ee2b81dab625369473c3a11dc3a5eb03d288f868a0c3f60bf693b35a676","signature":"411c558ddfdef650e590b3eee15829ff8efc820b6456dde9bacdaf8a19b9385e"},{"version":"9e9a4eb8b61b2e816448f43ceb9cb812557aaf023a5b3d6314481ac7c3eb7530","signature":"945db498071dcdc8b7c6f3ebe1aa3923f8daf684d84ab5af621f5f4d127ec5ca"},{"version":"44bb029cda827025d0d15cced0419a884118891ed406587d6f546c5353f07d98","signature":"77c82107fe9fa152910d013c7b5a61554b3f8d6919fb29324be04fedc91aa46f"},{"version":"6c91b6e82d59e349467a2e413f1965c6eb48f2f472819ca2dea835170dca6ca0","signature":"b5bc39afe68fa495c62c44293ca5aea585738d8d3bbbf375483ab8e587528b23"},{"version":"31ddb5a9b1cedf7fbc56fff75a97bc504b96f4df2204c7c45afa3150115c39aa","signature":"2a499f5a9196f0306f744c20a49e4b172c69713ec3234a59f168c686b12d9520"},{"version":"3d7b9603ccdd03dc6cbefa8b324da7dbfee3c9d19590d58231ae3b9e86deaa98","signature":"533c37afc84f4a66e5d320a3d8bd4d8fa4d7756da0712e42abc776962f08ce84"},{"version":"d83b448c9e3e5fb2bd90e06a3fbb4d7c1f964ef2483dda730a2304108be5281d","signature":"14b994430a17c83325fae751d73b4b91dc638fb2a13b138935416812efc5b08f"},{"version":"11f851e9c4b1e64a497d121244c2cd1ee65c4c23a4f8ede0ad5cf5255082292a","signature":"063588b80e4ea3380df2cec9c15c99d4e442075aa4daee65f899da35791ea7ca"},{"version":"b1c005bf848730a351c5c28c0367ad69e166f4c86a3ecc05dceca8bb6c69cd52","signature":"7a556bdb2f531ed1a37a59882388b506096e10668b8a6aac5e1a43e41cfc06d6"},{"version":"be5c4cb1753e91076028a8949b7109c3a89f42d41ae3f0f175173a21dff7426b","signature":"12848da39546517140b9f4b17b7d0c9a9d91657225c839afe34bf0549aead842"},{"version":"ec86ac1cbde3f168149cfbc7d48e171324f27cb23c190a712645d0884d552220","signature":"02ac97652b56323526d333a584057e5553eb421cafcea8cc6236316990b035a8"},{"version":"f757059586ca80970656ea29588fab3eccbb9912044c6a5799235bf8810bb631","signature":"a75023d9e41a78a7e453afe6a8e21f944e62fabe49a1ba7e4e9b867bbf5d280e"},{"version":"e74f882de5b1045e70b19755a03b730aa0979aa832a51c534cf1b67971041eac","signature":"9633c6dfdba9dbba498db27ef9b4d4dd2afc71db9920cbf4a0452f566a613258"},{"version":"4e0a508778cefa4898e9c3bfea431db8f6b9bda9d7e9344d06aa2c668522f5b6","signature":"8049b6108a6e0e9f55ca2d0d061fe0121bdeb1a8c5b9240cc3da7d2cdd1e74a4"},{"version":"3ef9b9fe1153fd2e3cbf74af49db14a0e17caf398939209852281a2d37a6e039","signature":"401545b2fa7c40a45ec19cf00926addea4987c95c7c1e8943270774c876b68e9"},{"version":"57e296db3838d52339d4aa0bd95f40120b4fe1b514b251911b626ba6b47a477a","signature":"0fc0c1e35e42c295ddd822600742ad7b6d8469daa236585225e2cb4416abc996"},{"version":"5e170a15b314caa0e3897d5fbe595c723c406b9b804c21ee5150356938ef174a","signature":"6814a0599ce7feb30613525fd5aab4b0edd97beeec5d9be766024892b074883a"},{"version":"cf504bc822046231b283b42aa1a4faa7cd952c9e0ee392719bea072c4aba5514","signature":"909e2071058d2a069786efc55c3ca0644ce038623869fb7e97a912d65921d77e"},{"version":"071f3deb2c96ba5dd81668fcf4f909d6402b64c0c053846ac9d2aa561a136b03","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cc39c13ff8d9df5c8ef59f0e717e3b5b4e96f7dc05859a531f1f703718d4192f","signature":"faf77a35be7f5648e12ab952b900ee69526103333b3d7b86498adf346a207d89"},{"version":"bd1a9517733ab7c67709b9030af160d659b5285abb81d1399871b3d4ab6b0bce","signature":"a3bbd087770ec8da617bd5aff121de0c9cf9d0349332fe5f7745514c9c493ec6"},{"version":"caa19c07e2136777d963a0a9524abc2cf22af3a0e8da9dd939ddfc3fbdc2ffe3","signature":"be1b859329d61b0f74fe1393fd56c1542d1807ddf5a035b27a7153c471f53e32"},{"version":"7735162c45b2819ac4b735b8e2326caf71177e785dd2cc25c4984b3a904145d9","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"54a679711ac37f6cc5ed4e16610fa49191127e35225593ef9babe912a72d773a","signature":"5b09eaef203954c253a646fea5d827882c557488a4ec3fd8cc50493e9ac5ef4b"},{"version":"30f2c7a70d0fd0363f30b0e55bf89f2c04e90fa501585e1af3d406e76d2ef303","signature":"da2c86818b2628998aaeb7093e18386535412b90d6482e6e55e57bb9826f067c"},{"version":"c218f8601e1bca97803a7a3f88d6dc522d6ae5a6e118b40a243a40c1038754cf","signature":"6d6e3b1d30af0c368c85a34df9c95d2f1318f7080ef2e5749aa4bdaf637f073f"},{"version":"2c93f2b498960067914e1152268bd72dc39d44c4eee922535c6151da1a6b0c2c","signature":"7350f43a093be766aba20830ce8da6d5e1196d3bc17184977283e038cd281fbd"},{"version":"cf5e9045189489050d69af59709addc83565b16d14144d69a03056b9f0c7f939","signature":"311e004a849383cdcdf5bc484d374e5c55b8494a7a0b86f08ae78a9aa7cd0871"},{"version":"22ed0fda8041c0562e9a65b935f503599c568d84cef71a95f67c027da64431a4","signature":"b77f832192160295ab2d1946a77f431f71cba0625eb52cf617c3e711b487a24b"},{"version":"d263f4d83c384654db3c189d9c53513fb69058748e0d7f5d3f0433ddd80b45c1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6f41ba1485ed154a23dba9ed63ee3fc33532f529eeeb0f1c3fb12ac4a40eba2b","signature":"ac64a066fc27b1687ea0777aaf98076ea0dffc4a2a3f6cd5412368dd9cae7562"},{"version":"f1d4563a4b1767dc0eb821a44609484863ac408dd989d73295ce6050c8fcb203","signature":"abe72455f516e18ed06bbb7e01ea1450572ff48cd86d4153c5853474dae5e8c1"},{"version":"61bcf60d4a962169fa70c92624cd3834b1584222b16b2755311fb209a9cbfed1","signature":"4ae59f31cbb1d8f65520a2852714b43fa800e651ddf50dc1a3e68c56d6537f9c"},{"version":"b4639e81e7c4f8024fb65fd2fb30f3f9016135bda7e7166fc611b97c4fe7c5cb","signature":"654865d2998e7e7aa50e64fba9f1dcd717a7f378ee65b7e40311035c011e91ae"},{"version":"f023c0ec02678d705c269b03215ab1c0c13e2c6ad552000d4ba10a30fc072543","signature":"5c8b9555d016a2af98ade9db7e0e0943a13bf4ceb12444e7a2fc12d57c61621b"},{"version":"ebe12ac9f54879bce92c74a322a00dfa3ec3acb6e85a515b13ef97b95d268b41","signature":"11765fbe5109442f4b394444d28cb7bc392c1242fcdbe042f484a033de1bd762"},{"version":"1e2a1b8c972e3866deef2fab690c1d4211a5fe50f3207806990191f0dccf95cc","signature":"b79d4edff2b414e35a3bd893e38505d7b8fee3cc678f7c8c06d3ded65ec13913"},{"version":"68a15e8b09cb0a75983c0209cd657a42386be4d7ac01f264b9aeb40896ed49f2","signature":"868c432b61889f1028f7b0d5ac70541268dd34a158b7def3d464c0ebc5a0306b"},{"version":"6e04f8b03ea72bae19ccdfe3e901a7d5182c0f3b023b97b8955fb5161f876087","signature":"af5df0ec94e1b585b6f359b0bae4899299520d3f246a8c1fc00791d8f34900f7"},{"version":"578015da0ecf6fb49aaf4d86e90e8ce9f46a7b6ac293ca9d810a291d42501841","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3f3d2aada46728776c4bc528db2a81024caa76b63e6afd102ad8edc53c4ec170","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"fd3f52f94cdfc786f71dcddcab8c03055972b2ff324e0179d4437687977a478c","signature":"d88a3aba0e92a8eb13e01eb920af5a46d9a6d22c43a4a6dc8c7a4d93736beb56"},{"version":"8ff023c206b3b51592b2615082b0660b64869cbb2700f592d2dc81af2ecba0c8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c3dd41729dbaa3701aaeb57ff9f365ea1790ab4a7155311378d5ffa968c01a7a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3dcf3a02e2278643f961d919fc834cace521b1f339c8d5a9195af491118c60ee","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c81f4dfd6219a3a1ccaf2d0b9957edcd10e2a1fe888c95025d964a9c5b75bea6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a4a9601ce3aa37568e5fab2a24ade7e83a4c39f17cb49bdcd4e8b346c0c34a77","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"76ef6c6e3bc9884b324f31d2f256b54550210ad2682f7cca80b5c5ef0c1749bc","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7daa994fb67d50371da033a2e88fc46a09a2216623f2958d9cbff761a14d936a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9fc1cb3ad0c8acf8d749476abadc977c8f8449b45a16bd045a41803c37f1e236","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7133fda9a3c02f29d644254d3e585451ce26a7dda79cb3a744bd018c4f38fce8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2bd59323b45d43ed60764e4306339bcd9f078207ea769dfdfe99ac59d0ea0b98","signature":"1af3e359f2c3a3b25e6cf0532c9b12b27c8ade0e7eb582007452ce06db289d9b"},{"version":"8a97d6c8b72f9fc66b1281d1ca235736958e8327b9fcd7f2065957218e474f86","signature":"bf3ca96bc59503b3214f0618af79741f5a28de7d7ac663c13578af8a12fdc385"},{"version":"4b1818ea1c348f92ed8efe1c7ae76e2d87ae6ab15057ad011eb97899f8e929a4","signature":"dbd0c498b5edc07924a5f7ebf0ae90efb9436dc2229792eaf175e16c03248f98"},{"version":"5bbf60bf8b06a1e76352363c76743b0e96bc0e917f26bf8540162d32bbc5fc14","signature":"800a7b9dc46ee24c46db2afa893e0bcf0b8c8bb1bd9b8db5361e34fa3c2aad18"},{"version":"ed0c7c8654bd978cdf57d19918154e62b23e5e4b8db2cc68956fe6f2c8ed7bd0","signature":"b7b254f81d9a367bcf98769a957c2f8dfef2267573a862b52ee2748647af39e1"},{"version":"c3bf0f8d509184626a138b1d4dd67202d5a7fcb7dc570903892a3e84d76ff6cc","signature":"d949e121e0b71673df6140eda41341316408ac47555ba72b02b4abd3f0b6bc3e"},{"version":"5fbe83c9492d80ec1cf1aa1983a4e341f25a70a8088ffb5e9f559fedd56a5f3c","signature":"7bb19ff78f5dad94999cc2e0debb65a5ef8812b4507a7652b0b3fc455a9e8ddb"},{"version":"46b8af0331df242732a621f51281b06fda610781d324d4cc2862caaea51ee48f","signature":"d96bc2df413362d899c2a26a8be1fbf15d38eb758a9d65112d7eee95611f0bb4"},{"version":"96c379a249fcc96f61e4615014136d2f677c4555c064085f2698993d2cb1e8f5","signature":"ca0d4f9fee10dac6884f9cbd977eeb4e27683344cac0c99bc23e901e7d0babb9"},{"version":"c8ddb3957f36556bee0f129d66c53ea37972ebc4f122d9bdb95a50ef61992fec","signature":"96a7977f7405149cb2a3637eabba9eeec8fea99592f0e5e037efbd74d67ff9d2"},{"version":"311de2e1b699cade870a4a538ba3f485bdb749024308f00da269d347de78d017","signature":"512843e9d917c0a57276d58b2e060897baa591256abbc441228fa24f003b3539"},{"version":"ad98755cebe0206ee29ed0a9954f495df0e632c38d434c5e336ea5f8c314316c","signature":"7d52f0155efb4fbfbeb7a71bb9437c364c94315acf276096ea28168fc24aeb80"},{"version":"bc2e91fde80f675bbfd633f6ce2ef85bf1fe1ed7c66953849b62698c7ba7574f","signature":"58a633a5995b70987959aee4115c9d2c0137c014053d64c92c2e2d03785333e1"},{"version":"77a7d0ded96cb55f19efed7a33b7e8770725553e6b10696cce8d22270747a2e6","signature":"d22b2ed965a6ef70592065bc5e129d113648ff38efe84b3393591b802d92726a"},{"version":"e45929cd6ad09870977900120ba0a8ee288df77430d6632fbf385dc956360a71","signature":"9a1fa87e956dd9e945a728ecbf0bcdf59b5bc35bd4ab98618c8f51fe319ae756"},{"version":"c9c0b455f5cb19237cb638bcd411e12d8da2395780d0fac6b21a3b342d3f9264","signature":"d3fcd7e5c042241fda26edb5e44b7987838092f2c30b9fcfd5fde8cc5af1b958"},{"version":"7a9e98b5e6a9cfb9020c7b7bdf1a859bf56331d3bcf6daaf7a1d1b5c16dca76b","signature":"29e0a461350da6f5c7ac57339ad2e541e5492c422f6ecc2f66413b4a741a1e1e"},{"version":"6dd2ce1011ceedc6c5701ce9be652e6081256473012998175e389eea39174884","signature":"66aebe870c5c940805b59e3ed00f2e366eb0633ee94f069f4d3147e4b052693c"},{"version":"af8a2ab913d22ceb1a6c51d29c315941eb6fe950a24eaa871b6af91586b32fca","signature":"4a26aafff5702c778bf7349914063554cabacbf4b74ba530aea9fd2b5c060e1b"},{"version":"30137406242997f2c452f99ccfcc257a339f3deeae31036cb7530e4f6f898ea7","signature":"e5d7539a72d07ef9c3d686776f885111a063fc63c60c975d027b6b643970a358"},{"version":"892cc2bd897c6473aba0101a74d045be5a74d3936768c2650ab00046ea8353c7","signature":"af8541ad25caf543ae81e642a69d481f7bb2d0b642df88c46a1fe8910626a935"},{"version":"3880037de3f3d703a5d4b40607696c16ea82fe724165a5fe1b46bea7f81b106a","signature":"562105feb1d69fc9516ca37ffc5e5af73d1339f62eaac2693c4856b3a06f1a21"},{"version":"2d3ff82a2827be5efbe37a13edd32b9b4eaae83a8d5efd3f3befe219ade82f8f","signature":"8cc1e098f03dc6645371b775e7cdb7f6eb24ce76942b98c5d2d43919ddead4c4"},{"version":"149bd8fece4a9d4e6003f4d2fab64212feef825cd6a2bc7ecdba2657dd803f39","signature":"56cce191669f569e4f1eadfd36e7a99bc9958d88a7533e25ac26c0b8e6236e6d"},{"version":"5ce3756f05ca0810e17437fb6763cefc74938c8c1b2b25fe51dbf04c24d8ac92","signature":"f7592b9eb1b3d9d1583aec0153fe74f9970f47d18bc1aac8f9d4d9e1783de183"},{"version":"afa5261372f697ceec606191408cb08ef759a3d3c3eced2d1054048244ad7647","signature":"137de1e22724e42c5f197f61b17ec1264467852dd37b27811c311c97d705c138"},{"version":"08668719eaa802b11c18d6c00c66498d42c3132cd71d3df7504df31014d6daf8","signature":"dba53de0cd1e77ba275a61f6203783f10a2b35ff248306fbd6d9689303d50f03"},{"version":"0f59b150e306de736e08d3f5b2e138beecaff95df79f61faf7449f4938f21b06","signature":"f19a0c7e1142fc0502d9e0014961ee6a6fe8b9fc26c4602a72aea8c904f15349"},{"version":"46ca644ebcf087df43cefe5642ff2bcc5dfe44f17cc5e6d43faac563ca7e0875","signature":"bc100a6821798c9203c229f4f702ed13caf45a78529be319523cf8101b0e69e2"},{"version":"b541bc9abb7b3d39bf07d4fa1e7e608b356217b721cd48d18e78ef18199bb071","signature":"72d589144cb568b0b803e59532ac2df4c04998cc89d175d259815ce6a1acd5ec"},{"version":"3ad2b70e54edef2c3d3ba3a1aa107682c8ea6953200a0c7ed23ce3160a4752be","signature":"c06a0af398fbcda321340eec8b267d723380c145b7713ee1a16643e09a4711f0"},{"version":"14ab87e343c248918c0104c3c489dadce4967ea23fb6b70787ba3ff749d2df01","signature":"73256b8ea6edc26ff0d24122c1db849a3b10757cd78aa5d658daad533529919e"},{"version":"55f57b1b0faadcef5da97aec5d9c3dccc94b9f56e0cedb1a18ce5a8811e7249b","signature":"5689535a15d03e0a240802149a23706b8be75dc050ae0be9de884bc7c7878fa9"},{"version":"a25d24c59dcfac6bb38b57f8ca65146705d879138a6e5a6ff6ee60d7127d8c59","signature":"4980c890de11b6db5b6c980dab1d996bdcd746a36199849a49a276ba80371339"},{"version":"65417ced218ed4e2159bfecb014f5d7e1cd351f963f6e0c8895cdf0611636aa5","signature":"cf7f4bea29a7e73deddc02ac52ba0c28143c4a54bee3364fd5e209b681ae8981"},{"version":"6c3a4f7bc5bdb50177c76089a49c1580f0d3792ce360fa6e506613403442f0b2","signature":"780a11c3f58a96e85193d04cb8f474720c37d6db82e64142639e0aeca7c14661"},{"version":"c912d1c80e077b598e0dc3ab978847a260a0b18579193dc8195dab79e09c9b65","signature":"99ffa97c910c30821679211070c64f0cd84154659d100d7bfc383dcb86045bce"},{"version":"b94df587d430a1f7ffe9d794b26497e17fc31d4d1ed63b6cc3e0a804fa260509","signature":"dad887fa4ed8c7e1be19c2b3529a9ef7905414b5b866c8647668eaf942dd630e"},{"version":"e278385c2205b853625d59bbc9f9cbdcd7bcc30dd1cab918e03455554027e7fc","signature":"7ce2ac19364777e91c04ed2fd74e45348bda0c7a48dd79df0b4a4f00e9be9995"},{"version":"c99f47a579eab4b35c4bcad73c625f43e43002e4042350ace00608a362382b1c","signature":"b6e0fdbea00785e9bb65deffde1e09d4e36e81330507ea23885559a847460db0"},{"version":"6b66ef1ea3dcace743c3158d7bdd0bfdb736a8e0903ee59ce34b614d25e19b14","signature":"7a27ad47dd1e1399758aba0f970f1f9254f107ef9e1397617249f166f57fa7e7"},{"version":"a741e402812a85f7f6cdbd1e027e46f9e85720c8c94d9c03a3d451b188416869","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4b7d5a30863faa4eb7abc1900236b9004ee2405919100e66108752907d9253f2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"25f1426e122cc852acaa660da5da2c1d9166e7986aaa5fb78e7acb1f88d1704d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e96ee61d669373e81e89d6ed3857446816a015c81c4d947b8d57b685b1cd7329","signature":"47aeb932730902d4d8c41ec941269a416311f709495b5d20f2ddeb6f8b483073"},{"version":"a69e48d66e1c7549d57d1f4d8b90ac85854b55c11bcc16980d6234caf2061f1b","signature":"1416dec78fa5f6be6be2e406d0cc50d6f98ce0c77dad79080d5e5a80be18084e"},{"version":"82c5e491b0319645c6155e6012e39d94109cf3cb945c8555d8da7e8805ecff42","signature":"e28422e9a6af42ba47f7aef0833e002816fa287c438cdfb33754716547da6bfb"},{"version":"3f9862ce2a75340e7afca185dc81c6847e7fb9f759db6bc78d2a6b519bb0e49d","signature":"45e9bfbb6ba5a5dc06a8f9f080c53ace1285ff4e0b04a225448468ee532eb0f2"},{"version":"abdcc36c68ccca6c43c1ae78ad4336a873efd3d78378412ec013dec3f7995df6","signature":"83805f53c80fc8b715af907cad4ed7b70cd140e54f4525ed14fe8f37b6b3a738"},{"version":"a1f98c853bf18810d8b229083066aa710eca359edac6c210472089f7ceb2bca4","signature":"773e3d098838e2ff00d61a55ef560fbe2771df55d900d6849bc3de3eef5c9ae8"},{"version":"8e89058cf52003e3a034070634fe4e1d17d2ba18722957643956d1943ea96552","signature":"52fef2cc3ace541aa2f5f9c96b79dcc527785774b2925604d3c84955e01a0cd6"},{"version":"8624f92f1bbbab3e714feb09bef38fd335876434a3874fcebcb9ae046ac473f5","signature":"a6403bea9d1a1d1d408265797c6632760858edbd1165b47dbd18ae9a55360e94"},{"version":"574dd87b60cae8e087289abb24cec290e66f50c7595488d6ebf250dd13c407bd","signature":"425e9fab16d185ef0882c34b2665df5eda3ec844a9b3b3d5df06ceec263dc7cb"},{"version":"2ee48f1a7b07028cdb53721fa968dd630b8cd9bb132d0a5af0e9347c3dbe6644","signature":"6b1710221e0def096b2d020e5e5b74f1c1f574ebf9f0488badad59229bddf20e"},{"version":"c78318850aa730e4e4d4900d62112d87546cf76bd8c0f8a5389de62a8fab97e9","signature":"579ffed007e8f607d75f38496c6fe381f001777be5986719f9ab61671e8c4928"},{"version":"d3ea50145fc69d7d5042158bda70084edecc82ea9a17637e65228279ad44d8c3","signature":"32cc3d5c511365c3ad87c4fab6bf9cbad8fe64cda8138fa4a3e6d1ad7b501a60"},{"version":"1de0b2a321fedc8312bb012d2e7d6c2a4f3c1f244f57f3b4c90024ce697b89bd","signature":"3e4a13fa3a82198765067d7dc9ebfc78779046ee148aac9b06da6357be695006"},{"version":"dac6f666390294efe2066653f020dadb002c135ed96ec78650068e85b2927bf4","signature":"927eef31f2603821c449226b230c4dfd543e2187e894e4822d46f98ee1e1b060"},{"version":"9dda1d1b1c74d2a7dea69cd86a6d48afb99d5c1e7aabbac1ba5dd05ecf40de56","signature":"80ab17aaf1a46b0bc2e8c68d09df9be18b9e3f8e5e9e17b7ca81797e486b2c47"},{"version":"6b5986587bb935c68b558fe449e6d7e66a83ccf3887bf498ae9f7c9de507d07d","signature":"35e156f4b41ca83eff7e0cf178b4d0e03fba4e6f81a28e8239c37a8176d92bdb"},{"version":"08f44d1ed9b99a954ea9a8523a47fa7eca9ac9e37c361b71b27999c918dabdc8","signature":"32e76e4776022bd907f215302553b8a6de44e2346d3ad857d5e5da95e529cf87"},{"version":"a6bc0506fd785d58fc01916eed093992884c77047de6ef24a1984e870958f3b9","signature":"6c67fa30e0db9490403ce70c9bd112dc16256f74e793f48501b0af56cf31c2f5"},{"version":"8999b9a228194cc3a06b03536351ef482d4af4bbf844a5e68043ef11ce307aca","signature":"564acc0cef9c387a11d6089cd755304c81641036d46e7fe3810c00c2ccdcea22"},{"version":"86823e2761e93272b5cbc0941241547711c6f46383c4609218a65cb7e2f16117","signature":"93ff2618e49bfb06e7c63dc70b3670c683bdfe554cac80fc94d4d3d6c4add74b"},{"version":"11b9c3d93d309e1f5b4db0aadfb647e759ea287aa2c988216c659c9bf8921897","signature":"34da1e99fefcdf0c678bb9084bde33530c33109b7b55fea44d43d2bf30b991e1"},{"version":"38cac19d8699e5bec7a09d03c03f3ca4aa71426bad3eb3c536ddba8a019bdb0e","signature":"8f9a45904777bce21a37d6a2f2fc0c16443222e113877e21d8d76f038bf0c896"},{"version":"889c6cf5d2f17ebbce07ec946521f4f1a8fc55d9530b4959a7ae954ce7f0300a","signature":"a5cc378c3effa6f02780a72acd7c8111fc0346940d685205a5f7ff4e4f4b2224"},{"version":"a3c9238b76558a2b9b60b4280bea5f7ff4d5764b0d3c6668d0c3c814274f15fa","signature":"c5906c1e499e174ca1746e431b4a96cab0a3332912431c59faf460cb69f959c9"},{"version":"7cd585cd1d8de2ccf3f261c68cebc241676f131796095784012cbc038381c588","signature":"1c9fbb019e31e325d23b95b4d1712239673b3864a8a53f4ce595f2b97559f1a8"},{"version":"bc814d190ee61848ffd155d66922f2ba1465745e38d2df77a10bbba3a7da46f4","signature":"792dd988d9aea0f0008be0a7ed777727ae9ab8cd7b02b0ed18a02c694daecbc9"},{"version":"83185fff3417888a1b2ca7005244ba0efc30c6b79017acdaf4b2292799227b21","signature":"ba000331a8a0915160cf82ffd04d583bed6ea5547a117b8d88ed7d3ff6eece7a"},{"version":"78aaa954239d1cc5d45c0c176735cfe8e592450f9c183367cc581f41f088bf1f","signature":"525d97773ff298b2f2fbdf14a791f0587ea0172827b6ac8821f3eb70b20aaf1e"},{"version":"fc77a931ae1856ca79f793f47221a38ca82b3d60bf750afb3982bfd9711d957d","signature":"0ce70420a3f859e7ada24e14d433bf75f91954ea538e9f25cf32a9afe7e86539"},{"version":"ecb2f83825e00ba07c0c992c869efe61535e66e28dfb3773794612bf53b462f0","signature":"d6f37129cc58c234bb3932756578fd5e922c3533885b1cb2e5b54e11caa29aab"},{"version":"63245f98833f518a7f1777a373e82f407e8df95ad573f9b231660369587ad247","signature":"5bbc828ff668bd2cad6f88b3f8bc1e85e3ab4a84af3eae83b3931bddb79d5d5f"},{"version":"7e6fe9eb7b3b835af8097252426effb753743f965cea5e3bfe0c95f11684e54c","signature":"0bb66f919e376030a0b8ed236b96bde51c15c5b4aa50b85c8855b0f99dcae130"},{"version":"304f75ea85e3a8d73919cc2871eb7347d51fd67aa0bccb7f5f35d865a5f424f0","signature":"31f22cee584992be54d06fdaa9dec55d060c358cf67c4d162fb2f5fc0c98283f"},{"version":"140a7665a48036171135a19b4861ecbf0d0c5eb144e0167b97a713a2a3dfaea8","signature":"b4444c70cf4bbb122a1983ec33c297027a05fc1dfc23376d3b140745a58a2ed6"},{"version":"cadcf3eac24d033bd12c3c15337b26cf496ec22600fc0378c806cd1f72f10747","signature":"94e6d27e3abb71e332ba85d07c493fc6e2c61479b04ce1686cd9b81326f1e10c"},{"version":"e4a60880e39d8de38f5d57c04b3444068204c5f36a1bb1c7800f5cae4f63f182","signature":"fa281b36685faa9c4a9d379f8a1ebb2f13f7a09f19e184becbc8e58848dc2396"},{"version":"7104eb14aa794ac738589409c6698195df1ed3158e315ac68000bf6fc170c8c5","signature":"aed26c8732502b8a3775846cef9cb70533d2795a9296b8a4d5db4a0a02125b09"},{"version":"e8238b9d635189889f11ec832863908f83858aedfe765296c8d3066deccfa876","signature":"68597f354e4595d3f1f99cfed58a445a66dd9b5d4ed5d8133e445ee2a3de6cfb"},{"version":"8765a7981a3b7f728339ee9c136a01ed4547a90434eabbebf6893b690d8a7fee","signature":"1aaeb72b56fbfc7fe35e5c5195bc4b102fdcb25a30fa39a8dcf26e4e03afe4f0"},{"version":"914576a1818eb89fab3e321f93591795215285a41d85b1f666ce30b886e9c6b5","signature":"39df2da2a2737d9f0561b052a23093c44d84bab8f276b5bdf2b3e41094666a45"},{"version":"a77019b312f58492f516a7bf5564303a82861bb4c8f8f81339748033ebf85a87","signature":"24bc52911181a6e9ce7ccd5c8fc3b03b998f5a3ea71cf80e3c93051b68523ac9"},{"version":"42cd47c2be24847a02f6007848fc308f27ddf00ccead43e06c5ca5ae454a05cb","signature":"acecc43b3af2def804ce8cbe159aa3750963cd8cf5834d6c6dbc0178d4eae0be"},{"version":"03ff1e0be37d04df80c6dba4edc0bde784aa23b2feb7911768bff74300b59bbb","signature":"b204b8dc8a299d4fce994ceac613f46bfffcf4486c9a5b9f457f42f5f518b0fd"},{"version":"0f43032b234a65cf7b723f4582623d18b4f2eebd389d5fae41f9ddccb2d44b2e","signature":"aff579fd7e28364c3dcd761ff28ca7f456c0215c16c2523024ee1a4c1d29fdba"},{"version":"a994e0ffe163a8473185bcf7afe8021efa6aed157348e200c55c99da67b3b765","signature":"8ce894bcfcdba14681f819fb0450ea391c44fecd82b98df97b25765fb4e7ca84"},{"version":"e5950aec63f876beae5b55824d5d25e641e2207f2eb06071ceadce7101e109a8","signature":"3a0f946578957c30bbaa7f927012973474919f8d43e8daaf94f2996039a2e773"},{"version":"03777b94217631564140a042934fc9a08555bad3639d924ea857aa097b96fb12","signature":"8b50aeb59f6987c56aa1d3329396f4e6c4973133fcea93a0257227c1ffd59080"},{"version":"a2f457296174673fb2977579ff037e92c389a4c96061812d796960b3827d71cd","signature":"700ebb95b8e92fec141e74fe28c89dded7d0d75d17d041b2f33dec147c6dd925"},{"version":"becd89717f17094624e6616f7587fc3c51d18d4c7c6414e30686fc1f61843467","signature":"71c8984f817976f2868e4b97031ff767baa0a3bc31e29a03cdb0f38dabb3c6de"},{"version":"58056c72d7c5e90ad3f21cce7b37e21f5df28537b942a604e226a490fa2935b5","signature":"a53c9821c1526959393efaf002082f8644ab2054f490e9aa6a0d23862d0ecba5"},{"version":"008e2dc7991ff10115da4c7e96c4afaecc606769415f4fd8668fc312f55c893b","signature":"73be23d9b3917e48d86bc0f8625980f6ef348eb2174f301ada759df5170d66ae"},{"version":"a83bb89a08a8dfaf811d879749c6f0c7e956bef4fcd017df63c0b5a2c0f54b5c","signature":"ca270e6e8203c8757397bd31f1233d30d56757a6be9ab66ea2c4e7a53f395187"},{"version":"ab7c6916976916364e364948aaabf628d20afbfa1e5f1a17d14e3db0043b035a","signature":"9a90add49c114832a7fcc7b7f76ba501f3a3f00ae806a85301d65b36f0734e69"},{"version":"d896fbb1a1155107f2867063fde994530a47c5b659d6683e1935fa64901e7629","signature":"20311a8eea15739e7c72b3a2b56389f0944428c7e329b1a91a6083740999fcb6"},{"version":"a395ce8468ae5f5b3bd28556b2505c212020784c1741ba641cd877d81b8798bc","signature":"1017bb3050be6ce40e4cb8a95d0a6415be5b45becd8ff6c62c6341f2f29a9590"},{"version":"54507e17ab7acf644eaddc241605d626e3482e9c947dd2802a2430301b124af2","signature":"98127978590f8f3ad2496ddc8309e7dfda8e191570e18cbd7a146e0cb9d089cd"},{"version":"6f94549d36277cee1171d19b30c9df4bac5009624ca895f6542fff2abb642c5f","signature":"0d27b4098a7f8d9daca5e7f0304750773f03dd567b4d7d49db9a983be5a2e57e"},{"version":"113564e00e92b4bc771ff4f942329c2bfc4be2334d6ccee53d5107c482c32a92","signature":"a08a57ef55f654a3a70741e50af5bbe47a873ac74a8eaf65b3eb4684136ab742"},{"version":"187b3e36e643d6484ba0286c361eadcf4d3f4174865c8bb2e92ede5cfc0206c7","signature":"742178df5f8a476681d0544713fe87cee7a790042b11b27da9b102e80c318bb7"},{"version":"f2678cfcf093027c0c9d59c5b2b829f6304b678e097f681f114b8104a8439d1f","signature":"6ce1bbbe89ecd9412354aab6dfcd60ebd86405a4af89e0d22be797438eac91e5"},{"version":"2006418e0ed472ea2c7b9a81c131817aa7b05ba48006901a8769c4d68800db7d","signature":"243160e9793898a75bb1706e22e14be7dc4f7503439d0bd4385c9002bb73a9f3"},{"version":"ab984ba6802a5c44e617110c6bfa96f20f57f66a551ac6f169043f3b88d8695f","signature":"2f0415ffbf291a21f7b8e32657ea9757e9b49bcd4b1dad5524ee463a1927916d"},{"version":"15454c01076dd3a894064668fe11cd046c3f822465a255b9f880000c200aa0e5","signature":"e56160533522c5bde8996c49e96ca8541fdcfe0c32e0cd0df304cbd1a06c0da2"},{"version":"39d29432fc333562dbe6aaf8e5063bfa3d2163c4e4bf3005f0fd3bdbae770fd4","signature":"b92e4bdcee67fb609851b73ecad31f057ae04215c54a4abd3ba4e2f0e24c629a"},{"version":"3103a62aceb181e145c6d39927f4edc71312d09fe78f5cf6c5447ca9114805a6","signature":"8d866b691c98d47c7eecc6462e0581e3c9e62a4b13b87c6a7f830f0c2b918015"},{"version":"dfe883016c9da74b1e23c5f10f5dd7bbb3f6f802871b6659ab113b995537eb03","signature":"aa36daae0f0633d472253091285064a9ff68e4ba5fa71cbe61cf5bdb1874e8ed"},{"version":"9b4c78f85943b7da3ca9c51e8a22f915ab81189ea61882c54746d0285e6557bc","signature":"51497d9e66c36bf79dea9f8202f104c084ac4a93a0a940a6dd1d2e0d30af0f6b"},{"version":"8ecc0c5e190c12237a251e64e8621e34ad99c9cb7910a1a2f00b5d0a5fa8d231","signature":"aa43218ec3abf932ac6aa3fc859581b8f2fd8c9469e3c919408062e57d232bda"},{"version":"3b897977effda5098d0e4807780ee32cdbdc46f7040970378529c28e69ae59e9","signature":"4910911105559d5ec48fa179743ed357001b66fa8df1227bdb5820ec71c3c5bc"},{"version":"b015aafba6c56e0fe60a6564bb08cc054f9e1c0a71c1640d8d90391f56683609","signature":"4586ac84bca04bf36a1ed0d8b6c0fd542860be317c67a0842311e28165beee5d"},{"version":"4cbe2311a5919c3ec7bbd29a6489ba9266ee91775ddec5904812a7f514da1332","signature":"b5e0ea031f83d837aab204deb17bb3dc7bb49f3bc3d5f5ae00de7055b0957bdb"},{"version":"277701c9b0dd09fbdbea95abeb8320e000af44d141bda7bf32a1021990f0d7c6","signature":"a9a36500eae5c5d23d90dc889ae116ee7afe97061abc80743514a3bd287fc850"},{"version":"51edf78bae3b5deffba7d39b64e9cf2864419ad0e8e3d76f3d3e3737ff526b48","signature":"32265e46d9c9f8cbc102ff9a45d3332302eee0adefd7ada5e2330730a4c19b74"},{"version":"21c1fd70a85c1449c253eae941998ae919bc66887edc63591832bcd396ce37b3","signature":"60e6043a56300fa24f867fa24168c0ce827d6154625db4aa79ed2d49432f06af"},{"version":"2f2e15879914bda1a5c3ce9d4da7ee16a360a777c2da4f68d21ab7ef4023377c","signature":"e605c17826925ef50254a6f1fe1b7615d239bf637b30cdb0df4637d362fe265a"},{"version":"7ed7d8dfba58434b1a474c0619eac2442ef84a74ed635873482abfddb6637524","signature":"307b1f6b818d93140e0f0a31acaf65d20eb606098df57cc97103fb0d83e79529"},{"version":"878e67dcb9d4e991ea86e7fc18d2fcc9756e01671ab69fa15892c6b823a69a0b","signature":"c44cc8c4930b76d1fbf935484045f099a078a0e56218439da28da5d829065a89"},{"version":"cce224fd11825f13171c955d295ebbe71fda68a4c8de1c6e6269c411da5d48c0","signature":"3944dcf2a67281ec8c1e82a4be113656a0476e1c2d052426eef824530915c366"},{"version":"18dca8464c2db41f85b39737ee1a36cd13ee4f85a12d80796333c779511b393a","signature":"1ac9fb8cd09e61aaf85cced63abfbebfe7620efd14a30559e8c4197a629212b7"},{"version":"73179d9bed7d28d279c73fdf5c7ec4dbc78493af676d383b3f3c5c35d839e7c7","signature":"7a90b447685ae9f5e8acda68c5e22524cae89e6cd8674f5f711abd4e9f7aca8e"},{"version":"23e4c56820f11593ad37c2f0ee6e57b022f3bad1e7b88e8e8cfafe4a5631d166","signature":"8545b0f7558460da62b0b9e7fa97d0c4a5bae26cb1b0ec5f4cef91a829dcf4a1"},{"version":"b81c813a557be66ee878d40d1f35ad2b043b1049095a998fe1ca3808d387afe5","signature":"4ea82f415bb35563eae553ebdd9cfc541d18967c536d1bd821f38ddf50836ec5"},{"version":"e4610420b12497de67b5b24dac77dbb041dededc416997f13913d562c986247c","signature":"e5e40cb7b930c754df523177c49f9bed8a660457768c13685d21c641d2f41023"},{"version":"d9aba09758928ec2439f08c4736980c6e52ddf6a0cab476c161b3592a34d44e5","signature":"ffaea7fcaed416769800cd74682a38d1335953e1eb903bb59c22e45cced12b52"},{"version":"9de6fc0c7fceb53327f21628d5ad52df39032e16f6367fb98180d11b38caafcf","signature":"5ad606a8ca9d6d3baf284b4af21e08329b8bb2b9963eb9c8730a4f4a0251026b"},{"version":"6cecd92e8cbe1140a7920e582b939a631027a31ebbc9d5908e5f9f9dd4442926","signature":"5bfb91d2e51019e18a467050246cc0c653bc49f1708e076d3f17717235ceecbd"},{"version":"20a54c69949161b88cf62c3cfebb877cbcf6f5585c105ac73b0c407b20be2b43","signature":"51e03b177ae1693a016731d78123a9375e88191258438907cfb8c28289ccb8bd"},{"version":"4ebc54a2d1855b8f8b46af579eba0a255053642bb75b57a862b908f7e03d5972","signature":"3f8727ac0cd4d782cd6c6804091114e9d4265989fa33de523f3e4468eaad2d0a"},{"version":"38b9d08c9067ba2e8972d2eb3712c741bf760e24cba35be628dcdc05e9a400a8","signature":"e9708da92b0cb69d4b46485f491a6f053fa07145eadaa8101b16fa6738100f9e"},{"version":"270a13a0e0d9ad66c43951af65b76e37902cd7f7b94cac791d6f09b4bd41ef16","signature":"8d88910cc0104f243e391b4773efc30f79f6f066d5f16868060f72211676a008"},{"version":"f6d00fb4092f3f8efcb39b43bd019bd4efbe520567dd5a80ac52c5677674b5d7","signature":"d316a8da36d661ba0c2110e7fa8961db330ee9c39732cdce1b693c8608a06100"},{"version":"482b821f8daf1f7c4e629ed541004d05d86885158a89b93c4cbee00e9773a3fe","signature":"9784ebd09778c432d5098168d18baeef0b8990067538891236adf586c77c450d"},{"version":"a904e4187d38589f1507a050bbe86097630f4b8672de475ddfffadb25c265e8d","signature":"c6595f388cd13a3953de18d7fa043404199216753a8dd09f62ff7e23e6252318"},{"version":"3d3673b81e5f75a94ae2dda40bdf0f667d54c9f526aa5b23b5d5bea695311571","signature":"c02958dbcf88cb888224bf3850bd8bcd7a30638b4e9b92bd22587471cf6c9835"},{"version":"bb991da84034bcb8af11d3f7fb49538acf99a98939d8a49f1122277f21d5a279","signature":"ec6b17ccdaceada5d0ff2bfe58f759e4b79e1d056974fec97791a9475b0657b3"},{"version":"e154eeb896a628fe826dede4fc20b57b2ce76d098b2aa06282ba76fc10241d46","signature":"5c8e01f96eccbc91b7243158ddef84763ac292ad5e914a352f8422f4b374aa6a"},{"version":"7f2bf15a826bfe7e8d9e2a5c7c91382a67c024f1bd8b94f9726de29ea97ced01","signature":"057e3888c2fd6ff7a84ab0ec9ebfa9bb1bc8399836e87973a812b21c9431767b"},{"version":"0e82b1b1053a5de4c429e12a1d21eee1ec4806e458b832e4774186ca0f7e4236","signature":"37f7acbfb476967d35f33541ea813afd46bee4432026b3d709b41a0fa1b0168c"},{"version":"501c3ad8df42f8c41f813263f8c1d0c17191cc1280f5ade7e7f5a13b9ad21b9e","signature":"735a96c77b39a4f34dd68e5b0baab16f5afca71703f10803585bed866e419c29"},{"version":"cd7ae76ac2c3829a44e1a3bf6fdbd087df521a39eaecaaa850f3d8c343acc105","signature":"1ce23953edc19a9ae5913fecc28971b7598293637ea38553ef00a6689834f294"},{"version":"4cfffac6954a2085e03731a6aef2d38f9cc4e0404e4d1341da5e787e81af7282","signature":"19fc3abb4682a127b753ceb3ad5e0e48c03531ea67ba1e7305ca571fb7012ed2"},{"version":"9d845b9d3b5d420766a82189a907b341bd58d687613b5f1d3cd770e93602bf35","signature":"0e92e6224e167b5d58f377e0513c39bb3b513cb2916a2166f87a20d3ac9cc629"},{"version":"113efa2b0709ef4b795e789e648243a12aa147dea8d30a5b859e1c0579ab81c7","signature":"f8ac07e911fd9a3bff7d0a2cb3b8589e14a3b52a3d26a2fad493348f494edfac"},{"version":"26d49ab867c9c2a00e7abd5c4a9c0553a5194d5a26f318a624deaf5b6db56f63","signature":"24cf0f162a2bcae8f5ae4b678da2ca97a91699cfc30898c606cda9ebec4d9f69"},{"version":"80621ac28c75bf6c664ce92a4dc1984cc4ae39d18163f634edb6f39aca131eed","signature":"7b90b0d17d565ad2bcb84936503634ee9f77cf9f40b2353797af5c1dd26b6161"},{"version":"859738240041a31cf82d088489fa91f20f43477453d907fad115b1fe7fd66b3b","signature":"75bb75b5a48ff82403af76163372c541d80017c3a1bd023912a49315e0b7c857"},{"version":"7e34b3113146b88b7cf3feb9c38d54bb5004494787f9667352b9e68513123c8d","signature":"d4f8ca3691504bb90bd9512be095355b7ffd1c0287f7225cabdb2d95b376c8fd"},{"version":"beb68763eeb1a7b7953619adfb6e7cc591338af1c76e6db75310341c10cbe62f","signature":"ac21403a12cbe347469f0e99c204b42c9fea77ab189763d4becbd9edff555e63"},{"version":"8528a96d9da62c5d72d57a0bdc794a77df682f5cb2ed2da7365cc712ac90c460","signature":"b4d1585d23ab5fd5c64c34668928c806d69b8de34bf14688a38db25c17c59d39"},{"version":"8d62bd12e1ce49dd77fe7852c9c776c1a08db690561ad6764b4b357637fe0afe","signature":"a78d3790cc5ae1b4930c299095023c07eccff89cca32e2939a4722a716c9cb55"},{"version":"12ec2200ee91a045a93de21c96802413078541664d7b6888bbc79ca8c1488244","signature":"218f98e76e42a83449d9ba009fba0dbb4f53f12dfb7b899befb95df9c2a334b0"},{"version":"c8795d93f810b55161ca74c681e7199cc580e07cf4a6fcc0b644fa923ea930ae","signature":"bae6a411b96f00598def766b83e30124448aa32d8826fbea105091099c2d5f68"},{"version":"918da7060dccc0242d58264f54360706d293ea3caf562b073d29180649b3f51c","signature":"0754b554b1f0f853d5cd801739c0c0f51858e5f27aeffe06d327f3c48c1d79ae"},{"version":"ba16835bb72ffc1d9787367308742465e4d18de980e0840a76b3660de4b2784e","signature":"3fdf4a05021e1b6beab9f24cb60619a354bdea5b3af5000ddc2d834b1e392a57"},{"version":"206adf107dbc82b1df1b01ebc42dc115e6cf99df68bcfbe2f0ca64436dd5a723","signature":"6729af65023caaaa5e219c29bb52d35e46a47b3128222b404ba24960b97bc907"},{"version":"7ed8567d72959fc070e30d0571356f25c6eb750aed88e5e9dc7a6351c7e23b6b","signature":"ff88fbeb9fc34d6ce2fa3a9ec0526dfb4d9f227e7e48dca9282fadebc1b9b3f5"},{"version":"2a30c825fb7fc2c60fb4e4a26cf2fd105668e19bbf7b3fb563baf09e6e32de82","signature":"e7dc47606500af2c3ea9d69e3d9ed293bdbbbf81bfcb0c48df6568c1ffee4b02"},{"version":"cd065d8de27478d8300d9faf20bed3bb099f883e5e6505dd502a5c197989ea10","signature":"55b35c14620fb1583e0fd5bec90694e957840bb65c16ee338580c6961ffe2de5"},{"version":"626aee6b7812dd82475bc0033ca3868267cb59146bf1d646796a18545a06831b","signature":"f4453522d7a12c9e68c046016ee99bfc759d7c07df309510c331c750111ed4dc"},{"version":"2acdebf8e59ea499f73448a5fd3f3adc716ea358f58a02a12ab84eb9776df27c","signature":"9446380d967f3cb34d51b74cb317bf65dbf2a5bf8bf73892553be82a579be921"},{"version":"7acd203bbccecdebfcc523e1bda4303b32953318fb290210bd0b39ebb91b8118","signature":"347ca2f28c70154d1081c55c3772b0e5073d3482e261847eb6ed655894401136"},{"version":"3836e1ab460cc361173d5f1aef7f302c4fa03abe6194968de6fb8a1822277e15","signature":"1ca25ed8697e2993469486745ba68b7c9568143b8c399b45db6b9405a6848407"},{"version":"1c7a9e0199e95951c4b47e6b8d5f7b99ca36855302b6ce1a0a380db7749d3800","signature":"a8981e806c16cf4a988385695a5e55294adfe356e59f54a9c3a13161f0e9edbd"},{"version":"2606bb4d741d90e54b1b94c3c26bbe9199866093edbc0bfd6d7d14c8fc3d1b5b","signature":"bc9ff410757b4a4d670c277e183cca8c92d9133e1c22cfa4920aa3e885e02d96"},{"version":"9e1055fc07da757b71c3e169a5669303e4652c338c06b6ce46a815bf2d99260f","signature":"9e8ba799a6c8fbe2ceb3b358d84bed46f5f7558aca856e3f63f14c444dfcb27e"},{"version":"c059e8d4ee13412a9d817d1a0a5a3acce021e27ea235817bf8bea3901f9d40a9","signature":"c34b363d2b6cac61ffe29e155fdac051d7caaa197bcd33c1dbebb9632c10dcf0"},{"version":"30ebb34101ceea5a3d2eacb2a8464260d2edc3599374f50b44cd126c30b07d28","signature":"57ef2bfa07808447a73e8c64f5fc664184daacd3570c05c1efe56bcfda8a2eee"},{"version":"0c0f21b2b2173e3f325fb91099b32f3e19843b80a92f4ff2ff5a3d4235afa72a","signature":"e5861628b3c26867cdd721803b8fd63ac2568b1a32ece964adb39bcc895a1ce2"},{"version":"b6ab5c4c25d03afcedcaeac2f296db2512b99e67e3eda8109a223fff3483c934","signature":"e21a61ef0088c9d954930ad728286dd23c0dc3790192a6ee38dfe04a0f074140"},{"version":"92f971441944c16a305337d047a6ee4156e819f0e49e4f7d5ab5b87b6d42b6a8","signature":"b1021f4fb12bd15f1062a739a33f8a6bdb8791cf52f45d0babc5b1c0b4ee901a"},{"version":"8474a06d8021e426e1ac10c5bbb8793732d58749ccb4f6ac3c19849f3e39cbd2","signature":"414485e877b73dfb0beb3576aa20b367aca492e350800e76fcce97c7e9db675c"},{"version":"6b2535919d4b0ade8069db0b70c78f7c9976dd902afb594e05e2860324aacf89","signature":"d9a1a7448109c82c9e991536d02c1787bcbdb043dd9009ddf6585f6dafaa613e"},{"version":"c353a00a14f77db4ae0a0e9893f06fe66e0f5cec36e640898d5eee06b82f58fe","signature":"c5bb2e36d8a842199f3de85755758e5b85094e03b7365b58466c7cd88a65bbf7"},{"version":"3a82f75bc26972bafa8aefbe453f1d9d884118e20397520fc563b17411707d70","signature":"20283420a1a06d38899bfa3f54d16f6902cb186f0907fc3b8977de158d081693"},{"version":"b5e5fc85cdca70aa98cbd2462f5a9c56b1099ac2ea560736609d77fe0971c918","signature":"f04a644b838d29b7f8f587a96973e222aab5a5c1ef56d948e8e282e7b9801837"},{"version":"df8555faa85a08f82765a194bf35def786ec416523e1492cf479636a0450e1f4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"55d2d924dfdc41f7c8ac262f569609e07ee6dbf5c9d8e9247d5d41345e199639","signature":"50e3607e594928df010fb295c28768f3dabf543bf1bf40999426ff7a6f9331bb"},{"version":"80293569ac80d5bd82ee00a3fdfed54495561016dcf201384e527a89daf5d6e3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3e5663a0c11b62d472065a30246a405f83e1715a2406a27da1ae7288f45d6dd6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8e6d872cf02801cf5c4cb501eeab810dab68468917d91807d62617ddc6f2ed44","signature":"1342888987d6078504543599fffb3dd6029c2c0f768b489cd232fb10bedb675a"},{"version":"5a701b2ab3a57bd50884b41091135888312c439b8d75123f9d3b6584b58d4d78","signature":"8c40edee0d9d2d04dee8654ba2f8f239f1662ba5d78ee296068d1e17dece3391"},{"version":"970ecd5919dea2a062633fd83d661b7c3b76335bc9663ee492abc8ea9d278e59","signature":"ecacb7a344532575d0bcd497fdd22d7e66c42117aadfa1fd211a47dbde3b364f"},{"version":"1cd04c130c981572d78cfc54f6d1f6bfe5eb71570af36bf51c3987d6ba5d6426","signature":"d698adb4c9461d06a5ac598b671d45d79371643e15bfa2932742b05e95ebe8ae"},{"version":"bb5b5c94e919115cb8e02fbd379712799d21fb7134cce7dddd0f0e773b172173","signature":"0717bfe8ecec022eb7f964ce697b1b0d749e217864e7a11b438c462ca1e55412"},{"version":"db9c9aabd4720b18cfb7a161cf40552bb8fd2a39b307b3454834673792c8d026","signature":"99173fef2fa963dad3c3a06cef6bff8e4a9e4fea656ec6414aea2f84126ffc23"},{"version":"a9f19e9fec49f5abee045aa42e49b3ff0f3ba2906b0b0e71af07dd04df4ed24d","signature":"08e14aa9343103c4781efad6b4d287b2d577a8266f51f389b2ed4db8957cb5ed"},{"version":"03b72907cecbd439aea347e2608bb94e382d8eaf100c2b4f187b4685f5a9b0bf","signature":"bc1cfb9f737c4d343b2fe2a3709bb926857aadc6fd235554ef991b823967203b"},{"version":"5d0bc2306b8f111545fc6b3dd819a10e6ed1142c1454313781df8359ba7721d0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8906f5389f8ec9b8ca3632220a4c69301fd1b85ff3f2202de56382a29c495c24","signature":"abed2e07ab4d9ae16437a73aaa4382973966df349afdf5eaa654b3f766c30625"},{"version":"8f9cf80afa94f40fe6a140bf81aeb5e67363a4fbb57ee06cb7362f6e36fa01f6","signature":"faf996ebf8da4963f073c2b1a118f139b2b9719909bebc4af3ec0f36d9ed0509"},{"version":"9f8de68f50bb9d234e1f6b5a23284fe88474ffc5d43e51ffc35783d8b7234ce3","signature":"48a7b2d0ae71a82a37b92ec7adcf75ed4a690bbebe3cb0590552b1c2df890f1c"},{"version":"b1535397a73ca6046ca08957788a4c9a745730c7b2b887e9b9bc784214f3abac","impliedFormat":1},{"version":"1dab12d45a7ab2b167b489150cc7d10043d97eadc4255bfee8d9e07697073c61","impliedFormat":1},{"version":"611c4448eee5289fb486356d96a8049ce8e10e58885608b1d218ab6000c489b3","impliedFormat":1},{"version":"5de017dece7444a2041f5f729fe5035c3e8a94065910fbd235949a25c0c5b035","impliedFormat":1},{"version":"d47961927fe421b16a444286485165f10f18c2ef7b2b32a599c6f22106cd223b","impliedFormat":1},{"version":"341672ca9475e1625c105a6a99f46e8b4f14dff977e53a828deef7b5e932638f","impliedFormat":1},{"version":"d3b5d359e0523d0b9f85016266c9a50ce9cda399aeac1b9eeecb63ba577e4d27","impliedFormat":1},{"version":"5b9f65234e953177fcc9088e69d363706ccd0696a15d254ac5787b28bdfb7cb0","impliedFormat":1},{"version":"510a5373df4110d355b3fb5c72dfd3906782aeacbb44de71ceee0f0dece36352","impliedFormat":1},{"version":"eb76f85d8a8893360da026a53b39152237aaa7f033a267009b8e590139afd7de","impliedFormat":1},{"version":"1c19f268e0f1ed1a6485ca80e0cfd4e21bdc71cb974e2ac7b04b5fce0a91482b","impliedFormat":1},{"version":"84a28d684e49bae482c89c996e8aeaabf44c0355237a3a1303749da2161a90c1","impliedFormat":1},{"version":"89c36d61bae1591a26b3c08db2af6fdd43ffaab0f96646dead5af39ff0cf44d3","impliedFormat":1},{"version":"fcd615891bdf6421c708b42a6006ed8b0cf50ca0ac2b37d66a5777d8222893ce","impliedFormat":1},{"version":"1c87dfe5efcac5c2cd5fc454fe5df66116d7dc284b6e7b70bd30c07375176b36","impliedFormat":1},{"version":"6362fcd24c5b52eb88e9cf33876abd9b066d520fc9d4c24173e58dcddcfe12d5","impliedFormat":1},{"version":"aa064f60b7e64c04a759f5806a0d82a954452300ee27566232b0cf5dad5b6ba6","impliedFormat":1},{"version":"7ffb4e58ca1b9ed5f26bed3dc0287c4abd7a2ba301ca55e2546d01a7f7f73de7","impliedFormat":1},{"version":"65a6307cc74644b8813e553b468ea7cc7a1e5c4b241db255098b35f308bfc4b5","impliedFormat":1},{"version":"bd8e8f02d1b0ebfa518f7d8b5f0db06ae260c192e211a1ef86397f4b49ee198f","impliedFormat":1},{"version":"71b32ccf8c508c2f7445b1b2c144dd7eef9434f7bfa6a92a9ebd0253a75cb54a","impliedFormat":1},{"version":"4fd8e7e446c8379cfb1f165961b1d2f984b40d73f5ad343d93e33962292ec2e0","impliedFormat":1},{"version":"45079ac211d6cfda93dd7d0e7fc1cf2e510dad5610048ef71e47328b765515be","impliedFormat":1},{"version":"7ae8f8b4f56ba486dc9561d873aae5b3ad263ffb9683c8f9ffc18d25a7fd09a4","impliedFormat":1},{"version":"e0ab56e00ef473df66b345c9d64e42823c03e84d9a679020746d23710c2f9fce","impliedFormat":1},{"version":"d99deead63d250c60b647620d1ddaf497779aef1084f85d3d0a353cbc4ea8a60","impliedFormat":1},{"version":"ba64b14db9d08613474dc7c06d8ffbcb22a00a4f9d2641b2dcf97bc91da14275","impliedFormat":1},{"version":"530197974beb0a02c5a9eb7223f03e27651422345c8c35e1a13ddc67e6365af5","impliedFormat":1},{"version":"512c43b21074254148f89bd80ae00f7126db68b4d0bd1583b77b9c8af91cc0d3","impliedFormat":1},{"version":"0bfacd36c923f059779049c6c74c00823c56386397a541fefc8d8672d26e0c42","impliedFormat":1},{"version":"19d04b82ed0dc5ba742521b6da97f22362fe40d6efa5ca5650f08381e5c939b2","impliedFormat":1},{"version":"f02ac71075b54b5c0a384dddbd773c9852dba14b4bf61ca9f1c8ba6b09101d3e","impliedFormat":1},{"version":"bbf0ae18efd0b886897a23141532d9695435c279921c24bcb86090f2466d0727","impliedFormat":1},{"version":"067670de65606b4aa07964b0269b788a7fe48026864326cd3ab5db9fc5e93120","impliedFormat":1},{"version":"7a094146e95764e687120cdb840d7e92fe9960c2168d697639ad51af7230ef5e","impliedFormat":1},{"version":"21290aaea56895f836a0f1da5e1ef89285f8c0e85dc85fd59e2b887255484a6f","impliedFormat":1},{"version":"a07254fded28555a750750f3016aa44ec8b41fbf3664b380829ed8948124bafe","impliedFormat":1},{"version":"f14fbd9ec19692009e5f2727a662f841bbe65ac098e3371eb9a4d9e6ac05bca7","impliedFormat":1},{"version":"46f640a5efe8e5d464ced887797e7855c60581c27575971493998f253931b9a3","impliedFormat":1},{"version":"cdf62cebf884c6fde74f733d7993b7e255e513d6bc1d0e76c5c745ac8df98453","impliedFormat":1},{"version":"e6dd8526d318cce4cb3e83bef3cb4bf3aa08186ddc984c4663cf7dee221d430e","impliedFormat":1},{"version":"bc79e5e54981d32d02e32014b0279f1577055b2ebee12f4d2dc6451efd823a19","impliedFormat":1},{"version":"ce9f76eceb4f35c5ecd9bf7a1a22774c8b4962c2c52e5d56a8d3581a07b392f9","impliedFormat":1},{"version":"7d390f34038ca66aef27575cffb5a25a1034df470a8f7789a9079397a359bf8b","impliedFormat":1},{"version":"18084f07f6e85e59ce11b7118163dff2e452694fffb167d9973617699405fbd1","impliedFormat":1},{"version":"6af607dd78a033679e46c1c69c126313a1485069bdec46036f0fbfe64e393979","impliedFormat":1},{"version":"44c556b0d0ede234f633da4fb95df7d6e9780007003e108e88b4969541373db1","impliedFormat":1},{"version":"ef1491fb98f7a8837af94bfff14351b28485d8b8f490987820695cedac76dc99","impliedFormat":1},{"version":"0d4ba4ad7632e46bab669c1261452a1b35b58c3b1f6a64fb456440488f9008cf","impliedFormat":1},{"version":"74a0fa488591d372a544454d6cd93bbadd09c26474595ea8afed7125692e0859","impliedFormat":1},{"version":"0a9ae72be840cc5be5b0af985997029c74e3f5bcd4237b0055096bb01241d723","impliedFormat":1},{"version":"920004608418d82d0aad39134e275a427255aaf1dafe44dca10cc432ef5ca72a","impliedFormat":1},{"version":"3ac2bd86af2bab352d126ccdde1381cd4db82e3d09a887391c5c1254790727a1","impliedFormat":1},{"version":"2efc9ad74a84d3af0e00c12769a1032b2c349430d49aadebdf710f57857c9647","impliedFormat":1},{"version":"f18cc4e4728203a0282b94fc542523dfd78967a8f160fabc920faa120688151f","impliedFormat":1},{"version":"cc609a30a3dd07d6074290dadfb49b9f0f2c09d0ae7f2fa6b41e2dae2432417b","impliedFormat":1},{"version":"c473f6bd005279b9f3a08c38986f1f0eaf1b0f9d094fec6bc66309e7504b6460","impliedFormat":1},{"version":"0043ff78e9f07cbbbb934dd80d0f5fe190437715446ec9550d1f97b74ec951ac","impliedFormat":1},{"version":"bdc013746db3189a2525e87e2da9a6681f78352ef25ae513aa5f9a75f541e0ae","impliedFormat":1},{"version":"4f567b8360c2be77e609f98efc15de3ffcdbe2a806f34a3eba1ee607c04abab6","impliedFormat":1},{"version":"615bf0ac5606a0e79312d70d4b978ac4a39b3add886b555b1b1a35472327034e","impliedFormat":1},{"version":"818e96d8e24d98dfd8fd6d9d1bbabcac082bcf5fbbe64ca2a32d006209a8ee54","impliedFormat":1},{"version":"18b0b9a38fe92aa95a40431676b2102139c5257e5635fe6a48b197e9dcb660f1","impliedFormat":1},{"version":"86b382f98cb678ff23a74fe1d940cbbf67bcd3162259e8924590ecf8ee24701e","impliedFormat":1},{"version":"aeea2c497f27ce34df29448cbe66adb0f07d3a5d210c24943d38b8026ffa6d3c","impliedFormat":1},{"version":"0fbe1a754e3da007cc2726f61bc8f89b34b466fe205b20c1e316eb240bebe9e8","impliedFormat":1},{"version":"aa2f3c289c7a3403633e411985025b79af473c0bf0fdd980b9712bd6a1705d59","impliedFormat":1},{"version":"e140d9fa025dadc4b098c54278271a032d170d09f85f16f372e4879765277af8","impliedFormat":1},{"version":"70d9e5189fd4dabc81b82cf7691d80e0abf55df5030cc7f12d57df62c72b5076","impliedFormat":1},{"version":"a96be3ed573c2a6d4c7d4e7540f1738a6e90c92f05f684f5ee2533929dd8c6b2","impliedFormat":1},{"version":"2a545aa0bc738bd0080a931ccf8d1d9486c75cbc93e154597d93f46d2f3be3b4","impliedFormat":1},{"version":"137272a656222e83280287c3b6b6d949d38e6c125b48aff9e987cf584ff8eb42","impliedFormat":1},{"version":"5277b2beeb856b348af1c23ffdaccde1ec447abede6f017a0ab0362613309587","impliedFormat":1},{"version":"d4b6804b4c4cb3d65efd5dc8a672825cea7b39db98363d2d9c2608078adce5f8","impliedFormat":1},{"version":"929f67e0e7f3b3a3bcd4e17074e2e60c94b1e27a8135472a7d002a36cd640629","impliedFormat":1},{"version":"0c73536b65135298d43d1ef51dd81a6eba3b69ef0ce005db3de11365fda30a55","impliedFormat":1},{"version":"2a545aa0bc738bd0080a931ccf8d1d9486c75cbc93e154597d93f46d2f3be3b4","impliedFormat":99},{"version":"9f30bec23d54f9d9f5d541b81396c677c3d2cffe316408191b1ef6272bcef627","signature":"5272a45a3368fd3d5b08c29dd0afb26098a2ece5834819bf5e3de14c9c4ad41c"},{"version":"85c06406342b95a85ae3704081c8383a8f7a1d50df94efbee946eedd0fef2e57","signature":"3dd356c08322fb7c79a49f242d2b9c1cf64a54a7cdbee83a902f23e9cd8503d2"},{"version":"89121c1bf2990f5219bfd802a3e7fc557de447c62058d6af68d6b6348d64499a","impliedFormat":1},{"version":"79b4369233a12c6fa4a07301ecb7085802c98f3a77cf9ab97eee27e1656f82e6","impliedFormat":1},{"version":"2b37ba54ec067598bf912d56fcb81f6d8ad86a045c757e79440bdef97b52fe1b","impliedFormat":99},{"version":"1bc9dd465634109668661f998485a32da369755d9f32b5a55ed64a525566c94b","impliedFormat":99},{"version":"5702b3c2f5d248290ed99419d77ca1cc3e6c29db5847172377659c50e6303768","impliedFormat":99},{"version":"9764b2eb5b4fc0b8951468fb3dbd6cd922d7752343ef5fbf1a7cd3dfcd54a75e","impliedFormat":99},{"version":"1fc2d3fe8f31c52c802c4dee6c0157c5a1d1f6be44ece83c49174e316cf931ad","impliedFormat":99},{"version":"dc4aae103a0c812121d9db1f7a5ea98231801ed405bf577d1c9c46a893177e36","impliedFormat":99},{"version":"106d3f40907ba68d2ad8ce143a68358bad476e1cc4a5c710c11c7dbaac878308","impliedFormat":99},{"version":"42ad582d92b058b88570d5be95393cf0a6c09a29ba9aa44609465b41d39d2534","impliedFormat":99},{"version":"36e051a1e0d2f2a808dbb164d846be09b5d98e8b782b37922a3b75f57ee66698","impliedFormat":99},{"version":"d4a22007b481fe2a2e6bfd3a42c00cd62d41edb36d30fc4697df2692e9891fc8","impliedFormat":1},{"version":"9d62e577adb05f5aafed137e747b3a1b26f8dce7b20f350d22f6fb3255a3c0ed","impliedFormat":99},{"version":"7ed92bcef308af6e3925b3b61c83ad6157a03ff15c7412cf325f24042fe5d363","impliedFormat":99},{"version":"3da9062d0c762c002b7ab88187d72e1978c0224db61832221edc8f4eb0b54414","impliedFormat":99},{"version":"84dbf6af43b0b5ad42c01e332fddf4c690038248140d7c4ccb74a424e9226d4d","impliedFormat":99},{"version":"00884fc0ea3731a9ffecffcde8b32e181b20e1039977a8ae93ae5bce3ab3d245","impliedFormat":99},{"version":"0bd8b6493d9bf244afe133ccb52d32d293de8d08d15437cca2089beed5f5a6b5","impliedFormat":99},{"version":"7fc3099c95752c6e7b0ea215915464c7203e835fcd6878210f2ce4f0dcbbfe67","impliedFormat":99},{"version":"83b5499dbc74ee1add93aef162f7d44b769dcef3a74afb5f80c70f9a5ce77cc0","impliedFormat":99},{"version":"8bf8b772b38fc4da471248320f49a2219c363a9669938c720e0e0a5a2531eabf","impliedFormat":99},{"version":"7da6e8c98eacf084c961e039255f7ebb9d97a43377e7eee2695cb77fec640c66","impliedFormat":99},{"version":"0b5b064c5145a48cd3e2a5d9528c63f49bac55aa4bc5f5b4e68a160066401375","impliedFormat":99},{"version":"702ff40d28906c05d9d60b23e646c2577ad1cc7cd177d5c0791255a2eab13c07","impliedFormat":99},{"version":"49ff0f30d6e757d865ae0b422103f42737234e624815eee2b7f523240aa0c8f8","impliedFormat":99},{"version":"0389aacf0ffd49a877a46814a21a4770f33fc33e99951a1584de866c8e971993","impliedFormat":99},{"version":"5cb7a51cf151c1056b61f078cf80b811e19787d1f29a33a2a6e4bf00334bbc10","impliedFormat":99},{"version":"215aa8915d707f97ad511b7abbf7eda51d3a7048e9a656955cf0dda767ae7db0","impliedFormat":99},{"version":"0d689a717fbef83da07ab4de33f83db5cbcec9bc4e3b04edb106c538a50a0210","impliedFormat":99},{"version":"d00bc73e8d1f4137f2f6238bb3aa2bbdad8573658cc95920e2cdfa7ad491a8d8","impliedFormat":99},{"version":"e3667aa9f5245d1a99fb4a2a1ac48daf1429040c29cc0d262e3843f9ae3b9d65","impliedFormat":99},{"version":"08c0f3222b50ec2b534be1a59392660102549129246425d33ec43f35aa051dc6","impliedFormat":99},{"version":"612fb780f312e6bb3c40f3cb2b827ea7455b922198f651c799d844fdd44cf2e9","impliedFormat":99},{"version":"bcd98e8f44bc76e4fcb41e4b1a8bab648161a942653a3d1f261775a891d258de","impliedFormat":99},{"version":"5abaa19aa91bb4f63ea58154ada5d021e33b1f39aa026ca56eb95f13b12c497a","impliedFormat":99},{"version":"356a18b0c50f297fee148f4a2c64b0affd352cbd6f21c7b6bfa569d30622c693","impliedFormat":99},{"version":"5876027679fd5257b92eb55d62efee634358012b9f25c5711ad02b918e52c837","impliedFormat":99},{"version":"f5622423ee5642dcf2b92d71b37967b458e8df3cf90b468675ff9fddaa532a0f","impliedFormat":99},{"version":"70265bc75baf24ec0d61f12517b91ea711732b9c349fceef71a446c4ff4a247a","impliedFormat":99},{"version":"41a4b2454b2d3a13b4fc4ec57d6a0a639127369f87da8f28037943019705d619","impliedFormat":99},{"version":"e9b82ac7186490d18dffaafda695f5d975dfee549096c0bf883387a8b6c3ab5a","impliedFormat":99},{"version":"eed9b5f5a6998abe0b408db4b8847a46eb401c9924ddc5b24b1cede3ebf4ee8c","impliedFormat":99},{"version":"dc61004e63576b5e75a20c5511be2cdbddfdbcdff51412a4e7ffe03f04d17319","impliedFormat":99},{"version":"323b34e5a8d37116883230d26bc7bc09d42417038fc35244660d3b008292577b","impliedFormat":99},{"version":"6ada175c0c585e89569e8feb8ff6fc9fc443d7f9ca6340b456e0f94cbef559bf","impliedFormat":99},{"version":"e56e4d95fad615c97eb0ae39c329a4cda9c0af178273a9173676cc9b14b58520","impliedFormat":99},{"version":"bd821b87e2c0fb5f509cedf47da465c447451835ce0fe2a752c4fc53a9f95a5b","impliedFormat":99},{"version":"f1d7352c0f7041abb43e1054abb14fb8c53a13dd54bcc1d67b97d2c02bb5028c","impliedFormat":99},{"version":"fc820b2f0c21501f51f79b58a21d3fa7ae5659fc1812784dbfbb72af147659ee","impliedFormat":99},{"version":"08f88f75fc2f516413477606a4122b6d3f6eb6680e8eb79f3fda5a5d2ed306df","impliedFormat":99},{"version":"6ab9821afd2a06879620eb4e041b9492a90f294e9b733ae5eb022edaa3964a45","impliedFormat":99},{"version":"003533cc3fa10cc457668d4256d21a65706a67a04251962cfe85d240502f8d67","impliedFormat":99},{"version":"6c00cb8a4b187505dfe21aff242b07f69f84f5c832e8ab4357af69daaee1b0df","impliedFormat":99},{"version":"de14ddf9d780367c6a117bd8a1718d491aff66094186523b3eea680ea7035a7c","impliedFormat":99},{"version":"ee06b94d0521cfaf91e4b003518eeefc45bbd594b0c22955fe35be282958252b","impliedFormat":99},{"version":"9e5f8fdaeb03f1699392b4724a58ca7b47c5cbb6762920d2bfc722c265495ede","impliedFormat":99},{"version":"d05bd4d28c12545827349b0ac3a79c50658d68147dad38d13e97e22353544496","impliedFormat":99},{"version":"a1597b0039f39e9f3eeaf120f02d0c94a826fad30b027a2abfdb8d580c89be70","impliedFormat":99},{"version":"04ace6bedd6f59c30ea6df1f0f8d432c728c8bc5c5fd0c5c1c80242d3ab51977","impliedFormat":99},{"version":"57a8a7772769c35ba7b4b1ba125f0812deec5c7102a0d04d9e15b1d22880c9e8","impliedFormat":99},{"version":"1de82ba3718b2b3bc5333c5bc35da5cfc46d1b654edc012de46bbce48126fcce","impliedFormat":99},{"version":"8bdbb5e0426b40c11dbb4b86045f008c619ba02050126ede6501f7c59376d1b1","signature":"d6ab9d3f4bfde85a62fea7182d4e68ba2104946541f47eab58799009a38ba2db"},{"version":"91cdcde79d172273c1b10cd8abc58cc86ad915f3f3224241ff63705fa0b55117","signature":"2eefd9c7b8dddc8d713b7ec2f408480a4c1ff14f1975c85f91834b8886963f8d"},{"version":"fbe6ac1edabe62f9565a5dcf644865c3b839c759c6b81a0991e7314186c77c14","signature":"5e5a13138a956d69dc4e30dcc820b816b253b6907b02e168e1067a6f026bd4b3"},{"version":"86d4ff8ba66b5ea1df375fe6092d2b167682ccd5dd0d9b003a7d30d95a0cda32","impliedFormat":99},{"version":"736097ddbb2903bef918bb3b5811ef1c9c5656f2a73bd39b22a91b9cc2525e50","impliedFormat":1},{"version":"4340936f4e937c452ae783514e7c7bbb7fc06d0c97993ff4865370d0962bb9cf","impliedFormat":1},{"version":"b70c7ea83a7d0de17a791d9b5283f664033a96362c42cc4d2b2e0bdaa65ef7d1","impliedFormat":1},{"version":"eb541103f61ea69ee1795085a473b727d46fe49ebc7091c721623b5ecd87c0f7","impliedFormat":99},{"version":"014ba72e2add59d6d2d2e82166647982c824639e2902ccd7b3103cf720a0cb65","impliedFormat":99},{"version":"e22273698b7aad4352f0eb3c981d510b5cf6b17fde2eeaa5c018bb065d15558f","impliedFormat":99},{"version":"b78c801c3c21015ee487f6494448bcff55bb6b61f41172dfc2c26f2218d99138","impliedFormat":99},{"version":"de97e016d8dd4869febd5bccce02eb96957089d04b74ea5d1dc0e66112493b64","impliedFormat":99},{"version":"671ccab2e6a253d2516c0e4699b3077fc30cdb70b4436d8c79d76c91266a1a94","impliedFormat":99},{"version":"a11fbd8ffbee6e5a7fe4c7c23e6a391be615de2e710a6946d7d1f947a85a1374","impliedFormat":99},{"version":"2d383c515b9b606aefcde23da9c312a69bc7976b75abb85c02592f7a8589a343","impliedFormat":99},{"version":"e760f7860d08e9d42b6ecd7dd341602fbc0c13d60eb30beaf1153f1c7c44d66d","impliedFormat":99},{"version":"fb04e1ca667399e7302c033656cc285e6c1cff9c29f264cf229dd25e3962a762","impliedFormat":99},{"version":"ca6fb77e3480af8f2287ccb756ac88d047ba8a8bcc0512f6720ac1216e274ea2","impliedFormat":99},{"version":"c0cc44b0ad2fd65c933d187c4faad6157efbed33c3c21023802aa6a89d9b9d13","impliedFormat":99},{"version":"ddaf5d3ddc45282b19fb0fecec91c87fc9b4d1f45c2ee611677345c81383c5c5","impliedFormat":99},{"version":"5668033966c8247576fc316629df131d6175d24ccf22940324c19c159671e1c1","impliedFormat":99},{"version":"d76df1670eeb97afbab6c87b8cd31bbd09dbf9026ff0ca533b5d7d3fc0291f79","impliedFormat":99},{"version":"e9b947b944887cf2cdea2d6188f412a25125eade82f2fe2334658af86f14cded","impliedFormat":99},{"version":"bc05fb9d657d30e61d50d690615f379b0d0415b8f29e69196e1dc6bfc664dc57","impliedFormat":99},{"version":"e315bab2f28d53f9ab473d9de610c455b6c414757bb19589b31ec8f490cebd4d","impliedFormat":99},{"version":"d999dd5abf4befbdab5f1248193cbea69b323b71131a02bb120f9462807fcd5a","impliedFormat":99},{"version":"031f1805f87171e8a9125cd99105bea4a869018ab2356c2e29dca7c86925510c","impliedFormat":99},{"version":"bdcb070ed484b40b84dad668b58e4861f7c3d36f38632072dad5f905bd8cc0cb","impliedFormat":99},{"version":"54a97fd1e2f33041d7b4cbbe8fe3235f97fdf1a05e9fa41e78417851bb8f1c78","impliedFormat":99},{"version":"f63e0743e2ac9f7eb4d3b8bc110834465f164eb9e75e4f531046e4ff9822f3a4","impliedFormat":99},{"version":"0372d6d6a41ae89f9aa86eef998b44bc0ba035d9d09c9226e0a150ef4578fc3d","impliedFormat":99},{"version":"cd8a4297d0ab56dc571dadd2845e558c9d979fe1e120a0dec537935bc8a36dd2","impliedFormat":99},{"version":"079a12cb0e0c42655d77da5185e882b4cc94bd5c6c2131171a9289fc1f4287fc","impliedFormat":99},{"version":"50cf14b8f0fc2722c11794ca2a06565b1f29e266491da75c745894960ebbce06","impliedFormat":99},{"version":"d4ce52c42c23981d958206037138e05f7b48d41faa1cfaba7e9eecce8c2e5489","impliedFormat":99},{"version":"8a3be5afe0275ce84a6a6298010e66d54d2d2f8e927df6bcde0ac326b5e81792","impliedFormat":99},{"version":"167edfac7664bec77aa2efb2ce9d515c41b5cc4269091a946b3fa6ec4e7e8738","impliedFormat":99},{"version":"218997a627f0efc4b8be5e6bc0b58e0c9edf250baa3674b661d3f7e6a7ef21e8","impliedFormat":99},{"version":"c3f1acbd39f587a7539d435d6c78ce8647b3bfdc5435df153a70cb2656b52b80","impliedFormat":99},{"version":"a1f568855887e2b5121e8b5c4cacd66dc5428259981c47026d95be1d844adf71","impliedFormat":99},{"version":"1db6f2cf99c83598669df0c7166405b0e83c153a03b59115000df3cb1afba79c","impliedFormat":99},{"version":"49af73d71b88a99b1a211ec02bacd321c21397062d253c605bb8d140082eb7b0","impliedFormat":99},{"version":"a50de7f1a7eadab7732d80dcf9c8a0c0d7d00e33315425316757006b5bec6e46","impliedFormat":99},{"version":"4c6fe268c2a984b3f14031a4b09ae7b2d9e51673258f4b4352e48d0c6ebed679","impliedFormat":99},{"version":"3bc3d81dbfb842bcf15454aacbe37cfeac57f1d15e829812ad02a05cc42be873","impliedFormat":99},{"version":"016952415e1ad35f9070b4d454946e79cd3881f3c76c0978d759b168fe033018","impliedFormat":99},{"version":"6bf5dc0c9f6b6c79fce77b56c985dadca4d4d474c9abf9139ae0785cb5c01992","impliedFormat":99},{"version":"d507276467f554e383b4fa058f42b8fdf5c15f1d1db84a2372bb569ce9d57a66","impliedFormat":99},{"version":"b3af5d182c9ef267d58cf43f3e51ab73986862436790a4dbf076b5994758d53f","impliedFormat":99},{"version":"1c7f26a88f861afdccd8f9d9e793f1affef4635d16d2609c487480c41c42f253","impliedFormat":99},{"version":"4d4551dcb3fd19a4f22aaa63c6c391d42ce44a15602a6f6a19d582709edb24d9","impliedFormat":99},{"version":"a76075b5aba8187b1fc5c8f565745daed6e4341e64b44e6ec41412a16d575d62","impliedFormat":99},{"version":"f836bf3653e31c3bba120071196c95d416b83c5d860ce27549975f8785cd670a","impliedFormat":99},{"version":"058d970583137cface729371715449aac0c1388bf7a5ba15e0be952677485fe3","impliedFormat":99},{"version":"31c45c074a9acb94dbe340d9336d3c915635eac2df3308916fcf41f2ba6ab84c","impliedFormat":99},{"version":"774256d456ca1d8266f6e2170a51bad2659cb7116334d1e7977595999533a5d0","impliedFormat":99},{"version":"07916d3ee50b94b3217c0bac71fa9d70d48f51586481c7cf61af2a8ebc2a9db5","impliedFormat":99},{"version":"f687f35c2206a319dc7d8f0b751e182638c912838ff54034fb782beae50f7cac","impliedFormat":99},{"version":"1ea2d362005804d980325c2fe6ca0abbb145197b856a64d80016554129966c97","impliedFormat":99},{"version":"7a81f15892b1c8d0cbfb35605038ce5c6d0cf93542946aa0b8c415dbefdea1cd","impliedFormat":99},{"version":"22ef1a1604bed6e226888a2414676ef477a7ad5d6ed907a62d6e40c831797366","impliedFormat":99},{"version":"2ab500573da35083b48fa8f4fe719860099d1502df3384f977eb22ab6b14caf7","impliedFormat":99},{"version":"9fd7d60e314f01c950ba31932c150dcec5db2c82de3c7fe0d0d24ee8b54f1fca","impliedFormat":99},{"version":"841d1f32f66723468772802e1558eb36ae0226c95d0527a3ebfcfa2c75b6f6d0","impliedFormat":99},{"version":"d3327c9f7dce1e11f5ce85b8ca921668a13068980f7f4ea4daedbc2c81589e9b","impliedFormat":99},{"version":"a698ef4e27ad6053ad8c189b53c468f857501046aacb554eb52be0403ba7f262","impliedFormat":99},{"version":"94b576c860480aac3eafdf904cd81755f5c9b16c3e0ef3253953a8f4fd8cecce","impliedFormat":99},{"version":"8dc7c54b72cb2a49a7639dccd99a559c243667a74abfb09545cf8afaecc58056","impliedFormat":99},{"version":"d2166d3793936235216ee5d014bcf0d8695f3a954ad54c01b1976c05f544ceea","impliedFormat":99},{"version":"41bf8c3193b575946682ca243de53370f61917035c3ff3fb747067bc680f2509","impliedFormat":99},{"version":"c34ca1da5a16e87b73cbade190eed99dfe5215df514fc92071ad5ef1de1131c1","signature":"cc4378fa4ecfd466c3ae4491a7d8595f61fec07131d6b961583109bb9aecf551"},{"version":"9129c3784df7f9813773a51302ae4db1e94ffe625023e918193e67ecaa28b9ad","signature":"24644a17b266badb345ca337c9d9c80300473c40b2c87a28e5a3ddc011551909"},{"version":"549ca0847eae8fe6672e77c4f68ad497e21aa459334a08bcbdc891efb65677ef","signature":"71e597ff732221dcbf043d2de4000ccc5326c9ac63b12f2a27f89b5adf18e609"},{"version":"3c22ea48384e01f1e7cd7c50ba24a4e4b151392a3ffc002e4fbf5e488457efe3","signature":"22c51e70701555882fd248a93bda5c759c024c0b88a58ce37a54ef186729e795"},{"version":"33d8347327eb8efe4a8503013c32a8b4536a2842dd55f3ca1b65d79eec32c126","signature":"212577e3f6db3f7bfb26e82ef9385a9c0c241b3906ccb6d80e4ae6bdd657e00d"},{"version":"643955dd419798329a8dfc0d772efb666df91938a3e1fd0646253783a6cb49f9","signature":"0270d8376c084b2e07697ef2de94f943eef66b6de4b77fb20147d306f645e990"},{"version":"29f6fb29eaa8f3d680d116346c6220c0767762d2c11b1d8e3b512f1715d03894","signature":"8981293638dcfc12c0e02bfdc33353c92e1a9821e73ebe269ad367434fb5510c"},{"version":"378e053ab58ce57875970ea938bebb30c685813cab965283191b971ff837e48c","signature":"dfb01e57f4a98a678b16d78007abe78b8600ec7545e63331d72a0daa6ce961ad"},{"version":"fccbed3384435f8a983487f98fbb794b9f29c61da9ded9d059a8cfa15676bc23","signature":"54d8ac0a02cacde5162ddc4bf4a5e973fb1f76eaabaca37782bb822ecb91f058"},{"version":"0b943d8397fc7d8ac1a23a0de3bb23e68e75092436292b3db709affd6bbc6484","signature":"a9dadd65d2aa2cf96d962c488059826f5484b70093ef76d1f871c961fa912eff"},{"version":"dc1d17e168090dd4df773ba839410a2e106ff0f43163a994f681a0044b4de578","signature":"6113e636ad4fffa72648f2a41eb42ef83262089973d54e080d130cb4219dab4c"},{"version":"17ae4d2c0c3487e077a7f6db647df1ebc56194a135262b8af15b19a3a1072452","signature":"7d5919c7ef0b53f308a78754ac25e352473a2de6bf6e25e109cd03dd493aab54"},{"version":"36bb2af4092c1e38205c625a86c4716d886c299c24bbce969076c1a5653fc491","signature":"9adbd23317b76a09a9364daa02f7ad358ef30c277dca1b05e8df356f7751702e"},{"version":"0083cf5a71517844e6e3f71b504f0c921141068c42936b9208ce2754c4aa8086","signature":"2e717c399a5cc34076335b2718b56c3cb2263caae14384b8b971f4af16103d3f"},{"version":"3ca5a20c56112eb875ae0f86af92e3504a07f5d791da9e024e2cf1b871d6dfad","signature":"098fb9262c019dd7c7d2bc1efe85f61d7fef30a8c6ea0267398aee95321cf1f5"},{"version":"f98c50ed21c5ffdf20628ce7f1cd694637600b1c178be6e8b6740864e421d9cc","signature":"7e2734061c31bb7fcc162aa37af53a181cb8db1bc2ea1168ecf1c816cf52e045"},{"version":"04e564b1244256a78028b4c640a0c063ebef8304b5744d6a8f1c09f34f7c1587","signature":"d23b1f070ca79bf4cececb66c23b78eb3f35e10b3ef7d0119549521c9fd2ccbf"},{"version":"822d455d9ad873f074745ef689e696456d9cc046009453a57b7f34b41465a5bc","signature":"2fe07fd890f914dbdf16fa8e6270c867ccf9999973599c1f75da3177ed5c0278"},{"version":"6a671dd6e44ffe6f84f6f6c18176d30f641c05842f1af36accd2e9ca16450af2","signature":"03f8247a05f82c8f96aac14f06944d2dab5061d08fe71d98e429251144b7d9d8"},{"version":"a458e8e9584b1c9f8d6db0fce8507de7553bf52e4916d6f5eee18693f36f7f90","signature":"1b758ade259220a7723152591ae4997ead9ed62664cd36c08289f94fbaaa5511"},{"version":"574fede341569986c736fd5468e28194913dfe16685e804a5d5fb0a24e71384c","signature":"cb0d718f6419c7cf0ae1316734875540e46cc33e10e9aeecdf79e2f29f6eab4f"},{"version":"c01b5c70837403d939eb49e6cd2a7ca812c28c8b9145b20517be5b2be2884d83","signature":"291d5759f6ff0d7180456b40f53ac4ec52d6fb91b6688aa2748e70feabcc8124"},{"version":"49ab6f3ff577c5423e0be5e03cf295aa6b22dac03c17c10a79bd64cd133eca48","signature":"4bd61fa62afcedd4e842ca0d3de983b761f6729d267e9a0d1aaf4c15a998c4e9"},{"version":"111fe2fc2a03b54c7f6b0ca9fc40b44f5c142858696867393de4ce08a81cc143","signature":"50d64ee04b0476a1348ef61e8f7e8d49883be123bbb3bf18eb9870d3febd73db"},{"version":"48193d602f5f2727f1f0dba57b9f8f198c8dede37b0d4a023fc7b6b22208f67b","signature":"74b2ceb70d6eaae4dac30827745318df518df3e547528f5ddf8a93bf0ac289c6"},{"version":"8c85cef4fa742fc0c376aee61ee28221dd268da5fd7874ffb6e210e71de197ed","signature":"6fb16d7f85050f01ba2e8248d33306db324277a87040aed2ac58e20343a0c2ac"},{"version":"c56099230a4d6b6479db912f210ac0a705b650309457073814dba6264e656a83","signature":"c404855e249e727a187122c5a1809d1e93cf3bb3af6d64d68dabae61754a2da7"},{"version":"3a8cce3c9f00f77f85b0800be6ac3ad3a619684863e4e56d795efe5382cf1caf","signature":"37fa56790fd8a57b9e8e21bd7f2aa4cdd33b7a833ae9626d8bcb9eb41e0288e2"},{"version":"b6ea1a2f8ffcfad4ee1f18f3d9f3684ef2b8b10c90c1f72144ed2865b2dcd453","signature":"a377867f70cb021f6b57a076f3124d8e5c9e207ec1152e0fa5e6db763ef1b409"},{"version":"a3f970582aa9c0ff8a7990bcc8f9be6cbf6063ea082e7954c87e39912b24d447","signature":"de82dd11ce4b81aee57b38fa6794ddaff9dd8421844abc4ed6573582ac675157"},{"version":"302d3cbdd32beffc04087cfc12cb46c63d8b97d5d5e1ff7be05bf5cd0a86aea0","signature":"ed02f8c6d224e08e9458832a973a1347b7aca09e9b028509067f3a6eea456e9b"},{"version":"ec0c9334ce775f084c4dc1574a297012b66f00266377af8ba93909f45f78e607","signature":"9c9221954c7e4354f0499f4aabb84a43506be7e4686dcac7eb43455863c65130"},{"version":"307b1fbf5984e69183cb1a625c5731d038d07e091ee419f030bd4bf3c0a58fbe","signature":"e22176f88be4840e38913cd8d2ecd30bbf400a00b25024f700ee2edd7b173c02"},{"version":"c11a1c7386ed92f424dd4170a7dd8449753142b6fd2939a24316a7d9c39db179","signature":"929656ff244aa687d3287dfb03d592c39043a9cc57bb4cbdd35712230b43b96b"},{"version":"624a2484fd5ea9f5dd450990568de217deadda22c676fb5b79ed2fe184d054ab","signature":"5a751a7dc15220b5b24e11d4a5d8728ab4a83e6787e59499e86dc41f8acf5cc4"},{"version":"8cc036453f78f58f2657e5ff52bb5af95e3efd5eab523ed42252bc5449fa0315","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8ab61002066e314d8b266725a4ed3db857e41c7aa11927aa4a38386370204af6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ca973ee48e138941af5747d17cc31073d1d08f57241a81b8a6370fba035c57c8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"883302f9d5d8a7800deab84b6a25a3120dd0877748c8f83f651e30b069f0ca2c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2f0277d622e090d744ad739acf1113c61644176a78de414f05093fe587766d1c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"afcf004ee208d0c1630059de2791c1641d806d3788e2801e05b471fe6a72b6a2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"98a392f5ddf126f90210fb87cd4988042afb5e0557fc03ba32911bf5bcc0dd0b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2701c611cada4d7d6a354fbc73754848950ffc53032fb560d34a93914dbecc11","signature":"2b9aaf389c15fa7ad7278aba64edae7db672fab3a6e44b95ad28a37b252a48d6"},{"version":"b4b9ac3c096a51a1a127bf2282b347c87db05dd1da22f33d140afd75bcdc8f77","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"31cf5e25aaf868c1ba0e195b7b62f5cb516b70a5bd6e2ffd8eebad1bb011c24b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c8fa10463a099bc87dfa145b710752192ece654ed08157d6e8bc1ca6fd83b73c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0253ca94846bb56a34746b1477fb3056f68fd66868eaf5882bed6c8d8eef7bc1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9b80069148c23348d866893c51792242c00832e28ee92964dff0c305ad1ba8b6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2ce5c938b33684398ed23f32a911b5ac8433e3c85ef84e75e1eac96da7ef3bd4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"65b9594e69ea0e93b7f2d12c18a3c17c5a8a4f13f092d7e7701605bdfedf187c","signature":"7a5c0dbf3696c0ba77a7a119a5ad131c1fa6a959fa284527d8a46b390bebc0a9"},{"version":"294fec2ff7cf14219715ef178115c68c54c304d984b7fe4409728cfdcf910331","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"eeca00e97ce1c893d0b328211da89a8fd39bdab347da2855b8c99b8d1f433727","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"eddd32b79454e4df90e6e3bd8d43a997c8813aa61e2be0c79875c87a5d9f7b2a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2c217dd4af49f75ea5671d76d7129d3d3154589fd0193c323ec2c687ed13ca62","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"70879fcfe03c15515033be18baa3afa57f4f4a6d6bce8801e87050b02e04df55","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"18af7cf6926730ac881ed4698c671c5efe5963ec30811f23935dd6a352b0884d","signature":"06204db393a51c743e3f66ab6d961ff115b2339c936b98c2d7ef7574bf8072c3"},{"version":"41a4b706f190423fb86f0fe568b685dc59c4a76760b506b02c10a3eb70ff880d","impliedFormat":1},{"version":"e26d25ac80157bf4dfaf1b15917520d7d5aa515389c7c0464911ab0129e58835","impliedFormat":1},{"version":"e305ba550c25a1807b5c432b31563f897c82111c8bce166e560029c94df204c3","impliedFormat":1},{"version":"a3e0936d6b795d2fc46850dc9c590152942ee297f3271dbd3d8588c3983592c6","impliedFormat":1},{"version":"2013af14579369e7822fb5f20f01268f3338141d4a382d5969144fe9c6a25ccb","impliedFormat":1},{"version":"6b7fcb23322518af97092aafb777aefa9196a1db215071fe8f96ae6f6101b498","impliedFormat":1},{"version":"a98d81cd9207ec9f1bd54d454809a60b267043dd56b165bdb6912bf9d63d4759","impliedFormat":1},{"version":"cd1aff7bdfa3af19aae3a21c958b932af6941ed89af069bd789f9ba5ad43377c","impliedFormat":1},{"version":"33e9caf10988726e5f7be53d2a4ebaad8db16e51b5f81e5ed4dbb0068f3a88fb","impliedFormat":1},{"version":"cc0d535d54cfec869fe4d28b9acda7e3427b25d8d84fdfe495021ad4cdee301e","impliedFormat":1},{"version":"f3e743e40c9a3b22de28624c8428e1df4cedc2fa3b3d99c546ebb95592ba1978","impliedFormat":1},{"version":"f8dab311b48db5e3b4361d76a629201098efdaa1b785120353f0ad221b263682","impliedFormat":1},{"version":"261b2daf69470a9e9d1ef26aa5e0c23f1611af380eb5e5f031c776ba57367741","impliedFormat":1},{"version":"e181f3ee5845726fa8077b39742a875d4fbaeb35f036765f95a77afcf982f989","impliedFormat":1},{"version":"9227c7d1182bc93c52d39d65472fef3f850a0d0145e1fac7388758d995878ccb","impliedFormat":1},{"version":"d90fecb86a618f308931b2efa45d7beeb73185db71ff184c1066b4e5f5900771","impliedFormat":1},{"version":"69af21e2252e417f1f26d2e6b1ded1f20dba11f066ce0690c53946f4369e00cf","impliedFormat":1},{"version":"fd4ae6100e700ea9e36ab622968f12ec03792f98f7da286cc44649395022453f","impliedFormat":1},{"version":"1adede2d971ad167525195ed811b8e0af2d17c728350689c470b6d5c5a3aa60c","impliedFormat":1},{"version":"4486b61dcb644ea4527e90b5bfbe7600b9ed50371d334a0c01e8fe661a99d952","impliedFormat":1},{"version":"dae0c9cc1c106f7b91a726813cfafe489dec4fb1e3f4f7c684d14e5f99f96b95","impliedFormat":1},{"version":"bf5fe5a9da7069bca0bb83fdc31ba9f624ac778c3ce25a06a3099d83a92a4858","impliedFormat":1},{"version":"7a0958a789740f9ddba6b7d80fbb1ae4d97da910970c1d59965ebc80304ad22c","impliedFormat":1},{"version":"54c9f10d962306a39448b924e73e631bba8221b5797ca4b2dcfb00dca78827b8","impliedFormat":1},{"version":"7125d9240eb556853fdaf7c892b60c630f6d01bd11580a32e9f299b0b91ca4f5","impliedFormat":1},{"version":"b339079a6ce4d7e86dc68ea8af379ee8700dfb205da5546ffdd3cdfe4be43df4","impliedFormat":1},{"version":"a97af3b565a5649c8cd983df40878da9a4bc080cf1a918199574f82a25fa727f","impliedFormat":1},{"version":"6e5996003f6555309d988ceae5b9952f3de47fc6e5110e5d64c612d4f37cb490","impliedFormat":1},{"version":"f07a1939b55120050d3fb95633ac6f8ebdb594e63eaf8f14c123480cc4bec03a","impliedFormat":1},{"version":"325e48e1f58a88830852e00f4fe7c515afef925a1254975a3ba9c54c9156f3fb","impliedFormat":1},{"version":"79caaa6f2db6cd3a1362f50b48ebae937a234b421c66516b8c98282b7d713d12","impliedFormat":1},{"version":"e7f0aa1f6c72a395ef5ac8749c03a31b00ea527abca2db5aea63804fef2dbb72","impliedFormat":1},{"version":"edca8fd9f3960acb4223f2a596d1f944320193afb076cb8b96afc19fb83144ec","impliedFormat":1},{"version":"511a250228acb3131a8c9eabaf45a53c3015c8a5abffc1c2e8b37942b779ef31","impliedFormat":1},{"version":"496927b9d1a6a7bea58453b9749b878ee1040ddc22de6db6e689db11ec111ebb","impliedFormat":1},{"version":"d17c142d0bbf9f20c696ed0224d1740fbbda0b0211225f2e48732809e15f2505","impliedFormat":1},{"version":"67e2a7d5facca25f1df14bc4fa4167337405fdc98b47df49b98aa2dcdaff5696","impliedFormat":1},{"version":"e16f1bf72fcbea703f1b9dba81e6faaf65e29d24128f9e09504c9a862f41c9d1","impliedFormat":1},{"version":"2e47b85f55b2c6606b137b4be24ea386c58bd9621370d2373e95b530c955102e","impliedFormat":1},{"version":"1f9fcdb7e24f1f9b391bb9200e32f6353781a1c604159a6257e9d8f1df7b9bee","impliedFormat":1},{"version":"3c788378c8c8d9525c168ce0578d536607896146b88bb5f6670ab19834be30b3","impliedFormat":1},{"version":"d16aba532f5391ccb1a61f90cf8e904ecabf3105641c398df0f4a79cac27d472","impliedFormat":1},{"version":"8d3a30c921fa2ea91d1bffc7fd9b8c42dc74bc819c503208122bd2e84cbffb5f","impliedFormat":1},{"version":"e4fb93fd2ec72ebaaa1e657435d607ed155da0005e6e30c440257d9fa877d834","impliedFormat":1},{"version":"ad103c0c76b809a8830ca4f9a8c8cb43b53d578bc63dc8c63b1342f1de90f8d8","impliedFormat":1},{"version":"7e15e2f23da806eabcf4bd0f1e48d7a5baeface210f05837171b50eed7d2b894","impliedFormat":1},{"version":"fb5e2afe7c4f988b615085166421f8d88464f6234abcc740ed861eccc0e17ba4","impliedFormat":1},{"version":"652af46b250ee5144ecb0eef7bcfa1647c88fd170cca974bdff9bb315f349f52","impliedFormat":1},{"version":"790daabde36636c46a264b1628f488ae49b2b378810383a1059ee722e82ceab8","impliedFormat":1},{"version":"89a58d0ab59eec78a6b6532e5d748f9942568891619633c890638a2912224ad5","impliedFormat":1},{"version":"9af24ffe92056dea7acff1dee779be364ad35e5f9861ca417d17bfb447a0a230","impliedFormat":1},{"version":"8fab0b106acb9de629cc3f7bf784187cd59d506d734917c4f140d02f0dcd167d","impliedFormat":1},{"version":"d0ba3b6aaef0c96be907938b6fb2a3a04a5db59de34a40f7e426bc7f10bb46d6","impliedFormat":1},{"version":"d91c919538e393ed3c649270a73f239ea7cd9f312dcee7dad037869a6eb0eee0","impliedFormat":1},{"version":"fcf5f4ff294643e6ea5100d09f40668a3a8744b73b8f1c397fac4b17ccecc72f","impliedFormat":1},{"version":"af3bebc2d30fe79abc9a505bc890d16af72f8ea21ec59009e9d57c2d8f6e0b01","impliedFormat":1},{"version":"784c657f85bebb1a7d94ef05e10f1cad4abdf32798203ef8631f7c3aca2390dd","impliedFormat":1},{"version":"e488fdf1efc9112b9ae08aaf2be027c3cc5603b916582c45d73bb3885728543f","impliedFormat":1},{"version":"a43b695758408470608b548841c97ad3827e453fe81ad835e29b9871129785f7","impliedFormat":1},{"version":"96dd9a7f52627f94b64da26c1ce05d2350941487861c8c27a0014c67273c8a40","impliedFormat":1},{"version":"0e754d4ed9a6cd6c131515ba94f3f1095fb10ac3cb0c20c2cbeef9e895f924c9","impliedFormat":1},{"version":"72cac4a4359c6a5e2e5c0ece767455797e46871350324dfe42ab14238f675729","impliedFormat":1},{"version":"cd091878f6b6994d9307156bda8a4419c7c41c524228d9e830f5fa618d70672e","impliedFormat":1},{"version":"bd3bbe444bc7cc28757c7669fb186a9ba326d4b65dbf99e18b2b5b9ca66edeb9","impliedFormat":1},{"version":"b10c73b3d7703d2d870d35631428cdec737d4bbc06706b6fcc6f6e058b8e1594","impliedFormat":1},{"version":"2543b46883befbefe10c2de9f3a0e7809de7baa09e192edda748443fd15d38d3","impliedFormat":1},{"version":"01cdf83ab596024078a6ce08ff990770326ceaf16f9081a8e369b9bc5110cadd","impliedFormat":1},{"version":"bbb9a17f2654caa1d34f49428c0e48ca0fd0d9550f5f82da6f544c924afa17b3","impliedFormat":1},{"version":"a9d8ba2c15cd99f51aa291034a1afff2f67f1f88259d2162eddaa25e3644032a","impliedFormat":1},{"version":"affa88c9484982a9aa35b30480059dadfe98c3bbd92f76513ae2d1d7e68096d2","impliedFormat":1},{"version":"3436c4b40e71c333e253578f6f3176870d4963d5f4ec14862ba5e40794bef8bc","impliedFormat":1},{"version":"a02f786598d002e2e42854d9bb4fc5a4ac03589538055a0eca03c9ec7ad35457","impliedFormat":1},{"version":"348863ce75f819f43557d134e5e7ff11a8ae582ac879349cdf9156bb696012f7","impliedFormat":1},{"version":"235ede03bdeeb87ca21b68fb1398ebc4749a924e2a7219977b71bfc9c51574a7","impliedFormat":1},{"version":"ec83a163716e8a8c2324e3f6b64c907ba7e5247b43df47f52edef954232c0211","impliedFormat":1},{"version":"15a76d1390ae38fe474023a51778887a6e39cc4204f65519a448ed7d1931275f","impliedFormat":1},{"version":"d7a9a81362adcd395f9db48531a89df84461595d189a234796e85ef983399042","impliedFormat":1},{"version":"7ac3584d37571a5ba61326f50860d843072ea95673e53d23b0b684635db9db00","impliedFormat":1},{"version":"96354248b7b7fe792c9545b7b153ef20763677692e9baa9aa6d1afbb17376ba7","impliedFormat":1},{"version":"5dce9f1eea7d40ad9f10295dff47b7de6fbb24b33858c0ff91aa75043e9899d3","impliedFormat":1},{"version":"d324bb068a3a98f3d7ff92eed388935a5ed4bd46b7678c5bf057b5a2ee9416d3","impliedFormat":1},{"version":"fe6e76ab5933ca777c6ce422c7023d44799d4832a8c5ce35e3592c8430867329","impliedFormat":1},{"version":"cc5beca247a7da7286a82c0f3b84686a922d0a402ead5d11b9ddd0dcdef5c762","impliedFormat":1},{"version":"24661a3d44d16268a8ac8260f35651526c54496ed5f29e559c066b7b7e6776c9","impliedFormat":1},{"version":"7c7ddd5cfbbab70612c216ab1d1f982468118fead1d57948ed31b41cd692c2fb","impliedFormat":1},{"version":"461ffe558e162cb3e451654eed59d0090a267818fde655088616be907007d654","impliedFormat":1},{"version":"829df07ac748fd372b8bcace5b46e6ce0420d2fd65c23f64b86e8a099b69a21b","impliedFormat":1},{"version":"162ff76724612621b2623b71139fae21981b276595c5e93a909b464c6eaf6310","impliedFormat":1},{"version":"4ec0abadee52b5287cba7929ce1c34e81a67a046c0299908158497ee85ff20b7","impliedFormat":1},{"version":"3b60b6d30d8be5b573e75d148abd155fb74cbce0795055965ad505afa4b181f9","impliedFormat":1},{"version":"5ea0f9746d216da9d45885462fdad43ed26dc4473ac6d289a94661c9a1e7bbbf","impliedFormat":1},{"version":"96198fa503333a1856039319ad4bc45c6e32afe2ede6a050e23fee2127139a40","impliedFormat":1},{"version":"e9dbaa3c845b96ebd98a7a3c59296fad4f5cbe4c2e471d0c54a248dab6d575f0","impliedFormat":1},{"version":"63c5fe7a04273a69125008737aa3c18212a1276b3a2f3892080c346cb589a716","impliedFormat":1},{"version":"adca096d8a06e8fe1f6f8a1d95dd176e0ec2216f5dce683c9c3656a9bb1e1f10","impliedFormat":1},{"version":"fe5cfccf2b757c44e7251d6ac822f4892d63f0dbbab920da4f24b893e655e836","impliedFormat":1},{"version":"e2a5e2a231048f1b0a8c6c123d524adeb3eda1464ac2413fd039cf5afa57bc54","impliedFormat":1},{"version":"8701130ab14da66b4e908e13c3ece584b420399cc543bafca971c414059ee5e8","impliedFormat":1},{"version":"811e9e98ddaacadbbcc92015fafd5f5ce0dbebc14f3536cbe225094b1d61f885","impliedFormat":1},{"version":"f7c8e5f19d7159bde8f8e9c6561c6e517953457faa86018a7f963b72863380fe","impliedFormat":1},{"version":"c0bc4b28c78bccbb158fb2e8b3e37a86fee5f26b6098a857befd864790da7cd8","impliedFormat":1},{"version":"e7b604762369c8fa5ffafb6e238a2c7af296e5a25bfa25cb33191b525f064cd0","impliedFormat":1},{"version":"dcabfb44bd25183c919819f87428fc589b20c3b9586825ec456f94cdb67bd316","impliedFormat":1},{"version":"41c39405eb8d94777d8b30d2bb295c258391ac4a45deef8d2f569b29bb82938c","impliedFormat":1},{"version":"f5786f9b0a39c790d245b33436e75576990e41e995e1fff1b0919833a57f4357","impliedFormat":1},{"version":"970025f12906d26ea3c1c381199eb6702b9c8cb0bec44edc02e86e004cf95eb1","impliedFormat":1},{"version":"8dafc03ad3ee7acabdb9254c702b80755bdafa7d7548cc6ffc21814e83055abd","impliedFormat":1},{"version":"e0dba6e973edfd7a5d8a7307ad1e6ec014b51fb7dac507ae132d1f9429016252","impliedFormat":1},{"version":"cc2f61e4781ad29f2aa93d4850de1b1d8313f242631f10ca17cc99411eb63022","impliedFormat":1},{"version":"6679676c1dc90d9c371f3de8430bf070ed36d2677d9ce3d2a336b54c5c40c2d7","impliedFormat":1},{"version":"cf2b168364792895c95f8f98f8fb662f07787e518e6d25b0f7c4aca9927a1bb5","impliedFormat":1},{"version":"db115e097d9ccc281414e7cedebfb0435d5bb14022f147331fb1bdad09404885","impliedFormat":1},{"version":"aa78c2b93bce87b73fccd6726cac3cac4f62927460ff3495f2a05553b4c04d3e","impliedFormat":1},{"version":"298104d50f65103c256ad79ff5128141000e4544a6afaa998d3099bee3975b84","impliedFormat":1},{"version":"a99f5ced5c95d7603c94c66a4619dfd0e737351bf20757de516b7f8ca193cce9","impliedFormat":1},{"version":"341b20f291eecbfefa6760a69f7f3f18b2094edcc794f4e78e903a5f0dd86fa6","impliedFormat":1},{"version":"238dd354909fc4a682e0cc4bd0d1eee8ba03197a4efa3cb284e502355eaec8de","impliedFormat":1},{"version":"8a3156a33e38b19f00d543a9f7a96054a0a4b051533449fb04267b4e533f55ef","impliedFormat":1},{"version":"bfd14082a4db87c2847135aab3d617ad7b488b3e65ac82f1620742548ed630f3","impliedFormat":1},{"version":"e2aa5b5cbc067b485de95616efb852886f4a1a43685ed7ea0ee8e08fec961cb2","impliedFormat":1},{"version":"3d6d27c275808a7e8540b1778a5d3808542518acda03f5c1ea4c9c5831058ea0","impliedFormat":1},{"version":"f534c1a02cd756679611ca2b36431b51715a0c59a070d413e292dfa23b9b5c6d","impliedFormat":1},{"version":"e26cccd0ef5654714877908c1674bee29a8e53d60c8c2d82bdffc12cac6b0fb5","impliedFormat":1},{"version":"ccf581fb8928f37fbd6509a7d8fa0d32156fc4eb414f434bf81cf7bd6849f7b8","impliedFormat":1},{"version":"69b227120a5245cddb0805eea82a0bea405872bcf595d2fce9fc03dd16133291","impliedFormat":1},{"version":"d6f495bfd0020102c67a6f80e411b00b913a001c468001b7cbe8c592c748f301","impliedFormat":1},{"version":"eb4463ba66be74eb04aeba3bded1f485a6ee90bed8c28a2c2573f0c983834790","impliedFormat":1},{"version":"a76187885d25a8aed20f71760c116bfb89ed1612d125bc190ab25a1a7a87ed91","impliedFormat":1},{"version":"76d36099aa1a0c7ea690ee1675ac4dd86cc62e2643cd40c097899268f8d2f7a8","impliedFormat":1},{"version":"815d7ee4cb5383f94b88688b8f2d70ce3e5df4de147d3669d225f8bfcffad673","impliedFormat":1},{"version":"afc6a3b94e405b3ae5d5038fe66f3b2412300c93ba1250805ee1a7ad19964ff6","impliedFormat":1},{"version":"b168bf198df3af94f54863a77ca14dcdb67af689d4cd876328f7c70bbb7b985f","impliedFormat":1},{"version":"54a40fe6e389146cb444299ef2d7d6e4ec83b05a9df2e7611fd1d1d862b2743a","impliedFormat":1},{"version":"65ec140c7cde7edee0f611bc84ce505cfa71916571e76f5cc197ba1dcde32f2f","impliedFormat":1},{"version":"969a96cb343d30bd8a28bad66c77049aeb0fabd9f608ad82caee751082685eeb","impliedFormat":1},{"version":"b30e161d3bbbe3f8d15b0bc5d9b47d1935ffffc7ced1099fcd84536c906af711","impliedFormat":1},{"version":"3eaea558e36977f924d85b3207406594568053d7a52950081b7603d112edefce","impliedFormat":1},{"version":"4918bfaa32bc0f63380d84b19bf5adff3188797b17b055417bbfdb09bd531d1d","impliedFormat":1},{"version":"5750305060905aff115cc0a6f295357099856fe72d76a1193204c23b4b9417f9","impliedFormat":1},{"version":"98195f663b1c09572bf794bf2fd7351b05d5895ed471589c0c79ae2e7c7d6b97","impliedFormat":1},{"version":"355e8226f1a83a02c2c5dc22781defbcf171df314f6f3205318851fd4550132f","impliedFormat":1},{"version":"8abeb772b7a7763686fd699980b74d9895863a758f2a6b824edb3d5e5c235078","impliedFormat":1},{"version":"dc1e2e53c9935f62739ca37d9acd484e83fd25bc051e5a330c9be0acbe774253","impliedFormat":1},{"version":"71a542cb540a3c0d4d954d311937a4df56157b0797489ea5cb8a9e57f449aefe","impliedFormat":1},{"version":"49fbd0ae0b53936499ca6293450230273cf299e017ac4a1cc8de936d50d8e696","impliedFormat":1},{"version":"d1a6aec1239a47bbcbbc55324d6ed293f79d4554f6bb0e411e206a9e22c50aa6","impliedFormat":1},{"version":"4e81f6b7048f1022bd8dd7dc18e43b4aca8967c4ffbfc8fe80bd4277936e5be3","impliedFormat":1},{"version":"5ff6328b404fb34d2828b501bd16f75bf17590d2b03a66a469a0359da07a06fa","impliedFormat":1},{"version":"149fbdfda86091cc37779a6eb3f01ac5c73d6e6d34a71e76bb3702a1ebdd8bf6","impliedFormat":1},{"version":"3c1e92d9a7a0c81d02f016c47de63a39706bb0e36231f2e6727a08cbbef6cfa2","impliedFormat":1},{"version":"ebec459a7d4933732cb453254997b9b9e7d17319dd40a29946f985719e297927","impliedFormat":1},{"version":"394dec85f81a33891c71f4e6a1b9a40afdbe93210d5dec749a2791deb57df5e4","impliedFormat":1},{"version":"19d2786de07b0dd973e5515d84182d2734f1c1ecf602929f75b30081fb20fcce","impliedFormat":1},{"version":"728d3db3ffdbd649c96fd64cb5766993cc8cb10b4ef207403fb98304eba04f57","impliedFormat":1},{"version":"8e4ae5371abcf89ca3059e621b666f1a340db0575f0c8635431417528f2d6367","impliedFormat":1},{"version":"de4f5917a7bf2c62cd3ab4c171620fd6e88a4a92902f02c0808ae67793e6baad","impliedFormat":1},{"version":"8f09f0abdd346cdbb1e545a44c85dcefeb047d07080628562a328d3e88160beb","impliedFormat":1},{"version":"4557c1259460570a893f9adb1547b5bdd19948497740c53fbaf654b20ded5855","impliedFormat":1},{"version":"ddf8a6692f74ae9ebdece687ec1cd9ff63811c5af27381b40c7996053a1b0504","impliedFormat":1},{"version":"de735626154dab7ceb24b208728b7461aaa5ad8848152ab0b39192e7bc0aa4be","impliedFormat":1},{"version":"c6296acd69aca14033c815aebc1b4c7fe72b92e44145dfe6432fe855c8c7b463","impliedFormat":1},{"version":"fe7df3fef3d25c0455e7cd4f36fdff7ad7b4d2163e7285a74d6a98a6cb48a282","impliedFormat":1},{"version":"7e9bf7c76b60c4402bd48996bfb0d1fa552a576991f9f73dbb856d15b0346793","impliedFormat":1},{"version":"29199bf01375b374d516e5c8d5d8ce1dac3c07cc52b00eebc0a7ba7b05baeb1c","impliedFormat":1},{"version":"7696d51d4ce2f2f21e9f73214396bfd823bee6c57f65d39e6a2264c40dd021f2","impliedFormat":1},{"version":"4b74f4072dacae8ba4f21abf5d042e5da499d027b0aac8e2d8f42f5f591453a8","impliedFormat":1},{"version":"d3b884fb07719c9457497fa9d5146f7977fed29df303347ff4175f630b903fcf","impliedFormat":1},{"version":"665320db7cce83346ce47ab3baa657b3115d796d28d8f778a98e7f23962b1247","impliedFormat":1},{"version":"3f471b5d1f378519b8276a869cb746d5cfc9b2bf4d7e5cbd0bdec3f9b5f81fe7","impliedFormat":1},{"version":"4afef388856e350489141ad7d90ab0767a02954d53d953f558237e04c89b5d0c","impliedFormat":1},{"version":"7b16f7f12771f8e25117321d1cb607e06be688ed8db002fe2cb13b716d0662b7","impliedFormat":1},{"version":"9412339ec64000a6dce5898fd848f025d31ec3b446872070cc041ade876c0c1f","impliedFormat":1},{"version":"726bc5d2505bf6051e80ede05a576e21d65118c077a8407ef4f9eca8d5445464","impliedFormat":1},{"version":"05f2af853ef135671b9482077d19a2935e3d924debbcf2f4803110bde114f113","impliedFormat":1},{"version":"134078b75b0105535f6164680bd73f88f9ddaa84b7e0126aeddd2af7ea27bcb0","impliedFormat":1},{"version":"264f0b10beaa4141a6bf228d2e22b19ff7baa76b39388da319bffcd1ced741e5","impliedFormat":1},{"version":"98682512d496bb618315de173d7a25ec363676da2457939f42b75a23c5acab87","impliedFormat":1},{"version":"1471ca4439a9dc9787976d1c9ca2b913908f4029465c87372d2efe2069c375f0","impliedFormat":1},{"version":"54b8abfc4713160ce97f91758ee1e8a547ba67c7328177d5e4c40613a3e87f79","impliedFormat":1},{"version":"60894b9993d7e1a7be37933c9bfe96b228ebc206cada93a44bca9d30e12d8d8f","impliedFormat":1},{"version":"236ec9d640fd6438a08dd2be3fb739352f147de529b4aa1953e4d4f74d6638b8","impliedFormat":1},{"version":"e75ea841bf22a156a7ae8f95eb7b1d4479da8c291019148d0a643027356b76c4","impliedFormat":1},{"version":"4111a7444998538da1f6f76378412547281e30cc5a7249b32e7402f66f83a492","impliedFormat":1},{"version":"4b9a4b3456012104409fde7f7631be98068daaafe1c49b627b8d92f033960b67","impliedFormat":1},{"version":"7f7840713032b2ad3bbc379ee2d401489b4e563294f7c87dbaf2424a6682beb2","impliedFormat":1},{"version":"b52b80732805d494ffee704b80f689772e1db9440c1728b907f7b25b3328d5ea","impliedFormat":1},{"version":"a805a58a4c72c7d513295fa7102284dd9cf76c1470e1845a6d7e9afa4bafa609","impliedFormat":1},{"version":"724e0ca25a06f553306e33a45951c368346cf1fc4b26b8bf4bf88b1479131659","impliedFormat":1},{"version":"b0880e598b7256855af6b9ea2aebdf47372444114264b56da9d25ea5f95d064e","impliedFormat":1},{"version":"3ff6607fc3c3a85814ec3d6e05e358d99773ee7c7b5e9deed5e086e39f5372a6","impliedFormat":1},{"version":"62247290540b91ed85258d7e8c67c7786e38bc1111429da9fa42b1b34e4ffdd2","impliedFormat":1},{"version":"656ee3f8184b5dfb87200f73350e57827dba056130d15064406487c0993f0e6b","impliedFormat":1},{"version":"b14c19907984b69ce25e011e6391ff6a150bb31f344d643f2a3d5af9aeb4ba73","impliedFormat":1},{"version":"2b77a0e88653109c708203a50fa23bb50406a9c8c7f61883c92e009f778387d6","impliedFormat":1},{"version":"2aa50966f709107108e7ad733c129c81f9e42731492948a9c23d4f8a0de5ae1e","impliedFormat":1},{"version":"080afc7aa193ecf03c78afe2e3d81cc3b18fa482f1bd955b4dca67bcbf220eb1","impliedFormat":1},{"version":"7282df0d72afd1c283644f5827159ffe0c899850fe121811e6e7e3eb77416868","impliedFormat":1},{"version":"07c70d4602003ffd9f23f8f6fc2693b7cf024a323a6d6146b11659105ae588fa","impliedFormat":1},{"version":"79eb7464a0215c82cf6fdc55b2378dc0a3aed416f03dc647fb6956975d446ec1","impliedFormat":1},{"version":"8cd341d72d1ce25d33dbe1681a9a5f27fecfcf65d426a0d0bb80ce97a1e37d50","impliedFormat":1},{"version":"4f750b488d0d1019bf8b6651e68689debd6106312ca7f4fca22627fc2d0acc04","impliedFormat":1},{"version":"4307994ad4d3a8d842a7d7da76f45f84e5eeaf1580e9b6071dd5fa6b8b21de19","impliedFormat":1},{"version":"87b54711b1c9791dd95b4ff88f814b489089f5b128f29e8a5fb7f6b8123f739e","impliedFormat":1},{"version":"4763437e8a65ec15310aa20a4ec288eae3de1b94b9426336ac423fddd482d70f","impliedFormat":1},{"version":"e2398ace0c73a5da036dbd6cab98008a251c709c56f1b665b6202e99ae3450dd","impliedFormat":1},{"version":"1a59a1ed95ac47cb6d1798a4dee9088b847f41491e57545e788ede35ed1204e5","impliedFormat":1},{"version":"c89ec75e2ebb2f5c2dce2d5f85ab59951cdd217748a49a6e0bd102fe69f5eb75","impliedFormat":1},{"version":"e653e5173f22f243892807fcd85800dfa4efe84be40e0ed1cccd15d62e1c9da4","impliedFormat":1},{"version":"973c290e94835130e51e934d078ab80e975a7bdf5b9563f5fa8de080a06c588c","impliedFormat":1},{"version":"5abc40134600b35d15fcc7305d5bc9e29942c64dbec6413137b55e77b689da1d","impliedFormat":1},{"version":"85c3303460c6339a77ec37ed9b7e06974f6d8e1351181b3f6f0c5180e0e7a76f","impliedFormat":1},{"version":"88d761b9b53ee5aa4ffe94d18e1c05c31ba8778beddcb8418f303c184f4f61f4","impliedFormat":1},{"version":"8ad1059fc2cf09ab5208fdc50a974a1a0c0f3737d81c2aa0718c583716b49f7b","impliedFormat":1},{"version":"ded7cab1c0c297958efedb1c4569674117693e0ebb6e8a1366cf7a629ba490c6","impliedFormat":1},{"version":"997c088bb8c6e1d869a689a3db0157d3efea26fa3fe49ece5f01044321949769","impliedFormat":1},{"version":"aa4d9968e228a15f4f93ba782c05f19de5aab2598ef8acd819ce72d6f4c9953e","impliedFormat":1},{"version":"1c4420d12393decf20734fff3f09f44daea5433869633ed11e9fed9b316523ec","impliedFormat":1},{"version":"25e74c23ca9d123ad4726803694ca249b958270fa3e98eb3de7f339f15241a45","impliedFormat":1},{"version":"87537c5c41597d3355cd28531f715bcfa77b73f0622d49b28b6064b3c0ba0ab3","impliedFormat":1},{"version":"56acbddbfb96d3e9502c73d87f216fd16b9954ce7fc345adaa51a054bbd548cd","impliedFormat":1},{"version":"ea5f8a7e470eec67d9b3a57a311393d9b8146d59e7d3965fc2a07745aabb50d1","impliedFormat":1},{"version":"d75a15490d5dc9b8bdd209200429a4cd31c139b1503e22e1ef743e6d4fd160f2","impliedFormat":1},{"version":"fb238a904625a2a0942f8f0aad2c96d5ba7b684b59890599a10d73c1bfd3f771","impliedFormat":1},{"version":"35c91fef4484772dedb9c253a07ad91912a8e349838bef1e7c85b93b6acd39c8","impliedFormat":1},{"version":"b92d1f42b729031910871b073bbf05b8d68994a91dc43a7fc64f88abff16db54","impliedFormat":1},{"version":"beaeb7cae58c1b074e1e5c4c0cc4e205b1763f0e9f413d7062951e1cf539b450","impliedFormat":1},{"version":"2059b7deab3beb764a2368200ead025358b48fe470bd6850500785d94c8fb5cf","impliedFormat":1},{"version":"da08b48bf74446b6f988e95f501693844d59d445487d89d2364b8bf431c8a21e","impliedFormat":1},{"version":"17c4db14970964450f5bdcd1349e6f3035419db7f7f9f88ee966b3b34cbaa8b3","impliedFormat":1},{"version":"a9e6f34151c8629364892059c1689e2f99775b3642a24854d0330112d6892cff","impliedFormat":1},{"version":"bf5fb8cea51021d395c45a092c1e97534d498c91812b99fdb07c658cf5990585","impliedFormat":1},{"version":"bfca510544abac5e94459e2474b5d4ec143953e0e664f08bd789b6b3f1a2d57d","signature":"e1c4a8043b4cb75b05e3f74a962dca3102808379956299b7a5840dac50afb6fb"},{"version":"8886b6a0bfbe22b4dad300580445f890c30037df4d6cdf5fc6737db0b6abf358","signature":"f7c40dae304c15b5ffb61e0bffc5f48c03fd3440f1d5a4c2d14f7d7ed3e4c862"},{"version":"006a31c6ae90e0a32a43f131e71f40af36fee2ccbe630c67e89a614ea086eb40","signature":"0274669c63081de789d9e45f6bb4f5e424c7ca2a289b7e3093e86c0bddbc08ca"},{"version":"6f604c875f792abf866c9e005868a356bf61dadbbbd9accc5fdf21a2f54a1b6d","signature":"00fbb365a27a2e87ccb013ac9a455f3fed26be397ee2deb7ffcf76d5b4efb79c"},{"version":"bac854177cdf377ed0d3203109c4d2a5dc0e12629e190493ed59310bac59d4e9","signature":"cfcb4b38c0009bacd9680ccd510354dc62935cf64c289dc28715ace710637961"},{"version":"e7c935166444068c3eb09500cb50994cef6a3ba4a22fddcc4a7147c8937d1a2c","signature":"fb891c7c97af75b1638fcbf3b1cf51815a1f7aa9192f202bc4fbe295827537c6"},{"version":"daccea50b423a8639f41acbf09c58562c6981dc70c6c011c162b286c60b9ea9a","signature":"fc5fd2e7323999c98157bad9e3127b063b17e115c5298bc9d5967a8781eda752"},{"version":"6b5a973456b16b12503638552a525ff80f2473bba42882bb6c54d53fa8044459","signature":"8bccf01cb22376a08d48b284667f4b812ed8d38f2b723d6aeaa8c96d133d2ab8"},{"version":"cd01b1f268961d8cb362666359c882f3dfcd1cb3995d41ca2a4aa830d5058582","signature":"542542c33ba947f131b795d3630b41a32a460aa588a03aeaa5eecc38f3592041"},{"version":"a0aa3c0f42613ad29b0d793f4335a72a945faba43cc11e980d0e6e9302e4df4c","signature":"064b44fe73f0f7a084ed8e01bfb2ccbf0a6203b191fd521fc2f9c76d21e652c2"},{"version":"9737fddf5be4703209236aad30a91491d7c8812b488e95f472ffbd5b362b60e6","signature":"f3a1e1f72b8affbcc2c49613db77c9ff271fb02b9db2615fcfc355b194855b08"},{"version":"fd84ed3ea315e35cc6f517bf0b9cd112d463a26b7923256dfdbf175b1b22ad68","signature":"d86e8a694d25e37475962cecce34728a00aad3f3ae57454fb83e80c2e559cf84"},{"version":"82b15f6f40595d004e5a4054be14f1aa08da11f1d551d9aa624b112d5bf2cadc","signature":"38d9938720a626eaf80d7c415abdc14d165e67cc0a3a5fcc4f40f0a16d2ce5ff"},{"version":"7a9e337782f61f070af099cfda7aebfc939891982e2c70a40be6f56fd0541b6b","signature":"862936d7bccd7159ad7be6a060b97a4ecd73534f01f678bc6f844c6e8d677452"},{"version":"96215e8d738ab2aa6743287a85f309ca453131d604ab38e00469de31858579fd","signature":"da19036047eb5653fa5c982df7cd191f9329637e42372cedba82c9c9c75061f7"},{"version":"4209442ecd03b6cf5a4fb37f4ab23bf40b387dfef729f6556556cd3a1ae15dd4","signature":"2a718b26b22619bc0eaed2d9a958dedb8d9e52e68294bce22da1de23b74bb8dd"},{"version":"7d8570e9fc6b57c35e87a5fe6a633265959e868224a630da83c864acf5b90d25","signature":"bf42ca6a76956be05c82f152a8a702c561b5d68da2751c079f316c06de4c9632"},{"version":"e228c74df7c567b51085b10cd495ab8f543c5e18dddc533017b877dffa013322","signature":"b5078f3d864b9faa6b707bbeedc88cf66ed76ea68cc6abbb6657674ca9aad8c9"},{"version":"b0e7848e24be2154080c93c6129af32a91d941c10d2075630af61f3aadfb0923","signature":"a9f87e788da2428d806e863b53b1884812d37787c2b08d6c819253768d7300d5"},{"version":"9a5b98a9faf5ac222da8dd0d26128ef53ff801a72345ae293913f62399a45009","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"03b9d2859d208ff11d4e9101a6e2c6a956cc85de3bba967cf2f48c9c3edaf8cb","signature":"588d1b028b2f8f84644f372a7c04868b63de7e7f82d84a558b485007f7c17555"},{"version":"36c18e5dabc73dbeb2c7f65db1f14182c73a34eb9cfbc261225ada0bb9018bdb","signature":"a9ae5baa5573b6c8b87d3962c500f926c7498936182231186650c40b83fc39b3"},{"version":"e1899e2a3c2f53d0b43d87765006a74ca9e879de4dfda896ac62ebe7b59b94a1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d614c17220e0d2e13b6d10471479a1515c92be6808d889304702fbc097d15366","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7cdc3094419939dcd0a97549f716f79446ab5898332f8acd5164987b71032dd6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"048955e8ea4fff7090afeacc7a8d9a0d843199be15fb1ab402679f63d2a18a54","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0cfcd2156217783339ab722166f27ff9da99ec9194d22e9248791d26623dc36d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4d9d666d380658af5bacf49ab6844cd56749720cc33cd076ebc640d6b95712b1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"32c2851b8a07afe131d392b5474abe76bfbaae077a78353b3d76012d85b04ad6","signature":"1c673b5e90e9c0d4d79cf9a20d521cf4a0c1599948fc08375c144664a961389f"},{"version":"c1994b30201c90e29e13473fda3e5f22cf0bfb49b223beb2918819751b70c6f9","signature":"73dcef7405b59cce04dfcb6f53f903273fcd42fd9a7bd2fe68189dffd5ffedb3"},{"version":"d9141e5ff962b3354c79e8b66855b69d22a6f17403acb98bf51c00115ff51670","signature":"78fd9f116c4a198c60620ad7374fbf67fdc41baa66b8da57db3b80cb6b23098a"},{"version":"f3d0cb1b6aed52dd25b273f2a3ac15e6a93a15486336e6a80721124fa684ae9c","signature":"8483914db284e07599e4fe920f9b5ae7450f8ea5617bae006eb4e201bfacbba5"},{"version":"a897a063e3a7f64bbe9d9eaceaae4e35915b754f5e77a2ef1e4d98f7f2c39464","signature":"d406c57bf0c60a88a4cddaf0944ed69a7554658e068fb62559e2d823f9255236"},{"version":"93f5f0ee9475dd4efa82e2f75e8236045467d2170643cbc7913cbe6eb1a08753","signature":"109a47009ff1ea87255fbb9bd75f5f3a918ac7ab44ed123b521028f580aff53b"},{"version":"7ebb4b6d7875b2e7beead058c92ad71787c387696b0417dd4bd43c96282f3fb4","signature":"8146af3e7bf09f628b043910cfa6b72b86f8109aa7b06167b9eb51a3f0a75da4"},{"version":"ce42b87cee6040e06af43bfcb549a2f4b1547dc5f34182e02a179d7d689a65ae","signature":"5e25c87cc967b7bcd7949f75916a6757b59aada3685fddd966093696c85163b1"},{"version":"8588652fcc593c5cd18443011bf1d2f77ecdfee0263128bd791a4a5648ccb2cd","signature":"ed09ce0bd7cf961caa2bbaa0265743b1a22acb59fe82fc227698550a7f0b1e14"},{"version":"ad36905895c93e9869aa8e39847e0e14d10e4277f722be2cdfc1cb125acc55d9","signature":"7afb481364c9e976ea5c55b9b02006f2496e68cc009eedc57f38264121a77836"},{"version":"98e9a4c0f2e11973753af34fca47091e102656cb4603fc97a76be56aa14fcb61","signature":"91ee1b220ead097d3cc5b596db9be622f0dccd9c05e4f4cf069f2e1db077511a"},{"version":"42a4b4015ffec3e2a419476134a75a5686a31e6eb324a15d8c40a2f40b837e6b","signature":"44248c8a13f35779d07d3168c64fe9a1040ea2e66bfc4ff92567095c5b243e55"},{"version":"e6cce19f4311e741a2b958a7a2eb4e1ef2ba3b4316ea2a9d1c06037c8762d241","signature":"78361a8f013fc8aea9c04034475febbc49998b63c3fe09f56dacba1e1c73f8fe"},{"version":"f1e45d3999270d9468cca90d6c95a74367296c9ece50a08f243225c97eaba62a","signature":"11fa086538a611fe1a99a34d1378e2579a4de6eac405ad7fb9eeaa51836977c0"},{"version":"461fff2084a25080a50471a81d02babc83465d6dad5ebdcce6fc2339334eaf75","signature":"6840721f787baca46b15289facff041cf00967e6d746b6e2fcf657881b6e6c5d"},{"version":"fee5fb28703c416840f9cbd5a51aea0792f6671934a0298462532d6d9f0a98c5","signature":"1d8429a365d644633813437c38052b178d2177b4fa150670d7f9cba6cabae8c7"},{"version":"95445c3662a17b1ca8988d1e5fe59e03e86578cb7dabbbe119ecb47ec6bde73d","signature":"4929cd61e267755bf505ff0a66adda55af5d318b84798ab1b46ead808203bc59"},{"version":"1782a5b1a0c1a52a7900e34ace7d49f7315f85c75765e0948fb7ab5a686519a4","signature":"d21e563fc29f32dab8756bf5797d4c39b98ff0828fe02f8b766a8cd0f2130729"},{"version":"245dcee5a8758e766645edea2f590acadb483db2bf91495dbc797b77ba7f6030","signature":"b524e9c8c9572a85e539e60885e7cd27a4a3734d72040582434a25e486702df4"},{"version":"b34064e4b8e3dcb7fb647344f7af0c14d563092bf789c7c78b4e40592758162b","signature":"bc3dc9e5cb7a7493571d35b9b2fa5a1f39cc7ad76f998ad62e7a98b56fb8df6c"},{"version":"88ea57045aef28c44eaa1c980fdb42bda1b9824d738fc773dcecd7add4eef207","signature":"063c721b1237aa52f454a374210ba793cc38a5267af12e5f937c7f36bb33b6c6"},{"version":"008e4695665fa17db9111537757b095733fb71938b0c991a922e800a727a27bf","signature":"3e0b6c4d0b2d1c058853b3054d0ca2f00a36d93b462a4cbc97e0e20de4917691"},{"version":"3a2ee489c82522d7be3abd8e665c2c161b66b63412a2716670ba6b30c95d848c","signature":"de0b7ec69d1de3d88340668f43c9b8ef7086f96671d741ffb16d82ef844fc18e"},{"version":"db4c881c4d0036d8676e76f60ff17c6fcf240dbea3e48b5961e17d9a3b73831c","signature":"a2885e55e65c47dde0e39e7dbe3f9d931149d439b3b3c2a4480ae20b609bdc83"},{"version":"fe2d6fc0be9c83a37c497539e9c0a155ae4a0d737f2fbec8c4e62cb8b86d36cf","signature":"3d577b57ecd8ee26a71f8dcaa01d354301d4155aa8fc228210f7980278d5a40e"},{"version":"2f47a5ca2d6a3ce01c95afaa1b4c8e282f2aad225b8230747d8adcf96e990ac4","signature":"1c32f7eb0955263ecf7ec259db68a48d7a3dac279d08e7d8460314f82d0f8af9"},{"version":"5067adfe92f48ccc3efe80a230501fbdf4133c523f3382315bd276f638e5f3e5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f42121deecd22cc13234b10bf6941119c5a4b2b14041e6092a41ed0527faa949","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"996e89ff3c753b5827005b3038b59a40af40ee2425a84c42d8c36b29ec0d5bd4","signature":"3c4e06cfccaf61e890399a0f86638295927ab217e0faaac5e8e7c2a830604f9d"},{"version":"f48f78d45ac9ccc364dcb59b51c8c18624da563d88effac4c2bb8e30f1f65cf4","signature":"7b3fe3dc7a57dab64ad89df76681f912b6782a94c9bfd6f8db407b657c6433dc"},{"version":"d752def82d0f1daca49abf03c505bfbda0207a6ab17d8c3c8fe62c161a33d343","signature":"3504adfa9605ad21003156ee158e7d62866484e1902196481fa8ea0caa80435d"},{"version":"0e2c0bb4f07bff63736681697439642da0a71ec76139d921354fb4cc15bda15a","signature":"8eb5d3569824aba69bac738c173d655d8fb61b8585dc4417a04c591d8e1b26bb"},{"version":"f4d274c7cc654c02b314c49f6b5a7fbf31094c2a269b2eb1b0b46a9f8db7cfe0","signature":"0b4872603cdab838437f754c0ab373796accb15efe9d82e3d45782ce193369a8"},{"version":"602d1500478260b8bc7dbfbceff1cf69a8e2a466b5fc36d6787723a0bafe6ba9","signature":"0735b64f94e6320ed941a3296d6d58e7846ca666e9125e325a86e5494452c46f"},{"version":"5c0720b8fcd02488817f6325d7234762262ec9050f7ded1a20202c5a10c81929","signature":"79c6356c6c8f507a2a50d19631687fa929f556d84b71d48ef3e5096a9dd55337"},{"version":"133d7b34b9de5f77d7c5edb37bf3b4b265e0974cd0f1b73bdecc59e722989b83","signature":"a257a955f81d30464899ba91ac6e7caa9c165d10f49b0e06bc9cae4cdad3bafd"},{"version":"9bb62f791ca6807f95f797b1d0dde629861242b2dcfa33bb0ae97c47048a9f89","signature":"911dcb2bdcd90baf815f68fe90307bbc7dca6f52bbfa3360211741e1ef3898dd"},{"version":"ebf107562fd8f61c97e6b222dba121f7ca2e1ed8e3fe2f3afc30477691f837c5","signature":"81142ee61fe760d78d04ded56e1aacadc0596742f5c13fcff335b1f462cf54ea"},{"version":"ec203d6cbe7e79de36658e9bc31ac8883764b2b1922d082b5995ac74da0c5943","signature":"956b5043e6b257ef9a756ef3a4ded1cb6e6d17ec9a6ae475894956d271bc2296"},{"version":"d830d562e119db0cb2e1cca32a829f5923d499a33a6edbc7df8fc988dd7681cd","signature":"619b2bf107c7c61e145876687ac47175b223d7cbbe8414b8d1ab5186064bd02f"},{"version":"536461e3c082c670e05328f21c90473eaef75a7c151791f3b5684801908e8ce4","signature":"0e81f3c44d6d754411d9b3fda7802a8c6c9567fcc7298542fafd23d879519d12"},{"version":"86e579f292f4c9a03ab668d04c70f96337adb23bd1ce45e5ae3eebd97ddce1e4","signature":"ba31eeb48994cf91f0acacad869ecb708d6840d7a8df864ecb86522256949502"},"920205f073d9e7a7cec8b42adeda1b66e3287253232703d75676ae0ee280ce5b",{"version":"8e209754fc98efbd0db41c1da78eb4f46d55d6da176ad9c6988b5947c02ea59c","signature":"107444c304efac92d71733fe0dbffdffd2f9a99634aec3d4e8f4a8a4ecb1c5e5"},{"version":"83ae6145eb9c0a3b70f8153c1b2ea4738894f37bc50056f1e198549be03dcafd","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8aa0a852f50e208589ed241e4febb9212b8e6389041d5473fe87a7ee05abf35c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6b34a6f3217920c7cab89d81ea67dc64b48ebca1b4fe06e38852af49ea78068b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"25a882f252aa48226334373d5d63c8ad515b5c13a4ecd52da28d2d586abb457d","signature":"b30c66f8aeae088710859fc3c16836dedd29bdc025e7304fc50dce105b9c04e6"},{"version":"8d0f1cd6f225c29791c453aed0e97cc67d5998dbb5dcf6f9bfc082f8078edf6f","signature":"1d1927e0f32fe7113f0d8d5fabb07d479115d00e67a8a935d5f8f447e81fd876"},{"version":"5ec75989bc3f747f0f80b52c25ab505e9027f43e2d7160f1a5936424e7d11eb0","signature":"0d7b280414b0cb316adfab6c3609f4d3b0c34aa5f942a74f5d0b330aee061cbc"},{"version":"9ca0b9a530fa06d1809a3640f15047a3b8e4208470e655e2c80481305e056977","signature":"336127e3f895363130d7781e36dd97c66ed0beb436f761203f17b46772f55552"},{"version":"5223c2a2e1d1561f9ed8b1b40497bb3469b76748e63bf665a0c5d5f2e2db8fe6","signature":"4a68e8778087ddeb83520b7ed367b8e7a5413545211f1c12181b036b98b46972"},{"version":"402d7dc3e5c84bc724bb4e93cae19e6c47dab840eca709ca6f1ab5a120db8cb5","signature":"c6e9794ab00dabf9766d12dea53be438bfc18291e390e4f52846c7bd4a76ef91"},{"version":"0f4cd2ebe07e4bba58d08b8b333c8d52f83d49418d00b90bbbf54824eb5c4b1c","signature":"f10c4d9ebd838c5dee36c49d6327228d9c1de2375fccafff86909a1abb8f9a31"},{"version":"d55d2a1044af451a37f3518fdcfd4f8220a836ee4ec4ab76325b476f7323c9e1","signature":"8f0537d31f337710a710ca9da779d9fbe37da09f82a2bccb3159ce054a303771"},{"version":"e3efcd1416dddca40adbc1b84e63ed0c1f2237117adae902caab2940ecb49180","signature":"d691af9aa01aeecf1e2c9153b4ef6b880c405c8b0b1a1d8e6cbab5723e5ca387"},{"version":"a12d6c59bd1e8a28df09134739123c5046238e35e45b59adaffdbaae9bdef401","signature":"f1223da6f0fc1fca4ad9ae12e3c41227b7da0de5f55b38d9b871f3c651464039"},{"version":"fae2076383068d42680208d9e2ae564dd4077e0d3d1477e2915fedcb14b6a849","signature":"651a00540b7a7805a8de79cc6972dede798970c3b718c8374be90813037437cb"},{"version":"afc9190b510c9a2e4d839770e13f1f03546015413bcae1e0538974df7a3483bc","signature":"c685b52193d4c3022b8210703605d2b21a467ae9387aa15a8d9940785400fbde"},{"version":"8958058a659e78a065bc37368145661b711c4aae6bc0042ecd56f7eaffa5c8e1","signature":"68e39ca8f799d0bf5813199aaa097b4ee78866aadcff13cdaeed80f61fc0c36e"},{"version":"ef99eb1c01d181055cf19267e1e77060bd68afc68f11d3df1c7c0e6264ef507e","signature":"bb8324ccfbe6b8c5d014d251e08005c6edbf78874e9143a9b0502ea7d55fd604"},{"version":"790135c9dfdc9223fdbc0c8d2e908f6ea423c10a343035ab117750d0948d9337","signature":"200b6769b0036e06c05756ef6b1a155067c82ac37bd83adfc07dde3df9733dfb"},{"version":"57b21aa90f949c8e425e9e38ad715d671f1efa9335a57c20e88a06ba4cad6fd1","signature":"e70d22c4992d706b4da004112f80e350fdb7f5baa47029298ef17ec1d9b0d5d1"},{"version":"a2b88a21a2be1413f9e83aa904c1bf000d4d2317c7ad2fda21072aeef01502e2","signature":"57334c942f8bb1e6d4f71112e6b6ef09ecb4b823462f2008c461b70261a4cf95"},{"version":"299e2c44d49ed7f8a2be65b32381a2f1d4dc29f5f58cda2c722a974f65bc8cc0","signature":"088caa2b135042535767194dc7262bf930344e10b55c27ac8b5e19632407ecc2"},{"version":"3e062f101770dbfb5c213d0d0c541bdfe4e1061decaa856d47487ba822a53d3e","signature":"3bc6339203e14955ed7d1f9bed916418c12a6a692d269a609b3a33dc4a863951"},{"version":"70442efc456b60235ae408e157a6f0caa8ceacfac13f5af9d27265a6a3f57dc8","signature":"7112040a65b2d587224c9acfa4eff7c0ac117f0717d268d37d956f9961a7eff1"},{"version":"eaaa3d42b27d1992c2af437e9bccd7085236f0889b155db04ab5c2bf48f531bd","signature":"cb9d2bede0762fcbf1f1f6e59445d5671d08ddfd920f8ea01612d81b3a4384e1"},{"version":"11fe25d3baf912c908f4a63d2e05da668fd9a9838f258130cca57cc5be4744d2","signature":"166bad473a3c79783dc0342fcf4194bbd10eefcb21e24c1a4282bd71721429ed"},{"version":"0d3b1051343fdb013414fb6f6c0660838c623dfa38605e26b2fdca99aa594588","signature":"e7ecaac00ca47343ea2a525058f36c7db24fccd91b74ce24bfb05f5057514156"},{"version":"5f1a2dcf661477ecc37fcaff7cbbb4e8c69a8b5f8e25c0c0af9d436c96bac17c","signature":"a406bd45d11ccf4449bed75df91936ec197ad7de0facd342bb0ac55597dd7cb4"},{"version":"7aed574c07d60cc22247ea00c12e43f63ba380faf4c57abbaae6918c9fefc142","signature":"41770f47a4610b077aa385f08215f7dd99e8dda8643a10a1bbdb1a386a58b641"},{"version":"5fb5890e01d4926bac82a299a50fdd6c2967306cfaf032e0edd9bf29bfc96c29","signature":"866bc33412b93d1a44a41d1a8ee37e688082c1979b4dafc2ef88edc53a7a999d"},{"version":"f0d1668a2958e336807c33f5a63e9fb7a80eecb21177002901c8b18a0bc7cedc","signature":"f6eb9961bbb1fc5507f52b8081d6e82eb3ef5eab264a5d926518296dc4244127"},{"version":"438172ff2ce4e3f0ff709eafc95fa97108232dad20b179e4afb255aca1be1853","signature":"a0a545639911994ff57683e710206d749c7d92037f30c37ab2cc070178a7fc3d"},{"version":"2fa2289f4a44d6b119747ebdf9dc89780e997d9cf90242390d4bc624913db00a","signature":"35b8792f84ca377922829247470bd076930e1b1d50eb2abcf351fd4cfd3096d2"},{"version":"2d96a60d94607204ca301f60f5967ab2e500205872d93f6a1ee0a8fcbe42cfc9","signature":"58d3355ec6456b6484a31ed45c1aa8360a6f57752ddcef27438e3f145aa488af"},{"version":"6de7c4598aedd55a66a22bd23ea5fa6a79f59160ed0159fbf583ca2ac2650fd6","signature":"b40047380cb999fd7b125f2f890c129d367a15e779eab73d18dac869d34dbe4e"},{"version":"721f9fa7ea09b0eb7bc49997c7b02d9a33778fcb082790e2b3208c07d3b9941f","signature":"98cc84a3bb34e7efcdab6bedb1621ca4d12a94aa55bb5daf20ec8857ce42dd3b"},{"version":"5a9718c55449587edd2121093e1ed79cc25413c42051e3b8e430f6fd318b5213","signature":"5146399dfd1697da344345f55d124ec0bd1360bc0e90262396b38f50d1cc4dd9"},{"version":"648e7baf4ca6388a770c21407dbaa46adc09e4d2dfd20287611602cf9f763224","signature":"155a0ce1ae5dea70b7c62b88bad1d252f860edbe6973cfb381851ae84fbc57b0"},{"version":"a9227f585fa40e22f451e3ee590661f7826a17a45f4d9a7a0a1b198afa418268","signature":"7bef485675c2b5c8a43e4679d81f41a178e796dc67ba039c0e736b06462eb1d9"},{"version":"472b6a47537332b3041e73cb35419cd12532b5e4f0534abe0918435aa7bc632e","signature":"eeafa09209ef552b40702ff99dd64b72dcb5cf47bf6d7d220352e0b99def0f2a"},{"version":"d37df1a697bae4625bf7aa3035ada9e09c59000b7730d4bb5211a501adcaf2ab","signature":"c0eb3acffe92a379e6956e9348b07760f3996f9bd7882732a520cbb7e225251e"},{"version":"716faab8ba78f44754bb4dbce7302eacc7f03eebab81f5ea3d8f66f49ef642fe","signature":"c55b5dd40b4e4244911fb70bec24eab327488b4b6414513f29d5c0d4669d8399"},{"version":"08a423eeb434c825fb8c78608ce900b20d673286af790ce154d9d6bd477ca466","signature":"443618ff6091ad5b52a77dfd029420299db2fc31735f4405482ccc63a6044c0c"},{"version":"9270b456137fde0fbe4223955412bf31ab49936414a7c0dcf6432aff8e2b78c9","signature":"b9e0a6bfd2e8a789e396e72b3816b415e0d9d0088411d28132e67c2a3d447fd7"},{"version":"d04364ee40c7ac2c5ed91583ee45893ff55081eedc05760602dbcfd381565788","signature":"6151678b79e8c0d91566dc420df9fc3a78605b2c97bee9813469e83d00b0078f"},{"version":"3ef8e9d0636754f930b936b8b8fa0fa4adc8da486612ea188755a9de697a9252","signature":"c4972937fdd1931aa30ce28bb6b8ce30ad6f93041011d50722ed065af37ae3fd"},{"version":"0a71ecd37ce2f79c8ed0d7ec4bc7feae3445a2abd50ded4a88b87099a7ea56bc","signature":"bb8f5c8174b21b9b1a9d318205301ddeec2d0cf85ba3a7cd68ca9bfa0517f36a"},{"version":"a094898c49035daa400f0dec0bab7b4847d9b6711a5e685c542a15bf6570dc35","signature":"de3471094714e2f22ec35ff92df78f1f6fe7d1ca8fab53917a1d90792f4f3296"},{"version":"9c9e9cf17c0e03dda662ef2e2454eb0e7f5a3e50a7af986b510b1f472f3c7a2f","signature":"6dba4b891a0a8dcf8169b5036d8c89887af23a77aab0eeb92a6435c672c0544b"},{"version":"f28290a574cb2f33c5373327caa2b0b384fc43473b8f5a2b2c7e3ee9b284efc0","signature":"7271cc611ce47a0e08567001313cb058f965667e8960d42ee0be5f5ddaca8a69"},{"version":"3dd1f5b93047a39b98ec6866de9691653a0da1c520a6122a010621a089879309","signature":"c8d9f0716eac76f852bcef67e50d4b44ef444df32cdd6cdb9e18ec408043aeb7"},{"version":"55ff5cfa06820c06218f3c77194ff65e4c8bd4957fd23658ee544ffa09270cef","signature":"6f40e57c7efa4b25f86730dbcd1498b15ab679564cfabc54e2454826fb443ad2"},{"version":"ed910ae05ad5158659317a4d61b611f36a6dd94bdf8611fe39a2bec1fcc01db7","signature":"cd09cb9b335e1a378ede556e1a96dfd9fd412e9caa02bf73cc09d256252beb47"},"1cf312f6363cab7b3a2e6c67480fa5a5924eb6488bb43b766244327a6ab75db3",{"version":"dcbd3305da0fa9f9bfa2b6775179a16b18185d820810dd243be17ca852b7bd99","signature":"b0124b48e9bffcc064d24eabc0201dc38517629255e0441ee741835130edc7ee"},{"version":"137593b1f892b793008342a563b1f2397a19cec73c0804f7dbc4c058c944f513","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2e608b125a029f8c25d4ab404861770b43684df2db9749c92cbb4005433e963d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"95167e0eaef206c11c5eac7e16d2d8d9580da10efe450aac434812c43d4c3bc9","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7c24ad45e2417a93308da517c015517bac139e832719bd0465d9a40f0b07f35b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"281723cf910ada044e92699c67291852a3d9ec126ee0be23d9d4992a97c7ba8e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"37dec5e7ab115af745f9d30d64599d76d8c5506bae18e3d3f8e811f0432ee394","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"190ea4f3303d22f9874483ced69d02cb84512c2f7d78a8e5317273d678e47115","signature":"3ac75bb555870b81c6df3ea2f485a805cc92ed4f0101147eb49b03abb8af0d71"},{"version":"296178b677435ff4592977a4f851dfc003f3cedc7fbd9abed5cabf836f7800cf","signature":"ac8a2f4d1f18ae09215f2c3b7a9be5623890d20dba85055f0baa55057e0c60b9"},{"version":"bfba2fafa3540ca7f0645a8224c08e10a177f4b9919b777cd2812974cb8f27f9","signature":"ea0eeaa20eb610a89ca03507e6880aa8b4e3625665ca47beab4a9b2057bd1f3e"},{"version":"0c6e8ade312aff3966b2c72f95efb47358eee3dcc23b5312e17792fa8f91c928","signature":"cf65707352be96547e90a932227cfc57bb9a3a71bccc6659328bd161ebffc36a"},{"version":"d18801f6952daa987650feb3ec7ab5026edfa2884153f1823d0eae119cc1557f","signature":"a476746c1a3430e74dc7d6764252eb4efaab3f931bb8ed285d82185b2efb30ba"},{"version":"86a98cf77daeaa37220aa6733438e79a64341cf027b09a34cdd53a9ae9ad7cbe","signature":"19711303e7061f14777d29f11b17b98a16e80b3f9b71e4f1753c378b0567bf5f"},{"version":"504fcefff7a5316397d1745a08cb7a462a5ab610ca811427d9680f31032bcb71","signature":"a9303ed10475470183bdae2cd301c10b7d40c0ae06050733e5a022a311c4305f"},{"version":"3ce165fb847f5de8d1730aad08c50027d1c9c8d12c8e17de387d2e202e028d27","signature":"2a346de3340c2b612e54eaf8f283d10e06620b02d5ad511a26faa5178f473b06"},{"version":"67f06c07ea4caea29065886bd3b963d9414a2ddf283ae8da02dbdecae026ac9b","signature":"9e393f86592057353b72a95bc607d014297b1fa0a3912ac955df2dafd500f408"},{"version":"e7d14230ad1e5a1828d8d6cf5150cd2dbd07434c756cd3a252914b98dfd0c797","signature":"8ff5a0f7789ee8905d1a2adf9d42d40fe416c23bbc81957875906d660485a78a"},{"version":"a422ae3585f760c7d4610dae4614f070e2bdcaf4bc94c8182a350c109a77c987","signature":"8effeae9d5feff439f4774eb1889786a489f772d0d740ddf5f16938a9a4238c5"},{"version":"2d2ac4c473c27f104f7555d2d09069a1211f6096e999792013412163fa2f43c4","signature":"80e816ce6ab347f332104a3b4295fcd234484a8c0f78af931f6f679bc819854c"},{"version":"3bd0a863062d81723bc5d44d555002f184bde7a5aafc67c358f278ba9db4d150","signature":"91570384a3cf7c6b21ba47912ce2702c6958f0778956eea916006f15faa71122"},{"version":"622f03adfb4d39824a30ac8abb6c2f7660527c3b79acccf74d1c7472a5463bf6","signature":"4622c6f0c30f82b77a659fd0a197f27783e090585167a5fa92ed886e5c37a7b8"},{"version":"5d2f83c743291ea87c5ac07302a4e77164c5c1f264fad49019d78948a0077720","signature":"b8cd04a7091daf156ea439422d5d471be7d2fb115e3ee2155a65dad5f40f89a7"},{"version":"1aadf3c39d08e4aeea1b9950040079b0fa8baa1d5f9644667cbbb6b9c8c0837a","signature":"bb4b02b4dd58d4e434c952da55797cf052d30d36b09bd14aadfbc70a037c84ce"},{"version":"c91f4f63d6c2c84d2e0f7368a97dfb126ac74804e775e5aa5bcefd853917e69e","signature":"96d032d99c255b941936f513419610586f7e642f2abb57d1b8d2581f7d442eb8"},{"version":"72f2b2704bc36d69c78827d1f2c75ac4805d218e75da1ce9a4543370e6e7c2f2","signature":"38df43baf0855698792e9af6ab80eb4bdf4f3ca3131ca06931b6e6b8a218eb20"},{"version":"6333d1e1d79c893053a569277d87feaaf86f0f768a4b2bbad44e9ab24989b141","signature":"868858093d7e907db33c133444100e83f71982e50c28f0190d804533535cfc08"},{"version":"3e13ea8165a048ce6848d5ce3dff84dd051459c02f3cbbf8a17eafbe8afe4761","signature":"3fd2cca637c19e2dd3f641f9029c5a55176f4605009eab8fba3807d102a0e34b"},{"version":"f51d0b8cfa092a592caf543d772238f191037278e2004d58e7354447d830863f","signature":"a5a69817f699d0a399feba1ffd1de3b257911352ff7eb6ba5e91ef538af838a1"},{"version":"344c8bcb0db4ebfc98177a482885125c894f0312f61c9bd2ffd3864e47622fb4","signature":"46713144a8e07e24962b43c73b40a4f4b16e696eb52b8b519876fcd1f5e6eaf3"},{"version":"cafb09e88a64368c04eab6ad16d76fbb8638f5459d8660b475b5035a170a3aef","signature":"c1687553d0c7972ac41c68995d3e8bfba6761558bd8d9cdc37c8309826cca0f8"},{"version":"4056fa415788fb428681ff6d118600c813bae18a8939c0997e3a3a0eebbd462b","signature":"0d6217dda609332c34662a73eceb1fb383c61f787774fdf8a1da00030aeea79a"},{"version":"701d18960c7fdb3d53f81c7081a871759da5846297845a6df470e448c1ee46ff","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4e44af9a27051b8e06a5c6130c587952d20bebb6c644b96f4f9194fd3af18a33","signature":"da3929dec86ac7c8bad44758a2cfcc729cfe5d5556692a184ec657fe8d266711"},{"version":"09247acd1dc428b54b0180850f0c935da2d82f2337b34e5f9a176746597fb466","signature":"b3ee5184dbf154ca01ff12e4703e5dc467ad7c79d8b16c98b8361118de54cea4"},{"version":"1251b05bdf0f9d8118139c3ed7bb0ea60466ee664c2d58e710be9b39032c6f6c","signature":"436462fa5201a375c9dbac742a2f3e0b71d98b4760239af217ce75e0f7a87868"},{"version":"a5dbd4c9941b614526619bad31047ddd5f504ec4cdad88d6117b549faef34dd3","impliedFormat":99},{"version":"e87873f06fa094e76ac439c7756b264f3c76a41deb8bc7d39c1d30e0f03ef547","impliedFormat":99},{"version":"488861dc4f870c77c2f2f72c1f27a63fa2e81106f308e3fc345581938928f925","impliedFormat":99},{"version":"eff73acfacda1d3e62bb3cb5bc7200bb0257ea0c8857ce45b3fee5bfec38ad12","impliedFormat":99},{"version":"aff4ac6e11917a051b91edbb9a18735fe56bcfd8b1802ea9dbfb394ad8f6ce8e","impliedFormat":99},{"version":"1f68aed2648740ac69c6634c112fcaae4252fbae11379d6eabee09c0fbf00286","impliedFormat":99},{"version":"5e7c2eff249b4a86fb31e6b15e4353c3ddd5c8aefc253f4c3e4d9caeb4a739d4","impliedFormat":99},{"version":"14c8d1819e24a0ccb0aa64f85c61a6436c403eaf44c0e733cdaf1780fed5ec9f","impliedFormat":99},{"version":"d36518bd617ff673c7d9f372706f241932a43f27673187f2a8472e93c40041c6","impliedFormat":99},{"version":"f8eb2909590ec619643841ead2fc4b4b183fbd859848ef051295d35fef9d8469","impliedFormat":99},{"version":"fe784567dd721417e2c4c7c1d7306f4b8611a4f232f5b7ce734382cf34b417d2","impliedFormat":99},{"version":"45d1e8fb4fd3e265b15f5a77866a8e21870eae4c69c473c33289a4b971e93704","impliedFormat":99},{"version":"cd40919f70c875ca07ecc5431cc740e366c008bcbe08ba14b8c78353fb4680df","impliedFormat":99},{"version":"ddfd9196f1f83997873bbe958ce99123f11b062f8309fc09d9c9667b2c284391","impliedFormat":99},{"version":"2999ba314a310f6a333199848166d008d088c6e36d090cbdcc69db67d8ae3154","impliedFormat":99},{"version":"62c1e573cd595d3204dfc02b96eba623020b181d2aa3ce6a33e030bc83bebb41","impliedFormat":99},{"version":"ca1616999d6ded0160fea978088a57df492b6c3f8c457a5879837a7e68d69033","impliedFormat":99},{"version":"835e3d95251bbc48918bb874768c13b8986b87ea60471ad8eceb6e38ddd8845e","impliedFormat":99},{"version":"de54e18f04dbcc892a4b4241b9e4c233cfce9be02ac5f43a631bbc25f479cd84","impliedFormat":99},{"version":"453fb9934e71eb8b52347e581b36c01d7751121a75a5cd1a96e3237e3fd9fc7e","impliedFormat":99},{"version":"bc1a1d0eba489e3eb5c2a4aa8cd986c700692b07a76a60b73a3c31e52c7ef983","impliedFormat":99},{"version":"4098e612efd242b5e203c5c0b9afbf7473209905ab2830598be5c7b3942643d0","impliedFormat":99},{"version":"28410cfb9a798bd7d0327fbf0afd4c4038799b1d6a3f86116dc972e31156b6d2","impliedFormat":99},{"version":"514ae9be6724e2164eb38f2a903ef56cf1d0e6ddb62d0d40f155f32d1317c116","impliedFormat":99},{"version":"970e5e94a9071fd5b5c41e2710c0ef7d73e7f7732911681592669e3f7bd06308","impliedFormat":99},{"version":"491fb8b0e0aef777cec1339cb8f5a1a599ed4973ee22a2f02812dd0f48bd78c1","impliedFormat":99},{"version":"6acf0b3018881977d2cfe4382ac3e3db7e103904c4b634be908f1ade06eb302d","impliedFormat":99},{"version":"2dbb2e03b4b7f6524ad5683e7b5aa2e6aef9c83cab1678afd8467fde6d5a3a92","impliedFormat":99},{"version":"135b12824cd5e495ea0a8f7e29aba52e1adb4581bb1e279fb179304ba60c0a44","impliedFormat":99},{"version":"e4c784392051f4bbb80304d3a909da18c98bc58b093456a09b3e3a1b7b10937f","impliedFormat":99},{"version":"2e87c3480512f057f2e7f44f6498b7e3677196e84e0884618fc9e8b6d6228bed","impliedFormat":99},{"version":"66984309d771b6b085e3369227077da237b40e798570f0a2ddbfea383db39812","impliedFormat":99},{"version":"e41be8943835ad083a4f8a558bd2a89b7fe39619ed99f1880187c75e231d033e","impliedFormat":99},{"version":"260558fff7344e4985cfc78472ae58cbc2487e406d23c1ddaf4d484618ce4cfd","impliedFormat":99},{"version":"413d50bc66826f899c842524e5f50f42d45c8cb3b26fd478a62f26ac8da3d90e","impliedFormat":99},{"version":"d9083e10a491b6f8291c7265555ba0e9d599d1f76282812c399ab7639019f365","impliedFormat":99},{"version":"09de774ebab62974edad71cb3c7c6fa786a3fda2644e6473392bd4b600a9c79c","impliedFormat":99},{"version":"e8bcc823792be321f581fcdd8d0f2639d417894e67604d884c38b699284a1a2a","impliedFormat":99},{"version":"7c99839c518dcf5ab8a741a97c190f0703c0a71e30c6d44f0b7921b0deec9f67","impliedFormat":99},{"version":"44c14e4da99cd71f9fe4e415756585cec74b9e7dc47478a837d5bedfb7db1e04","impliedFormat":99},{"version":"1f46ee2b76d9ae1159deb43d14279d04bcebcb9b75de4012b14b1f7486e36f82","impliedFormat":99},{"version":"2838028b54b421306639f4419606306b940a5c5fcc5bc485954cbb0ab84d90f4","impliedFormat":99},{"version":"7116e0399952e03afe9749a77ceaca29b0e1950989375066a9ddc9cb0b7dd252","impliedFormat":99},{"version":"6681ca725a8f1db188c7610b5d4e861748ed3ef8720c371c5f29b7df40e78388","signature":"d80742a6a41d9f569db06f2a6f1ce341e38a8023e1f172dfc596ebf59b320d89"},{"version":"b53aedd97ce9ebdcaf94f60bb120b172149f79045edf37b22858ff10bb61e09e","signature":"d9ed1c6c07bd03524f35e2b7cf385c3278909b3ed2daafb4b74d460d8b6420ce"},{"version":"cbed3bdcd1abfe7d4b5e3fd8e300ab83497a0f8fa2afde5cb114b4e0333ebb4f","signature":"c8cea0f80c3f03c528c1ee5bcd4584ac807b414b573259bc90cd3787ca4645b5"},{"version":"f7e9aabb1fe15bb4c0bcb0d87f3815efe32d1202c493f2b7c74e72da79f5782e","signature":"fbb3b5930925a6d1b69cf5ffee5ad666886c802997b2c11a5e8bd64854c93e92"},{"version":"80d23e36921e787529d3ccae753675b91180dc2326b4c1a3d8f270205b85af79","signature":"cbb6e618cfae37c1feb246b78260428cf2bcba79a0c9bf1ac60d511a4158692d"},{"version":"a42e8e4acc9001e4b0755a715d51984e5f811edf70fad8b1bfab31de44d1957e","signature":"41badd0aa8f75d1c99e248365bc2d935ef96fe71f2adc823ff8be96cced86246"},{"version":"6e9d27d84d9287f4abf2dd99e9c68adb39cdfca4bf68e361d54cf65808db7b5a","signature":"b907f8a604634f5f107ff64f226972dbb1b80eb3236fa9414c52aeedfa1113fd"},{"version":"91a40fc61a4c26b60c359978a9964a0c37a676b52a077b02c028c1dd19a362ed","signature":"02d62b21f2b1b3ae90d6f4c2a2177c849c94a135893850b697a16146152533b6"},{"version":"4d9ac1ae59eaa55088de16aa37191db82e512889ea2e3475c923acb0fb5dd1ec","signature":"faf77a35be7f5648e12ab952b900ee69526103333b3d7b86498adf346a207d89"},{"version":"6b486afb7a460cd1738855703f3a9240568831d82ea6b57cec16a1331e4cf453","signature":"8494e8d1afa0d76f70eea09873120b790df6fe7b084458941c2ce07b55155b33"},{"version":"8a5547b3ef575d99d9981c3f2cec446e2b348ed27d728e3758ce04518afe2236","signature":"b681b6db43bbd4ab1e807d0c66d398749e445595ab26829bc2769b84f478b9f9"},{"version":"45ccd6a5512cc223aef125bfce5fd59f5eeaafec7c248f06a33f1754a188af99","signature":"259df420f73303696c1787aa08bb9ca11c4450327b9fc6e7bcafce758bedbeb2"},{"version":"32ce0be3756a5d6053e57667fd7ff472bb67cebf9886812ec25c8a89984e9959","signature":"9ff47fb4c4e952dc70a99e1fb04787148bd3c14f15d212f1fe1512c39d3ba531"},{"version":"a748bd5db0d9bc5efef204771a625a9f0c6830d02e6f1135af109dbfcebe5088","signature":"436759811402e264efb204dda538ba920dfa1ff2be85883a93757e29732637ba"},{"version":"f4e9480c8e205244fcc90823ccc444fd7557655ec58191e8befcceb29e1bef83","signature":"4478ca9bdbf267e8ba293c55d26d03b720b9006964a13d4ee05afbed4509335e"},{"version":"f749d4843e5fa4533b0e8bae2784314231b4404e332e8d56abc2692272965212","signature":"6151678b79e8c0d91566dc420df9fc3a78605b2c97bee9813469e83d00b0078f"},{"version":"fcbf74a67f906f676320338be4cf1a0391dcaa0e84d0d13e2c5285121ff75f5d","signature":"26ba71f79eb1ccc526399c703ceb9595712a793b7d939584ca92df4a3ce4fea8"},{"version":"7ecfb0fe515e7462466e1fffddf551ab3cbbb0120b9c60d8b0882da1184d719b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2d6e05af866b9a2fba0ca03edd8461339787f298085067eac7179ff66826fbf6","signature":"845a8c55efa3e6c366c3d4fe6aba5af60a822523d537e8a3930466b565b738a4"},{"version":"c2c3162ced58953283cd7a5bdbcbe0a77515186f6aa81218c77655fb0193e2e5","signature":"df7ee96f49527b1acea7ff54bce98f57bbc2045e7d4dd94382078e5a17c1c703"},{"version":"fa4fd1a6c106daad4d2048e50518a1707039d574d96f2282addc0157b2143b29","signature":"fddaa084c125913ec394f657d67da4f30ebaedd92123e4fb8cc1238a6803bc3a"},{"version":"22c9076e33fb7efff1af1d8c1a9c3e38eb017a1a07e62d78b8b64ab33664f2d3","signature":"2650442ef418219533ac780e02ab13d230f9fa3e26c197c10381bbe73798d111"},{"version":"e5769cdc04a37f831d1f4674a80962dcdcfcd962379bf2bd5e70e6d37d7fdb98","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"93b01da13427e2d00a8d73767869b61102d6ced5b8e6ef297006047e7a36b63a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1ef8ba1adc4b3ac94a3aaae93f7d3551054e12c6aea1d0a934a767ad06304022","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9f53838fcfa25e477da1c8aa9dd33dd3b909172577f8344ef9fe9eac41bf9e75","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"327fad4419515282efd2774fc49d6e072e42913fe21d9191426abe9179e079c7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9b4f7f2d993b46d9044fecff29a83efbd9ebcc84f049274014c0b239f4b54f7f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"23a5597b552c4fa9218e4ff75c6fde15c735a495a7eba796b21a28102ab16307","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"efedfed6289043e78d06720efe8eaef631d5b68d527707965021ff334e844855","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ff82bd72673318d22c7de013868369fb07fb2d0acc39f22c05fcd6e5d37f1a93","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e0b6e509dc7206211b236c554688d9c08c860747896a5ca433271c9acbd54c50","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e9c4df328547ccb9f37f1ad14a92f8198d473d3e880fcb46f2183771f684e526","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7617e24f1ef9fffb0a6d0e8a9f02b8a4bf3020c98ad70a44b2af1a194afc265c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7361e20f9c2b294daa8a369dbbd81e4c976a9b27de8aaee675f10e55782ef6fa","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7814b46afd5f860d40c236a7e5933460f59d659d0e4205190dfd8d2b2f01424e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5b46123ad07cfb12175246764494642b5389d84d3974d63e960ab30ecc650c6d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"21efa49cf3ee66574229c70debaf76b065447a5dd8711222bab2c41993501f41","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6d8bb2bef17669472b95eefc0d599cc39e71c58fd924469b50400f59d0ffaada","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4259ecfb83a44d7a8fa2d4733dd53a8adcd97144d4c630d679185904e86ff631","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8f33490c1ff132d5483cf4e1eb80e6e6495eeee76803ad9e0bf039c16f6214f0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"518309af6fc8c1863c9dcbf94b5f556713af021c91f1faa6f3ce5af742cccfc9","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"38135d23f0f1ea3c8e052959218c17bd76609c2b9d8367f918dc1771fe108b95","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"251eee6a7c6c126ac3d94c95cb3a7edbe5115443766279655cc0f8bd8b2b6399","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"54ce27db9cefd311fa33b93578f36d65af5eedc8504812511f846b5b1e370d66","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5346c0a2dbbce8c7d766af2d9d2c728190ceb4b99d42e6b74f904c259e9d3a63","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1a24fed98959b3e69d0d3a7558e1fb74912fcae7165402e57813b7661f62387a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"101caab6f0581dbc33c6c35ba67710fa3a9e7fc2379a2e7861b7ed3793982e2f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ce37ce0f5ffb955703019abd7097f0d168520f6246dc8c6b5476ded5106ab637","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4ef9f7daf829b1a3d25312069f01259dc62817d6ad32dc5a8308da13c932bbeb","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ef6356b0213080ec7b7fdc383a20df947e5036d89cc0584a9483718711ee5aa8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f1669908a2919eaaca00a2d247943b171e70beedf9ebcc743ccf6572392a26c3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6720624b133b9619b09af5b9c64f6e85d27ea62f4936e9b792c679f981a75ecd","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1b6ebf8c916316dc7fac4cad4cb9ff0c54477bed67853a2685c250a8c07ad6d5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b8b23471ce6df155d4a670e836a15d6f45c126a2fdaba54497b7be0ef6c11cb0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c1fa2dffaed2554b03b50c42d29bf0b4bc799f42f7339c697eaef6699caf2f90","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"486b0156ecdfb26dc385a71c3d676290947df506fb32ddfdd12af357eda9252a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ef998ac6f1f8b50f0bd69150d4ff0732a86f41d54d4d2158d0be8981fabf04b9","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"34bd75b6379933f0a0371170d95905d43f72c8a3a2ee431fba5129470947bc84","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"50867469b61b6d4bf22fef913b1324b3470db44ae7d2d560638e355a7eac0a2b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"bd6074dbcef6177b94d63a539d60447a71cc249f93982528095f888e24d1fd9f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cb237a7bd94fff6341f661acef3e225e7b00f795af6c8c1578c4ffeffe4e6728","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a9fedd6778da350c16e2af28370c956bbd36b784b05573c79c829673742526b0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e735f471370fd237f20bc27e9804763a94dfb5ed12de1531190a2703048a70a5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"15dc13db52a86d7a4c6afa8343701c747584d26e79eb2f706d21e9aa3574d6cf","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b40be88c76a5ffd4e33a87c1c88d2d0f4f06f92715f5c48c311c9051044a7127","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e7f23f82b8ba9390be05b42e924ced743fbc9d860e53fd9bb2edb65189024052","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"55fe9b8705c6a60649022dff468ba1f6e0d396eb63edce5a3071c1ec073e274c","signature":"407c70ddc24d5c90bc55d198041d27c6ce2cb0f42fe30a091ef7533f5ac3686f"},{"version":"ad3602cf898d906862e748a847deed9392213387659382a47bda93090c24d2e7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"47beeb485aff1ad4e1fbd8ac6e8d36a24e150de10a30b6899840faa301655414","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"487344aba34994c6bea6db3099ad923d847a27e7c900106a17d5273f8cccaa06","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cb5982a6af0b3d46b79ae5df2d6a483ba51687f950580ca035889641a4e0b99c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0568baac3cdb203dd85282b137b649bbf8171ae2f1a61264cfdfdc1dd5f6c1ab","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e5373c23f9ed849a1e6aa414293d7f1d1de18be48a395129a657fcbcdd7a79ce","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"722efeab5c97ae89cbd4c34579f21583772ba4364870931b0961cd592b4f1b69","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f94f20f484a8c07b6c7b24334c8c16f13a4bc1b158f0830fde6a0aa3f5df39ba","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"03d37d15468e3a01cf7c1563a11829c2c9ae1fc2e02f2d6066408292fb34723b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"05fe7fc1d2436468dbd4fff9af37fe4354a41f84c943d1f2aabe4ecd645419de","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a5f1a3661ab26f668a8195833f02b4085991113b44a0bc7f790e9c9af257f07a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"979e031806f5e09fcc4ef162915496375462215f7667dc184c25f4dff7a7820d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f475d6f2f778630ee452181493fbd495b9e91751ba5c97fb3368e00452256508","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"06210c01d4afe0f05c3bcb4257cf6c9c8bb4dfba43640532421bbea7336dfc9a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"32552e3b687bed279decc2ff81ba12b6d194998a102b5592537ef0a7bc246ed0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"bd975c1c7b49004a6c56e0b147faf4fa07a14651e7a78be5fa43fdf1f887562f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"15d2ade2ff1496cb0867c5d1c235daccb1d08d0d83922ef1ea9e938477a59d82","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"aa33cbb02fd821d64369dc1fe4a3ed0d8723a1a6a8b1ef7ec0507a20834cc435","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e0af4861eb98aaf719edf37c8ee96a3b7dd5ec7d1d92e9dac3d7c447e54e162b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"86727e341ac0b578c884d6a23e8f71ee339ee5908d68eea1d3f06b206ceab13c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"42ede7aa3739a3121163b6956bf56d5894a0b93302635990c79cb6ef222b9e2d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7f21b2e5f827fd2bfa35959f943d5f7c38bd76195247f63a7e00c35c869583c4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cfbb7a79b2aa6358fc674159b086c24e181c16d1ac93590b0b74fe527f66fa47","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4b4b45b5cefad21565fa5e4af782b523c5a39a7f4059988e39bca972d55b9061","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d9df7cbe639e6f1e809ba979e619017e5b1814eb6b6747328273dccc67cd1068","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"71cf715fe7ac9cbda3398c62715e9e41e205bb9f66c14db522c05d33d9bed871","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f6f0c2a313ddfde4cb9a17f94cbbca58e5a8bb25f222a42fbcd19c3416e31764","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"230da823cfd1db9e7f1420e97899558fe51a540913f53f112589f4145b5afbfe","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9c59991578b1c1a6fb0c76e7e5e10e92c68491de73b522f20835c46f6a1c7bf2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1b3ae68d67f5f8f07c19b842f2c73fd01965236873fb00f33cc579f1656e0334","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b31f35a308e26516e3591787664eba7b7b4bb363bbb9f9ec483f506eea0372a0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"fcf4e15889b48532a4fab0550d27792e595ac7e53b656745561bbf0499175c37","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"bd4cc633b78ea5197833520871416deb53dddd1b78bd69451928da323d60ad1a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3665249d1b9375ef703a1d63bb105b20a0285e21723734bcbb106d819bab8023","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"371a8f3dbb785198863556065633f99806e0b8d4fb21cc7368d0649a623f4afb","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1ea8f433cce46067db7b344864ccf0cdd8cab2c887ca2afda8a0077332196f2d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7be41f5460ff85e60093e7bae93ee5d31aacb60c2e6a9f1410c17f633a8b096a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"36e3142e0ca6b104455d191002153bf35a7647fc36aab983261911937acbcf9d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"963e29c03860a04f42f2ca7723bf2f6c8aabcce3c2aed54a011716e20c1d65df","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3690127a28f2bf897229496caf89706414e6463e83e7458af2208affcd82c1bb","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f4f21c1fd681856856de07956c2919756374a07cca623df98b4a34fe75a7cfc8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e6d9a4831d10ff7ea1ff521b5820c35069a8d055a3cc2094a51071f6e705cf33","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b1f060af83c8bd46d96afdf676b95a9701d61b11f5bca3b2c4d13c11c5138068","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6817fac9afb1da6e43271471fccf52f2f33608d4c27a36b3986dcfbb50028dde","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"59e9816e5edb0a209b423850444c205a9d7f278301c59c04d42e55d6c067071d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8a8d39d70c699f5cf9372096a662445f7a50038dab08dadaf8207db793a020bf","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b06ce1aa92f3ddea6d0ee51a3445087bbfd7fce5eb4945579e4641c701cc88de","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c46c3a7959aaf643c2250c0aa8d245d96bf54fe0cee88612d6a7215442abc23b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"07d067492e43fc44e34638ea4e5e03e8e21d04e083fc3be2cdcbfef90f0b4798","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3821128f44a90df228db4c5396487382d7c77bac23824b7e2e4d97c426cedfe8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9a55a93de53f4dcc5811eb28b868ace3a794867ae5c5249d8744225455564c29","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"75025c0fb0474f942e5b65d41af32ce4e22f47842dfb3a065c7104ac824789ed","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"27811c361c44cc1f41b7fe8a0838d1037a20cccf4c6ba9a15abb9d09d37f01ae","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3a9bea559d82c1df383cd1151b369e6c02bc0ac02232c05de78a5c872ecc2dc7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"95be543b6b8a868938af0b9905a3665e9d50dbeef1729861f73b91153b381cf3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"04c8d72089f7cd6ccae20f8e3459677ceb5bbd29ff42711e9b26e068073c709d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"66a15b05f710ef0dd3d0309898b9e3dfed37a44d4e3e555a943e73d58840228b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a9795eaf6bf5f3459c8006761f4d5ab32fb036e2fb7c9fb32744d2d1f62cecef","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3dc9d62cddce1baf59b2b2a35d8ac2b22c6568863b361165e6e9392c3bd4108e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9eae30ca01bfbc3bd8aa7611bf132f210b23a8da866d20f9f95c8c167e823470","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"eded0d3c7d645529545b8faf7178a799a89831855f782699bbf4f6d7ffb53ce4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8265bbdb78c59a9f6c98b07392372ec6093b02e8bf23e8aecd0775b0ae1e5c84","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cdd21ddbcdf8e5073e31fe7f730fe3c4023c66309625b87b5b9785fe140190db","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0e705ee1611dc0b863185919b434bd876f1c0389ba2f6e2a750af6c944bf2a2a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"db9074b978b58ab1dbce3c1d415969ffabbee1ba08ebbab5d79b6259b1b24ad5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6edf5d7e4a6c1e53c71f59d7b824273284f7f86df6e96d4a0345f335a7790780","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"86628d7e65d5c767e9e7125614f302f449165b8a5619beb8d360488548058556","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"02098a352a967d5bfa079b974b367eaf89f234bfcb48e0ea9d4fd3a958964cd4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b25d0dd5f71a90ac4c975ced9738cc77ff40e10923d0774568017691e150c527","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"abd6d932a4f5ffeb10ea89ddc43d53467aabd67f1424f03634d2bdb9e91bb39f","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"29e3e70324dde5b5d43a0efa781f696e4af198263b054fdbc06f683eca0fe26e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7f53bec43be335cd9a6d0f8894d58f2151919e3239482d4b7cdf73c85a1ad6b4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b17e5a4e393e12a682e3c0a3f64eee7b33aec3959eb48acd59e0588836d38a43","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"77caa9a483c4e9e912297bfdd899ea973c57ad4b0f149749bca6174d7a730595","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7ea58b235be0c0704cf916c58b0f8fd947573073f348d1d69e6abae80e922f51","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b443d9c86ce1fc6c6108b95dc7cee0f6a398839c35997f812fd1d02b895d4632","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4fab0835f3f0569611b4185f74019264c8ac4338acdae9786c36fc5e73165f72","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"095cc9d0709e52c5869e31a60f719773a43b60940f726480b689e301eb661d9c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7766f347d618e8f747f17629494a89905aa35b4d924e4495f078865a56b08ddb","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"fa48fcd01f34642798ffbaa1931c701ec1959745c58297d51eff9914542a67de","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5ae00f9eb7202bad96cf17277b37f8eb7ea8dea3e1d29766ca904c2b81f54043","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"177f040db68f89cb309e1a040bbaca4316b5933ae80535473e3bfbee11cd42c2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0fe9f16ec294c6d5b89e1e6a6104e974eb718609032190f3fc4fca38baf023b4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e0875a51dcc08220ab37ee44e0d604789fb0c24c6435826eff6522f9af79faf6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"731b1e71ccf858619a73df7c6906ec005db94b254d41e40622266b18649543d3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f4d9742c42ea0b6f70687b6d12392f5c8bf944d61af1db87ad03c48362ce687d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"50f79c93edc75a5399ca0e9995c8c9469cfe19748125122e2a915f9111ab701b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0f71169795ff0d707630c272a879dd66c35c80e967bcab7de85bb8abc729cdca","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"79f59bc9b7e7539e21e9afef10cdbe7072540ba621c8c1c782384db467b5889b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"248dcfdd5ef7f53d445bb8e05b80fd4abc799d0a61222d13b80a1330f93ea6c5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"35707dbd962f597cc72a0ccefdcbda1c0cbbddaa11bf9a072fa9dc2f2b40eac5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d4605c8c58bc7e8bed8afe8d69f4746ecdd4e0c088214be14705d46dbbbfd135","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ff24bec700f0c92265e9064c7ca0405e03bef54639ef75bb9c92899ec3ee2761","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"79455fe5803e368c08c032e6af6e7366c8cbce2750702f9553091732e738beb2","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"98021cc3dddc6c8ce5498e301424f2314a9f17deb130c609269c900716a6c129","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"59c7aa491c54490b46def8fe721d55d4d7f4eea308e9de99f6a332c60422d7b6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6eaea317fe5b6bbceeddd6440306eb6dfe56c86796b5e90b0c86f03d98fff955","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"bfba3f8e0cc98428a9f110ef67eac45fea19e55d73d70930a4b236383c4d39b5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"960d12344ca062e11aa8e328a45d997c78f80be9e2061371a13ffa3f92b17866","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"816e55d799ee015f214847234b7210605fac058ce104f7349f17f602f1f18249","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4f5e84de8e08e963712fe5c7ec6316457b9b7e2558034de5c347e637bcdd7688","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"247f6e8e3baaf11711d02bf7cd26640cb952e84df97baececa364996b7d98832","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"490941e1dc98b5aa4e528adc4a574af3f1beeab09fb7a0454315eb2dd1290a84","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d46047c1aa49661472f39b4a5a3ce9d7e22c2d26ea84b1e3281690ab6e4e08fb","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1d9812662b25cf3d67aff1a04b33d22e0ea0c441df0621963d761b4c4f718750","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"47422c0d9dd3164ddfd5386431d31e7278b3fd89f64cdac2958ae88c084b6220","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a8d6ed3f24a6c0d44618d20937b1184e8f731720e7f47033697837d7f1361778","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cd7afa06d56f8f16c55b9f3f410cba3d07fdae46ca2be192e2ff865d711173e7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7e64c571d9959ff47a6b54c0bad83c166e167b7fcd7a4a3b41dda9122c453035","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a6f1c92202fed15e56dd23387b6bc38dc6dfee6ecbd9bfcc3514afe99496226c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ce59761534d08b543a8258092baf9cfa2766e21c7f31f46b9d381a5c1952de04","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a5032259dba8af052f8650efd4b8290d61a0213e30c43255cbae505fcf6d1581","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a5aa299fc56fa056f610240ce32aa81a1b43e3dd285e76990e67e15a154b6377","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9997da7b891e6e5ebc0b3f3c33143ff5097f4aa969b1a7d077cd0c113e9033de","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ec80f7ad05865aa145a03395e48710a388452c5573da23b8680f9fc4c40f7023","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d6d1dc2bd87e66c5a42db5939ed437b0fd47462d0d773a80e19af531775c6259","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"330f19e202a378b129f9ed576514b89bbaa5e86e53743b56121c8484b52f62c4","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9d9dfba9ce8ef128d97227c80833f6b38d0d22a6edf3bd8af547a2e14be2ef0e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1f0d923fc674598c42ed2e04274ee62d3c8935949ff261c218c4765e66e298ca","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"8db6cea0ac2a3c56e96660ad1a5b349a17a28413c22411cf771c7c09ab741236","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"74d6b55d87c23c553025dc89ff857a5ba504483e9663d69a66c6a8910317e0c7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4b4f0cefe777af87ef2e3881bb703f3d97004420b14c205039c84a5b8f2f4999","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"40cb304a657165257bbeddf8d6768a0e1d66dda96568fee914466b785488c848","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ca5a2320c781052b195b38bd95a7424e01468dc5e78ef946d8eae4d218437eb8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"4ee066f9213bb1b739a0b708b2cf93b43d82cbcc0d4de9bcf50f3c751b94eba6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2b20cd7cbed9a1c22a77b3198be96416d8a553107ba0d59dd026a99b9cb8bee8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"831e5cdeeceac168c25e4a6d1447d92c79eec9ca78506c2b13940459b2f59ff6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1583aa9c5c34c4ac177ab57d5fc7faa7418f50ec6d36664ec59c37a816b0f681","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"979046616859199ce8a1e4dff11f4b7ed6b5438d17f23ca56a127da9bb54a022","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0ac122db96c399714ce9b034fb1e5c31de8245c89206a77c92d5ee0e61a31035","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d86dc38f9155daf5e2cf38bcbce20b288769387d21485ca0b002284d5e6d6939","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"5665af2d1f1340232629ff097cdba41501407e3462d24f8a7898e1263421cd4c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b6c06376582cf390169268d7bf8d66caf827189962f669703d0311129809f2ad","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3aeb51b34ad2c8ff6d48a6d35fbd8e6bfe18f2f9d7f11081f4a0bd8a98ab7486","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"77f9d5a53ce5884498db4d6706a39c24e8314fb74a506cf0e1b3ad4dd8837766","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"36fa81ee20a15d8b1c88986eb734c35e4b21f8cafaadc96b57c56917d512f33c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"36ebcc137df00eab82e1386148d32f8b1b296bfbd32d5523bcccf2f61303dedc","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"32c17dbfe6a87a1ea5e5635ff90c89a5520a8bc0ef492e692b78719a56fd3781","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cb926f3112ff0b26f201d824575a42de3d34997b70cdad37d4b7d5ff1d71c749","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ebd54d09bcb969873eff5f50835348d41ed084785a1a53110b155cfe0875b7f1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b1d57277c40e6512296d6635e280c6228aeb08d789aff2eb4ef865eecf86cc78","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d2725bce76e4f685e5c3ff860a876ec62f90e7a6deb5a4b12f50682849d13b92","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ea949e13a70371d1fcda1f9d942431331f9b97d1f163934d459d7618cade7a0a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"229dc03fc2cf8b704df4b11c92185ec6b8cfee49f84e12e469a594cc4282f7b8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0c36b091f752cc65409291a95695c6700c64850635f2d756bb562873571b2abc","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"0db0a903134f8c811660031caee19cd55718801d8df691ad0183df5881683c25","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"638b8bab7c7cacf253f36fa58f89199863581071f42b361feacc6093376d9d51","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"57ebd17c1bf0e09d222252bb67deb0cef31476b2476c680ea5ceeca29cfa3efe","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"abca280586a6922df35d85b7bad2d9439e0f1d73534702a8421f7a94bba3d048","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"09b4d72988c4682aa5713bc6a6df7892a7b8e2f10e1af3dc02985c6d4aef84ad","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"c1ead1b6660d8946abad77f5713f19f5166434d37349269042f6852907bd15c7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e5885dd0d9cc2b4c5f949425b53146d0ccba82a9ecd83ee8b2581bc8263adcc9","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1e1d5c76c59c49b2b6f32b7065d0a95bfd229908da2db72526ebdb9312ed2cdd","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"74d912b4ea13dccf1b9fc0df5f3ce8f463e394d64a230f672b027e72ae0e860c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"334a5e09d34fdf02396fa9f55485fd1044980aa9ceb37c80753f7512d9ea2eb5","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"337727f763bfbc5e1df652443773de1939a76dedad4832d4cc708edca7bbfec0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cde7715fe8ab4fa90ab5e5b8b7505810395eb5d45a55156900782f7f2770d9e7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d8e7169e311463a1404f687796203159a89b24d2cc524869db8b8cd97ba1c993","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"860d9d37bb3309553cb0b777bc4669534a5bb0dcbb3892f8866f3b2265abd596","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"945b34137fa3efb0ae22928275c1c42a722dbb81a26c57fb02f64f71e5daa3ce","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"79284e228f99ba638a0369e996a03ba4396499e8ef4696b6e70b2585252701a7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"71b0faaeefb657ca920f6bda0d7ee1d0bcc74714589bf43c149e8bf5259b6509","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b7370679e484bc416fbb12e183b387ba2bcc59456086985b9c5c97aad247dbd9","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a4472ee67979719b5d0d2bdcca830fbb25a38b47a8add7b489afa8244ad30ffc","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"375edd14d51274fae04b49b14d5eebff7311bc157e1c9163c473f1f790993bbf","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"76d196f751bcbf33a2a8e39799e99ee2137e8ca331cef0d5a39a2e46494c1c67","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"78228dfdc1a7024c9e4eabee22c057409886b9014ebd22319ee8a0a9ed31e799","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b93111717ab2133d04653e946a0480e5aaef9f65060baabf168a6b1e82886041","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"6e6e33716fbe3896f141f9ae6206022031c56bd38f6eee8e733627852272a31f","signature":"f9f2cc38a63fa13585c4f34258f3bf2e9d8e5f917ff3d171af85b401916a4c9b"},{"version":"69a8edc242108dee4cc4fc982fb72e8e179c63469e92fc510ec6d8c25759637a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"adae966a8f12db9e20fa63e474d7e907a53c488329c7bd1f0daa706179b3abb7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"420271b2700242703d5feeee719f0a7524c7c999f20a3c90c0c1ee66f228e02b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"bc705423f5e581231d5ab8472659fb37060aa3ade1a052f301ccfaa5eb836748","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"427d2c82634aca3c84b6711a7e0eec282b9cf71c1630596d094011e0ad5c40ab","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"bcdcf4b25ad274742c966538247cb4bf97a15ef23ca57bab40c360bbd8c171ee","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"42f20b516f6c99f5f5fe2670d3bcc38e07a56f3aded38d59e8db2ee0e8789ba6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"564f677fd8e9b2b73657415fb3e95068870e85cf214a64ddd77af57276d93c2d","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1d19897a3a3f53ba616971dc855f33691d64fbe5db24ccdabc2eebbc2931ee05","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"ceb20d5a77f9707eb639fac7d7a9d2a6167c4f64b7ff2d3f46bf2a60fe230d51","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"35bd7fa89a59ca3b136c221ef604ba3a8e30cc88bb03cbc4d26fee0659d02ce7","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9ce1dbafdc6b8218467ef36a142255e32db446f6a507fcc6354655bb5848e2e3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"e1e7ab8405eb11b2a3ad0c2e698843b77699ff100d895d4590e56070c08d3628","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a85610ba8978234a59d37629fafe05c27fb2154cb62b6b775eb8119802e0a38e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"16b0bad438541ba9aaad7a4279d8fa1c2d4b1b79e1dca7bfefb402cd2f1a6db0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"cbb2d8f2651ea33a9096e54800a3874fe7cfc3106289fdff4a8e0da7384e0f60","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"f328b3203d26b5f709e5a082bc956c2e95fbd9fbdca6abee0802b59b633e0dc0","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"34fc137aa6085dd4f915e8b4a8c0d48d6b07d4fda84716ba91619d5ba39566bb","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"d54083855fcc2ed66dedb389bf2efc33b892dcf572829e43758309fe7bcaabb3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"611b7b131854b5340278d13b87af6d01206dd496e3d27d78a430718a110ed929","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"491a1d80866fa9775bf4da9a612d5b599eca6e632411f83ed07fcc0d84910f8a","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"53ea8ad75643aa52476ff744c0f5aa02c4aeb9d7b6ce79d04068908509034387","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"92b4e74da47abeb6adad273237300b333215fc08b1c2c539457f0d6bb09ae289","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"489193e0e98c7911e4d55515469734cb7c5b157cdeeb542b60669d37f87709f1","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"afb8eb7b6919bc4707e871b34cef7df47ed0f2a0c3222edab8f0e70c9e7fa6bf","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"76e4ef5941cdf8f18a821b1c056f5b09e6e286bc05afde2c3e2e98090cf40aab","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9d3a716a1836a0ee669e9f5fcfb592ecd4252a28465e46b08186f354e6ad3485","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"dad6373c8a9550584d688ba58d25f86292e7056d260434d9fa253fbf56ad7614","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"3414d416e51f2846b21f4aa1492fd44bf9ffeaece26743ae1527154e3da6f7fe","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"a55e617b397760261401e44eca2fbb5d5a3d6ad079b5c05d7dff6e27d4b4c0fa","signature":"4c372df16f354b44e6e653a4442eb9f26b95f2d43efcbaa75b59506276b92df7"},{"version":"dc9137db60c0c21520091a315d00b45c8df95f40c9164f04571814892e35c190","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1a91017cd23f0501948ce9d4a5529f61ee87aeeed9d5d9526b18a603b7d7ca8b","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b5072b60226b52cd54b64f9cfc412a8ff9834d1f74cbcea0b003821b1e23d03c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"84dc57614faba58c08fdc3301654e9443077d0657d2a3f435c8944c32e19ccde","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b247732a1ae37a5e0307d4333ef15e3d5393951e3236be0287adeaa48d553b35","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"b1538a92b9bae8d230267210c5db38c2eb6bdb352128a3ce3aa8c6acf9fc9622","impliedFormat":1},{"version":"6fc1a4f64372593767a9b7b774e9b3b92bf04e8785c3f9ea98973aa9f4bbe490","impliedFormat":1},{"version":"ff09b6fbdcf74d8af4e131b8866925c5e18d225540b9b19ce9485ca93e574d84","impliedFormat":1},{"version":"d5895252efa27a50f134a9b580aa61f7def5ab73d0a8071f9b5bf9a317c01c2d","impliedFormat":1},{"version":"2c378d9368abcd2eba8c29b294d40909845f68557bc0b38117e4f04fc56e5f9c","impliedFormat":1},{"version":"56208c500dcb5f42be7e18e8cb578f257a1a89b94b3280c506818fed06391805","impliedFormat":1},{"version":"0c94c2e497e1b9bcfda66aea239d5d36cd980d12a6d9d59e66f4be1fa3da5d5a","impliedFormat":1},{"version":"9b048390bcffe88c023a4cd742a720b41d4cd7df83bc9270e6f2339bf38de278","affectsGlobalScope":true,"impliedFormat":1},{"version":"1f366bde16e0513fa7b64f87f86689c4d36efd85afce7eb24753e9c99b91c319","impliedFormat":1},{"version":"fb893a0dfc3c9fb0f9ca93d0648694dd95f33cbad2c0f2c629f842981dfd4e2e","impliedFormat":1},{"version":"89e326922cadcc2331d7e851011cf9f0456a681aaf3c95b48b81f8d80e8cdfba","impliedFormat":1},{"version":"5d08a179b846f5ee674624b349ebebe2121c455e3a265dc93da4e8d9e89722b4","impliedFormat":1},{"version":"96d14f21b7652903852eef49379d04dbda28c16ed36468f8c9fa08f7c14c9538","impliedFormat":1},{"version":"4ef960df4f672e93b479f88211ed8b5cfa8a598b97aafa3396cacdc3341e3504","impliedFormat":1}],"root":[268,269,[850,857],[1794,1807],[1818,1820],[2052,2066],2068,[2070,2077],[2098,2101],2107,2108,[2143,2268],[2283,2304],[2312,2314],[2317,2343],[2346,2418],2420,[2457,2478],[2481,2525],[2780,2796],2801,2803,2807,[3116,3172],[3250,3511],3589,3590,[3652,3654],[3722,3777],[4009,4168],[4212,4483]],"options":{"allowJs":true,"esModuleInterop":true,"jsx":4,"module":99,"skipLibCheck":true,"strict":true,"target":4},"referencedMap":[[385,1],[386,1],[387,2],[393,3],[382,4],[383,5],[384,1],[389,6],[391,7],[390,6],[388,8],[392,9],[343,1],[346,10],[349,11],[350,12],[344,13],[362,14],[373,15],[351,16],[353,17],[354,17],[359,18],[352,1],[355,17],[356,17],[357,17],[358,4],[361,19],[363,1],[364,20],[366,21],[365,20],[367,22],[369,23],[347,1],[348,24],[368,22],[360,4],[370,25],[371,25],[345,1],[372,1],[736,26],[737,27],[735,1],[796,1],[799,28],[1792,29],[797,29],[1791,30],[798,1],[959,31],[960,31],[961,31],[962,31],[963,31],[964,31],[965,31],[966,31],[967,31],[968,31],[969,31],[970,31],[971,31],[972,31],[973,31],[974,31],[975,31],[976,31],[977,31],[978,31],[979,31],[980,31],[981,31],[982,31],[983,31],[984,31],[985,31],[986,31],[987,31],[988,31],[989,31],[990,31],[991,31],[992,31],[993,31],[994,31],[995,31],[996,31],[997,31],[999,31],[998,31],[1000,31],[1001,31],[1002,31],[1003,31],[1004,31],[1005,31],[1006,31],[1007,31],[1008,31],[1009,31],[1010,31],[1011,31],[1012,31],[1013,31],[1014,31],[1015,31],[1016,31],[1017,31],[1018,31],[1019,31],[1020,31],[1021,31],[1022,31],[1023,31],[1024,31],[1025,31],[1026,31],[1027,31],[1028,31],[1029,31],[1030,31],[1031,31],[1032,31],[1038,31],[1033,31],[1034,31],[1035,31],[1036,31],[1037,31],[1039,31],[1040,31],[1041,31],[1042,31],[1043,31],[1044,31],[1045,31],[1046,31],[1047,31],[1048,31],[1049,31],[1050,31],[1051,31],[1052,31],[1053,31],[1054,31],[1055,31],[1056,31],[1057,31],[1058,31],[1059,31],[1060,31],[1064,31],[1065,31],[1066,31],[1067,31],[1068,31],[1069,31],[1070,31],[1071,31],[1061,31],[1062,31],[1072,31],[1073,31],[1074,31],[1063,31],[1075,31],[1076,31],[1077,31],[1078,31],[1079,31],[1080,31],[1081,31],[1082,31],[1083,31],[1084,31],[1085,31],[1086,31],[1087,31],[1088,31],[1089,31],[1090,31],[1091,31],[1092,31],[1093,31],[1094,31],[1095,31],[1096,31],[1097,31],[1098,31],[1099,31],[1100,31],[1101,31],[1102,31],[1103,31],[1104,31],[1105,31],[1106,31],[1107,31],[1108,31],[1109,31],[1114,31],[1115,31],[1116,31],[1117,31],[1110,31],[1111,31],[1112,31],[1113,31],[1118,31],[1119,31],[1120,31],[1121,31],[1122,31],[1123,31],[1124,31],[1125,31],[1126,31],[1127,31],[1128,31],[1129,31],[1130,31],[1131,31],[1132,31],[1133,31],[1134,31],[1135,31],[1136,31],[1137,31],[1139,31],[1140,31],[1141,31],[1142,31],[1143,31],[1138,31],[1144,31],[1145,31],[1146,31],[1147,31],[1148,31],[1149,31],[1150,31],[1151,31],[1152,31],[1154,31],[1155,31],[1156,31],[1153,31],[1157,31],[1158,31],[1159,31],[1160,31],[1161,31],[1162,31],[1163,31],[1164,31],[1165,31],[1166,31],[1167,31],[1168,31],[1169,31],[1170,31],[1171,31],[1172,31],[1173,31],[1174,31],[1175,31],[1176,31],[1177,31],[1178,31],[1179,31],[1180,31],[1181,31],[1182,31],[1183,31],[1184,31],[1185,31],[1186,31],[1187,31],[1188,31],[1189,31],[1190,31],[1191,31],[1192,31],[1193,31],[1198,31],[1194,31],[1195,31],[1196,31],[1197,31],[1199,31],[1200,31],[1201,31],[1202,31],[1203,31],[1204,31],[1205,31],[1206,31],[1207,31],[1208,31],[1209,31],[1210,31],[1211,31],[1212,31],[1213,31],[1214,31],[1215,31],[1216,31],[1217,31],[1218,31],[1219,31],[1220,31],[1221,31],[1222,31],[1223,31],[1224,31],[1225,31],[1226,31],[1227,31],[1228,31],[1229,31],[1230,31],[1231,31],[1232,31],[1233,31],[1234,31],[1235,31],[1236,31],[1237,31],[1238,31],[1239,31],[1240,31],[1241,31],[1242,31],[1243,31],[1244,31],[1245,31],[1246,31],[1247,31],[1248,31],[1249,31],[1250,31],[1251,31],[1252,31],[1253,31],[1254,31],[1255,31],[1256,31],[1257,31],[1258,31],[1259,31],[1260,31],[1261,31],[1262,31],[1263,31],[1264,31],[1265,31],[1266,31],[1267,31],[1268,31],[1269,31],[1270,31],[1271,31],[1272,31],[1273,31],[1274,31],[1275,31],[1276,31],[1277,31],[1278,31],[1279,31],[1280,31],[1281,31],[1282,31],[1283,31],[1284,31],[1285,31],[1286,31],[1287,31],[1288,31],[1289,31],[1290,31],[1291,31],[1292,31],[1293,31],[1294,31],[1295,31],[1296,31],[1297,31],[1298,31],[1299,31],[1300,31],[1301,31],[1302,31],[1303,31],[1304,31],[1305,31],[1306,31],[1307,31],[1308,31],[1309,31],[1310,31],[1311,31],[1313,31],[1314,31],[1312,31],[1315,31],[1316,31],[1317,31],[1318,31],[1319,31],[1320,31],[1321,31],[1322,31],[1323,31],[1324,31],[1325,31],[1326,31],[1327,31],[1328,31],[1329,31],[1330,31],[1331,31],[1332,31],[1333,31],[1334,31],[1335,31],[1336,31],[1337,31],[1338,31],[1339,31],[1340,31],[1344,31],[1341,31],[1342,31],[1343,31],[1345,31],[1346,31],[1347,31],[1348,31],[1349,31],[1350,31],[1351,31],[1352,31],[1353,31],[1354,31],[1355,31],[1356,31],[1357,31],[1358,31],[1359,31],[1360,31],[1361,31],[1362,31],[1363,31],[1364,31],[1365,31],[1366,31],[1367,31],[1368,31],[1369,31],[1370,31],[1371,31],[1372,31],[1373,31],[1374,31],[1375,31],[1376,31],[1377,31],[1378,31],[1379,31],[1380,31],[1381,31],[1790,32],[1382,31],[1383,31],[1384,31],[1385,31],[1386,31],[1387,31],[1388,31],[1389,31],[1390,31],[1391,31],[1392,31],[1393,31],[1394,31],[1395,31],[1396,31],[1397,31],[1398,31],[1399,31],[1400,31],[1401,31],[1402,31],[1403,31],[1404,31],[1405,31],[1406,31],[1407,31],[1408,31],[1409,31],[1410,31],[1411,31],[1412,31],[1413,31],[1414,31],[1415,31],[1416,31],[1417,31],[1418,31],[1419,31],[1420,31],[1422,31],[1423,31],[1421,31],[1424,31],[1425,31],[1426,31],[1427,31],[1428,31],[1429,31],[1430,31],[1431,31],[1432,31],[1433,31],[1434,31],[1435,31],[1436,31],[1437,31],[1438,31],[1439,31],[1440,31],[1441,31],[1442,31],[1443,31],[1444,31],[1445,31],[1446,31],[1447,31],[1448,31],[1449,31],[1450,31],[1451,31],[1452,31],[1453,31],[1454,31],[1455,31],[1456,31],[1457,31],[1458,31],[1459,31],[1460,31],[1461,31],[1462,31],[1463,31],[1464,31],[1465,31],[1466,31],[1467,31],[1468,31],[1469,31],[1470,31],[1471,31],[1472,31],[1473,31],[1474,31],[1475,31],[1476,31],[1477,31],[1478,31],[1479,31],[1480,31],[1481,31],[1482,31],[1483,31],[1484,31],[1485,31],[1486,31],[1487,31],[1488,31],[1489,31],[1490,31],[1491,31],[1492,31],[1493,31],[1494,31],[1495,31],[1496,31],[1497,31],[1498,31],[1499,31],[1500,31],[1501,31],[1502,31],[1503,31],[1504,31],[1505,31],[1506,31],[1507,31],[1508,31],[1509,31],[1510,31],[1511,31],[1512,31],[1513,31],[1514,31],[1515,31],[1516,31],[1517,31],[1518,31],[1519,31],[1520,31],[1521,31],[1522,31],[1523,31],[1524,31],[1525,31],[1526,31],[1527,31],[1528,31],[1529,31],[1530,31],[1531,31],[1532,31],[1533,31],[1534,31],[1535,31],[1536,31],[1537,31],[1538,31],[1539,31],[1540,31],[1541,31],[1542,31],[1543,31],[1544,31],[1545,31],[1546,31],[1547,31],[1548,31],[1549,31],[1550,31],[1551,31],[1552,31],[1553,31],[1554,31],[1555,31],[1556,31],[1557,31],[1558,31],[1559,31],[1560,31],[1561,31],[1562,31],[1563,31],[1564,31],[1565,31],[1569,31],[1570,31],[1571,31],[1566,31],[1567,31],[1568,31],[1572,31],[1573,31],[1574,31],[1575,31],[1576,31],[1577,31],[1578,31],[1579,31],[1580,31],[1581,31],[1582,31],[1583,31],[1584,31],[1585,31],[1586,31],[1587,31],[1588,31],[1589,31],[1590,31],[1591,31],[1592,31],[1593,31],[1594,31],[1595,31],[1596,31],[1597,31],[1598,31],[1599,31],[1600,31],[1601,31],[1602,31],[1603,31],[1604,31],[1605,31],[1606,31],[1607,31],[1608,31],[1609,31],[1610,31],[1611,31],[1612,31],[1613,31],[1614,31],[1615,31],[1616,31],[1617,31],[1618,31],[1619,31],[1621,31],[1622,31],[1623,31],[1624,31],[1620,31],[1625,31],[1626,31],[1627,31],[1628,31],[1629,31],[1630,31],[1631,31],[1632,31],[1633,31],[1634,31],[1635,31],[1636,31],[1637,31],[1638,31],[1639,31],[1640,31],[1641,31],[1642,31],[1643,31],[1644,31],[1645,31],[1646,31],[1647,31],[1648,31],[1649,31],[1650,31],[1651,31],[1652,31],[1653,31],[1654,31],[1655,31],[1656,31],[1657,31],[1658,31],[1659,31],[1660,31],[1661,31],[1662,31],[1663,31],[1664,31],[1665,31],[1666,31],[1667,31],[1668,31],[1669,31],[1670,31],[1671,31],[1672,31],[1673,31],[1674,31],[1675,31],[1676,31],[1677,31],[1678,31],[1679,31],[1680,31],[1681,31],[1682,31],[1683,31],[1684,31],[1685,31],[1686,31],[1687,31],[1688,31],[1690,31],[1691,31],[1692,31],[1689,31],[1693,31],[1694,31],[1695,31],[1696,31],[1697,31],[1698,31],[1699,31],[1700,31],[1701,31],[1702,31],[1704,31],[1705,31],[1706,31],[1703,31],[1707,31],[1708,31],[1709,31],[1710,31],[1711,31],[1712,31],[1713,31],[1714,31],[1715,31],[1716,31],[1717,31],[1718,31],[1719,31],[1720,31],[1721,31],[1722,31],[1723,31],[1724,31],[1725,31],[1726,31],[1727,31],[1728,31],[1729,31],[1730,31],[1731,31],[1732,31],[1737,31],[1733,31],[1734,31],[1735,31],[1736,31],[1738,31],[1739,31],[1740,31],[1741,31],[1742,31],[1745,31],[1746,31],[1743,31],[1744,31],[1747,31],[1748,31],[1749,31],[1750,31],[1751,31],[1752,31],[1753,31],[1754,31],[1755,31],[1756,31],[1757,31],[1758,31],[1759,31],[1760,31],[1761,31],[1762,31],[1763,31],[1764,31],[1765,31],[1766,31],[1767,31],[1768,31],[1769,31],[1770,31],[1771,31],[1772,31],[1773,31],[1774,31],[1775,31],[1776,31],[1777,31],[1778,31],[1779,31],[1780,31],[1781,31],[1782,31],[1783,31],[1784,31],[1785,31],[1786,31],[1787,31],[1788,31],[1789,31],[1793,33],[732,29],[3720,34],[3668,35],[3666,36],[3669,37],[3673,38],[3662,39],[3672,40],[3685,41],[3721,42],[3655,1],[3684,43],[3683,1],[3660,1],[3667,44],[3663,45],[3661,46],[3671,47],[3659,48],[3670,49],[3664,50],[3693,51],[3694,52],[3690,53],[3689,54],[3710,55],[3713,56],[3712,57],[3714,55],[3711,58],[3709,59],[3679,60],[3695,61],[3678,62],[3716,63],[3674,64],[3675,65],[3708,66],[3696,67],[3680,64],[3682,68],[3681,69],[3692,70],[3697,71],[3715,72],[3676,64],[3698,73],[3701,74],[3700,75],[3699,76],[3704,77],[3703,78],[3702,65],[3677,64],[3705,64],[3707,79],[3706,80],[3717,81],[3719,82],[3688,83],[3686,84],[3687,85],[3691,86],[3718,64],[3665,1],[2798,87],[1821,29],[1822,29],[1823,29],[1824,29],[1825,29],[1826,29],[1827,29],[1828,29],[1829,29],[1830,29],[1831,29],[1832,29],[1833,29],[1834,29],[1835,29],[1841,29],[1836,29],[1837,29],[1838,29],[1839,29],[1840,29],[1842,29],[1843,29],[1844,29],[1845,29],[1846,29],[1847,29],[1849,29],[1850,29],[1848,29],[1851,29],[1852,29],[1853,29],[1854,29],[1855,29],[1856,29],[1857,29],[1858,29],[1859,29],[1860,29],[1861,29],[1862,29],[1863,29],[1864,29],[1865,29],[1866,29],[1867,29],[1868,29],[1869,29],[1870,29],[1871,29],[1872,29],[1873,29],[1874,29],[1875,29],[1877,29],[1876,29],[1878,29],[1879,29],[1881,29],[1880,29],[1882,29],[1883,29],[1884,29],[1885,29],[1886,29],[1888,29],[1887,29],[1889,29],[1890,29],[1891,29],[1892,29],[1893,29],[1894,29],[1895,29],[1896,29],[1897,29],[1898,29],[1899,29],[1900,29],[1901,29],[1902,29],[1907,29],[1903,29],[1904,29],[1905,29],[1906,29],[1908,29],[1909,29],[1910,29],[1911,29],[1912,29],[1913,29],[1914,29],[1915,29],[1916,29],[1917,29],[1919,29],[1918,29],[1920,29],[1921,29],[1922,29],[1923,29],[1924,29],[1925,29],[1926,29],[1927,29],[1930,29],[1928,29],[1929,29],[1931,29],[1932,29],[1933,29],[1934,29],[1935,29],[1936,29],[1937,29],[1938,29],[1940,29],[1939,29],[2051,88],[1941,29],[1942,29],[1943,29],[1944,29],[1945,29],[1946,29],[1947,29],[1948,29],[1949,29],[1950,29],[1951,29],[1953,29],[1952,29],[1954,29],[1955,29],[1956,29],[1957,29],[1958,29],[1959,29],[1960,29],[1961,29],[1963,29],[1962,29],[1964,29],[1965,29],[1966,29],[1967,29],[1968,29],[1969,29],[1970,29],[1971,29],[1972,29],[1976,29],[1973,29],[1974,29],[1975,29],[1977,29],[1978,29],[1979,29],[1981,29],[1980,29],[1982,29],[1983,29],[1984,29],[1985,29],[1986,29],[1987,29],[1988,29],[1989,29],[1990,29],[1991,29],[1992,29],[1993,29],[1994,29],[1995,29],[1996,29],[1997,29],[1998,29],[1999,29],[2000,29],[2001,29],[2002,29],[2003,29],[2004,29],[2005,29],[2006,29],[2007,29],[2008,29],[2009,29],[2010,29],[2011,29],[2012,29],[2013,29],[2014,29],[2015,29],[2016,29],[2017,29],[2018,29],[2019,29],[2020,29],[2021,29],[2022,29],[2023,29],[2024,29],[2025,29],[2026,29],[2027,29],[2028,29],[2029,29],[2030,29],[2031,29],[2032,29],[2033,29],[2034,29],[2036,29],[2035,29],[2037,29],[2038,29],[2039,29],[2040,29],[2041,29],[2042,29],[2043,29],[2044,29],[2045,29],[2046,29],[2047,29],[2048,29],[2049,29],[2050,29],[3778,29],[3779,29],[3780,29],[3781,29],[3782,29],[3783,29],[3784,29],[3785,29],[3786,29],[3787,29],[3788,29],[3789,29],[3790,29],[3791,29],[3792,29],[3798,29],[3793,29],[3794,29],[3795,29],[3796,29],[3797,29],[3799,29],[3800,29],[3801,29],[3802,29],[3803,29],[3804,29],[3806,29],[3807,29],[3805,29],[3808,29],[3809,29],[3810,29],[3811,29],[3812,29],[3813,29],[3814,29],[3815,29],[3816,29],[3817,29],[3818,29],[3819,29],[3820,29],[3821,29],[3822,29],[3823,29],[3824,29],[3825,29],[3826,29],[3827,29],[3828,29],[3829,29],[3830,29],[3831,29],[3832,29],[3834,29],[3833,29],[3835,29],[3836,29],[3838,29],[3837,29],[3839,29],[3840,29],[3841,29],[3842,29],[3843,29],[3845,29],[3844,29],[3846,29],[3847,29],[3848,29],[3849,29],[3850,29],[3851,29],[3852,29],[3853,29],[3854,29],[3855,29],[3856,29],[3857,29],[3858,29],[3859,29],[3864,29],[3860,29],[3861,29],[3862,29],[3863,29],[3865,29],[3866,29],[3867,29],[3868,29],[3869,29],[3870,29],[3871,29],[3872,29],[3873,29],[3874,29],[3876,29],[3875,29],[3877,29],[3878,29],[3879,29],[3880,29],[3881,29],[3882,29],[3883,29],[3884,29],[3887,29],[3885,29],[3886,29],[3888,29],[3889,29],[3890,29],[3891,29],[3892,29],[3893,29],[3894,29],[3895,29],[3897,29],[3896,29],[4008,89],[3898,29],[3899,29],[3900,29],[3901,29],[3902,29],[3903,29],[3904,29],[3905,29],[3906,29],[3907,29],[3908,29],[3910,29],[3909,29],[3911,29],[3912,29],[3913,29],[3914,29],[3915,29],[3916,29],[3917,29],[3918,29],[3920,29],[3919,29],[3921,29],[3922,29],[3923,29],[3924,29],[3925,29],[3926,29],[3927,29],[3928,29],[3929,29],[3933,29],[3930,29],[3931,29],[3932,29],[3934,29],[3935,29],[3936,29],[3938,29],[3937,29],[3939,29],[3940,29],[3941,29],[3942,29],[3943,29],[3944,29],[3945,29],[3946,29],[3947,29],[3948,29],[3949,29],[3950,29],[3951,29],[3952,29],[3953,29],[3954,29],[3955,29],[3956,29],[3957,29],[3958,29],[3959,29],[3960,29],[3961,29],[3962,29],[3963,29],[3964,29],[3965,29],[3966,29],[3967,29],[3968,29],[3969,29],[3970,29],[3971,29],[3972,29],[3973,29],[3974,29],[3975,29],[3976,29],[3977,29],[3978,29],[3979,29],[3980,29],[3981,29],[3982,29],[3983,29],[3984,29],[3985,29],[3986,29],[3987,29],[3988,29],[3989,29],[3990,29],[3991,29],[3993,29],[3992,29],[3994,29],[3995,29],[3996,29],[3997,29],[3998,29],[3999,29],[4000,29],[4001,29],[4002,29],[4003,29],[4004,29],[4005,29],[4006,29],[4007,29],[2971,1],[738,90],[742,91],[743,29],[740,92],[741,93],[744,94],[739,95],[527,29],[644,96],[648,97],[643,1],[646,98],[645,96],[647,96],[616,99],[615,1],[614,29],[785,100],[781,101],[780,1],[783,102],[784,102],[782,103],[562,104],[566,105],[564,106],[561,107],[565,108],[563,108],[314,109],[313,110],[2306,111],[2305,1],[2102,1],[2103,112],[2311,113],[2307,114],[2308,115],[2309,115],[2310,114],[2104,116],[2105,117],[2456,118],[2435,119],[2445,120],[2442,120],[2443,121],[2427,121],[2441,121],[2422,120],[2428,122],[2431,123],[2436,124],[2424,122],[2425,121],[2438,125],[2423,122],[2429,122],[2432,122],[2437,122],[2439,121],[2426,121],[2440,121],[2434,126],[2430,127],[2455,128],[2433,129],[2444,130],[2421,121],[2446,121],[2447,121],[2448,121],[2449,121],[2450,121],[2451,121],[2452,121],[2453,121],[2454,121],[2093,1],[2090,1],[2089,1],[2084,131],[2095,132],[2080,133],[2091,134],[2083,135],[2082,136],[2092,1],[2087,137],[2094,1],[2088,138],[2081,1],[2806,139],[2805,140],[2804,133],[2097,141],[3235,142],[3236,142],[3238,143],[3237,142],[3230,142],[3231,142],[3233,144],[3232,142],[3210,1],[3209,1],[3212,145],[3211,1],[3208,1],[3175,146],[3173,147],[3176,1],[3223,148],[3177,142],[3213,149],[3222,150],[3214,1],[3217,151],[3215,1],[3218,1],[3220,1],[3216,151],[3219,1],[3221,1],[3174,152],[3249,153],[3234,142],[3229,154],[3239,155],[3245,156],[3246,157],[3248,158],[3247,159],[3227,154],[3228,160],[3224,161],[3226,162],[3225,163],[3240,142],[3244,164],[3241,142],[3242,165],[3243,142],[3178,1],[3179,1],[3182,1],[3180,1],[3181,1],[3184,1],[3185,166],[3186,1],[3187,1],[3183,1],[3188,1],[3189,1],[3190,1],[3191,1],[3192,167],[3193,1],[3207,168],[3194,1],[3195,1],[3196,1],[3197,1],[3198,1],[3199,1],[3200,1],[3203,1],[3201,1],[3202,1],[3204,142],[3205,142],[3206,169],[958,170],[2079,1],[257,171],[4484,1],[4485,1],[4486,1],[4487,172],[4488,1],[4490,173],[4491,174],[4489,1],[4492,1],[4494,175],[255,1],[4495,176],[201,1],[3592,177],[2797,1],[4496,1],[2270,178],[2271,179],[2269,180],[2272,181],[2273,182],[2274,183],[2275,184],[2276,185],[2277,186],[2278,187],[2279,188],[2280,189],[2282,190],[2281,191],[3602,177],[4493,1],[3657,1],[3658,192],[146,193],[147,193],[148,194],[103,195],[149,196],[150,197],[151,198],[98,1],[101,199],[99,1],[100,1],[152,200],[153,201],[154,202],[155,203],[156,204],[157,205],[158,205],[159,206],[160,207],[161,208],[162,209],[104,1],[102,1],[163,210],[164,211],[165,212],[197,213],[166,214],[167,215],[168,216],[169,217],[170,218],[171,219],[172,220],[173,221],[174,222],[175,223],[176,223],[177,224],[178,1],[179,225],[181,226],[180,227],[182,46],[183,228],[184,229],[185,230],[186,231],[187,232],[188,233],[189,234],[190,235],[191,236],[192,237],[193,238],[194,239],[105,1],[106,1],[107,1],[145,240],[195,241],[196,242],[2315,243],[85,1],[2316,29],[2817,244],[2078,29],[2818,245],[2816,29],[3055,246],[2096,247],[2069,248],[2814,249],[2815,250],[83,1],[86,251],[3053,29],[87,29],[4497,1],[3591,1],[97,252],[244,253],[242,1],[243,1],[89,1],[239,254],[236,255],[237,256],[258,257],[249,1],[252,258],[251,259],[263,259],[250,260],[88,1],[96,261],[238,261],[91,262],[94,263],[245,262],[95,264],[90,1],[281,29],[479,265],[480,29],[290,266],[282,267],[283,29],[284,268],[285,29],[286,29],[287,29],[288,1],[289,1],[513,269],[481,270],[270,1],[487,271],[272,1],[271,29],[302,29],[580,272],[402,273],[273,274],[403,272],[291,275],[292,29],[293,276],[404,277],[295,278],[294,29],[296,279],[405,272],[715,280],[714,281],[717,282],[406,272],[716,283],[718,284],[719,285],[721,286],[720,287],[722,288],[723,289],[407,272],[724,29],[408,272],[583,290],[581,291],[582,29],[409,272],[726,292],[725,293],[727,294],[410,272],[299,295],[301,296],[300,297],[493,298],[412,299],[411,277],[730,300],[731,301],[729,302],[419,303],[594,304],[595,29],[597,305],[596,29],[420,272],[733,306],[421,272],[603,307],[602,308],[422,277],[533,309],[535,310],[534,311],[536,312],[423,313],[734,314],[608,315],[607,29],[609,316],[424,277],[745,317],[747,318],[748,319],[746,320],[425,272],[708,321],[707,29],[709,322],[710,323],[298,29],[848,29],[494,324],[492,325],[610,326],[728,327],[418,328],[417,329],[416,330],[611,29],[613,331],[612,287],[426,272],[749,295],[427,277],[622,332],[623,333],[428,272],[554,334],[553,335],[555,336],[430,337],[495,29],[431,1],[750,338],[624,339],[432,272],[751,340],[754,341],[752,340],[755,342],[625,343],[753,340],[433,272],[757,344],[758,345],[339,346],[486,347],[340,348],[484,349],[759,350],[338,351],[760,352],[485,345],[761,353],[337,354],[434,277],[334,355],[653,356],[652,287],[435,272],[769,357],[768,358],[436,313],[849,359],[651,360],[438,361],[437,362],[626,29],[642,363],[633,364],[634,365],[635,366],[636,366],[439,367],[413,272],[641,368],[771,369],[770,29],[546,29],[440,277],[655,370],[656,371],[654,29],[441,277],[579,372],[578,373],[660,374],[442,362],[552,375],[545,376],[548,377],[547,378],[549,29],[550,379],[443,277],[551,380],[776,381],[297,29],[774,382],[444,277],[775,383],[712,384],[663,385],[711,386],[661,387],[662,388],[445,277],[713,389],[779,390],[664,275],[777,391],[446,313],[778,392],[556,393],[515,394],[447,362],[516,395],[517,396],[448,272],[666,397],[665,398],[449,399],[576,400],[575,29],[450,272],[787,401],[786,402],[451,272],[789,403],[792,404],[788,405],[790,403],[791,406],[452,272],[795,407],[453,313],[800,31],[454,277],[801,314],[803,408],[455,272],[514,409],[456,410],[414,277],[805,411],[806,411],[804,29],[807,411],[813,412],[808,411],[809,411],[810,29],[812,413],[457,272],[811,29],[674,414],[458,277],[676,29],[675,415],[677,29],[678,416],[459,272],[558,29],[460,272],[818,417],[815,418],[816,419],[814,29],[817,419],[475,272],[821,420],[823,421],[820,422],[461,272],[822,420],[819,29],[828,423],[462,277],[429,424],[415,425],[830,426],[463,272],[679,427],[680,428],[557,427],[682,429],[560,430],[559,431],[464,272],[681,432],[593,433],[465,272],[592,434],[683,29],[684,435],[466,277],[396,436],[832,437],[381,438],[476,439],[477,440],[478,441],[376,1],[377,1],[380,442],[378,1],[379,1],[374,1],[375,443],[401,444],[831,265],[395,4],[394,1],[397,445],[399,313],[398,446],[400,447],[491,448],[835,449],[467,272],[834,450],[833,451],[483,452],[482,453],[468,399],[837,454],[567,455],[836,456],[469,399],[573,457],[568,1],[570,458],[569,459],[571,378],[572,29],[470,272],[700,460],[472,461],[698,462],[699,463],[471,313],[697,464],[839,465],[844,466],[840,467],[841,467],[473,272],[842,467],[843,467],[838,378],[705,468],[706,469],[577,470],[474,272],[704,471],[846,472],[845,1],[847,29],[256,1],[335,1],[84,1],[2344,1],[2610,473],[2589,474],[2686,1],[2590,475],[2526,473],[2527,1],[2528,1],[2529,1],[2530,1],[2531,1],[2532,1],[2533,1],[2534,1],[2535,1],[2536,1],[2537,1],[2538,473],[2539,473],[2540,1],[2541,1],[2542,1],[2543,1],[2544,1],[2545,1],[2546,1],[2547,1],[2548,1],[2550,1],[2549,1],[2551,1],[2552,1],[2553,473],[2554,1],[2555,1],[2556,473],[2557,1],[2558,1],[2559,473],[2560,1],[2561,473],[2562,473],[2563,473],[2564,1],[2565,473],[2566,473],[2567,473],[2568,473],[2569,473],[2571,473],[2572,1],[2573,1],[2570,473],[2574,473],[2575,1],[2576,1],[2577,1],[2578,1],[2579,1],[2580,1],[2581,1],[2582,1],[2583,1],[2584,1],[2585,1],[2586,473],[2587,1],[2588,1],[2591,476],[2592,473],[2593,473],[2594,477],[2595,478],[2596,473],[2597,473],[2598,473],[2599,473],[2602,473],[2600,1],[2601,1],[859,1],[2603,1],[2604,1],[2605,1],[2606,1],[2607,1],[2608,1],[2609,1],[2611,479],[2612,1],[2613,1],[2614,1],[2616,1],[2615,1],[2617,1],[2618,1],[2619,1],[2620,473],[2621,1],[2622,1],[2623,1],[2624,1],[2625,473],[2626,473],[2628,473],[2627,473],[2629,1],[2630,1],[2631,1],[2632,1],[2779,480],[2633,473],[2634,473],[2635,1],[2636,1],[2637,1],[2638,1],[2639,1],[2640,1],[2641,1],[2642,1],[2643,1],[2644,1],[2645,1],[2646,1],[2647,473],[2648,1],[2649,1],[2650,1],[2651,1],[2652,1],[2653,1],[2654,1],[2655,1],[2656,1],[2657,1],[2658,473],[2659,1],[2660,1],[2661,1],[2662,1],[2663,1],[2664,1],[2665,1],[2666,1],[2667,1],[2668,473],[2669,1],[2670,1],[2671,1],[2672,1],[2673,1],[2674,1],[2675,1],[2676,1],[2677,473],[2678,1],[2679,1],[2680,1],[2681,1],[2682,1],[2683,1],[2684,473],[2685,1],[2687,481],[957,482],[862,475],[864,475],[865,475],[866,475],[867,475],[868,475],[863,475],[869,475],[871,475],[870,475],[872,475],[873,475],[874,475],[875,475],[876,475],[877,475],[878,475],[879,475],[881,475],[880,475],[882,475],[883,475],[884,475],[885,475],[886,475],[887,475],[888,475],[889,475],[890,475],[891,475],[892,475],[893,475],[894,475],[895,475],[896,475],[898,475],[899,475],[897,475],[900,475],[901,475],[902,475],[903,475],[904,475],[905,475],[906,475],[907,475],[908,475],[909,475],[910,475],[911,475],[913,475],[912,475],[915,475],[914,475],[916,475],[917,475],[918,475],[919,475],[920,475],[921,475],[922,475],[923,475],[924,475],[925,475],[926,475],[927,475],[928,475],[930,475],[929,475],[931,475],[932,475],[933,475],[935,475],[934,475],[936,475],[937,475],[938,475],[939,475],[940,475],[941,475],[943,475],[942,475],[944,475],[945,475],[946,475],[947,475],[948,475],[861,473],[949,475],[950,475],[952,475],[951,475],[953,475],[954,475],[955,475],[956,475],[2688,1],[2689,473],[2690,1],[2691,1],[2692,1],[2693,1],[2694,1],[2695,1],[2696,1],[2697,1],[2698,1],[2699,473],[2700,1],[2701,1],[2702,1],[2703,1],[2704,1],[2705,1],[2706,1],[2711,483],[2709,484],[2710,485],[2708,486],[2707,473],[2712,1],[2713,1],[2714,473],[2715,1],[2716,1],[2717,1],[2718,1],[2719,1],[2720,1],[2721,1],[2722,1],[2723,1],[2724,473],[2725,473],[2726,1],[2727,1],[2728,1],[2729,473],[2730,1],[2731,473],[2732,1],[2733,479],[2734,1],[2735,1],[2736,1],[2737,1],[2738,1],[2739,1],[2740,1],[2741,1],[2742,1],[2743,473],[2744,473],[2745,1],[2746,1],[2747,1],[2748,1],[2749,1],[2750,1],[2751,1],[2752,1],[2753,1],[2754,1],[2755,1],[2756,1],[2757,473],[2758,473],[2759,1],[2760,1],[2761,473],[2762,1],[2763,1],[2764,1],[2765,1],[2766,1],[2767,1],[2768,1],[2769,1],[2770,1],[2771,1],[2772,1],[2773,1],[2774,473],[860,487],[2775,1],[2776,1],[2777,1],[2778,1],[490,488],[489,489],[488,1],[206,1],[2800,490],[2799,491],[1814,492],[1816,493],[1815,494],[1813,495],[1812,1],[3656,496],[2106,1],[229,1],[231,497],[230,1],[2067,29],[4205,1],[4179,498],[4178,499],[4177,500],[4204,501],[4203,502],[4207,503],[4206,504],[4209,505],[4208,506],[3630,507],[3604,508],[3605,509],[3606,509],[3607,509],[3608,509],[3609,509],[3610,509],[3611,509],[3612,509],[3613,509],[3614,509],[3628,510],[3615,509],[3616,509],[3617,509],[3618,509],[3619,509],[3620,509],[3621,509],[3622,509],[3624,509],[3625,509],[3623,509],[3626,509],[3627,509],[3629,509],[3603,511],[4202,512],[4182,513],[4183,513],[4184,513],[4185,513],[4186,513],[4187,513],[4188,514],[4190,513],[4189,513],[4201,515],[4191,513],[4193,513],[4192,513],[4195,513],[4194,513],[4196,513],[4197,513],[4198,513],[4199,513],[4200,513],[4181,513],[4180,516],[4172,517],[4170,518],[4171,518],[4175,519],[4173,518],[4174,518],[4176,518],[4169,1],[2419,1],[3076,520],[3081,521],[3088,522],[3071,523],[2845,1],[2853,524],[2975,525],[2978,526],[2950,1],[2963,527],[2970,528],[2870,1],[2952,1],[2851,1],[2949,529],[2995,530],[2852,1],[2843,531],[2977,532],[2979,533],[2980,534],[3051,535],[2944,536],[2899,537],[2957,538],[2958,539],[2956,540],[2955,1],[2951,541],[2976,542],[2854,543],[3021,1],[3022,544],[2881,545],[2855,546],[2882,545],[2902,545],[2828,545],[2973,547],[2972,1],[2962,548],[3066,1],[2115,1],[3087,549],[3029,550],[3030,551],[3026,552],[2136,1],[2929,1],[3031,553],[3027,554],[2141,555],[2140,556],[2135,1],[2128,1],[2133,557],[2132,1],[2134,558],[3028,29],[2117,559],[2124,560],[2126,561],[2116,1],[2121,562],[2123,563],[2125,564],[2120,565],[2118,1],[2122,566],[2137,1],[2131,1],[2139,567],[2138,1],[2114,568],[3097,569],[3100,570],[2889,571],[2888,572],[2887,573],[3103,29],[2886,574],[2875,1],[3105,1],[3114,575],[3113,1],[3106,29],[3107,576],[2820,1],[2959,577],[2960,578],[2961,579],[2824,1],[2964,1],[2838,580],[2819,1],[3043,29],[2826,581],[3042,582],[3041,583],[3032,1],[3033,1],[3040,1],[3035,1],[3038,584],[3034,1],[3036,585],[3039,586],[3037,585],[2850,1],[2847,1],[2848,545],[2984,1],[2989,587],[2990,588],[2988,589],[2986,590],[2987,591],[2982,1],[3049,553],[2842,553],[3075,592],[3082,593],[3086,594],[2920,595],[2919,1],[2914,1],[3062,596],[3070,597],[2945,598],[2946,599],[3024,600],[2934,1],[3047,601],[2924,29],[2939,602],[3050,603],[2935,1],[2938,604],[2936,1],[3048,605],[3045,606],[3044,1],[3046,1],[2942,1],[3020,607],[2111,608],[2922,609],[2926,610],[2940,611],[2943,612],[2932,613],[2927,614],[3069,615],[2998,616],[2918,617],[2829,618],[3068,619],[2825,620],[2991,621],[2983,1],[2992,622],[3009,623],[2981,1],[3008,624],[2813,1],[3003,625],[2846,1],[3023,626],[2999,1],[2833,1],[2834,1],[2954,1],[3007,627],[2849,1],[2873,628],[2941,629],[2879,630],[2923,1],[3006,1],[2985,1],[3011,631],[3012,632],[2953,1],[3014,633],[3016,634],[3015,635],[2965,1],[3005,618],[3018,636],[2917,637],[3004,638],[3010,639],[2858,1],[2862,1],[2861,1],[2860,1],[2865,1],[2859,1],[2868,1],[2867,1],[2864,1],[2863,1],[2866,1],[2869,640],[2857,1],[2909,641],[2908,1],[2913,642],[2910,643],[2912,644],[2915,642],[2911,643],[2839,645],[2901,646],[3065,647],[3063,1],[3092,648],[3094,649],[3058,650],[3093,651],[2112,652],[2109,652],[2856,1],[2841,653],[2840,654],[2836,655],[2837,656],[2844,657],[2872,657],[2883,657],[2903,658],[2884,658],[2831,659],[2830,1],[2907,660],[2906,661],[2905,662],[2904,663],[2832,664],[3052,665],[2871,666],[3057,667],[3025,668],[3054,669],[3056,670],[2948,671],[2947,672],[2930,673],[2916,674],[2898,675],[2900,676],[2897,677],[3017,678],[2921,1],[3080,1],[2835,679],[3019,680],[3064,681],[2928,1],[2874,682],[2933,683],[2931,684],[2876,685],[2993,686],[3059,1],[2877,687],[2994,687],[3078,1],[3077,1],[3079,1],[3061,1],[3060,1],[2996,688],[2925,1],[2127,689],[2113,690],[2890,1],[2823,691],[2878,1],[3084,29],[2822,1],[3096,692],[2896,29],[3090,553],[2129,693],[3073,694],[2895,692],[2827,1],[3098,695],[2893,29],[2894,29],[2885,1],[2821,1],[2892,696],[2891,697],[2880,698],[2937,222],[2997,222],[3013,1],[3001,699],[3000,1],[2119,568],[2110,1],[2130,29],[3067,580],[3074,700],[2808,29],[2811,701],[2812,702],[2809,29],[2810,1],[2974,703],[2969,704],[2968,1],[2967,705],[2966,1],[3072,706],[3083,707],[3085,708],[3089,709],[3115,710],[3091,711],[3095,712],[3099,713],[3112,714],[3101,715],[2142,716],[3102,717],[3104,718],[3108,719],[3111,580],[3110,1],[3109,720],[3513,1],[3519,721],[3512,1],[3516,1],[3518,722],[3515,723],[3588,724],[3582,724],[3543,725],[3539,726],[3554,727],[3544,728],[3551,729],[3538,730],[3552,1],[3550,731],[3547,732],[3548,733],[3545,734],[3553,735],[3520,723],[3583,736],[3534,737],[3531,738],[3532,739],[3533,740],[3522,741],[3541,742],[3560,743],[3556,744],[3555,745],[3559,746],[3557,747],[3558,747],[3535,748],[3537,749],[3536,750],[3540,751],[3584,752],[3542,753],[3524,754],[3585,755],[3523,756],[3586,757],[3525,758],[3563,759],[3561,738],[3562,760],[3526,747],[3567,761],[3565,762],[3566,763],[3527,764],[3570,765],[3569,766],[3572,767],[3571,768],[3575,769],[3573,768],[3574,770],[3568,771],[3564,772],[3576,771],[3528,747],[3587,773],[3529,768],[3530,747],[3546,774],[3549,775],[3521,1],[3577,747],[3578,776],[3580,777],[3579,778],[3581,779],[3514,780],[3517,781],[224,782],[222,783],[223,784],[211,785],[212,783],[219,786],[210,787],[215,788],[225,1],[216,789],[221,790],[227,791],[226,792],[209,793],[217,794],[218,795],[213,796],[220,782],[214,797],[2086,798],[2085,1],[600,799],[601,800],[598,801],[599,802],[532,29],[605,803],[606,804],[604,110],[279,805],[278,805],[277,806],[280,807],[620,808],[617,29],[619,809],[621,810],[618,29],[588,811],[587,1],[325,812],[329,812],[327,812],[328,812],[332,813],[324,814],[326,812],[330,812],[322,1],[323,815],[331,815],[321,350],[333,350],[756,350],[305,816],[303,1],[304,817],[762,29],[766,818],[767,819],[764,29],[763,820],[765,821],[650,822],[649,823],[630,824],[632,825],[631,824],[629,826],[627,824],[628,1],[659,827],[657,29],[658,828],[542,29],[543,829],[544,830],[537,29],[538,831],[539,829],[541,829],[540,829],[311,29],[308,832],[310,833],[312,834],[307,29],[309,29],[772,29],[773,835],[499,836],[497,837],[496,838],[498,838],[306,1],[320,839],[315,840],[317,841],[316,842],[318,842],[319,842],[794,843],[793,29],[802,29],[507,844],[511,845],[512,846],[506,29],[508,847],[509,847],[510,848],[672,849],[668,849],[669,850],[673,851],[667,29],[670,29],[671,852],[827,853],[824,29],[825,854],[826,855],[829,29],[518,1],[522,856],[524,857],[521,29],[523,858],[531,859],[520,860],[519,1],[525,861],[526,862],[528,863],[529,861],[530,864],[584,865],[591,866],[589,867],[585,868],[586,29],[590,868],[640,869],[637,824],[639,870],[638,870],[341,107],[342,871],[694,872],[690,873],[691,874],[693,875],[692,876],[686,877],[687,29],[696,878],[685,879],[688,873],[689,880],[695,873],[701,881],[703,882],[574,29],[702,883],[275,1],[274,29],[276,884],[500,29],[503,885],[501,29],[505,886],[504,29],[502,29],[2479,887],[2480,888],[3634,889],[3633,890],[858,29],[4211,891],[4210,892],[3632,893],[3631,894],[203,895],[202,176],[336,896],[3002,243],[208,1],[2345,1],[259,1],[92,1],[93,897],[3599,898],[3598,1],[81,1],[82,1],[13,1],[14,1],[16,1],[15,1],[2,1],[17,1],[18,1],[19,1],[20,1],[21,1],[22,1],[23,1],[24,1],[3,1],[25,1],[26,1],[4,1],[27,1],[31,1],[28,1],[29,1],[30,1],[32,1],[33,1],[34,1],[5,1],[35,1],[36,1],[37,1],[38,1],[6,1],[42,1],[39,1],[40,1],[41,1],[43,1],[7,1],[44,1],[49,1],[50,1],[45,1],[46,1],[47,1],[48,1],[8,1],[54,1],[51,1],[52,1],[53,1],[55,1],[9,1],[56,1],[57,1],[58,1],[60,1],[59,1],[61,1],[62,1],[10,1],[63,1],[64,1],[65,1],[11,1],[66,1],[67,1],[68,1],[69,1],[70,1],[1,1],[71,1],[72,1],[12,1],[76,1],[74,1],[79,1],[78,1],[73,1],[77,1],[75,1],[80,1],[123,899],[133,900],[122,899],[143,901],[114,902],[113,903],[142,720],[136,904],[141,905],[116,906],[130,907],[115,908],[139,909],[111,910],[110,720],[140,911],[112,912],[117,913],[118,1],[121,913],[108,1],[144,914],[134,915],[125,916],[126,917],[128,918],[124,919],[127,920],[137,720],[119,921],[120,922],[129,923],[109,924],[132,915],[131,913],[135,1],[138,925],[3601,926],[3597,1],[3600,927],[3651,928],[3635,1],[3636,1],[3638,929],[3639,1],[3637,1],[3640,929],[3641,929],[3643,930],[3642,929],[3644,929],[3645,930],[3646,929],[3647,1],[3648,929],[3649,1],[3650,1],[3594,931],[3593,177],[3596,932],[3595,933],[261,934],[247,935],[248,934],[246,1],[199,936],[235,937],[205,938],[200,936],[198,1],[204,939],[233,1],[228,1],[232,940],[207,1],[234,941],[267,942],[260,943],[253,944],[262,945],[241,946],[1809,947],[1810,948],[264,949],[1811,950],[265,951],[254,952],[1808,953],[266,954],[1817,955],[240,1],[2802,956],[3251,957],[3169,958],[3167,959],[3170,960],[3168,961],[3252,962],[3171,963],[1805,956],[3172,964],[3273,965],[3134,966],[3283,967],[3284,968],[3162,969],[3285,970],[3288,971],[3287,972],[2070,973],[3286,974],[3290,975],[3291,976],[3295,977],[3293,978],[1806,956],[3292,976],[3294,979],[3303,980],[3298,981],[3301,982],[3300,983],[1807,984],[1819,985],[1818,986],[3306,987],[3302,988],[3305,989],[3299,990],[3297,553],[3304,991],[3131,992],[3308,993],[2059,994],[3309,995],[2057,994],[3310,996],[2075,997],[3311,998],[2071,999],[2076,1000],[3314,1001],[2066,1002],[3315,1003],[2064,1004],[3316,1005],[2063,1006],[2100,1007],[2062,1008],[2060,1009],[2101,1010],[2065,1011],[3312,1012],[2056,1013],[2077,1014],[2055,1015],[3313,1016],[2058,1013],[1820,956],[2098,1017],[2072,1018],[2099,1019],[2073,1018],[3307,1020],[3349,1021],[3358,1022],[3357,1023],[3352,1024],[3359,1025],[3355,1026],[3354,1027],[3360,1028],[3353,990],[3356,1029],[3347,1030],[2159,1031],[2160,1032],[2158,1033],[2161,1034],[2162,1034],[2163,1034],[2166,1035],[2165,1036],[2167,1037],[2168,1038],[2170,1039],[2169,1037],[2172,1040],[2171,1037],[2174,1041],[2173,1037],[2177,1042],[2176,1043],[2178,1044],[2144,956],[2179,1045],[2181,1046],[2180,1047],[2182,1046],[2184,1048],[2183,1038],[2186,1049],[2185,1033],[2188,1050],[2187,1038],[2189,1038],[2190,1051],[2192,1052],[2191,1038],[2194,1053],[2193,1054],[2195,1055],[2196,1056],[2197,1037],[2198,1038],[2199,1051],[2201,1057],[2200,1038],[2203,1058],[2202,1059],[2205,1060],[2204,1061],[2206,1061],[2208,1062],[2207,1051],[2210,1063],[2209,1038],[2212,1064],[2211,1065],[2214,1066],[2213,1038],[2217,1067],[2216,1068],[2219,1069],[2218,1068],[2221,1070],[2220,1071],[2222,1072],[2215,1033],[2224,1073],[2223,1068],[2226,1074],[2225,1051],[2228,1075],[2227,1038],[2230,1076],[2232,1077],[2231,1038],[2233,1037],[2235,1078],[2237,1079],[2236,1056],[2239,1080],[2238,1038],[2241,1081],[2240,1056],[2242,1082],[2244,1083],[2243,1084],[2246,1085],[2245,1086],[2247,1087],[2145,1051],[2249,1088],[2248,1051],[2251,1089],[2250,1051],[2147,1090],[2146,1091],[2149,1092],[2150,1092],[2152,1093],[2151,1092],[2154,1094],[2153,1092],[2156,1095],[2155,1092],[2157,1092],[2253,1096],[2252,1038],[2255,1097],[2254,1033],[3143,1098],[3135,1099],[3133,1100],[3371,1101],[3383,1102],[3420,1103],[3421,1104],[3422,1105],[3423,1106],[3440,1107],[3498,1108],[3446,1109],[3499,1110],[3448,1111],[3450,1112],[3496,1113],[3495,1114],[3497,1115],[2257,1116],[2256,956],[1804,1045],[3503,1117],[3508,1118],[3507,1119],[3511,1120],[3163,1121],[3730,1023],[3757,1122],[3731,1123],[3750,1124],[3758,1125],[3732,1126],[2259,1127],[3734,1128],[3735,1023],[3759,1129],[3733,1130],[3760,1131],[3745,1132],[3761,1133],[3749,1134],[3762,1135],[3736,1136],[3737,1137],[3763,1138],[3738,1139],[3765,1140],[3764,1141],[3766,1142],[3739,1143],[3748,1144],[3743,1145],[3746,1023],[3742,1130],[3744,1146],[3747,1147],[3767,1148],[3755,1149],[3768,1150],[3753,1151],[3769,1152],[3751,1153],[3770,1154],[3754,1023],[3772,1155],[3771,1111],[3773,1156],[3752,1157],[2262,1158],[2261,1159],[3590,1160],[2266,1161],[2265,1162],[2268,1163],[3654,1164],[3722,1165],[3774,1166],[3723,1167],[3775,1168],[3724,1169],[3776,1170],[3725,1171],[2260,1045],[3726,1169],[3727,1169],[3729,1171],[3756,1172],[4022,1173],[4028,1174],[4025,1175],[4031,1176],[4030,1177],[4032,1178],[4029,1179],[4034,1180],[4023,1181],[4035,1182],[4024,1183],[4036,1184],[2332,1185],[2334,1186],[2333,1187],[4033,1188],[4026,1189],[4027,1190],[4041,1191],[4061,1192],[4060,1193],[4050,1194],[4055,1195],[4051,1196],[4054,1123],[4052,1197],[2338,1198],[2339,1199],[4049,1200],[4053,553],[4047,1201],[4057,1202],[4059,1203],[4044,1204],[4039,1205],[4043,1206],[4048,1207],[4056,1111],[4063,1208],[4045,1209],[2335,956],[2337,1210],[2336,1211],[4064,1212],[4058,1104],[4040,1213],[4038,1214],[4037,1215],[4042,1200],[4046,1023],[4062,1216],[4071,1217],[4074,1218],[4079,1219],[4072,1220],[4075,1221],[4083,1222],[4078,1223],[4081,1224],[4076,1225],[4082,1226],[4077,1227],[4073,956],[4080,1228],[4087,1229],[4092,1230],[4096,1231],[4101,1232],[4103,1233],[4102,1191],[4105,1234],[4104,1235],[4124,1236],[4135,1237],[4126,1238],[4136,1239],[4128,1240],[4127,1241],[4133,1242],[4137,1243],[4125,1244],[4138,1245],[4132,1246],[4129,1247],[4139,1248],[4131,1249],[4140,1250],[4130,1251],[4134,1252],[4151,1253],[4153,1254],[4152,1255],[4220,1256],[4222,1257],[4225,1258],[4165,1259],[4164,1260],[4215,1261],[4227,1262],[3119,1263],[4229,1264],[4228,1265],[4230,1266],[4231,1267],[4232,1268],[4233,1269],[4234,1270],[3137,1200],[4235,1271],[3139,1272],[4236,1273],[3138,1200],[4237,1274],[3136,1023],[3140,1275],[4248,1276],[4108,1277],[3460,1278],[3465,956],[4331,1279],[3467,1280],[4328,1281],[3466,1282],[4332,1283],[3462,1284],[3461,1285],[4329,1286],[3459,1021],[4333,1287],[3463,1288],[3457,1171],[4334,1289],[3451,1290],[4335,1291],[3464,1292],[3456,1293],[4336,1294],[3452,1295],[4330,1296],[3458,1021],[3480,1297],[4238,1298],[3272,1299],[4337,1300],[2283,1104],[4249,1301],[3282,1302],[3278,1303],[4338,1304],[3276,1305],[2378,956],[3280,1306],[2380,1307],[2379,1045],[3275,1308],[3281,1309],[2381,1310],[4339,1311],[3279,1312],[3274,1313],[3277,1314],[2164,956],[4264,1315],[3424,1316],[4267,1317],[3425,1318],[4268,1319],[3427,1320],[4269,1321],[3429,1322],[4265,1323],[3439,1324],[3435,1325],[4266,1326],[3431,1327],[3363,1328],[3362,1329],[2383,1330],[4340,1331],[2382,1194],[4250,1332],[2317,1333],[2284,956],[4212,1334],[4341,1335],[4163,1336],[4162,1337],[4219,1338],[4224,1339],[4214,1340],[4221,1341],[2384,1342],[4226,1343],[2386,1344],[2385,1345],[4342,1346],[3437,1347],[3740,1348],[2258,956],[3741,1349],[2264,1023],[2263,956],[4086,1350],[4343,1351],[4084,1352],[2388,1353],[2387,1354],[3436,1355],[4085,1356],[3434,1357],[857,1358],[4109,1359],[4270,1360],[3368,1361],[4271,1362],[3365,1363],[4272,1364],[3364,984],[4273,1365],[3367,1366],[4274,1367],[3366,1368],[2175,956],[2285,1369],[3146,1370],[2286,1200],[4353,1371],[4106,1372],[1795,1373],[4344,1374],[3142,984],[4345,1375],[3147,1023],[4346,1376],[3484,984],[3141,1045],[4354,1377],[3504,1378],[4355,1379],[3505,1380],[4356,1381],[3506,1380],[2416,1382],[4357,1383],[3164,1384],[4358,1385],[3165,1386],[4347,1387],[2287,1123],[4348,1388],[3144,1389],[4349,1390],[3130,1391],[3491,1392],[2289,1393],[2288,1394],[4350,1395],[2343,1396],[4351,1397],[2313,1104],[3479,1398],[2290,1104],[3478,1111],[2293,1399],[2314,1400],[4352,1401],[2294,1023],[2304,1402],[2052,990],[4359,1403],[2457,1404],[2312,1405],[4117,1405],[3483,1406],[2420,553],[4239,1407],[2320,1408],[4240,1409],[3132,1410],[4275,1411],[3373,1412],[4276,1413],[3372,1414],[4277,1415],[3375,1416],[4278,1417],[3374,1418],[3289,1419],[3471,1420],[2389,1421],[2390,1422],[855,1423],[3361,1424],[4279,1425],[2357,1426],[4280,1427],[2353,1428],[4281,1429],[2354,990],[4282,1430],[2355,1428],[2359,1431],[2352,1432],[4283,1433],[2358,1434],[2360,1435],[2356,1436],[4070,1437],[4251,1438],[3346,1439],[4364,1440],[3332,1441],[3335,1023],[3323,1104],[3322,1255],[3324,1442],[3336,1443],[4371,1444],[3337,1445],[4372,1446],[3318,1200],[3319,1200],[3321,1023],[4373,1447],[3317,1200],[3320,1023],[2394,1191],[2395,1448],[3333,1449],[3344,1450],[4365,1451],[3342,1452],[2391,956],[2392,956],[3343,1453],[4366,1454],[3338,1455],[4367,1456],[3325,1457],[3326,1458],[3327,1459],[4368,1460],[3334,1461],[4360,1462],[3155,1463],[4361,1464],[3340,1465],[4362,1466],[3341,1467],[4363,1468],[3339,1469],[3328,1023],[4369,1470],[3329,1471],[4370,1472],[3330,1473],[3345,1474],[4374,1475],[3331,1123],[2393,956],[3485,1023],[3350,1476],[4284,1477],[3351,553],[2361,956],[4241,1478],[2068,1479],[4252,1480],[3148,956],[4375,1481],[2321,1380],[2322,1200],[4376,1482],[2318,1045],[2397,1483],[2396,1484],[854,1485],[2399,1486],[2398,1487],[3475,1123],[4285,1488],[2417,1489],[4253,1490],[2348,1491],[4377,1492],[3589,1493],[2267,956],[2074,1045],[4378,1494],[3728,1495],[3149,1496],[4254,1497],[3426,1316],[4379,1498],[2323,1499],[4380,1500],[2326,1501],[3412,1502],[1799,956],[4387,1503],[3402,1504],[3399,1023],[3419,1505],[3403,1506],[4388,1507],[3391,1123],[3411,1508],[3390,1509],[3406,1510],[4389,1511],[3405,1512],[3407,1513],[4390,1514],[3414,1515],[4391,1516],[3393,1517],[4392,1518],[3418,1519],[2325,1520],[4381,1521],[3398,1522],[3410,1523],[4382,1524],[3395,1525],[4383,1526],[3404,1527],[4384,1528],[3384,1342],[3385,1529],[3652,1525],[3386,1530],[4385,1531],[3388,1532],[3397,1533],[3396,1104],[3394,1023],[2400,1534],[3387,1023],[3389,1023],[4386,1535],[3415,1536],[4393,1537],[1798,956],[3413,1538],[4394,1539],[3392,1342],[4395,1540],[3453,1541],[2402,1542],[2401,1543],[4397,1544],[3455,1545],[4396,1546],[3454,1547],[3472,1548],[3441,1549],[3468,1550],[4398,1551],[3469,1552],[4399,1553],[3445,1554],[3432,1555],[2403,956],[3428,990],[3470,1556],[3430,1316],[4255,1557],[3474,1558],[4286,1559],[3166,1560],[2363,1561],[2362,956],[4287,1562],[2418,1563],[4400,1564],[3447,1194],[4401,1565],[2415,1566],[2404,1567],[850,1568],[4404,1569],[3443,1570],[4403,1571],[3442,1572],[4402,1573],[1797,1574],[4256,1575],[3128,1576],[4289,1577],[3121,1578],[4290,1579],[3122,1580],[2365,1581],[2364,956],[2366,956],[4291,1582],[3123,1583],[4292,1584],[3124,1585],[4288,1586],[3126,1587],[4293,1588],[3127,1589],[2341,1590],[1803,1591],[3153,1592],[4242,1593],[4095,1594],[2319,1595],[4406,1596],[2331,1597],[4405,1598],[3154,1599],[2405,1600],[2330,956],[4407,1601],[3509,1602],[4257,1603],[3510,1604],[2342,956],[2350,1605],[2349,1606],[3481,1607],[3482,1608],[4111,1609],[3152,1610],[4408,1611],[3151,1612],[3150,1613],[4410,1614],[4015,1615],[4011,1616],[4020,1617],[4411,1618],[4013,1619],[2408,1620],[2407,1621],[4412,1622],[4018,1023],[4413,1623],[4012,1624],[4414,1625],[4014,1200],[4415,1626],[4021,1627],[4009,1628],[4416,1629],[4010,1630],[4417,1631],[3777,1632],[4418,1633],[4017,1634],[4016,1635],[4409,1636],[3156,1637],[2410,1638],[2409,956],[4019,1548],[2406,956],[3449,1220],[4258,1639],[2054,1457],[4259,1640],[3438,1641],[3476,1123],[3477,1255],[4424,1642],[4065,1643],[4419,1644],[2295,1200],[4420,1645],[2296,1200],[4421,1646],[2299,1647],[4422,1648],[2297,1200],[4423,1649],[2298,1200],[4069,1650],[4068,1651],[4067,1652],[2234,956],[3253,1653],[3486,1104],[4260,1654],[3370,1655],[2367,956],[3266,1656],[3268,1657],[4294,1658],[3267,984],[4295,1659],[3254,1660],[4296,1661],[3409,1662],[4297,1663],[3408,1664],[2369,1665],[2368,1171],[3269,1666],[2370,956],[4303,1667],[3256,1668],[4304,1669],[3255,1670],[4305,1671],[3257,1672],[4306,1673],[3258,1674],[4298,1675],[3259,1380],[4299,1676],[3260,1677],[4300,1678],[3263,1679],[4301,1680],[3261,984],[4302,1681],[3262,1682],[2372,1683],[2371,1684],[4307,1685],[3264,1686],[4308,1687],[3265,1688],[4309,1689],[3369,1690],[2373,956],[4310,1691],[2303,1692],[4311,1693],[2300,1380],[2301,1380],[4313,1694],[4066,1695],[4312,1696],[2302,1697],[4426,1698],[3348,1699],[4427,1700],[4110,1701],[4425,1702],[2327,1703],[1796,956],[2291,990],[4428,1704],[3296,990],[3433,1705],[4243,1706],[3270,1707],[4431,1708],[4090,1709],[4091,1710],[4088,1711],[4429,1712],[3653,1713],[4430,1714],[4089,1715],[853,956],[4437,1716],[4093,1717],[3157,1718],[4432,1719],[3487,1241],[4433,1720],[2292,1721],[4438,1722],[3489,1723],[3490,1724],[4439,1725],[3488,956],[2412,1726],[2411,956],[4434,1727],[3494,1728],[4435,1729],[3492,1730],[4436,1731],[3493,1732],[2413,1056],[4244,1733],[4094,1734],[4442,1735],[3158,1736],[4443,1737],[4444,1738],[3159,1739],[4440,1740],[3145,1741],[4441,1742],[4098,1743],[4099,1744],[4314,1745],[4097,1200],[4245,1746],[4100,1747],[4446,1748],[4159,1749],[4445,1750],[3381,1751],[4217,1752],[4447,1753],[4155,1752],[4168,956],[4158,1754],[4157,1755],[4216,1755],[4166,1755],[4161,1755],[4448,974],[4156,1756],[4167,1756],[4213,1756],[4218,1755],[4223,1757],[4160,1756],[4449,1758],[3129,1759],[3271,1359],[4246,1760],[4261,1761],[3473,1762],[3502,1763],[4247,1764],[2347,1765],[4319,1766],[4113,1767],[4320,1768],[4114,1769],[4321,1770],[4115,1771],[4318,1772],[4116,1773],[4322,1774],[4119,1775],[4323,1776],[4120,1777],[4324,1778],[3501,1779],[4325,1780],[4118,1781],[4315,1782],[4107,1783],[4316,1784],[4121,1785],[4317,1786],[4123,1787],[4326,1788],[4122,1023],[2375,1789],[2374,956],[2377,1790],[2376,956],[4262,1791],[4112,1792],[4263,1793],[3161,1794],[4450,1795],[4148,1796],[4451,1797],[4146,1798],[4150,1799],[4452,1800],[4147,1021],[4453,1801],[4149,1802],[2328,956],[4145,1803],[4454,1804],[4143,1805],[4455,1806],[2329,1807],[4456,1808],[4141,1809],[4144,1810],[4142,1457],[3377,1811],[3376,1812],[2460,1813],[4457,1814],[2475,553],[2414,956],[4458,1815],[2474,1816],[2473,1023],[2462,1817],[2465,1818],[4465,1819],[2464,553],[2471,1104],[2470,553],[4466,1820],[2472,1821],[4467,1822],[2469,553],[4460,1823],[3382,1824],[4461,1825],[2461,1826],[4468,1827],[2494,1023],[2466,956],[2467,1828],[4469,1829],[2497,1830],[2504,1831],[4470,1832],[2498,1833],[2481,1834],[4471,1835],[2502,1836],[2503,1837],[4472,1838],[2499,1839],[2491,956],[2492,1840],[4473,1841],[2501,1842],[4474,1843],[2500,1844],[2493,1751],[4475,1845],[2496,1846],[4476,1847],[2495,1848],[2478,984],[4477,1849],[2477,1850],[2468,1851],[2482,956],[4462,1852],[3378,1853],[3379,1854],[4463,1855],[3380,1856],[4464,1857],[2458,553],[2485,1858],[2490,1859],[2486,1860],[2487,1861],[2488,1862],[4478,1863],[2489,1864],[2483,956],[2505,1863],[2484,1865],[4459,1866],[2459,956],[2463,1867],[2476,1868],[3444,956],[3500,1869],[4327,1870],[3160,1871],[3116,1872],[3117,1873],[4154,1874],[4479,1875],[3125,1876],[3118,1877],[3120,1878],[2511,1879],[2509,1879],[2508,1879],[2510,1880],[2507,1879],[2506,1879],[2512,1045],[4481,1881],[2515,1882],[2513,553],[4480,1883],[3400,1884],[3401,1885],[3416,1886],[3417,1887],[2514,1888],[2516,1889],[2053,1890],[2346,1891],[2517,1892],[1800,956],[2518,1893],[1801,956],[856,1],[1802,956],[269,956],[2519,1894],[2520,1895],[852,1896],[2521,1897],[2061,1898],[2522,956],[2524,1899],[2523,956],[2525,1900],[2107,1901],[2781,1902],[2780,1903],[2783,1904],[2782,956],[2784,1905],[2148,956],[2786,1906],[2785,956],[2787,1907],[851,956],[2788,1908],[2324,956],[2789,1909],[2340,1045],[2790,956],[2791,1910],[2229,1045],[2792,1911],[2108,956],[2793,1912],[2143,1045],[2794,956],[2795,1913],[2351,1487],[2796,1914],[1794,956],[4482,1915],[2801,1916],[2803,1917],[2807,1918],[3250,1919],[4483,1920],[268,1921]],"semanticDiagnosticsPerFile":[[2192,[{"start":5247,"length":6,"code":2322,"category":1,"messageText":"Type 'string' is not assignable to type 'undefined'."}]],[2255,[{"start":1387,"length":5,"code":2322,"category":1,"messageText":{"messageText":"Type '{ user_id: string; user_email: string; user_alias: null; user_role: string; spend: number; max_budget: null; key_count: number; created_at: string; updated_at: string; sso_user_id: null; budget_duration: null; }[]' is not assignable to type 'UserInfo[]'.","category":1,"code":2322,"next":[{"messageText":"Property 'models' is missing in type '{ user_id: string; user_email: string; user_alias: null; user_role: string; spend: number; max_budget: null; key_count: number; created_at: string; updated_at: string; sso_user_id: null; budget_duration: null; }' but required in type 'UserInfo'.","category":1,"code":2741,"canonicalHead":{"code":2322,"messageText":"Type '{ user_id: string; user_email: string; user_alias: null; user_role: string; spend: number; max_budget: null; key_count: number; created_at: string; updated_at: string; sso_user_id: null; budget_duration: null; }' is not assignable to type 'UserInfo'."}}]},"relatedInformation":[{"file":"./src/components/networking.tsx","start":27733,"length":6,"messageText":"'models' is declared here.","category":3,"code":2728},{"file":"./src/components/networking.tsx","start":28040,"length":5,"messageText":"The expected type comes from property 'users' which is declared here on type 'UserListResponse'","category":3,"code":6500}]}]],[2337,[{"start":328,"length":6,"code":2741,"category":1,"messageText":"Property 'environment' is missing in type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' but required in type 'PromptType'.","relatedInformation":[{"file":"./src/app/(dashboard)/prompts/components/prompt_editor_view/types.ts","start":368,"length":11,"messageText":"'environment' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' is not assignable to type 'PromptType'."}},{"start":784,"length":6,"code":2741,"category":1,"messageText":"Property 'environment' is missing in type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' but required in type 'PromptType'.","relatedInformation":[{"file":"./src/app/(dashboard)/prompts/components/prompt_editor_view/types.ts","start":368,"length":11,"messageText":"'environment' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' is not assignable to type 'PromptType'."}},{"start":1182,"length":6,"code":2741,"category":1,"messageText":"Property 'environment' is missing in type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' but required in type 'PromptType'.","relatedInformation":[{"file":"./src/app/(dashboard)/prompts/components/prompt_editor_view/types.ts","start":368,"length":11,"messageText":"'environment' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' is not assignable to type 'PromptType'."}},{"start":1684,"length":6,"code":2741,"category":1,"messageText":"Property 'environment' is missing in type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' but required in type 'PromptType'.","relatedInformation":[{"file":"./src/app/(dashboard)/prompts/components/prompt_editor_view/types.ts","start":368,"length":11,"messageText":"'environment' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' is not assignable to type 'PromptType'."}},{"start":2044,"length":6,"code":2741,"category":1,"messageText":"Property 'environment' is missing in type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' but required in type 'PromptType'.","relatedInformation":[{"file":"./src/app/(dashboard)/prompts/components/prompt_editor_view/types.ts","start":368,"length":11,"messageText":"'environment' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' is not assignable to type 'PromptType'."}},{"start":2529,"length":6,"code":2741,"category":1,"messageText":"Property 'environment' is missing in type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' but required in type 'PromptType'.","relatedInformation":[{"file":"./src/app/(dashboard)/prompts/components/prompt_editor_view/types.ts","start":368,"length":11,"messageText":"'environment' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' is not assignable to type 'PromptType'."}},{"start":3149,"length":6,"code":2741,"category":1,"messageText":"Property 'environment' is missing in type '{ name: string; model: string; config: { temperature: number; max_tokens: number; top_p: number; }; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' but required in type 'PromptType'.","relatedInformation":[{"file":"./src/app/(dashboard)/prompts/components/prompt_editor_view/types.ts","start":368,"length":11,"messageText":"'environment' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ name: string; model: string; config: { temperature: number; max_tokens: number; top_p: number; }; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' is not assignable to type 'PromptType'."}},{"start":3683,"length":6,"code":2741,"category":1,"messageText":"Property 'environment' is missing in type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' but required in type 'PromptType'.","relatedInformation":[{"file":"./src/app/(dashboard)/prompts/components/prompt_editor_view/types.ts","start":368,"length":11,"messageText":"'environment' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' is not assignable to type 'PromptType'."}},{"start":4135,"length":6,"code":2741,"category":1,"messageText":"Property 'environment' is missing in type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' but required in type 'PromptType'.","relatedInformation":[{"file":"./src/app/(dashboard)/prompts/components/prompt_editor_view/types.ts","start":368,"length":11,"messageText":"'environment' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' is not assignable to type 'PromptType'."}},{"start":4538,"length":6,"code":2741,"category":1,"messageText":"Property 'environment' is missing in type '{ name: string; model: string; config: {}; tools: { name: string; description: string; json: string; }[]; developerMessage: string; messages: { role: string; content: string; }[]; }' but required in type 'PromptType'.","relatedInformation":[{"file":"./src/app/(dashboard)/prompts/components/prompt_editor_view/types.ts","start":368,"length":11,"messageText":"'environment' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ name: string; model: string; config: {}; tools: { name: string; description: string; json: string; }[]; developerMessage: string; messages: { role: string; content: string; }[]; }' is not assignable to type 'PromptType'."}},{"start":5174,"length":6,"code":2741,"category":1,"messageText":"Property 'environment' is missing in type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' but required in type 'PromptType'.","relatedInformation":[{"file":"./src/app/(dashboard)/prompts/components/prompt_editor_view/types.ts","start":368,"length":11,"messageText":"'environment' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ name: string; model: string; config: {}; tools: never[]; developerMessage: string; messages: { role: string; content: string; }[]; }' is not assignable to type 'PromptType'."}}]],[2517,[{"start":1240,"length":3,"messageText":"Tuple type '[]' of length '0' has no element at index '0'.","category":1,"code":2493},{"start":1245,"length":4,"messageText":"Tuple type '[]' of length '0' has no element at index '1'.","category":1,"code":2493},{"start":1409,"length":4,"messageText":"'init' is possibly 'undefined'.","category":1,"code":18048},{"start":1534,"length":4,"messageText":"'init' is possibly 'undefined'.","category":1,"code":18048},{"start":1861,"length":4,"messageText":"Tuple type '[]' of length '0' has no element at index '1'.","category":1,"code":2493},{"start":1905,"length":4,"messageText":"'init' is possibly 'undefined'.","category":1,"code":18048},{"start":1943,"length":4,"messageText":"'init' is possibly 'undefined'.","category":1,"code":18048},{"start":3779,"length":4,"messageText":"Tuple type '[]' of length '0' has no element at index '1'.","category":1,"code":2493},{"start":3823,"length":4,"messageText":"'init' is possibly 'undefined'.","category":1,"code":18048}]],[2792,[{"start":227,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":309,"length":10,"messageText":"Cannot find name 'beforeEach'.","category":1,"code":2304},{"start":862,"length":9,"messageText":"Cannot find name 'afterEach'.","category":1,"code":2304},{"start":1031,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1069,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1154,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1231,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1293,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1436,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1515,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1620,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1667,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1712,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1791,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1873,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1931,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1978,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2325,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2402,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2710,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2757,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2805,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":3146,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3274,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":3599,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3648,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":3993,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4055,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":4092,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":4515,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4591,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":5256,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":5333,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":5600,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":5647,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":5688,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":5754,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":5818,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":5881,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":5938,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6003,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":6127,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6281,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6370,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":6428,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6494,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6567,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":6612,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6667,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":6714,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6778,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":6851,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6922,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":6995,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":7040,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7135,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":7518,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":7596,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":8048,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":8128,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":8605,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":8746,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":8872,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":8950,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":8991,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":9776,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9846,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":9900,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":10185,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":10228,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":11085,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":11162,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":11433,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[2793,[{"start":3234,"length":405,"code":2741,"category":1,"messageText":"Property 'spend' is missing in type '{ team_id: string; team_alias: string; models: never[]; max_budget: null; budget_duration: null; tpm_limit: null; rpm_limit: null; organization_id: string; created_at: string; keys: never[]; members_with_roles: { ...; }[]; }' but required in type 'Team'.","relatedInformation":[{"file":"./src/components/key_team_helpers/key_list.tsx","start":480,"length":5,"messageText":"'spend' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ team_id: string; team_alias: string; models: never[]; max_budget: null; budget_duration: null; tpm_limit: null; rpm_limit: null; organization_id: string; created_at: string; keys: never[]; members_with_roles: { ...; }[]; }' is not assignable to type 'Team'."}},{"start":3649,"length":406,"code":2741,"category":1,"messageText":"Property 'spend' is missing in type '{ team_id: string; team_alias: string; models: never[]; max_budget: null; budget_duration: null; tpm_limit: null; rpm_limit: null; organization_id: string; created_at: string; keys: never[]; members_with_roles: { ...; }[]; }' but required in type 'Team'.","relatedInformation":[{"file":"./src/components/key_team_helpers/key_list.tsx","start":480,"length":5,"messageText":"'spend' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ team_id: string; team_alias: string; models: never[]; max_budget: null; budget_duration: null; tpm_limit: null; rpm_limit: null; organization_id: string; created_at: string; keys: never[]; members_with_roles: { ...; }[]; }' is not assignable to type 'Team'."}},{"start":4255,"length":405,"code":2741,"category":1,"messageText":"Property 'spend' is missing in type '{ team_id: string; team_alias: string; models: never[]; max_budget: null; budget_duration: null; tpm_limit: null; rpm_limit: null; organization_id: string; created_at: string; keys: never[]; members_with_roles: { ...; }[]; }' but required in type 'Team'.","relatedInformation":[{"file":"./src/components/key_team_helpers/key_list.tsx","start":480,"length":5,"messageText":"'spend' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ team_id: string; team_alias: string; models: never[]; max_budget: null; budget_duration: null; tpm_limit: null; rpm_limit: null; organization_id: string; created_at: string; keys: never[]; members_with_roles: { ...; }[]; }' is not assignable to type 'Team'."}},{"start":4670,"length":406,"code":2741,"category":1,"messageText":"Property 'spend' is missing in type '{ team_id: string; team_alias: string; models: never[]; max_budget: null; budget_duration: null; tpm_limit: null; rpm_limit: null; organization_id: string; created_at: string; keys: never[]; members_with_roles: { ...; }[]; }' but required in type 'Team'.","relatedInformation":[{"file":"./src/components/key_team_helpers/key_list.tsx","start":480,"length":5,"messageText":"'spend' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ team_id: string; team_alias: string; models: never[]; max_budget: null; budget_duration: null; tpm_limit: null; rpm_limit: null; organization_id: string; created_at: string; keys: never[]; members_with_roles: { ...; }[]; }' is not assignable to type 'Team'."}}]],[3134,[{"start":3081,"length":4,"messageText":"Tuple type '[]' of length '0' has no element at index '0'.","category":1,"code":2493},{"start":3087,"length":4,"messageText":"Tuple type '[]' of length '0' has no element at index '1'.","category":1,"code":2493},{"start":3179,"length":4,"messageText":"'opts' is possibly 'undefined'.","category":1,"code":18048}]],[3358,[{"start":198,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":366,"length":9,"messageText":"Cannot find name 'afterEach'.","category":1,"code":2304},{"start":417,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":500,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":569,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":702,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":944,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1191,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1266,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1353,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1621,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1713,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2502,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2590,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2781,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2875,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2988,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3090,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":3429,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3509,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":3922,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4001,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":4113,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4271,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[3498,[{"start":3783,"length":17,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}}]],[4032,[{"start":2903,"length":4,"code":2741,"category":1,"messageText":"Property 'user_alias' is missing in type '{ user_id: string; user_email: string; }' but required in type '{ user_id: string; user_email: string; user_alias: string | null; }'.","relatedInformation":[{"file":"./src/components/key_team_helpers/key_list.tsx","start":3095,"length":10,"messageText":"'user_alias' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ user_id: string; user_email: string; }' is not assignable to type '{ user_id: string; user_email: string; user_alias: string | null; }'."}}]],[4155,[{"start":74,"length":23,"messageText":"Cannot find module '@base-ui/react/button' or its corresponding type declarations.","category":1,"code":2307}]],[4156,[{"start":63,"length":26,"messageText":"Cannot find module '@base-ui/react/separator' or its corresponding type declarations.","category":1,"code":2307}]],[4158,[{"start":89,"length":23,"messageText":"Cannot find module '@base-ui/react/dialog' or its corresponding type declarations.","category":1,"code":2307}]],[4159,[{"start":99,"length":29,"messageText":"Cannot find module '@base-ui/react/alert-dialog' or its corresponding type declarations.","category":1,"code":2307}]],[4160,[{"start":59,"length":24,"messageText":"Cannot find module '@base-ui/react/tooltip' or its corresponding type declarations.","category":1,"code":2307}]],[4161,[{"start":97,"length":28,"messageText":"Cannot find module '@base-ui/react/scroll-area' or its corresponding type declarations.","category":1,"code":2307}]],[4162,[{"start":7276,"length":1,"messageText":"Parameter 'v' implicitly has an 'any' type.","category":1,"code":7006}]],[4166,[{"start":91,"length":24,"messageText":"Cannot find module '@base-ui/react/popover' or its corresponding type declarations.","category":1,"code":2307}]],[4168,[{"start":67,"length":28,"messageText":"Cannot find module '@base-ui/react/collapsible' or its corresponding type declarations.","category":1,"code":2307}]],[4213,[{"start":57,"length":23,"messageText":"Cannot find module '@base-ui/react/switch' or its corresponding type declarations.","category":1,"code":2307}]],[4214,[{"start":4502,"length":7,"messageText":"Parameter 'checked' implicitly has an 'any' type.","category":1,"code":7006}]],[4215,[{"start":15781,"length":4,"messageText":"Parameter 'open' implicitly has an 'any' type.","category":1,"code":7006}]],[4219,[{"start":11141,"length":1,"messageText":"Parameter 'v' implicitly has an 'any' type.","category":1,"code":7006}]],[4223,[{"start":53,"length":21,"messageText":"Cannot find module '@base-ui/react/tabs' or its corresponding type declarations.","category":1,"code":2307}]],[4224,[{"start":15862,"length":1,"messageText":"Parameter 'v' implicitly has an 'any' type.","category":1,"code":7006}]],[4229,[{"start":9097,"length":9,"messageText":"Cannot find name 'afterEach'.","category":1,"code":2304}]],[4240,[{"start":401,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":442,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":647,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":711,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":957,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1064,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1307,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1383,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1651,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1701,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1948,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1998,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2220,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2297,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2528,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[4254,[{"start":1608,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1644,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1732,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1799,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1896,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1961,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2024,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2092,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2193,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2256,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2357,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2475,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[4264,[{"start":1907,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1950,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2028,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2097,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2301,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2405,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2514,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2577,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2677,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2739,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2854,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2921,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":3036,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3099,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3162,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3223,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":3332,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3395,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3467,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":3704,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3819,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3960,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":4069,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4131,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":4307,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4368,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":4464,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4556,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":4782,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":4842,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":5078,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[4273,[{"start":2211,"length":27,"code":2769,"category":1,"messageText":{"messageText":"No overload matches this call.","category":1,"code":2769,"next":[{"messageText":"Overload 1 of 2, '(id: Matcher, options?: SelectorMatcherOptions | undefined): HTMLElement', gave the following error.","category":1,"code":2772,"next":[{"messageText":"Argument of type 'string | null' is not assignable to parameter of type 'Matcher'.","category":1,"code":2345,"next":[{"messageText":"Type 'null' is not assignable to type 'Matcher'.","category":1,"code":2322}]}]},{"messageText":"Overload 2 of 2, '(id: Matcher, options?: SelectorMatcherOptions | undefined): HTMLElement', gave the following error.","category":1,"code":2772,"next":[{"messageText":"Argument of type 'string | null' is not assignable to parameter of type 'Matcher'.","category":1,"code":2345,"next":[{"messageText":"Type 'null' is not assignable to type 'Matcher'.","category":1,"code":2322}]}]}]},"relatedInformation":[]},{"start":2290,"length":26,"code":2769,"category":1,"messageText":{"messageText":"No overload matches this call.","category":1,"code":2769,"next":[{"messageText":"Overload 1 of 2, '(id: Matcher, options?: SelectorMatcherOptions | undefined): HTMLElement', gave the following error.","category":1,"code":2772,"next":[{"messageText":"Argument of type 'string | null' is not assignable to parameter of type 'Matcher'.","category":1,"code":2345,"next":[{"messageText":"Type 'null' is not assignable to type 'Matcher'.","category":1,"code":2322}]}]},{"messageText":"Overload 2 of 2, '(id: Matcher, options?: SelectorMatcherOptions | undefined): HTMLElement', gave the following error.","category":1,"code":2772,"next":[{"messageText":"Argument of type 'string | null' is not assignable to parameter of type 'Matcher'.","category":1,"code":2345,"next":[{"messageText":"Type 'null' is not assignable to type 'Matcher'.","category":1,"code":2322}]}]}]},"relatedInformation":[]}]],[4275,[{"start":505,"length":14,"code":2739,"category":1,"messageText":"Type '{ token: string; token_id: string; key_name: string; key_alias: string; spend: number; max_budget: number; expires: string; models: string[]; aliases: {}; config: {}; user_id: string; team_id: string; ... 42 more ...; deleted_by: string; }' is missing the following properties from type 'DeletedKeyResponse': project_id, last_active","canonicalHead":{"code":2322,"messageText":"Type '{ token: string; token_id: string; key_name: string; key_alias: string; spend: number; max_budget: number; expires: string; models: string[]; aliases: {}; config: {}; user_id: string; team_id: string; ... 42 more ...; deleted_by: string; }' is not assignable to type 'DeletedKeyResponse'."}}]],[4276,[{"start":307,"length":14,"code":2739,"category":1,"messageText":"Type '{ token: string; token_id: string; key_name: string; key_alias: string; spend: number; max_budget: number; expires: string; models: string[]; aliases: {}; config: {}; user_id: string; team_id: string; ... 42 more ...; deleted_by: string; }' is missing the following properties from type 'DeletedKeyResponse': project_id, last_active","canonicalHead":{"code":2322,"messageText":"Type '{ token: string; token_id: string; key_name: string; key_alias: string; spend: number; max_budget: number; expires: string; models: string[]; aliases: {}; config: {}; user_id: string; team_id: string; ... 42 more ...; deleted_by: string; }' is not assignable to type 'DeletedKeyResponse'."}}]],[4280,[{"start":162,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":205,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":319,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":384,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":517,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":602,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":751,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[4281,[{"start":119,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":155,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":397,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":451,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":699,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":788,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":880,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1165,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1233,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1492,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1560,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1820,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[4282,[{"start":211,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":252,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":384,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":454,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":615,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":698,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":788,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":875,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1063,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1159,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1503,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1570,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1738,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[4283,[{"start":877,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":917,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1015,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1106,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1371,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1444,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1769,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1848,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2010,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2082,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2521,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[4284,[{"start":144,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":177,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":286,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":359,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":488,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":554,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":618,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":732,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":793,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":961,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1031,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1172,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1248,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1396,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1468,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1599,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[4291,[{"start":234,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":274,"length":10,"messageText":"Cannot find name 'beforeEach'.","category":1,"code":2304},{"start":328,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":586,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":734,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":801,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":860,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":934,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1176,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1270,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1593,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1754,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1854,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2165,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2265,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2978,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[4314,[{"start":208,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":242,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":313,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":387,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":451,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":525,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":605,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":681,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":716,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":864,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":932,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1101,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1167,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1339,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1423,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1528,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1706,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1817,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1863,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1916,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1971,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2038,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2089,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[4324,[{"start":1769,"length":8,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":13971,"length":49,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ accessToken: string; token: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ accessToken: string; token: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}}]],[4328,[{"start":3421,"length":8,"code":2741,"category":1,"messageText":"Property 'spend' is missing in type '{ team_id: string; team_alias: string; models: string[]; max_budget: number; budget_duration: string; tpm_limit: null; rpm_limit: null; organization_id: string; created_at: string; keys: never[]; members_with_roles: never[]; }' but required in type 'Team'.","relatedInformation":[{"file":"./src/components/key_team_helpers/key_list.tsx","start":480,"length":5,"messageText":"'spend' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ team_id: string; team_alias: string; models: string[]; max_budget: number; budget_duration: string; tpm_limit: null; rpm_limit: null; organization_id: string; created_at: string; keys: never[]; members_with_roles: never[]; }' is not assignable to type 'Team'."}},{"start":5020,"length":49,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":5537,"length":49,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":6471,"length":49,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":7419,"length":49,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":8366,"length":49,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":9161,"length":43,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}}]],[4329,[{"start":495,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":540,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":691,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":779,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":952,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1017,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1082,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1148,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1221,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1400,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1460,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1540,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1629,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1706,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1895,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1978,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2174,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2240,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2311,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2382,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2453,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":2640,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":2724,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":3063,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3120,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":3184,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":3846,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[4331,[{"start":3122,"length":311,"code":2741,"category":1,"messageText":"Property 'spend' is missing in type '{ team_id: string; team_alias: string; models: string[]; max_budget: number; budget_duration: string; tpm_limit: null; rpm_limit: null; organization_id: string; created_at: string; keys: never[]; members_with_roles: never[]; }' but required in type 'Team'.","relatedInformation":[{"file":"./src/components/key_team_helpers/key_list.tsx","start":480,"length":5,"messageText":"'spend' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ team_id: string; team_alias: string; models: string[]; max_budget: number; budget_duration: string; tpm_limit: null; rpm_limit: null; organization_id: string; created_at: string; keys: never[]; members_with_roles: never[]; }' is not assignable to type 'Team'."}}]],[4351,[{"start":795,"length":13,"code":2322,"category":1,"messageText":{"messageText":"Type '{ organization_id: string; organization_alias: string; budget_id: string; metadata: {}; models: never[]; spend: number; model_spend: {}; created_at: string; created_by: string; updated_at: string; }[]' is not assignable to type 'Organization[]'.","category":1,"code":2322,"next":[{"messageText":"Type '{ organization_id: string; organization_alias: string; budget_id: string; metadata: {}; models: never[]; spend: number; model_spend: {}; created_at: string; created_by: string; updated_at: string; }' is missing the following properties from type 'Organization': updated_by, litellm_budget_table, teams, users, members","category":1,"code":2739,"canonicalHead":{"code":2322,"messageText":"Type '{ organization_id: string; organization_alias: string; budget_id: string; metadata: {}; models: never[]; spend: number; model_spend: {}; created_at: string; created_by: string; updated_at: string; }' is not assignable to type 'Organization'."}}]},"relatedInformation":[{"file":"./src/components/common_components/organizationdropdown.tsx","start":187,"length":13,"messageText":"The expected type comes from property 'organizations' which is declared here on type 'IntrinsicAttributes & OrganizationDropdownProps'","category":3,"code":6500}]},{"start":1034,"length":13,"code":2322,"category":1,"messageText":{"messageText":"Type '{ organization_id: string; organization_alias: string; budget_id: string; metadata: {}; models: never[]; spend: number; model_spend: {}; created_at: string; created_by: string; updated_at: string; }[]' is not assignable to type 'Organization[]'.","category":1,"code":2322,"next":[{"messageText":"Type '{ organization_id: string; organization_alias: string; budget_id: string; metadata: {}; models: never[]; spend: number; model_spend: {}; created_at: string; created_by: string; updated_at: string; }' is missing the following properties from type 'Organization': updated_by, litellm_budget_table, teams, users, members","category":1,"code":2739,"canonicalHead":{"code":2322,"messageText":"Type '{ organization_id: string; organization_alias: string; budget_id: string; metadata: {}; models: never[]; spend: number; model_spend: {}; created_at: string; created_by: string; updated_at: string; }' is not assignable to type 'Organization'."}}]},"relatedInformation":[{"file":"./src/components/common_components/organizationdropdown.tsx","start":187,"length":13,"messageText":"The expected type comes from property 'organizations' which is declared here on type 'IntrinsicAttributes & OrganizationDropdownProps'","category":3,"code":6500}]},{"start":1448,"length":13,"code":2322,"category":1,"messageText":{"messageText":"Type '{ organization_id: string; organization_alias: string; budget_id: string; metadata: {}; models: never[]; spend: number; model_spend: {}; created_at: string; created_by: string; updated_at: string; }[]' is not assignable to type 'Organization[]'.","category":1,"code":2322,"next":[{"messageText":"Type '{ organization_id: string; organization_alias: string; budget_id: string; metadata: {}; models: never[]; spend: number; model_spend: {}; created_at: string; created_by: string; updated_at: string; }' is missing the following properties from type 'Organization': updated_by, litellm_budget_table, teams, users, members","category":1,"code":2739,"canonicalHead":{"code":2322,"messageText":"Type '{ organization_id: string; organization_alias: string; budget_id: string; metadata: {}; models: never[]; spend: number; model_spend: {}; created_at: string; created_by: string; updated_at: string; }' is not assignable to type 'Organization'."}}]},"relatedInformation":[{"file":"./src/components/common_components/organizationdropdown.tsx","start":187,"length":13,"messageText":"The expected type comes from property 'organizations' which is declared here on type 'IntrinsicAttributes & OrganizationDropdownProps'","category":3,"code":6500}]},{"start":1828,"length":13,"code":2322,"category":1,"messageText":{"messageText":"Type '{ organization_id: string; organization_alias: string; budget_id: string; metadata: {}; models: never[]; spend: number; model_spend: {}; created_at: string; created_by: string; updated_at: string; }[]' is not assignable to type 'Organization[]'.","category":1,"code":2322,"next":[{"messageText":"Type '{ organization_id: string; organization_alias: string; budget_id: string; metadata: {}; models: never[]; spend: number; model_spend: {}; created_at: string; created_by: string; updated_at: string; }' is missing the following properties from type 'Organization': updated_by, litellm_budget_table, teams, users, members","category":1,"code":2739,"canonicalHead":{"code":2322,"messageText":"Type '{ organization_id: string; organization_alias: string; budget_id: string; metadata: {}; models: never[]; spend: number; model_spend: {}; created_at: string; created_by: string; updated_at: string; }' is not assignable to type 'Organization'."}}]},"relatedInformation":[{"file":"./src/components/common_components/organizationdropdown.tsx","start":187,"length":13,"messageText":"The expected type comes from property 'organizations' which is declared here on type 'IntrinsicAttributes & OrganizationDropdownProps'","category":3,"code":6500}]}]],[4352,[{"start":378,"length":8,"messageText":"Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":422,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":581,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":659,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":842,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":920,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1103,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1181,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1372,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304},{"start":1453,"length":2,"messageText":"Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.","category":1,"code":2582},{"start":1842,"length":6,"messageText":"Cannot find name 'expect'.","category":1,"code":2304}]],[4367,[{"start":3286,"length":15,"code":2339,"category":1,"messageText":{"messageText":"Property 'CustomGuardrail' does not exist on type 'Record | typeof GuardrailProviders'.","category":1,"code":2339,"next":[{"messageText":"Property 'CustomGuardrail' does not exist on type 'typeof GuardrailProviders'.","category":1,"code":2339}]}}]],[4373,[{"start":1308,"length":16,"code":2322,"category":1,"messageText":{"messageText":"Type '{ name: string; category: string; description: string; }[]' is not assignable to type 'PrebuiltPattern[]'.","category":1,"code":2322,"next":[{"messageText":"Property 'display_name' is missing in type '{ name: string; category: string; description: string; }' but required in type 'PrebuiltPattern'.","category":1,"code":2741,"canonicalHead":{"code":2322,"messageText":"Type '{ name: string; category: string; description: string; }' is not assignable to type 'PrebuiltPattern'."}}]},"relatedInformation":[{"file":"./src/components/guardrails/content_filter/patternmodal.tsx","start":196,"length":12,"messageText":"'display_name' is declared here.","category":3,"code":2728},{"file":"./src/components/guardrails/content_filter/patternmodal.tsx","start":316,"length":16,"messageText":"The expected type comes from property 'prebuiltPatterns' which is declared here on type 'IntrinsicAttributes & PatternModalProps'","category":3,"code":6500}]}]],[4379,[{"start":1310,"length":11,"code":2339,"category":1,"messageText":"Property 'displayName' does not exist on type '({ value, disabled, label }: any) => Element'."}]],[4382,[{"start":788,"length":8,"messageText":"Property 'children' does not exist on type '{}'.","category":1,"code":2339},{"start":1006,"length":7,"code":2559,"category":1,"messageText":"Type '{ children: Element; }' has no properties in common with type 'IntrinsicAttributes'."},{"start":1655,"length":8,"messageText":"Property 'children' does not exist on type '{}'.","category":1,"code":2339},{"start":2175,"length":7,"code":2559,"category":1,"messageText":"Type '{ children: Element; }' has no properties in common with type 'IntrinsicAttributes'."}]],[4386,[{"start":2737,"length":5,"code":2339,"category":1,"messageText":"Property 'value' does not exist on type 'HTMLElement'."},{"start":2867,"length":5,"code":2339,"category":1,"messageText":"Property 'value' does not exist on type 'HTMLElement'."},{"start":3883,"length":5,"code":2339,"category":1,"messageText":"Property 'value' does not exist on type 'HTMLElement'."}]],[4406,[{"start":5117,"length":4,"code":2339,"category":1,"messageText":"Property 'type' does not exist on type '{}'."}]],[4435,[{"start":2078,"length":20,"code":2741,"category":1,"messageText":"Property 'budget_reset_at' is missing in type '{ budget_id: string; soft_budget: null; max_budget: number; max_parallel_requests: null; tpm_limit: number; rpm_limit: number; model_max_budget: null; budget_duration: null; }' but required in type '{ budget_id: string; soft_budget: number | null; max_budget: number | null; max_parallel_requests: number | null; tpm_limit: number | null; rpm_limit: number | null; model_max_budget: Record<...> | null; budget_duration: string | null; budget_reset_at: string | null; allowed_models?: string[] | ... 1 more ... | unde...'.","relatedInformation":[{"file":"./src/components/team/teaminfo.tsx","start":3704,"length":15,"messageText":"'budget_reset_at' is declared here.","category":3,"code":2728},{"file":"./src/components/team/teaminfo.tsx","start":3399,"length":20,"messageText":"The expected type comes from property 'litellm_budget_table' which is declared here on type 'TeamMembership'","category":3,"code":6500}],"canonicalHead":{"code":2322,"messageText":"Type '{ budget_id: string; soft_budget: null; max_budget: number; max_parallel_requests: null; tpm_limit: number; rpm_limit: number; model_max_budget: null; budget_duration: null; }' is not assignable to type '{ budget_id: string; soft_budget: number | null; max_budget: number | null; max_parallel_requests: number | null; tpm_limit: number | null; rpm_limit: number | null; model_max_budget: Record<...> | null; budget_duration: string | null; budget_reset_at: string | null; allowed_models?: string[] | ... 1 more ... | unde...'."}}]],[4442,[{"start":2922,"length":13,"code":2739,"category":1,"messageText":"Type '{ token: string; token_id: string; key_name: string; key_alias: string; spend: number; max_budget: number; expires: string; models: never[]; aliases: {}; config: {}; user_id: string; team_id: null; max_parallel_requests: number; ... 44 more ...; key_rotation_at: undefined; }' is missing the following properties from type 'KeyResponse': project_id, last_active","canonicalHead":{"code":2322,"messageText":"Type '{ token: string; token_id: string; key_name: string; key_alias: string; spend: number; max_budget: number; expires: string; models: never[]; aliases: {}; config: {}; user_id: string; team_id: null; max_parallel_requests: number; ... 44 more ...; key_rotation_at: undefined; }' is not assignable to type 'KeyResponse'."}}]],[4444,[{"start":2259,"length":13,"code":2741,"category":1,"messageText":"Property 'last_active' is missing in type '{ token: string; token_id: string; key_name: string; key_alias: string; spend: number; max_budget: number; expires: string; models: never[]; aliases: {}; config: {}; user_id: string; team_id: null; project_id: null; ... 45 more ...; key_rotation_at: undefined; }' but required in type 'KeyResponse'.","relatedInformation":[{"file":"./src/components/key_team_helpers/key_list.tsx","start":1578,"length":11,"messageText":"'last_active' is declared here.","category":3,"code":2728}],"canonicalHead":{"code":2322,"messageText":"Type '{ token: string; token_id: string; key_name: string; key_alias: string; spend: number; max_budget: number; expires: string; models: never[]; aliases: {}; config: {}; user_id: string; team_id: null; project_id: null; ... 45 more ...; key_rotation_at: undefined; }' is not assignable to type 'KeyResponse'."}},{"start":4450,"length":21,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ accessToken: string; userId: string; userRole: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ accessToken: string; userId: string; userRole: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":4895,"length":21,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ accessToken: string; userId: string; userRole: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ accessToken: string; userId: string; userRole: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":5620,"length":104,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":6847,"length":94,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":7563,"length":94,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":8298,"length":94,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":9033,"length":115,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":10348,"length":96,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":11006,"length":21,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ accessToken: string; userId: string; userRole: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ accessToken: string; userId: string; userRole: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":12249,"length":115,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":12695,"length":111,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":13151,"length":115,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":13635,"length":113,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":14743,"length":102,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":15164,"length":21,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ accessToken: string; userId: string; userRole: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ accessToken: string; userId: string; userRole: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":15795,"length":21,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ accessToken: string; userId: string; userRole: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ accessToken: string; userId: string; userRole: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":16425,"length":21,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ accessToken: string; userId: string; userRole: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ accessToken: string; userId: string; userRole: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":17008,"length":112,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":18201,"length":102,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":18953,"length":102,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":19747,"length":112,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":20694,"length":112,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":21981,"length":112,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":25149,"length":112,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ userId: string; userRole: string; accessToken: string; premiumUser: boolean; token: string; userEmail: null; disabledPersonalKeyCreation: null; showSSOBanner: boolean; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}}]],[4448,[{"start":89,"length":23,"messageText":"Cannot find module '@base-ui/react/select' or its corresponding type declarations.","category":1,"code":2307}]],[4483,[{"start":1980,"length":293,"code":2345,"category":1,"messageText":{"messageText":"Argument of type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: false; }' is not assignable to parameter of type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }'.","category":1,"code":2345,"next":[{"messageText":"Type '{ token: string; accessToken: string; userId: string; userEmail: string; userRole: string; premiumUser: boolean; disabledPersonalKeyCreation: boolean; showSSOBanner: false; }' is missing the following properties from type '{ isLoading: boolean; isAuthorized: boolean; token: string | null; accessToken: any; userId: any; userEmail: any; userRole: string; premiumUser: any; disabledPersonalKeyCreation: any; showSSOBanner: boolean; }': isLoading, isAuthorized","category":1,"code":2739}]}},{"start":2424,"length":10,"code":2739,"category":1,"messageText":"Type '{ showTags: false; topKeys: never[]; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is missing the following properties from type 'TopKeyViewProps': topKeysLimit, setTopKeysLimit","canonicalHead":{"code":2322,"messageText":"Type '{ showTags: false; topKeys: never[]; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is not assignable to type 'TopKeyViewProps'."}},{"start":2638,"length":10,"code":2739,"category":1,"messageText":"Type '{ showTags: true; topKeys: never[]; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is missing the following properties from type 'TopKeyViewProps': topKeysLimit, setTopKeysLimit","canonicalHead":{"code":2322,"messageText":"Type '{ showTags: true; topKeys: never[]; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is not assignable to type 'TopKeyViewProps'."}},{"start":2857,"length":10,"code":2739,"category":1,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: TagUsage[]; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is missing the following properties from type 'TopKeyViewProps': topKeysLimit, setTopKeysLimit","canonicalHead":{"code":2322,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: TagUsage[]; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is not assignable to type 'TopKeyViewProps'."}},{"start":5736,"length":10,"code":2739,"category":1,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: TagUsage[]; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is missing the following properties from type 'TopKeyViewProps': topKeysLimit, setTopKeysLimit","canonicalHead":{"code":2322,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: TagUsage[]; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is not assignable to type 'TopKeyViewProps'."}},{"start":6118,"length":10,"code":2739,"category":1,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: TagUsage[]; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is missing the following properties from type 'TopKeyViewProps': topKeysLimit, setTopKeysLimit","canonicalHead":{"code":2322,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: TagUsage[]; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is not assignable to type 'TopKeyViewProps'."}},{"start":6926,"length":10,"code":2739,"category":1,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: never[]; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is missing the following properties from type 'TopKeyViewProps': topKeysLimit, setTopKeysLimit","canonicalHead":{"code":2322,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: never[]; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is not assignable to type 'TopKeyViewProps'."}},{"start":7351,"length":10,"code":2739,"category":1,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: undefined; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is missing the following properties from type 'TopKeyViewProps': topKeysLimit, setTopKeysLimit","canonicalHead":{"code":2322,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: undefined; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is not assignable to type 'TopKeyViewProps'."}},{"start":7757,"length":10,"code":2739,"category":1,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: null; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is missing the following properties from type 'TopKeyViewProps': topKeysLimit, setTopKeysLimit","canonicalHead":{"code":2322,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: null; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is not assignable to type 'TopKeyViewProps'."}},{"start":8309,"length":10,"code":2739,"category":1,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: TagUsage[]; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is missing the following properties from type 'TopKeyViewProps': topKeysLimit, setTopKeysLimit","canonicalHead":{"code":2322,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: TagUsage[]; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is not assignable to type 'TopKeyViewProps'."}},{"start":9294,"length":10,"code":2739,"category":1,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: TagUsage[]; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is missing the following properties from type 'TopKeyViewProps': topKeysLimit, setTopKeysLimit","canonicalHead":{"code":2322,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: TagUsage[]; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is not assignable to type 'TopKeyViewProps'."}},{"start":9836,"length":10,"code":2739,"category":1,"messageText":"Type '{ showTags: true; topKeys: never[]; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is missing the following properties from type 'TopKeyViewProps': topKeysLimit, setTopKeysLimit","canonicalHead":{"code":2322,"messageText":"Type '{ showTags: true; topKeys: never[]; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is not assignable to type 'TopKeyViewProps'."}},{"start":10256,"length":10,"code":2739,"category":1,"messageText":"Type '{ showTags: false; topKeys: never[]; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is missing the following properties from type 'TopKeyViewProps': topKeysLimit, setTopKeysLimit","canonicalHead":{"code":2322,"messageText":"Type '{ showTags: false; topKeys: never[]; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is not assignable to type 'TopKeyViewProps'."}},{"start":10874,"length":10,"code":2739,"category":1,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: never[]; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is missing the following properties from type 'TopKeyViewProps': topKeysLimit, setTopKeysLimit","canonicalHead":{"code":2322,"messageText":"Type '{ topKeys: { api_key: string; key_alias: string; tags: never[]; spend: number; }[]; showTags: true; accessToken: string; userID: string; userRole: string; teams: null; premiumUser: boolean; }' is not assignable to type 'TopKeyViewProps'."}}]]],"affectedFilesPendingEmit":[2802,3251,3169,3167,3170,3168,3252,3171,1805,3172,3273,3134,3283,3284,3162,3285,3288,3287,2070,3286,3290,3291,3295,3293,1806,3292,3294,3303,3298,3301,3300,1807,1819,1818,3306,3302,3305,3299,3297,3304,3131,3308,2059,3309,2057,3310,2075,3311,2071,2076,3314,2066,3315,2064,3316,2063,2100,2062,2060,2101,2065,3312,2056,2077,2055,3313,2058,1820,2098,2072,2099,2073,3307,3349,3358,3357,3352,3359,3355,3354,3360,3353,3356,3347,2159,2160,2158,2161,2162,2163,2166,2165,2167,2168,2170,2169,2172,2171,2174,2173,2177,2176,2178,2144,2179,2181,2180,2182,2184,2183,2186,2185,2188,2187,2189,2190,2192,2191,2194,2193,2195,2196,2197,2198,2199,2201,2200,2203,2202,2205,2204,2206,2208,2207,2210,2209,2212,2211,2214,2213,2217,2216,2219,2218,2221,2220,2222,2215,2224,2223,2226,2225,2228,2227,2230,2232,2231,2233,2235,2237,2236,2239,2238,2241,2240,2242,2244,2243,2246,2245,2247,2145,2249,2248,2251,2250,2147,2146,2149,2150,2152,2151,2154,2153,2156,2155,2157,2253,2252,2255,2254,3143,3135,3133,3371,3383,3420,3421,3422,3423,3440,3498,3446,3499,3448,3450,3496,3495,3497,2257,2256,1804,3503,3508,3507,3511,3163,3730,3757,3731,3750,3758,3732,2259,3734,3735,3759,3733,3760,3745,3761,3749,3762,3736,3737,3763,3738,3765,3764,3766,3739,3748,3743,3746,3742,3744,3747,3767,3755,3768,3753,3769,3751,3770,3754,3772,3771,3773,3752,2262,2261,3590,2266,2265,2268,3654,3722,3774,3723,3775,3724,3776,3725,2260,3726,3727,3729,3756,4022,4028,4025,4031,4030,4032,4029,4034,4023,4035,4024,4036,2332,2334,2333,4033,4026,4027,4041,4061,4060,4050,4055,4051,4054,4052,2338,2339,4049,4053,4047,4057,4059,4044,4039,4043,4048,4056,4063,4045,2335,2337,2336,4064,4058,4040,4038,4037,4042,4046,4062,4071,4074,4079,4072,4075,4083,4078,4081,4076,4082,4077,4073,4080,4087,4092,4096,4101,4103,4102,4105,4104,4124,4135,4126,4136,4128,4127,4133,4137,4125,4138,4132,4129,4139,4131,4140,4130,4134,4151,4153,4152,4220,4222,4225,4165,4164,4215,4227,3119,4229,4228,4230,4231,4232,4233,4234,3137,4235,3139,4236,3138,4237,3136,3140,4248,4108,3460,3465,4331,3467,4328,3466,4332,3462,3461,4329,3459,4333,3463,3457,4334,3451,4335,3464,3456,4336,3452,4330,3458,3480,4238,3272,4337,2283,4249,3282,3278,4338,3276,2378,3280,2380,2379,3275,3281,2381,4339,3279,3274,3277,2164,4264,3424,4267,3425,4268,3427,4269,3429,4265,3439,3435,4266,3431,3363,3362,2383,4340,2382,4250,2317,2284,4212,4341,4163,4162,4219,4224,4214,4221,2384,4226,2386,2385,4342,3437,3740,2258,3741,2264,2263,4086,4343,4084,2388,2387,3436,4085,3434,857,4109,4270,3368,4271,3365,4272,3364,4273,3367,4274,3366,2175,2285,3146,2286,4353,4106,1795,4344,3142,4345,3147,4346,3484,3141,4354,3504,4355,3505,4356,3506,2416,4357,3164,4358,3165,4347,2287,4348,3144,4349,3130,3491,2289,2288,4350,2343,4351,2313,3479,2290,3478,2293,2314,4352,2294,2304,2052,4359,2457,2312,4117,3483,2420,4239,2320,4240,3132,4275,3373,4276,3372,4277,3375,4278,3374,3289,3471,2389,2390,855,3361,4279,2357,4280,2353,4281,2354,4282,2355,2359,2352,4283,2358,2360,2356,4070,4251,3346,4364,3332,3335,3323,3322,3324,3336,4371,3337,4372,3318,3319,3321,4373,3317,3320,2394,2395,3333,3344,4365,3342,2391,2392,3343,4366,3338,4367,3325,3326,3327,4368,3334,4360,3155,4361,3340,4362,3341,4363,3339,3328,4369,3329,4370,3330,3345,4374,3331,2393,3485,3350,4284,3351,2361,4241,2068,4252,3148,4375,2321,2322,4376,2318,2397,2396,854,2399,2398,3475,4285,2417,4253,2348,4377,3589,2267,2074,4378,3728,3149,4254,3426,4379,2323,4380,2326,3412,1799,4387,3402,3399,3419,3403,4388,3391,3411,3390,3406,4389,3405,3407,4390,3414,4391,3393,4392,3418,2325,4381,3398,3410,4382,3395,4383,3404,4384,3384,3385,3652,3386,4385,3388,3397,3396,3394,2400,3387,3389,4386,3415,4393,1798,3413,4394,3392,4395,3453,2402,2401,4397,3455,4396,3454,3472,3441,3468,4398,3469,4399,3445,3432,2403,3428,3470,3430,4255,3474,4286,3166,2363,2362,4287,2418,4400,3447,4401,2415,2404,850,4404,3443,4403,3442,4402,1797,4256,3128,4289,3121,4290,3122,2365,2364,2366,4291,3123,4292,3124,4288,3126,4293,3127,2341,1803,3153,4242,4095,2319,4406,2331,4405,3154,2405,2330,4407,3509,4257,3510,2342,2350,2349,3481,3482,4111,3152,4408,3151,3150,4410,4015,4011,4020,4411,4013,2408,2407,4412,4018,4413,4012,4414,4014,4415,4021,4009,4416,4010,4417,3777,4418,4017,4016,4409,3156,2410,2409,4019,2406,3449,4258,2054,4259,3438,3476,3477,4424,4065,4419,2295,4420,2296,4421,2299,4422,2297,4423,2298,4069,4068,4067,2234,3253,3486,4260,3370,2367,3266,3268,4294,3267,4295,3254,4296,3409,4297,3408,2369,2368,3269,2370,4303,3256,4304,3255,4305,3257,4306,3258,4298,3259,4299,3260,4300,3263,4301,3261,4302,3262,2372,2371,4307,3264,4308,3265,4309,3369,2373,4310,2303,4311,2300,2301,4313,4066,4312,2302,4426,3348,4427,4110,4425,2327,1796,2291,4428,3296,3433,4243,3270,4431,4090,4091,4088,4429,3653,4430,4089,853,4437,4093,3157,4432,3487,4433,2292,4438,3489,3490,4439,3488,2412,2411,4434,3494,4435,3492,4436,3493,2413,4244,4094,4442,3158,4443,4444,3159,4440,3145,4441,4098,4099,4314,4097,4245,4100,4446,4159,4445,3381,4217,4447,4155,4168,4158,4157,4216,4166,4161,4448,4156,4167,4213,4218,4223,4160,4449,3129,3271,4246,4261,3473,3502,4247,2347,4319,4113,4320,4114,4321,4115,4318,4116,4322,4119,4323,4120,4324,3501,4325,4118,4315,4107,4316,4121,4317,4123,4326,4122,2375,2374,2377,2376,4262,4112,4263,3161,4450,4148,4451,4146,4150,4452,4147,4453,4149,2328,4145,4454,4143,4455,2329,4456,4141,4144,4142,3377,3376,2460,4457,2475,2414,4458,2474,2473,2462,2465,4465,2464,2471,2470,4466,2472,4467,2469,4460,3382,4461,2461,4468,2494,2466,2467,4469,2497,2504,4470,2498,2481,4471,2502,2503,4472,2499,2491,2492,4473,2501,4474,2500,2493,4475,2496,4476,2495,2478,4477,2477,2468,2482,4462,3378,3379,4463,3380,4464,2458,2485,2490,2486,2487,2488,4478,2489,2483,2505,2484,4459,2459,2463,2476,3444,3500,4327,3160,3116,3117,4154,4479,3125,3118,3120,2511,2509,2508,2510,2507,2506,2512,4481,2515,2513,4480,3400,3401,3416,3417,2514,2516,2053,2346,2517,1800,2518,1801,1802,269,2519,2520,852,2521,2061,2522,2524,2523,2525,2107,2781,2780,2783,2782,2784,2148,2786,2785,2787,851,2788,2324,2789,2340,2790,2791,2229,2792,2108,2793,2143,2794,2795,2351,2796,1794,4482,2801,2803,2807,3250,4483,268],"version":"5.9.3"} \ No newline at end of file diff --git a/uv.lock b/uv.lock index dd55a4f31b8..e36da722261 100644 --- a/uv.lock +++ b/uv.lock @@ -9,7 +9,7 @@ resolution-markers = [ ] [options] -exclude-newer = "2026-07-01T21:29:59.740039Z" +exclude-newer = "2026-07-04T17:13:14.93495Z" exclude-newer-span = "P3D" [manifest] @@ -3274,7 +3274,7 @@ wheels = [ [[package]] name = "litellm" -version = "1.92.0" +version = "1.93.0" source = { editable = "." } dependencies = [ { name = "aiohttp" }, @@ -3639,7 +3639,7 @@ proxy-dev = [ [[package]] name = "litellm-enterprise" -version = "0.1.47" +version = "0.1.48" source = { editable = "enterprise" } [[package]]